@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 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
- functionPath: string;
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
- runAfter: <F extends FunctionReference>(delayMs: number, function_: F, args: ArgsOf<F>, options?: RunOptions) => Promise<{
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.&lt;name>` /
151
+ * `agents.&lt;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
- runAt: <F extends FunctionReference>(date: Date | number, function_: F, args: ArgsOf<F>, options?: RunOptions) => Promise<{
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
- functionPath: string;
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
- runAfter: <F extends FunctionReference>(delayMs: number, function_: F, args: ArgsOf<F>, options?: RunOptions) => Promise<{
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.&lt;name>` /
151
+ * `agents.&lt;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
- runAt: <F extends FunctionReference>(date: Date | number, function_: F, args: ArgsOf<F>, options?: RunOptions) => Promise<{
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-DGVqs2Ft.mjs';
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-DbXUK1Qa.mjs';
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(`@lunora/scheduler: job "${record.id}" (${record.functionPath}) parked in dead-letter after ${String(attempts)} attempts`);
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
- if (!body || typeof body.functionPath !== "string") {
528
- return SchedulerDO.error(400, "INVALID_INPUT", "functionPath is required");
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, function_, args, options_ = {}) => {
12
+ const runAt = async (date, target, args, options_ = {}) => {
12
13
  const scheduledFor = date instanceof Date ? date.getTime() : date;
13
- return callDO(options, "/schedule", {
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, function_, args, options_ = {}) => {
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, function_, args, options_);
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 () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/scheduler",
3
- "version": "1.0.0-alpha.8",
3
+ "version": "1.0.0-alpha.9",
4
4
  "description": "Scheduling for Lunora: runAfter / runAt and Cron Triggers via SchedulerDO",
5
5
  "keywords": [
6
6
  "cloudflare",