-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathjob.zod.ts
More file actions
141 lines (126 loc) · 5.18 KB
/
Copy pathjob.zod.ts
File metadata and controls
141 lines (126 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import { z } from 'zod';
import { CronExpressionInputSchema } from '../shared/expression.zod';
/**
* Cron Schedule Schema
* Schedule jobs using cron expressions
*/
import { lazySchema } from '../shared/lazy-schema';
export const CronScheduleSchema = lazySchema(() => z.object({
type: z.literal('cron'),
expression: CronExpressionInputSchema.describe('Cron expression — cron`0 0 * * *` for daily at midnight. Build emits {dialect:"cron",source} envelope.'),
timezone: z.string().optional().default('UTC').describe('Timezone for cron execution (e.g., "America/New_York")'),
}));
/**
* Interval Schedule Schema
* Schedule jobs at fixed intervals
*/
export const IntervalScheduleSchema = lazySchema(() => z.object({
type: z.literal('interval'),
intervalMs: z.number().int().positive().describe('Interval in milliseconds'),
}));
/**
* Once Schedule Schema
* Schedule a job to run once at a specific time
*/
export const OnceScheduleSchema = lazySchema(() => z.object({
type: z.literal('once'),
at: z.string().datetime().describe('ISO 8601 datetime when to execute'),
}));
/**
* Schedule Schema
* Discriminated union of all schedule types
*/
export const ScheduleSchema = lazySchema(() => z.discriminatedUnion('type', [
CronScheduleSchema,
IntervalScheduleSchema,
OnceScheduleSchema,
]));
export type Schedule = z.infer<typeof ScheduleSchema>;
export type CronSchedule = z.infer<typeof CronScheduleSchema>;
export type IntervalSchedule = z.infer<typeof IntervalScheduleSchema>;
export type OnceSchedule = z.infer<typeof OnceScheduleSchema>;
export type JobSchedule = Schedule; // Alias for backwards compatibility
/**
* Retry Policy Schema
* Configuration for job retry behavior with exponential backoff
*/
export const RetryPolicySchema = lazySchema(() => z.object({
maxRetries: z.number().int().min(0).default(3).describe('Maximum number of retry attempts'),
backoffMs: z.number().int().positive().default(1000).describe('Initial backoff delay in milliseconds'),
backoffMultiplier: z.number().positive().default(2).describe('Multiplier for exponential backoff'),
}));
export type RetryPolicy = z.infer<typeof RetryPolicySchema>;
/**
* Job Schema
* Defines a scheduled job that executes background logic.
*
* @example Metadata Sync Job (Cron)
* {
* id: "job_sync_meta",
* name: "sync_metadata_nightly",
* schedule: {
* type: "cron",
* expression: "0 0 * * *", // Midnight
* timezone: "UTC"
* },
* handler: "services/syncStatus.ts:syncAll",
* retryPolicy: {
* maxRetries: 3,
* backoffMs: 5000
* }
* }
*/
export const JobSchema = lazySchema(() => z.object({
id: z.string().optional().describe('Unique job identifier (defaults to `name` when omitted)'),
name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Job name (snake_case)'),
label: z.string().optional().describe('Human-readable label'),
description: z.string().optional().describe('Job description / purpose'),
schedule: ScheduleSchema.describe('Job schedule configuration'),
handler: z.string().describe('Handler function name (must match a key in `defineStack({ functions })`)'),
retryPolicy: RetryPolicySchema.optional().describe('Retry policy configuration. [EXPERIMENTAL — not enforced] The service-job scheduler does not yet read retryPolicy; failed runs are not retried per this config (liveness audit #1878/#1893).'),
timeout: z.number().int().positive().optional().describe('Timeout in milliseconds. [EXPERIMENTAL — not enforced] The service-job scheduler does not yet enforce a per-run timeout (liveness audit #1878/#1893).'),
enabled: z.boolean().default(true).describe('Whether the job is enabled'),
}));
export type Job = z.infer<typeof JobSchema>;
/** Authoring input for {@link Job} — defaulted fields are optional. */
export type JobInput = z.input<typeof JobSchema>;
/**
* Type-safe factory for declaring background jobs in metadata-as-code.
*
* @example
* ```ts
* export const nightlySync = defineJob({
* name: 'sync_metadata_nightly',
* schedule: { type: 'cron', expression: '0 0 * * *', timezone: 'UTC' },
* handler: 'syncMetadata', // must be registered in defineStack({ functions: { syncMetadata: () => ... } })
* });
* ```
*/
export function defineJob(config: z.input<typeof JobSchema>): Job {
return JobSchema.parse(config);
}
/**
* Job Execution Status Enum
* Status of job execution
*/
export const JobExecutionStatus = z.enum([
'running',
'success',
'failed',
'timeout',
]);
export type JobExecutionStatus = z.infer<typeof JobExecutionStatus>;
/**
* Job Execution Schema
* Logs for job execution
*/
export const JobExecutionSchema = lazySchema(() => z.object({
jobId: z.string().describe('Job identifier'),
startedAt: z.string().datetime().describe('ISO 8601 datetime when execution started'),
completedAt: z.string().datetime().optional().describe('ISO 8601 datetime when execution completed'),
status: JobExecutionStatus.describe('Execution status'),
error: z.string().optional().describe('Error message if failed'),
duration: z.number().int().optional().describe('Execution duration in milliseconds'),
}));
export type JobExecution = z.infer<typeof JobExecutionSchema>;