@lunora/scheduler 1.0.0-alpha.8 → 1.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +41 -3
- package/dist/index.d.ts +41 -3
- package/dist/index.mjs +2 -2
- package/dist/packem_shared/{SchedulerDO-DbXUK1Qa.mjs → SchedulerDO-DeJHI379.mjs} +30 -6
- package/dist/packem_shared/{createScheduler-DGVqs2Ft.mjs → createScheduler-Df9JUjQb.mjs} +17 -9
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -45,6 +45,15 @@ interface WorkflowReference<Params = Record<string, unknown>> {
|
|
|
45
45
|
type CronTarget = FunctionReference | WorkflowReference;
|
|
46
46
|
/** The arguments a cron's target accepts: a workflow's inferred `params`, else an open record (function args aren't inferred). */
|
|
47
47
|
type CronTargetArgs<T extends CronTarget> = T extends WorkflowReference<infer Params> ? Params : Record<string, unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* The arguments a one-shot schedule target ({@link Scheduler.runAfter} /
|
|
50
|
+
* {@link Scheduler.runAt}) accepts. Unlike {@link CronTargetArgs} it preserves a
|
|
51
|
+
* {@link FunctionReference}'s inferred `args` (via {@link ArgsOf}) as well as a
|
|
52
|
+
* {@link WorkflowReference}'s inferred `params`, so scheduling a plain function
|
|
53
|
+
* keeps its today's arg checking while scheduling a workflow/agent infers its
|
|
54
|
+
* `params`.
|
|
55
|
+
*/
|
|
56
|
+
type ScheduleTargetArgs<T extends CronTarget> = T extends WorkflowReference<infer Params> ? Params : T extends FunctionReference ? ArgsOf<T> : Record<string, unknown>;
|
|
48
57
|
/** Narrow a {@link CronTarget} to a {@link WorkflowReference} by its runtime brand. */
|
|
49
58
|
declare const isWorkflowReference: (target: unknown) => target is WorkflowReference;
|
|
50
59
|
/**
|
|
@@ -93,7 +102,12 @@ interface ScheduleRecord {
|
|
|
93
102
|
*/
|
|
94
103
|
attempts?: number;
|
|
95
104
|
enqueuedAt: number;
|
|
96
|
-
|
|
105
|
+
/**
|
|
106
|
+
* The `ns:fn` path of the function to dispatch on fire. Absent when the job
|
|
107
|
+
* targets a durable workflow/agent instead — see {@link ScheduleRecord.workflow}.
|
|
108
|
+
* Exactly one of `functionPath` / `workflow` is set.
|
|
109
|
+
*/
|
|
110
|
+
functionPath?: string;
|
|
97
111
|
id: string;
|
|
98
112
|
/**
|
|
99
113
|
* Scheduler/workpool instance name the job was enqueued through. Echoed in
|
|
@@ -113,6 +127,14 @@ interface ScheduleRecord {
|
|
|
113
127
|
retry?: RetryPolicy;
|
|
114
128
|
scheduledFor: number;
|
|
115
129
|
shardKey?: string;
|
|
130
|
+
/**
|
|
131
|
+
* The `WORKFLOW_*`/`AGENT_*` binding name to start a fresh durable instance
|
|
132
|
+
* of on fire (the {@link ScheduleRecord.args} become its `params`). Set
|
|
133
|
+
* instead of {@link ScheduleRecord.functionPath} when the job targets a
|
|
134
|
+
* workflow/agent {@link WorkflowReference}. The runtime — not the DO — owns
|
|
135
|
+
* the binding, so the dispatch payload carries this through to the Worker.
|
|
136
|
+
*/
|
|
137
|
+
workflow?: string;
|
|
116
138
|
}
|
|
117
139
|
interface Scheduler {
|
|
118
140
|
cancel: (id: string) => Promise<{
|
|
@@ -122,11 +144,20 @@ interface Scheduler {
|
|
|
122
144
|
get: (id: string) => Promise<ScheduleRecord | null>;
|
|
123
145
|
/** All pending scheduled jobs (the DO's `/list` view). */
|
|
124
146
|
list: () => Promise<ScheduleRecord[]>;
|
|
125
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Schedule `target` to run once, `delayMs` from now. `target` is a function
|
|
149
|
+
* {@link FunctionReference} (dispatched as a one-shot) or a durable
|
|
150
|
+
* {@link WorkflowReference} — the generated `workflows.<name>` /
|
|
151
|
+
* `agents.<name>` ref — which starts a fresh instance on fire (args become
|
|
152
|
+
* its `params`). {@link ScheduleTargetArgs} infers the accepted args from
|
|
153
|
+
* whichever target was passed.
|
|
154
|
+
*/
|
|
155
|
+
runAfter: <T extends CronTarget>(delayMs: number, target: T, args: ScheduleTargetArgs<T>, options?: RunOptions) => Promise<{
|
|
126
156
|
id: string;
|
|
127
157
|
scheduledFor: number;
|
|
128
158
|
}>;
|
|
129
|
-
|
|
159
|
+
/** Like {@link Scheduler.runAfter} but fires at an absolute `date`/timestamp. */
|
|
160
|
+
runAt: <T extends CronTarget>(date: Date | number, target: T, args: ScheduleTargetArgs<T>, options?: RunOptions) => Promise<{
|
|
130
161
|
id: string;
|
|
131
162
|
scheduledFor: number;
|
|
132
163
|
}>;
|
|
@@ -617,6 +648,13 @@ declare class SchedulerDO {
|
|
|
617
648
|
* compatibility shim, not the hot path.
|
|
618
649
|
*/
|
|
619
650
|
private static releaseFirstSlot;
|
|
651
|
+
/**
|
|
652
|
+
* Normalize the mutually-exclusive dispatch target off an untrusted body: a
|
|
653
|
+
* one-shot function path (`functionPath`) or a durable workflow/agent
|
|
654
|
+
* instance (`workflow`, a `WORKFLOW_*`/`AGENT_*` binding). Returns `undefined`
|
|
655
|
+
* when neither is present so the caller can reject the schedule.
|
|
656
|
+
*/
|
|
657
|
+
private static resolveScheduleTarget;
|
|
620
658
|
protected readonly state: SchedulerDOState;
|
|
621
659
|
protected readonly env: SchedulerEnv;
|
|
622
660
|
constructor(state: SchedulerDOState, env: SchedulerEnv);
|
package/dist/index.d.ts
CHANGED
|
@@ -45,6 +45,15 @@ interface WorkflowReference<Params = Record<string, unknown>> {
|
|
|
45
45
|
type CronTarget = FunctionReference | WorkflowReference;
|
|
46
46
|
/** The arguments a cron's target accepts: a workflow's inferred `params`, else an open record (function args aren't inferred). */
|
|
47
47
|
type CronTargetArgs<T extends CronTarget> = T extends WorkflowReference<infer Params> ? Params : Record<string, unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* The arguments a one-shot schedule target ({@link Scheduler.runAfter} /
|
|
50
|
+
* {@link Scheduler.runAt}) accepts. Unlike {@link CronTargetArgs} it preserves a
|
|
51
|
+
* {@link FunctionReference}'s inferred `args` (via {@link ArgsOf}) as well as a
|
|
52
|
+
* {@link WorkflowReference}'s inferred `params`, so scheduling a plain function
|
|
53
|
+
* keeps its today's arg checking while scheduling a workflow/agent infers its
|
|
54
|
+
* `params`.
|
|
55
|
+
*/
|
|
56
|
+
type ScheduleTargetArgs<T extends CronTarget> = T extends WorkflowReference<infer Params> ? Params : T extends FunctionReference ? ArgsOf<T> : Record<string, unknown>;
|
|
48
57
|
/** Narrow a {@link CronTarget} to a {@link WorkflowReference} by its runtime brand. */
|
|
49
58
|
declare const isWorkflowReference: (target: unknown) => target is WorkflowReference;
|
|
50
59
|
/**
|
|
@@ -93,7 +102,12 @@ interface ScheduleRecord {
|
|
|
93
102
|
*/
|
|
94
103
|
attempts?: number;
|
|
95
104
|
enqueuedAt: number;
|
|
96
|
-
|
|
105
|
+
/**
|
|
106
|
+
* The `ns:fn` path of the function to dispatch on fire. Absent when the job
|
|
107
|
+
* targets a durable workflow/agent instead — see {@link ScheduleRecord.workflow}.
|
|
108
|
+
* Exactly one of `functionPath` / `workflow` is set.
|
|
109
|
+
*/
|
|
110
|
+
functionPath?: string;
|
|
97
111
|
id: string;
|
|
98
112
|
/**
|
|
99
113
|
* Scheduler/workpool instance name the job was enqueued through. Echoed in
|
|
@@ -113,6 +127,14 @@ interface ScheduleRecord {
|
|
|
113
127
|
retry?: RetryPolicy;
|
|
114
128
|
scheduledFor: number;
|
|
115
129
|
shardKey?: string;
|
|
130
|
+
/**
|
|
131
|
+
* The `WORKFLOW_*`/`AGENT_*` binding name to start a fresh durable instance
|
|
132
|
+
* of on fire (the {@link ScheduleRecord.args} become its `params`). Set
|
|
133
|
+
* instead of {@link ScheduleRecord.functionPath} when the job targets a
|
|
134
|
+
* workflow/agent {@link WorkflowReference}. The runtime — not the DO — owns
|
|
135
|
+
* the binding, so the dispatch payload carries this through to the Worker.
|
|
136
|
+
*/
|
|
137
|
+
workflow?: string;
|
|
116
138
|
}
|
|
117
139
|
interface Scheduler {
|
|
118
140
|
cancel: (id: string) => Promise<{
|
|
@@ -122,11 +144,20 @@ interface Scheduler {
|
|
|
122
144
|
get: (id: string) => Promise<ScheduleRecord | null>;
|
|
123
145
|
/** All pending scheduled jobs (the DO's `/list` view). */
|
|
124
146
|
list: () => Promise<ScheduleRecord[]>;
|
|
125
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Schedule `target` to run once, `delayMs` from now. `target` is a function
|
|
149
|
+
* {@link FunctionReference} (dispatched as a one-shot) or a durable
|
|
150
|
+
* {@link WorkflowReference} — the generated `workflows.<name>` /
|
|
151
|
+
* `agents.<name>` ref — which starts a fresh instance on fire (args become
|
|
152
|
+
* its `params`). {@link ScheduleTargetArgs} infers the accepted args from
|
|
153
|
+
* whichever target was passed.
|
|
154
|
+
*/
|
|
155
|
+
runAfter: <T extends CronTarget>(delayMs: number, target: T, args: ScheduleTargetArgs<T>, options?: RunOptions) => Promise<{
|
|
126
156
|
id: string;
|
|
127
157
|
scheduledFor: number;
|
|
128
158
|
}>;
|
|
129
|
-
|
|
159
|
+
/** Like {@link Scheduler.runAfter} but fires at an absolute `date`/timestamp. */
|
|
160
|
+
runAt: <T extends CronTarget>(date: Date | number, target: T, args: ScheduleTargetArgs<T>, options?: RunOptions) => Promise<{
|
|
130
161
|
id: string;
|
|
131
162
|
scheduledFor: number;
|
|
132
163
|
}>;
|
|
@@ -617,6 +648,13 @@ declare class SchedulerDO {
|
|
|
617
648
|
* compatibility shim, not the hot path.
|
|
618
649
|
*/
|
|
619
650
|
private static releaseFirstSlot;
|
|
651
|
+
/**
|
|
652
|
+
* Normalize the mutually-exclusive dispatch target off an untrusted body: a
|
|
653
|
+
* one-shot function path (`functionPath`) or a durable workflow/agent
|
|
654
|
+
* instance (`workflow`, a `WORKFLOW_*`/`AGENT_*` binding). Returns `undefined`
|
|
655
|
+
* when neither is present so the caller can reject the schedule.
|
|
656
|
+
*/
|
|
657
|
+
private static resolveScheduleTarget;
|
|
620
658
|
protected readonly state: SchedulerDOState;
|
|
621
659
|
protected readonly env: SchedulerEnv;
|
|
622
660
|
constructor(state: SchedulerDOState, env: SchedulerEnv);
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { default as createScheduler } from './packem_shared/createScheduler-
|
|
1
|
+
export { default as createScheduler } from './packem_shared/createScheduler-Df9JUjQb.mjs';
|
|
2
2
|
export { default as createWorkpool } from './packem_shared/createWorkpool-Ce0kNM2p.mjs';
|
|
3
3
|
export { createCronTrigger } from './packem_shared/createCronTrigger-Dh4VLxD9.mjs';
|
|
4
4
|
export { CRON_SCHEDULE_KINDS, compileCronSchedule, cronJobs } from './packem_shared/CRON_SCHEDULE_KINDS-C2FflEnq.mjs';
|
|
5
5
|
export { createQueueConsumer, createQueueWorkpool, httpDispatcher } from './packem_shared/createQueueConsumer-Cy-Mp-El.mjs';
|
|
6
|
-
export { SchedulerDO } from './packem_shared/SchedulerDO-
|
|
6
|
+
export { SchedulerDO } from './packem_shared/SchedulerDO-DeJHI379.mjs';
|
|
7
7
|
export { isWorkflowReference } from './packem_shared/isWorkflowReference-C9mQkMXt.mjs';
|
|
8
8
|
export { assertValidCronExpression, isValidCronExpression } from './packem_shared/assertValidCronExpression-B9m75qU0.mjs';
|
|
@@ -102,6 +102,20 @@ class SchedulerDO {
|
|
|
102
102
|
const next = pool.inFlightIds.slice(0, Math.max(0, pool.inFlightIds.length - 1));
|
|
103
103
|
return { ...pool, inFlight: next.length, inFlightIds: next };
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Normalize the mutually-exclusive dispatch target off an untrusted body: a
|
|
107
|
+
* one-shot function path (`functionPath`) or a durable workflow/agent
|
|
108
|
+
* instance (`workflow`, a `WORKFLOW_*`/`AGENT_*` binding). Returns `undefined`
|
|
109
|
+
* when neither is present so the caller can reject the schedule.
|
|
110
|
+
*/
|
|
111
|
+
static resolveScheduleTarget(body) {
|
|
112
|
+
const functionPath = typeof body?.functionPath === "string" && body.functionPath.length > 0 ? body.functionPath : void 0;
|
|
113
|
+
const workflow = typeof body?.workflow === "string" && body.workflow.length > 0 ? body.workflow : void 0;
|
|
114
|
+
if (functionPath === void 0 && workflow === void 0) {
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
117
|
+
return { functionPath, workflow };
|
|
118
|
+
}
|
|
105
119
|
state;
|
|
106
120
|
env;
|
|
107
121
|
constructor(state, env) {
|
|
@@ -211,7 +225,12 @@ class SchedulerDO {
|
|
|
211
225
|
// so the pool's concurrency slot is released — see handleComplete().
|
|
212
226
|
pool: record.pool,
|
|
213
227
|
scheduledFor: record.scheduledFor,
|
|
214
|
-
shardKey: record.shardKey
|
|
228
|
+
shardKey: record.shardKey,
|
|
229
|
+
// Present instead of `functionPath` for a workflow/agent target; the
|
|
230
|
+
// runtime starts a fresh instance of this binding. `undefined` when
|
|
231
|
+
// absent, so JSON.stringify drops it and the payload is unchanged for
|
|
232
|
+
// ordinary function dispatches.
|
|
233
|
+
workflow: record.workflow
|
|
215
234
|
});
|
|
216
235
|
try {
|
|
217
236
|
const headers = { "content-type": "application/json" };
|
|
@@ -408,7 +427,9 @@ class SchedulerDO {
|
|
|
408
427
|
if (attempts > maxAttempts) {
|
|
409
428
|
await this.state.storage.put(`${DEAD_PREFIX}${record.id}`, { ...record, attempts });
|
|
410
429
|
await this.state.storage.delete([`${RETRY_PREFIX}${record.id}`, `${HEADER_PREFIX}${record.id}`]);
|
|
411
|
-
console.warn(
|
|
430
|
+
console.warn(
|
|
431
|
+
`@lunora/scheduler: job "${record.id}" (${record.functionPath ?? record.workflow ?? "unknown"}) parked in dead-letter after ${String(attempts)} attempts`
|
|
432
|
+
);
|
|
412
433
|
return;
|
|
413
434
|
}
|
|
414
435
|
const rawDelay = backoff === "linear" ? baseMs * attempts : baseMs * 2 ** (attempts - 1);
|
|
@@ -524,9 +545,11 @@ class SchedulerDO {
|
|
|
524
545
|
}
|
|
525
546
|
async handleSchedule(request) {
|
|
526
547
|
const body = await request.json().catch(() => void 0);
|
|
527
|
-
|
|
528
|
-
|
|
548
|
+
const target = SchedulerDO.resolveScheduleTarget(body);
|
|
549
|
+
if (!body || target === void 0) {
|
|
550
|
+
return SchedulerDO.error(400, "INVALID_INPUT", "functionPath or workflow is required");
|
|
529
551
|
}
|
|
552
|
+
const { functionPath, workflow } = target;
|
|
530
553
|
if (typeof body.scheduledFor !== "number" || !Number.isInteger(body.scheduledFor) || body.scheduledFor <= 0 || body.scheduledFor > MAX_SCHEDULED_FOR_MS) {
|
|
531
554
|
return SchedulerDO.error(400, "INVALID_INPUT", "scheduledFor must be a positive integer epoch-millisecond number no greater than 999999999999999");
|
|
532
555
|
}
|
|
@@ -543,13 +566,14 @@ class SchedulerDO {
|
|
|
543
566
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- parsed wire data can omit args
|
|
544
567
|
args: body.args ?? {},
|
|
545
568
|
enqueuedAt: Date.now(),
|
|
546
|
-
functionPath: body.functionPath,
|
|
547
569
|
id,
|
|
570
|
+
...functionPath === void 0 ? {} : { functionPath },
|
|
548
571
|
...instanceName === void 0 ? {} : { instanceName },
|
|
549
572
|
...pool === void 0 ? {} : { pool },
|
|
550
573
|
...retry === void 0 ? {} : { retry },
|
|
551
574
|
scheduledFor: body.scheduledFor,
|
|
552
|
-
shardKey: body.shardKey
|
|
575
|
+
shardKey: body.shardKey,
|
|
576
|
+
...workflow === void 0 ? {} : { workflow }
|
|
553
577
|
};
|
|
554
578
|
if (pool !== void 0) {
|
|
555
579
|
const current = await this.loadPool(pool, body.maxConcurrency);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
2
|
import { c as callDO, g as getDO } from './do-client-CMJtLoHO.mjs';
|
|
3
|
+
import { isWorkflowReference } from './isWorkflowReference-C9mQkMXt.mjs';
|
|
3
4
|
|
|
4
5
|
const createScheduler = (options) => {
|
|
5
6
|
if (!options.namespace) {
|
|
@@ -8,26 +9,33 @@ const createScheduler = (options) => {
|
|
|
8
9
|
if (!options.originUrl) {
|
|
9
10
|
throw new LunoraError("INTERNAL", "@lunora/scheduler: `originUrl` is required so the DO can dispatch back to the Worker");
|
|
10
11
|
}
|
|
11
|
-
const runAt = async (date,
|
|
12
|
+
const runAt = async (date, target, args, options_ = {}) => {
|
|
12
13
|
const scheduledFor = date instanceof Date ? date.getTime() : date;
|
|
13
|
-
|
|
14
|
+
const base = {
|
|
14
15
|
args,
|
|
15
|
-
functionPath: function_.__lunoraRef,
|
|
16
16
|
originUrl: options.originUrl,
|
|
17
|
-
// Optional workpool / retry-policy passthrough. Absent for ordinary
|
|
18
|
-
// `runAfter`/`runAt` calls, which keeps the wire payload (and the
|
|
19
|
-
// DO's behaviour) identical to before this feature.
|
|
20
17
|
pool: options_.pool,
|
|
21
18
|
retry: options_.retry,
|
|
22
19
|
scheduledFor,
|
|
23
20
|
shardKey: options_.shardKey
|
|
24
|
-
}
|
|
21
|
+
};
|
|
22
|
+
if (isWorkflowReference(target)) {
|
|
23
|
+
if (typeof target.binding !== "string" || target.binding.length === 0) {
|
|
24
|
+
throw new LunoraError(
|
|
25
|
+
"INTERNAL",
|
|
26
|
+
"@lunora/scheduler: workflow/agent schedule target is missing its `binding` — pass the generated `workflows.<name>` / `agents.<name>` reference"
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return callDO(options, "/schedule", { ...base, workflow: target.binding });
|
|
30
|
+
}
|
|
31
|
+
const functionPath = typeof target === "string" ? target : target.__lunoraRef;
|
|
32
|
+
return callDO(options, "/schedule", { ...base, functionPath });
|
|
25
33
|
};
|
|
26
|
-
const runAfter = async (delayMs,
|
|
34
|
+
const runAfter = async (delayMs, target, args, options_ = {}) => {
|
|
27
35
|
if (!Number.isFinite(delayMs) || delayMs < 0) {
|
|
28
36
|
throw new LunoraError("INTERNAL", "@lunora/scheduler: `delayMs` must be a non-negative finite number");
|
|
29
37
|
}
|
|
30
|
-
return runAt(Date.now() + delayMs,
|
|
38
|
+
return runAt(Date.now() + delayMs, target, args, options_);
|
|
31
39
|
};
|
|
32
40
|
const cancel = async (id) => callDO(options, "/cancel", { id });
|
|
33
41
|
const list = async () => {
|