@lunora/scheduler 1.0.0-alpha.10 → 1.0.0-alpha.12

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.
Files changed (3) hide show
  1. package/dist/index.d.mts +410 -410
  2. package/dist/index.d.ts +410 -410
  3. package/package.json +3 -3
package/dist/index.d.mts CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Opaque reference to a Lunora function. Mirrors the `FunctionReference` shape
3
- * emitted by `@lunora/codegen` (and consumed by `@lunora/client`). We avoid a
4
- * direct dependency to keep this package usable from the codegen pipeline
5
- * itself.
6
- *
7
- * The runtime identifier lives in `__lunoraRef` — this MUST stay in lockstep
8
- * with the codegen emit + `@lunora/client`'s `FunctionReference`.
9
- */
2
+ * Opaque reference to a Lunora function. Mirrors the `FunctionReference` shape
3
+ * emitted by `@lunora/codegen` (and consumed by `@lunora/client`). We avoid a
4
+ * direct dependency to keep this package usable from the codegen pipeline
5
+ * itself.
6
+ *
7
+ * The runtime identifier lives in `__lunoraRef` — this MUST stay in lockstep
8
+ * with the codegen emit + `@lunora/client`'s `FunctionReference`.
9
+ */
10
10
  interface FunctionReference {
11
11
  readonly __lunoraRef: string;
12
12
  /** Marker phantom type — discriminates queries / mutations / actions. */
@@ -16,22 +16,22 @@ type ArgsOf<F extends FunctionReference> = F extends {
16
16
  _args?: infer A;
17
17
  } ? A : Record<string, unknown>;
18
18
  /**
19
- * Typed reference to a Lunora durable workflow — either the generated
20
- * `workflows.&lt;name>` reference object (`_generated/api.ts`, which carries the
21
- * `WORKFLOW_*` binding + export name) or, structurally, a `defineWorkflow()`
22
- * result imported directly. Both are matched by the `isLunoraWorkflow` brand and
23
- * carry the workflow's `params` in the phantom `__params`, so a `cronJobs()`
24
- * registration infers them.
25
- *
26
- * Declared structurally here so `@lunora/scheduler` can let a `cronJobs()`
27
- * builder target a workflow without depending on `@lunora/workflow` (and so the
28
- * generated `workflows.*` object needs no `@lunora/scheduler` import — it
29
- * matches structurally). A cron whose target is a {@link WorkflowReference}
30
- * starts a new workflow INSTANCE on each fire (the args become its `params`)
31
- * instead of dispatching a one-shot function. `@lunora/codegen` resolves the
32
- * concrete `lunora/workflows.ts` export statically; the runtime brand here is
33
- * the authoring-time guard.
34
- */
19
+ * Typed reference to a Lunora durable workflow — either the generated
20
+ * `workflows.&lt;name>` reference object (`_generated/api.ts`, which carries the
21
+ * `WORKFLOW_*` binding + export name) or, structurally, a `defineWorkflow()`
22
+ * result imported directly. Both are matched by the `isLunoraWorkflow` brand and
23
+ * carry the workflow's `params` in the phantom `__params`, so a `cronJobs()`
24
+ * registration infers them.
25
+ *
26
+ * Declared structurally here so `@lunora/scheduler` can let a `cronJobs()`
27
+ * builder target a workflow without depending on `@lunora/workflow` (and so the
28
+ * generated `workflows.*` object needs no `@lunora/scheduler` import — it
29
+ * matches structurally). A cron whose target is a {@link WorkflowReference}
30
+ * starts a new workflow INSTANCE on each fire (the args become its `params`)
31
+ * instead of dispatching a one-shot function. `@lunora/codegen` resolves the
32
+ * concrete `lunora/workflows.ts` export statically; the runtime brand here is
33
+ * the authoring-time guard.
34
+ */
35
35
  interface WorkflowReference<Params = Record<string, unknown>> {
36
36
  /** Phantom carrier for the workflow's `params` type — drives `cronJobs()` arg inference. Never read at runtime. */
37
37
  readonly __params?: Params;
@@ -46,31 +46,31 @@ 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
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
- */
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
56
  type ScheduleTargetArgs<T extends CronTarget> = T extends WorkflowReference<infer Params> ? Params : T extends FunctionReference ? ArgsOf<T> : Record<string, unknown>;
57
57
  /** Narrow a {@link CronTarget} to a {@link WorkflowReference} by its runtime brand. */
58
58
  declare const isWorkflowReference: (target: unknown) => target is WorkflowReference;
59
59
  /**
60
- * Per-job retry policy. Wired into the SchedulerDO's existing attempts/backoff
61
- * machinery. When omitted, the DO falls back to its built-in defaults
62
- * (`maxAttempts: 5`, `backoff: "exponential"`, `baseMs: 30_000`) so existing
63
- * `runAfter`/`runAt` callers keep today's behaviour unchanged.
64
- *
65
- * On exhaustion (attempts > `maxAttempts`) the record is parked under the
66
- * `dead:` dead-letter key for inspection — never silently dropped.
67
- */
60
+ * Per-job retry policy. Wired into the SchedulerDO's existing attempts/backoff
61
+ * machinery. When omitted, the DO falls back to its built-in defaults
62
+ * (`maxAttempts: 5`, `backoff: "exponential"`, `baseMs: 30_000`) so existing
63
+ * `runAfter`/`runAt` callers keep today's behaviour unchanged.
64
+ *
65
+ * On exhaustion (attempts > `maxAttempts`) the record is parked under the
66
+ * `dead:` dead-letter key for inspection — never silently dropped.
67
+ */
68
68
  interface RetryPolicy {
69
69
  /**
70
- * Backoff growth across attempts. `"exponential"` doubles the delay each
71
- * attempt (`baseMs * 2 ** (attempt - 1)`); `"linear"` grows it linearly
72
- * (`baseMs * attempt`). Default `"exponential"`.
73
- */
70
+ * Backoff growth across attempts. `"exponential"` doubles the delay each
71
+ * attempt (`baseMs * 2 ** (attempt - 1)`); `"linear"` grows it linearly
72
+ * (`baseMs * attempt`). Default `"exponential"`.
73
+ */
74
74
  backoff?: "exponential" | "linear";
75
75
  /** Base delay in milliseconds for the first retry. Default `30_000`. */
76
76
  baseMs?: number;
@@ -81,11 +81,11 @@ interface RetryPolicy {
81
81
  }
82
82
  interface RunOptions {
83
83
  /**
84
- * Logical workpool this job belongs to. When set, the SchedulerDO gates the
85
- * job behind the pool's `maxConcurrency` (see {@link WorkpoolOptions}).
86
- * Usually populated by {@link Workpool.enqueue}; callers rarely set it on a
87
- * bare `runAfter`/`runAt`.
88
- */
84
+ * Logical workpool this job belongs to. When set, the SchedulerDO gates the
85
+ * job behind the pool's `maxConcurrency` (see {@link WorkpoolOptions}).
86
+ * Usually populated by {@link Workpool.enqueue}; callers rarely set it on a
87
+ * bare `runAfter`/`runAt`.
88
+ */
89
89
  pool?: string;
90
90
  /** Per-job retry policy. Falls back to the DO's built-in defaults when omitted. */
91
91
  retry?: RetryPolicy;
@@ -95,45 +95,45 @@ interface RunOptions {
95
95
  interface ScheduleRecord {
96
96
  args: Record<string, unknown>;
97
97
  /**
98
- * Number of dispatch attempts already made. Absent (treated as 0) until the
99
- * first failure, after which `recordRetry()` persists it on both the
100
- * `retry:` row and the `id:` header. Surfaced here so `/list` consumers and
101
- * the studio see the field the storage layer actually writes.
102
- */
98
+ * Number of dispatch attempts already made. Absent (treated as 0) until the
99
+ * first failure, after which `recordRetry()` persists it on both the
100
+ * `retry:` row and the `id:` header. Surfaced here so `/list` consumers and
101
+ * the studio see the field the storage layer actually writes.
102
+ */
103
103
  attempts?: number;
104
104
  enqueuedAt: number;
105
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
- */
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
110
  functionPath?: string;
111
111
  id: string;
112
112
  /**
113
- * Scheduler/workpool instance name the job was enqueued through. Echoed in
114
- * the dispatch payload so the runtime can call back the SAME DO instance's
115
- * `/complete` to release a pooled slot. Absent for the default instance.
116
- */
113
+ * Scheduler/workpool instance name the job was enqueued through. Echoed in
114
+ * the dispatch payload so the runtime can call back the SAME DO instance's
115
+ * `/complete` to release a pooled slot. Absent for the default instance.
116
+ */
117
117
  instanceName?: string;
118
118
  /**
119
- * Logical workpool this job belongs to (set by {@link Workpool.enqueue}).
120
- * When present, the SchedulerDO only dispatches the job while the pool's
121
- * in-flight count is below its `maxConcurrency`; otherwise it stays queued
122
- * and drains as slots free. Absent for plain `runAfter`/`runAt` jobs, which
123
- * are never concurrency-gated.
124
- */
119
+ * Logical workpool this job belongs to (set by {@link Workpool.enqueue}).
120
+ * When present, the SchedulerDO only dispatches the job while the pool's
121
+ * in-flight count is below its `maxConcurrency`; otherwise it stays queued
122
+ * and drains as slots free. Absent for plain `runAfter`/`runAt` jobs, which
123
+ * are never concurrency-gated.
124
+ */
125
125
  pool?: string;
126
126
  /** Per-job retry policy (see {@link RetryPolicy}); absent means DO defaults. */
127
127
  retry?: RetryPolicy;
128
128
  scheduledFor: number;
129
129
  shardKey?: string;
130
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
- */
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
137
  workflow?: string;
138
138
  }
139
139
  interface Scheduler {
@@ -145,13 +145,13 @@ interface Scheduler {
145
145
  /** All pending scheduled jobs (the DO's `/list` view). */
146
146
  list: () => Promise<ScheduleRecord[]>;
147
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
- */
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
155
  runAfter: <T extends CronTarget>(delayMs: number, target: T, args: ScheduleTargetArgs<T>, options?: RunOptions) => Promise<{
156
156
  id: string;
157
157
  scheduledFor: number;
@@ -163,19 +163,19 @@ interface Scheduler {
163
163
  }>;
164
164
  }
165
165
  /**
166
- * Cloudflare Durable Object data-residency jurisdiction. Widening union —
167
- * Cloudflare adds values over time.
168
- * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
169
- */
166
+ * Cloudflare Durable Object data-residency jurisdiction. Widening union —
167
+ * Cloudflare adds values over time.
168
+ * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
169
+ */
170
170
  type DurableObjectJurisdiction = "eu" | "fedramp" | "us";
171
171
  /** Subset of `DurableObjectNamespace` the package consumes. */
172
172
  interface DurableObjectNamespaceLike {
173
173
  get: (id: DurableObjectIdLike) => DurableObjectStubLike;
174
174
  idFromName: (name: string) => DurableObjectIdLike;
175
175
  /**
176
- * Derive a jurisdiction-restricted subnamespace. Optional because older
177
- * workers-types releases (and test doubles) may not expose it.
178
- */
176
+ * Derive a jurisdiction-restricted subnamespace. Optional because older
177
+ * workers-types releases (and test doubles) may not expose it.
178
+ */
179
179
  jurisdiction?: (jurisdiction: DurableObjectJurisdiction) => DurableObjectNamespaceLike;
180
180
  }
181
181
  interface DurableObjectIdLike {
@@ -188,18 +188,18 @@ interface LunoraSchedulerOptions {
188
188
  /** Optional named instance — useful for tenant isolation. Default `default`. */
189
189
  instanceName?: string;
190
190
  /**
191
- * Pin the SchedulerDO (durable timers + cron state) to a Cloudflare
192
- * data-residency jurisdiction. Pass the same value as the worker's
193
- * `jurisdiction` so scheduled state co-resides with app data. Omit for the
194
- * un-pinned global namespace.
195
- */
191
+ * Pin the SchedulerDO (durable timers + cron state) to a Cloudflare
192
+ * data-residency jurisdiction. Pass the same value as the worker's
193
+ * `jurisdiction` so scheduled state co-resides with app data. Omit for the
194
+ * un-pinned global namespace.
195
+ */
196
196
  jurisdiction?: DurableObjectJurisdiction;
197
197
  /** Binding to the `SchedulerDO` durable object namespace. */
198
198
  namespace: DurableObjectNamespaceLike;
199
199
  /**
200
- * Origin where the Worker is mounted. SchedulerDO uses this base URL when
201
- * dispatching scheduled functions back to the Worker on alarm fire.
202
- */
200
+ * Origin where the Worker is mounted. SchedulerDO uses this base URL when
201
+ * dispatching scheduled functions back to the Worker on alarm fire.
202
+ */
203
203
  originUrl: string;
204
204
  }
205
205
  /** Per-enqueue options for a {@link Workpool}. Extends {@link RunOptions} minus the implicit `pool` (the pool sets that). */
@@ -212,41 +212,41 @@ interface EnqueueOptions {
212
212
  shardKey?: string;
213
213
  }
214
214
  /**
215
- * Options for `createWorkpool`. Mirrors {@link LunoraSchedulerOptions}
216
- * (same `namespace` / `originUrl` / `instanceName`) plus the bounded-concurrency
217
- * controls. A workpool is a NAMED logical pool inside the existing SchedulerDO —
218
- * it needs no extra Durable Object or wrangler binding beyond the SchedulerDO
219
- * the scheduler already uses.
220
- */
215
+ * Options for `createWorkpool`. Mirrors {@link LunoraSchedulerOptions}
216
+ * (same `namespace` / `originUrl` / `instanceName`) plus the bounded-concurrency
217
+ * controls. A workpool is a NAMED logical pool inside the existing SchedulerDO —
218
+ * it needs no extra Durable Object or wrangler binding beyond the SchedulerDO
219
+ * the scheduler already uses.
220
+ */
221
221
  interface WorkpoolOptions extends LunoraSchedulerOptions {
222
222
  /**
223
- * Maximum number of jobs from this pool that may be in flight at once.
224
- * Excess enqueues are persisted and drain as slots free. Must be a positive
225
- * integer.
226
- */
223
+ * Maximum number of jobs from this pool that may be in flight at once.
224
+ * Excess enqueues are persisted and drain as slots free. Must be a positive
225
+ * integer.
226
+ */
227
227
  maxConcurrency: number;
228
228
  /**
229
- * Pool name — the concurrency counter is keyed by this inside the
230
- * SchedulerDO storage (`pool:&lt;name>`). Default `default`.
231
- */
229
+ * Pool name — the concurrency counter is keyed by this inside the
230
+ * SchedulerDO storage (`pool:&lt;name>`). Default `default`.
231
+ */
232
232
  name?: string;
233
233
  }
234
234
  /**
235
- * Bounded-concurrency action queue (Lunora equivalent of `@convex-dev/workpool`).
236
- * Built on the existing SchedulerDO: `enqueue` schedules a job tagged with this
237
- * pool's name; the DO caps simultaneous dispatch at `maxConcurrency` and queues
238
- * the rest durably.
239
- */
235
+ * Bounded-concurrency action queue (Lunora equivalent of `@convex-dev/workpool`).
236
+ * Built on the existing SchedulerDO: `enqueue` schedules a job tagged with this
237
+ * pool's name; the DO caps simultaneous dispatch at `maxConcurrency` and queues
238
+ * the rest durably.
239
+ */
240
240
  interface Workpool {
241
241
  /** Cancel a queued/in-flight pool job by id. */
242
242
  cancel: (id: string) => Promise<{
243
243
  cancelled: boolean;
244
244
  }>;
245
245
  /**
246
- * Enqueue `function_(args)` into the pool. Resolves with the durable job id
247
- * and the time it was scheduled for (it may not run immediately if the pool
248
- * is at capacity).
249
- */
246
+ * Enqueue `function_(args)` into the pool. Resolves with the durable job id
247
+ * and the time it was scheduled for (it may not run immediately if the pool
248
+ * is at capacity).
249
+ */
250
250
  enqueue: <F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: EnqueueOptions) => Promise<{
251
251
  id: string;
252
252
  scheduledFor: number;
@@ -320,11 +320,11 @@ interface QueueWorkpoolOptions {
320
320
  queue: QueueLike<QueueJob>;
321
321
  }
322
322
  /**
323
- * Queues-backed producer: enqueue function dispatches onto a Cloudflare Queue.
324
- * Concurrency, retries, and dead-lettering are configured on the queue consumer
325
- * in `wrangler.jsonc` (`max_concurrency` / `max_retries` / `dead_letter_queue`),
326
- * not here — that's the whole point of using Queues over the DO workpool.
327
- */
323
+ * Queues-backed producer: enqueue function dispatches onto a Cloudflare Queue.
324
+ * Concurrency, retries, and dead-lettering are configured on the queue consumer
325
+ * in `wrangler.jsonc` (`max_concurrency` / `max_retries` / `dead_letter_queue`),
326
+ * not here — that's the whole point of using Queues over the DO workpool.
327
+ */
328
328
  interface QueueWorkpool {
329
329
  /** Enqueue a single `fn(args)` dispatch. */
330
330
  enqueue: <F extends FunctionReference>(function_: F, args: ArgsOf<F>, options?: QueueEnqueueOptions) => Promise<void>;
@@ -352,41 +352,41 @@ interface HttpDispatcherOptions {
352
352
  originUrl: string;
353
353
  }
354
354
  /**
355
- * Client-side scheduler — forwards `runAfter` / `runAt` / `cancel` calls to a
356
- * `SchedulerDO` over HTTP. The DO owns the alarm and the storage; this is a
357
- * thin RPC wrapper.
358
- */
355
+ * Client-side scheduler — forwards `runAfter` / `runAt` / `cancel` calls to a
356
+ * `SchedulerDO` over HTTP. The DO owns the alarm and the storage; this is a
357
+ * thin RPC wrapper.
358
+ */
359
359
  declare const createScheduler: (options: LunoraSchedulerOptions) => Scheduler;
360
360
  /**
361
- * Bounded-concurrency action queue — the Lunora equivalent of
362
- * `@convex-dev/workpool`. Mirrors `createScheduler`'s `namespace` /
363
- * `originUrl` / `instanceName` options and is built on the SAME `SchedulerDO`:
364
- * a workpool is just a NAMED logical pool inside that DO (concurrency counter
365
- * keyed by {@link WorkpoolOptions.name} under the `pool:&lt;name>` storage key).
366
- * It needs no extra Durable Object or wrangler binding beyond the SchedulerDO
367
- * the scheduler already uses.
368
- *
369
- * `enqueue` schedules a job tagged with this pool; the DO dispatches at most
370
- * `maxConcurrency` of the pool's jobs at once and queues the rest durably,
371
- * draining them as the runtime reports completions (`POST /complete`).
372
- *
373
- * ```ts
374
- * const pool = createWorkpool({ namespace: env.SCHEDULER, originUrl, maxConcurrency: 5 });
375
- * await pool.enqueue(internal.stripe.sync, { invoiceId }, { retry: { maxAttempts: 3 } });
376
- * ```
377
- *
378
- * Why not Cloudflare Queues? Queues natively cover concurrency-capped, retried,
379
- * dead-lettered, delayed dispatch (`max_concurrency`, `max_retries`,
380
- * `retry({ delaySeconds })`, `dead_letter_queue`), and are the right tool when
381
- * you just want to rate-limit fire-and-forget background work. This workpool
382
- * deliberately stays on `SchedulerDO` because it offers what a queue can't: a
383
- * hard concurrency cap (the DO is the single serialization point — no
384
- * cross-consumer overshoot), per-job cancellation, and per-job status
385
- * introspection, all keyed by a stable job id. Reach for Queues when you don't
386
- * need those; reach for this when you do. Either way, do NOT grow multi-step
387
- * orchestration on top of this — that's Cloudflare **Workflows** (`step.do` /
388
- * `step.sleep` / `step.waitForEvent`).
389
- */
361
+ * Bounded-concurrency action queue — the Lunora equivalent of
362
+ * `@convex-dev/workpool`. Mirrors `createScheduler`'s `namespace` /
363
+ * `originUrl` / `instanceName` options and is built on the SAME `SchedulerDO`:
364
+ * a workpool is just a NAMED logical pool inside that DO (concurrency counter
365
+ * keyed by {@link WorkpoolOptions.name} under the `pool:&lt;name>` storage key).
366
+ * It needs no extra Durable Object or wrangler binding beyond the SchedulerDO
367
+ * the scheduler already uses.
368
+ *
369
+ * `enqueue` schedules a job tagged with this pool; the DO dispatches at most
370
+ * `maxConcurrency` of the pool's jobs at once and queues the rest durably,
371
+ * draining them as the runtime reports completions (`POST /complete`).
372
+ *
373
+ * ```ts
374
+ * const pool = createWorkpool({ namespace: env.SCHEDULER, originUrl, maxConcurrency: 5 });
375
+ * await pool.enqueue(internal.stripe.sync, { invoiceId }, { retry: { maxAttempts: 3 } });
376
+ * ```
377
+ *
378
+ * Why not Cloudflare Queues? Queues natively cover concurrency-capped, retried,
379
+ * dead-lettered, delayed dispatch (`max_concurrency`, `max_retries`,
380
+ * `retry({ delaySeconds })`, `dead_letter_queue`), and are the right tool when
381
+ * you just want to rate-limit fire-and-forget background work. This workpool
382
+ * deliberately stays on `SchedulerDO` because it offers what a queue can't: a
383
+ * hard concurrency cap (the DO is the single serialization point — no
384
+ * cross-consumer overshoot), per-job cancellation, and per-job status
385
+ * introspection, all keyed by a stable job id. Reach for Queues when you don't
386
+ * need those; reach for this when you do. Either way, do NOT grow multi-step
387
+ * orchestration on top of this — that's Cloudflare **Workflows** (`step.do` /
388
+ * `step.sleep` / `step.waitForEvent`).
389
+ */
390
390
  declare const createWorkpool: (options: WorkpoolOptions) => Workpool;
391
391
  interface CronTriggerOptions {
392
392
  /** Args passed to the function. */
@@ -408,10 +408,10 @@ interface CronTriggerSnippet {
408
408
  wranglerJsonc: string;
409
409
  }
410
410
  /**
411
- * Produces the wrangler.jsonc fragment + dispatcher metadata for a recurring
412
- * function. The actual cron handler is mounted by `@lunora/runtime` — we only
413
- * emit the configuration here.
414
- */
411
+ * Produces the wrangler.jsonc fragment + dispatcher metadata for a recurring
412
+ * function. The actual cron handler is mounted by `@lunora/runtime` — we only
413
+ * emit the configuration here.
414
+ */
415
415
  declare const createCronTrigger: (options: CronTriggerOptions) => CronTriggerSnippet;
416
416
  /** Sub-day recurrence. Exactly one unit must be provided. */
417
417
  interface IntervalSchedule {
@@ -437,29 +437,29 @@ interface MonthlySchedule extends DailySchedule {
437
437
  day: number;
438
438
  }
439
439
  /**
440
- * One registered cron job, normalized to a compiled cron expression. Shared
441
- * verbatim with `@lunora/codegen` (which lifts the same fields out of the AST)
442
- * and the runtime dispatcher — keep the shape stable across all three.
443
- */
440
+ * One registered cron job, normalized to a compiled cron expression. Shared
441
+ * verbatim with `@lunora/codegen` (which lifts the same fields out of the AST)
442
+ * and the runtime dispatcher — keep the shape stable across all three.
443
+ */
444
444
  interface CronJob {
445
445
  /** Args forwarded to the function (or, for a workflow target, used as its `params`) on each fire. */
446
446
  args: Record<string, unknown>;
447
447
  /** Compiled standard cron expression, e.g. `"0 9 * * *"`. */
448
448
  cron: string;
449
449
  /**
450
- * `__lunoraRef` of the target function. Present for a function target;
451
- * absent when the job targets a workflow ({@link CronJob.workflow} instead).
452
- */
450
+ * `__lunoraRef` of the target function. Present for a function target;
451
+ * absent when the job targets a workflow ({@link CronJob.workflow} instead).
452
+ */
453
453
  functionPath?: string;
454
454
  /** Human-readable identifier — must be unique within one `cronJobs()`. */
455
455
  name: string;
456
456
  /**
457
- * Set when the job targets a durable workflow rather than a function: the
458
- * workflow's stable name (`defineWorkflow({ name })`) when one was declared,
459
- * otherwise `""`. `@lunora/codegen` statically resolves the concrete
460
- * `lunora/workflows.ts` export + its `WORKFLOW_*` binding for the emitted
461
- * dispatch map, so this authoring-time value is informational only.
462
- */
457
+ * Set when the job targets a durable workflow rather than a function: the
458
+ * workflow's stable name (`defineWorkflow({ name })`) when one was declared,
459
+ * otherwise `""`. `@lunora/codegen` statically resolves the concrete
460
+ * `lunora/workflows.ts` export + its `WORKFLOW_*` binding for the emitted
461
+ * dispatch map, so this authoring-time value is informational only.
462
+ */
463
463
  workflow?: string;
464
464
  }
465
465
  /** The ergonomic builder methods, excluding the raw `.cron` escape hatch. */
@@ -467,23 +467,23 @@ type CronScheduleKind = "daily" | "interval" | "monthly" | "weekly";
467
467
  /** The ergonomic schedule kinds as a runtime set (codegen reads this to detect cron builder methods). */
468
468
  declare const CRON_SCHEDULE_KINDS: ReadonlySet<CronScheduleKind>;
469
469
  /**
470
- * Compile one of the ergonomic schedule forms into a standard cron expression.
471
- * Exposed as a pure function so `@lunora/codegen` can reuse the exact same
472
- * compilation when it statically lifts a `crons.{kind}(...)` call out of the
473
- * AST — codegen imports this directly (no duplicated mirror).
474
- */
470
+ * Compile one of the ergonomic schedule forms into a standard cron expression.
471
+ * Exposed as a pure function so `@lunora/codegen` can reuse the exact same
472
+ * compilation when it statically lifts a `crons.{kind}(...)` call out of the
473
+ * AST — codegen imports this directly (no duplicated mirror).
474
+ */
475
475
  declare const compileCronSchedule: (kind: CronScheduleKind, schedule: DailySchedule | IntervalSchedule | MonthlySchedule | WeeklySchedule) => string;
476
476
  /**
477
- * Builder returned by {@link cronJobs}. Each method registers one recurring
478
- * job; the compiled expression is validated immediately so authoring mistakes
479
- * surface at definition time rather than at codegen.
480
- */
477
+ * Builder returned by {@link cronJobs}. Each method registers one recurring
478
+ * job; the compiled expression is validated immediately so authoring mistakes
479
+ * surface at definition time rather than at codegen.
480
+ */
481
481
  interface CronJobsBuilder {
482
482
  /**
483
- * Raw cron expression escape hatch (5- or 6-field, full cron-parser grammar).
484
- * The target may be a function (`internal.file.fn`) or a durable workflow
485
- * (`workflows.&lt;name>`); a workflow's `args` are inferred from its `params`.
486
- */
483
+ * Raw cron expression escape hatch (5- or 6-field, full cron-parser grammar).
484
+ * The target may be a function (`internal.file.fn`) or a durable workflow
485
+ * (`workflows.&lt;name>`); a workflow's `args` are inferred from its `params`.
486
+ */
487
487
  cron: <T extends CronTarget>(name: string, cronExpr: string, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
488
488
  /** Daily at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.&lt;name>`). */
489
489
  daily: <T extends CronTarget>(name: string, schedule: DailySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
@@ -497,41 +497,41 @@ interface CronJobsBuilder {
497
497
  weekly: <T extends CronTarget>(name: string, schedule: WeeklySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
498
498
  }
499
499
  /**
500
- * Create a code-first cron registry. The returned builder is chainable;
501
- * codegen discovers a `lunora/crons.ts` default export by AST, not a runtime
502
- * brand.
503
- */
500
+ * Create a code-first cron registry. The returned builder is chainable;
501
+ * codegen discovers a `lunora/crons.ts` default export by AST, not a runtime
502
+ * brand.
503
+ */
504
504
  declare const cronJobs: () => CronJobsBuilder;
505
505
  /**
506
- * Build a Queues producer that enqueues Lunora function dispatches. Concurrency
507
- * and retry policy live on the consumer's `wrangler.jsonc` config, not here.
508
- */
506
+ * Build a Queues producer that enqueues Lunora function dispatches. Concurrency
507
+ * and retry policy live on the consumer's `wrangler.jsonc` config, not here.
508
+ */
509
509
  declare const createQueueWorkpool: (options: QueueWorkpoolOptions) => QueueWorkpool;
510
510
  /**
511
- * Wrap a {@link QueueDispatch} into a Cloudflare `queue()` consumer handler.
512
- *
513
- * Each message is dispatched independently (concurrently across the batch). On
514
- * success the message is `ack()`-ed; on any failure — a thrown dispatcher or a
515
- * structurally-invalid body — it is `retry()`-ed, so Queues' own `max_retries`
516
- * + `dead_letter_queue` settings decide when to give up. Nothing is silently
517
- * dropped: a permanently-bad message rides retries into the dead-letter queue
518
- * where you can inspect it.
519
- */
511
+ * Wrap a {@link QueueDispatch} into a Cloudflare `queue()` consumer handler.
512
+ *
513
+ * Each message is dispatched independently (concurrently across the batch). On
514
+ * success the message is `ack()`-ed; on any failure — a thrown dispatcher or a
515
+ * structurally-invalid body — it is `retry()`-ed, so Queues' own `max_retries`
516
+ * + `dead_letter_queue` settings decide when to give up. Nothing is silently
517
+ * dropped: a permanently-bad message rides retries into the dead-letter queue
518
+ * where you can inspect it.
519
+ */
520
520
  declare const createQueueConsumer: (options: QueueConsumerOptions) => ((batch: MessageBatchLike) => Promise<void>);
521
521
  /**
522
- * Default {@link QueueDispatch}: POST each job to the Worker's
523
- * `/_lunora/scheduler/dispatch` endpoint (the same path SchedulerDO dispatches
524
- * through), authenticated with the admin bearer. A non-2xx response throws so
525
- * the consumer retries the message.
526
- */
522
+ * Default {@link QueueDispatch}: POST each job to the Worker's
523
+ * `/_lunora/scheduler/dispatch` endpoint (the same path SchedulerDO dispatches
524
+ * through), authenticated with the admin bearer. A non-2xx response throws so
525
+ * the consumer retries the message.
526
+ */
527
527
  declare const httpDispatcher: (options: HttpDispatcherOptions) => QueueDispatch;
528
528
  /**
529
- * Minimal projection of `DurableObjectState` for the SchedulerDO. Declared
530
- * structurally so unit tests can pass a fake state without booting the
531
- * workers runtime. The WebSocket methods are optional: they back the live
532
- * `/ws` subscription (push the job list on every change) and are absent in the
533
- * storage-only fakes, in which case the DO simply serves no live sockets.
534
- */
529
+ * Minimal projection of `DurableObjectState` for the SchedulerDO. Declared
530
+ * structurally so unit tests can pass a fake state without booting the
531
+ * workers runtime. The WebSocket methods are optional: they back the live
532
+ * `/ws` subscription (push the job list on every change) and are absent in the
533
+ * storage-only fakes, in which case the DO simply serves no live sockets.
534
+ */
535
535
  interface SchedulerDOState {
536
536
  /** Accept a hibernatable server WebSocket (workers `state.acceptWebSocket`). */
537
537
  acceptWebSocket?: (ws: WebSocket) => void;
@@ -554,30 +554,30 @@ interface SchedulerDOState {
554
554
  interface SchedulerEnv {
555
555
  [key: string]: unknown;
556
556
  /**
557
- * Fallback bearer token attached to the dispatch when
558
- * {@link SchedulerEnv.LUNORA_SCHEDULER_SECRET} is not configured. Sent as
559
- * `authorization: Bearer &lt;token>`.
560
- */
557
+ * Fallback bearer token attached to the dispatch when
558
+ * {@link SchedulerEnv.LUNORA_SCHEDULER_SECRET} is not configured. Sent as
559
+ * `authorization: Bearer &lt;token>`.
560
+ */
561
561
  LUNORA_ADMIN_TOKEN?: string;
562
562
  /**
563
- * Base URL where the Worker is mounted. SchedulerDO uses this at dispatch
564
- * time to call back into the Worker. Read at fire time (NOT taken from the
565
- * request body) to prevent SSRF via a forged `originUrl` field.
566
- */
563
+ * Base URL where the Worker is mounted. SchedulerDO uses this at dispatch
564
+ * time to call back into the Worker. Read at fire time (NOT taken from the
565
+ * request body) to prevent SSRF via a forged `originUrl` field.
566
+ */
567
567
  LUNORA_ORIGIN_URL?: string;
568
568
  /**
569
- * Shared secret used to HMAC-sign the dispatch body so the runtime receiver
570
- * can authenticate the call (header `x-lunora-scheduler-signature`). Without
571
- * it the dispatch is sent unsigned (optionally bearer-authenticated via
572
- * {@link SchedulerEnv.LUNORA_ADMIN_TOKEN}).
573
- */
569
+ * Shared secret used to HMAC-sign the dispatch body so the runtime receiver
570
+ * can authenticate the call (header `x-lunora-scheduler-signature`). Without
571
+ * it the dispatch is sent unsigned (optionally bearer-authenticated via
572
+ * {@link SchedulerEnv.LUNORA_ADMIN_TOKEN}).
573
+ */
574
574
  LUNORA_SCHEDULER_SECRET?: string;
575
575
  }
576
576
  /**
577
- * One pool's live backlog, as surfaced by `GET /status`. `inFlight`/
578
- * `maxConcurrency` mirror the durable {@link PoolState} semaphore; `queued`
579
- * is the number of pending (not-yet-dispatched) jobs routed to this pool.
580
- */
577
+ * One pool's live backlog, as surfaced by `GET /status`. `inFlight`/
578
+ * `maxConcurrency` mirror the durable {@link PoolState} semaphore; `queued`
579
+ * is the number of pending (not-yet-dispatched) jobs routed to this pool.
580
+ */
581
581
  interface SchedulerPoolStatus {
582
582
  /** Jobs currently dispatched-but-not-yet-completed (the held slots). */
583
583
  inFlight: number;
@@ -589,10 +589,10 @@ interface SchedulerPoolStatus {
589
589
  queued: number;
590
590
  }
591
591
  /**
592
- * App-level scheduler backlog, as returned by `GET /status`. `pools` carries
593
- * the per-pool breakdown; `backlog` and `inFlight` are the app-wide sums of
594
- * `queued` and `inFlight` across every pool — the SLO view's headline numbers.
595
- */
592
+ * App-level scheduler backlog, as returned by `GET /status`. `pools` carries
593
+ * the per-pool breakdown; `backlog` and `inFlight` are the app-wide sums of
594
+ * `queued` and `inFlight` across every pool — the SLO view's headline numbers.
595
+ */
596
596
  interface SchedulerStatus {
597
597
  /** Sum of every pool's `queued` count — the total pending backlog. */
598
598
  backlog: number;
@@ -602,58 +602,58 @@ interface SchedulerStatus {
602
602
  pools: SchedulerPoolStatus[];
603
603
  }
604
604
  /**
605
- * Durable Object that stores pending scheduled invocations sorted by their
606
- * `scheduledFor` time and fires them via HTTP on alarm. Storage layout:
607
- * `id:&lt;id>` maps to {@link ScheduleRecord}; `t:&lt;paddedTime>:&lt;id>` maps to the
608
- * id (used as a sorted index).
609
- *
610
- * On every mutation the DO recomputes the earliest pending task and updates
611
- * the alarm via `state.storage.setAlarm(time)`.
612
- */
605
+ * Durable Object that stores pending scheduled invocations sorted by their
606
+ * `scheduledFor` time and fires them via HTTP on alarm. Storage layout:
607
+ * `id:&lt;id>` maps to {@link ScheduleRecord}; `t:&lt;paddedTime>:&lt;id>` maps to the
608
+ * id (used as a sorted index).
609
+ *
610
+ * On every mutation the DO recomputes the earliest pending task and updates
611
+ * the alarm via `state.storage.setAlarm(time)`.
612
+ */
613
613
  declare class SchedulerDO {
614
614
  private static indexKey;
615
615
  private static json;
616
616
  private static error;
617
617
  /**
618
- * Resolve the effective retry parameters for a record: its per-job
619
- * {@link RetryPolicy} merged over the DO's built-in defaults. Callers that
620
- * never set `record.retry` get today's behaviour verbatim
621
- * (`maxAttempts: 5`, exponential, `baseMs: 30_000`, no ceiling).
622
- */
618
+ * Resolve the effective retry parameters for a record: its per-job
619
+ * {@link RetryPolicy} merged over the DO's built-in defaults. Callers that
620
+ * never set `record.retry` get today's behaviour verbatim
621
+ * (`maxAttempts: 5`, exponential, `baseMs: 30_000`, no ceiling).
622
+ */
623
623
  private static resolveRetry;
624
624
  /** Clamp an untrusted `maxConcurrency` to a positive integer, else fall back. */
625
625
  private static normalizeConcurrency;
626
626
  /**
627
- * Sanitize an untrusted retry policy from the wire into a `RetryPolicy` (or
628
- * `undefined` when nothing valid was provided). Keeps obviously-bad values
629
- * out of storage so {@link SchedulerDO.resolveRetry} never has to re-guard.
630
- * @returns The normalized policy, or `undefined` if no valid policy was found.
631
- */
627
+ * Sanitize an untrusted retry policy from the wire into a `RetryPolicy` (or
628
+ * `undefined` when nothing valid was provided). Keeps obviously-bad values
629
+ * out of storage so {@link SchedulerDO.resolveRetry} never has to re-guard.
630
+ * @returns The normalized policy, or `undefined` if no valid policy was found.
631
+ */
632
632
  private static normalizeRetry;
633
633
  /**
634
- * Idempotently release the slot held by `jobId`, returning the updated
635
- * {@link PoolState} (pure — the caller persists it). A duplicate release for
636
- * an id that no longer holds a slot is a no-op, so an at-least-once
637
- * `/complete` (or a complete racing a failed-kick release) can never push
638
- * `inFlight` below the true number of running jobs and oversubscribe the
639
- * pool. Pools persisted before `inFlightIds` existed fall back to a clamped
640
- * counter decrement.
641
- */
634
+ * Idempotently release the slot held by `jobId`, returning the updated
635
+ * {@link PoolState} (pure — the caller persists it). A duplicate release for
636
+ * an id that no longer holds a slot is a no-op, so an at-least-once
637
+ * `/complete` (or a complete racing a failed-kick release) can never push
638
+ * `inFlight` below the true number of running jobs and oversubscribe the
639
+ * pool. Pools persisted before `inFlightIds` existed fall back to a clamped
640
+ * counter decrement.
641
+ */
642
642
  private static releaseSlot;
643
643
  /**
644
- * Best-effort release with no job id (legacy `/complete` payloads). Drops one
645
- * tracked id if the set exists, else clamps the counter. Less precise than
646
- * {@link SchedulerDO.releaseSlot} — a duplicate id-less complete CAN
647
- * over-release — but every current client sends the id, so this is the
648
- * compatibility shim, not the hot path.
649
- */
644
+ * Best-effort release with no job id (legacy `/complete` payloads). Drops one
645
+ * tracked id if the set exists, else clamps the counter. Less precise than
646
+ * {@link SchedulerDO.releaseSlot} — a duplicate id-less complete CAN
647
+ * over-release — but every current client sends the id, so this is the
648
+ * compatibility shim, not the hot path.
649
+ */
650
650
  private static releaseFirstSlot;
651
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
- */
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
657
  private static resolveScheduleTarget;
658
658
  protected readonly state: SchedulerDOState;
659
659
  protected readonly env: SchedulerEnv;
@@ -662,192 +662,192 @@ declare class SchedulerDO {
662
662
  /** Called by the Workers runtime when the alarm previously set by `_rescheduleAlarm()` fires. */
663
663
  alarm(): Promise<void>;
664
664
  /**
665
- * Internal dispatch hook; overridden in unit tests to capture the outgoing
666
- * request. Returns `true` ONLY on an explicit 2xx response (`response.ok`).
667
- * Anything else — a network failure, a 5xx, OR a non-2xx such as 404
668
- * (receiver route not mounted) / 401 / 403 / 4xx — returns `false` and
669
- * enters the retry pipeline via {@link recordRetry}. Treating 4xx as
670
- * success used to permanently delete the job; since the receiver may simply
671
- * be missing (404) or transiently failing, we retry rather than silently
672
- * drop. After {@link MAX_RETRY_ATTEMPTS} the record is parked under a
673
- * `dead:` key for inspection — never silently deleted.
674
- *
675
- * The dispatch target is taken from `env.LUNORA_ORIGIN_URL` (NOT from the
676
- * stored record) to prevent SSRF via a forged `originUrl` on the schedule
677
- * request. If that env var is missing at fire time (a deploy/binding
678
- * regression — schedule time already enforced its presence) we return
679
- * `false` so the record is retried rather than silently dropped.
680
- */
665
+ * Internal dispatch hook; overridden in unit tests to capture the outgoing
666
+ * request. Returns `true` ONLY on an explicit 2xx response (`response.ok`).
667
+ * Anything else — a network failure, a 5xx, OR a non-2xx such as 404
668
+ * (receiver route not mounted) / 401 / 403 / 4xx — returns `false` and
669
+ * enters the retry pipeline via {@link recordRetry}. Treating 4xx as
670
+ * success used to permanently delete the job; since the receiver may simply
671
+ * be missing (404) or transiently failing, we retry rather than silently
672
+ * drop. After {@link MAX_RETRY_ATTEMPTS} the record is parked under a
673
+ * `dead:` key for inspection — never silently deleted.
674
+ *
675
+ * The dispatch target is taken from `env.LUNORA_ORIGIN_URL` (NOT from the
676
+ * stored record) to prevent SSRF via a forged `originUrl` on the schedule
677
+ * request. If that env var is missing at fire time (a deploy/binding
678
+ * regression — schedule time already enforced its presence) we return
679
+ * `false` so the record is retried rather than silently dropped.
680
+ */
681
681
  protected dispatch(record: ScheduleRecord): Promise<boolean>;
682
682
  /**
683
- * Claim + drain one due record with per-record fault isolation, so a storage
684
- * throw can never abort the whole alarm pass (which would skip the remaining
685
- * due records and the `rescheduleAlarm()` that re-arms the clock).
686
- *
687
- * Claims the job by deleting its time-index entry BEFORE dispatch (an alarm
688
- * re-fire then won't pick it up again), runs {@link drainRecord}, and on a
689
- * thrown storage op re-asserts the claim so the job stays re-fireable.
690
- *
691
- * A throw reaching here always means the job was NOT dispatched:
692
- * {@link drainRecord} swallows its own post-dispatch cleanup errors and
693
- * returns instead of throwing once a kick succeeds, so every escaping throw
694
- * comes from the pre-dispatch or failed-dispatch paths. We therefore always
695
- * re-assert the time-index claim so a later alarm re-attempts it
696
- * (at-least-once): the claim delete may have removed it and
697
- * recordRetry()/requeuePooled() may not have re-armed it before throwing, and
698
- * re-inserting the same key is idempotent, so a surviving claim is simply
699
- * rewritten to its prior value.
700
- */
683
+ * Claim + drain one due record with per-record fault isolation, so a storage
684
+ * throw can never abort the whole alarm pass (which would skip the remaining
685
+ * due records and the `rescheduleAlarm()` that re-arms the clock).
686
+ *
687
+ * Claims the job by deleting its time-index entry BEFORE dispatch (an alarm
688
+ * re-fire then won't pick it up again), runs {@link drainRecord}, and on a
689
+ * thrown storage op re-asserts the claim so the job stays re-fireable.
690
+ *
691
+ * A throw reaching here always means the job was NOT dispatched:
692
+ * {@link drainRecord} swallows its own post-dispatch cleanup errors and
693
+ * returns instead of throwing once a kick succeeds, so every escaping throw
694
+ * comes from the pre-dispatch or failed-dispatch paths. We therefore always
695
+ * re-assert the time-index claim so a later alarm re-attempts it
696
+ * (at-least-once): the claim delete may have removed it and
697
+ * recordRetry()/requeuePooled() may not have re-armed it before throwing, and
698
+ * re-inserting the same key is idempotent, so a surviving claim is simply
699
+ * rewritten to its prior value.
700
+ */
701
701
  private drainRecordGuarded;
702
702
  /**
703
- * Process one due (already index-claimed) record within an alarm drain:
704
- * apply the workpool concurrency gate, dispatch, and settle the result.
705
- * A saturated pool re-arms the job (backpressure, no attempt charged); a
706
- * free slot is reserved durably before dispatch and released immediately if
707
- * the kick fails (success holds it until the runtime reports completion).
708
- * Success clears the `id:`/`retry:` rows; failure routes to
709
- * {@link recordRetry}. Pool state is read FRESH from storage per record (see
710
- * {@link reservePoolSlot}) and never held across the dispatch() await, so a
711
- * concurrent /complete landing mid-dispatch can't be clobbered.
712
- * Once a kick succeeds, post-dispatch cleanup (clearing the `id:`/`retry:`
713
- * rows) is swallowed rather than allowed to throw, so a successful dispatch
714
- * NEVER propagates an error to {@link drainRecordGuarded}: every throw that
715
- * escapes comes from the pre-dispatch or failed-dispatch paths, where the job
716
- * is still re-fireable and the guard safely re-claims the time index.
717
- * @returns `true` only when the record was successfully dispatched (a 2xx
718
- * kick); `false` on pool backpressure or a failed dispatch (the job is still
719
- * re-fireable — already re-armed here). The value is informational (the guard
720
- * branches on throw/no-throw, not on this boolean).
721
- */
703
+ * Process one due (already index-claimed) record within an alarm drain:
704
+ * apply the workpool concurrency gate, dispatch, and settle the result.
705
+ * A saturated pool re-arms the job (backpressure, no attempt charged); a
706
+ * free slot is reserved durably before dispatch and released immediately if
707
+ * the kick fails (success holds it until the runtime reports completion).
708
+ * Success clears the `id:`/`retry:` rows; failure routes to
709
+ * {@link recordRetry}. Pool state is read FRESH from storage per record (see
710
+ * {@link reservePoolSlot}) and never held across the dispatch() await, so a
711
+ * concurrent /complete landing mid-dispatch can't be clobbered.
712
+ * Once a kick succeeds, post-dispatch cleanup (clearing the `id:`/`retry:`
713
+ * rows) is swallowed rather than allowed to throw, so a successful dispatch
714
+ * NEVER propagates an error to {@link drainRecordGuarded}: every throw that
715
+ * escapes comes from the pre-dispatch or failed-dispatch paths, where the job
716
+ * is still re-fireable and the guard safely re-claims the time index.
717
+ * @returns `true` only when the record was successfully dispatched (a 2xx
718
+ * kick); `false` on pool backpressure or a failed dispatch (the job is still
719
+ * re-fireable — already re-armed here). The value is informational (the guard
720
+ * branches on throw/no-throw, not on this boolean).
721
+ */
722
722
  private drainRecord;
723
723
  /**
724
- * Concurrency gate for a pooled record. Returns `false` (and re-arms the
725
- * job via {@link requeuePooled}) when the pool is at `maxConcurrency`;
726
- * otherwise reserves a slot durably and returns `true`. Non-pooled records
727
- * always return `true` without touching any pool state.
728
- *
729
- * The pool row is read FRESH from storage on every call — never cached
730
- * across the drain. Each reservation durably `savePool()`s before the next
731
- * record runs, so a same-pass reservation is still visible to the next
732
- * record's fresh read (the budget carries forward); and because dispatch()
733
- * awaits an outbound fetch between records, a concurrent /complete that
734
- * decrements the row mid-drain IS reflected here instead of being clobbered
735
- * by a stale in-memory copy (which would leak a slot permanently).
736
- */
724
+ * Concurrency gate for a pooled record. Returns `false` (and re-arms the
725
+ * job via {@link requeuePooled}) when the pool is at `maxConcurrency`;
726
+ * otherwise reserves a slot durably and returns `true`. Non-pooled records
727
+ * always return `true` without touching any pool state.
728
+ *
729
+ * The pool row is read FRESH from storage on every call — never cached
730
+ * across the drain. Each reservation durably `savePool()`s before the next
731
+ * record runs, so a same-pass reservation is still visible to the next
732
+ * record's fresh read (the budget carries forward); and because dispatch()
733
+ * awaits an outbound fetch between records, a concurrent /complete that
734
+ * decrements the row mid-drain IS reflected here instead of being clobbered
735
+ * by a stale in-memory copy (which would leak a slot permanently).
736
+ */
737
737
  private reservePoolSlot;
738
738
  /**
739
- * Accept a hibernatable live subscription to the job list. The scheduler has
740
- * exactly one subscription shape (the whole list), so there's no per-socket
741
- * registry or dependency tracking — every accepted socket gets the full list
742
- * on connect and on every change. The worker is responsible for gating the
743
- * upgrade behind the admin token before it reaches here.
744
- */
739
+ * Accept a hibernatable live subscription to the job list. The scheduler has
740
+ * exactly one subscription shape (the whole list), so there's no per-socket
741
+ * registry or dependency tracking — every accepted socket gets the full list
742
+ * on connect and on every change. The worker is responsible for gating the
743
+ * upgrade behind the admin token before it reaches here.
744
+ */
745
745
  private handleWebSocketUpgrade;
746
746
  /**
747
- * Re-list the jobs and push them to every connected subscriber. Called after
748
- * any change (schedule / cancel / alarm-fire) so live studios reflect it
749
- * immediately. A no-op when the runtime doesn't support hibernated sockets.
750
- */
747
+ * Re-list the jobs and push them to every connected subscriber. Called after
748
+ * any change (schedule / cancel / alarm-fire) so live studios reflect it
749
+ * immediately. A no-op when the runtime doesn't support hibernated sockets.
750
+ */
751
751
  private broadcastChange;
752
752
  /** The current pending job records (shared by `/list` and the live channel). */
753
753
  private listRecords;
754
754
  /**
755
- * HMAC-SHA-256 sign the dispatch body with `env.LUNORA_SCHEDULER_SECRET`,
756
- * returning a base64url signature, or `undefined` when no secret is
757
- * configured. Mirrors `@lunora/storage`'s signed-URL HMAC pattern (WebCrypto
758
- * `crypto.subtle`, available in workerd).
759
- */
755
+ * HMAC-SHA-256 sign the dispatch body with `env.LUNORA_SCHEDULER_SECRET`,
756
+ * returning a base64url signature, or `undefined` when no secret is
757
+ * configured. Mirrors `@lunora/storage`'s signed-URL HMAC pattern (WebCrypto
758
+ * `crypto.subtle`, available in workerd).
759
+ */
760
760
  private signDispatch;
761
761
  /**
762
- * Move a failed record into the retry pipeline with configurable backoff.
763
- * The retry budget/backoff comes from the record's {@link RetryPolicy}
764
- * (falling back to the DO defaults); on exhaustion the record is parked
765
- * under a `dead:` key for manual inspection.
766
- */
762
+ * Move a failed record into the retry pipeline with configurable backoff.
763
+ * The retry budget/backoff comes from the record's {@link RetryPolicy}
764
+ * (falling back to the DO defaults); on exhaustion the record is parked
765
+ * under a `dead:` key for manual inspection.
766
+ */
767
767
  private recordRetry;
768
768
  /** Read the durable `pool:&lt;name>` row, defaulting to a fresh `inFlight: 0` pool. */
769
769
  private loadPool;
770
770
  private savePool;
771
771
  /**
772
- * Re-arm a pooled job that couldn't run because its pool was at capacity.
773
- * No attempt is charged (this is backpressure, not a failure): the job is
774
- * pushed `POOL_BACKPRESSURE_DELAY_MS` into the future so a later alarm
775
- * drains it once a slot frees, keeping its `id:` header and retry policy.
776
- */
772
+ * Re-arm a pooled job that couldn't run because its pool was at capacity.
773
+ * No attempt is charged (this is backpressure, not a failure): the job is
774
+ * pushed `POOL_BACKPRESSURE_DELAY_MS` into the future so a later alarm
775
+ * drains it once a slot frees, keeping its `id:` header and retry policy.
776
+ */
777
777
  private requeuePooled;
778
778
  /**
779
- * Release a pool slot when the runtime reports an action finished. This is
780
- * the durable-semaphore decrement: dispatch() only KICKS the action and
781
- * holds the slot; the runtime calls back here (`POST /complete { id }`) once
782
- * the action settles, freeing the slot for the next queued job. Idempotent
783
- * and safe if the job/pool is already gone.
784
- */
779
+ * Release a pool slot when the runtime reports an action finished. This is
780
+ * the durable-semaphore decrement: dispatch() only KICKS the action and
781
+ * holds the slot; the runtime calls back here (`POST /complete { id }`) once
782
+ * the action settles, freeing the slot for the next queued job. Idempotent
783
+ * and safe if the job/pool is already gone.
784
+ */
785
785
  private handleComplete;
786
786
  /** `GET /pool?name=` — inspect a pool's slot usage + queued count. */
787
787
  private handlePoolStatus;
788
788
  /**
789
- * `GET /status` — the app-level backlog signal that powers the studio's
790
- * SLO view. Enumerates every durable `pool:&lt;name>` row for its `inFlight`/
791
- * `maxConcurrency` semaphore, counts the pending (not-yet-dispatched) jobs
792
- * routed to each pool with the same single-pass scan {@link handlePoolStatus}
793
- * uses, and rolls those up into app-wide `backlog` (sum of `queued`) and
794
- * `inFlight` (sum of held slots) totals.
795
- *
796
- * Pools that have rows but no queued jobs still appear (with `queued: 0`) so
797
- * a saturated-but-idle pool stays visible; a pool that only ever existed as
798
- * queued jobs without a persisted row is unreachable here (the schedule path
799
- * always writes a `pool:&lt;name>` row before the job's header), so a single
800
- * scan over `pool:`/`id:` is sufficient.
801
- */
789
+ * `GET /status` — the app-level backlog signal that powers the studio's
790
+ * SLO view. Enumerates every durable `pool:&lt;name>` row for its `inFlight`/
791
+ * `maxConcurrency` semaphore, counts the pending (not-yet-dispatched) jobs
792
+ * routed to each pool with the same single-pass scan {@link handlePoolStatus}
793
+ * uses, and rolls those up into app-wide `backlog` (sum of `queued`) and
794
+ * `inFlight` (sum of held slots) totals.
795
+ *
796
+ * Pools that have rows but no queued jobs still appear (with `queued: 0`) so
797
+ * a saturated-but-idle pool stays visible; a pool that only ever existed as
798
+ * queued jobs without a persisted row is unreachable here (the schedule path
799
+ * always writes a `pool:&lt;name>` row before the job's header), so a single
800
+ * scan over `pool:`/`id:` is sufficient.
801
+ */
802
802
  private handleStatus;
803
803
  private handleSchedule;
804
804
  private handleCancel;
805
805
  private handleList;
806
806
  /**
807
- * `GET /dead` — list the dead-letter records: jobs that exhausted their
808
- * retry budget ({@link recordRetry}) and were parked under `dead:&lt;id>`
809
- * instead of being silently dropped. These never appear in `/list` (their
810
- * `id:` header is deleted on park), so this is the ONLY way the studio can
811
- * surface — and recover — a permanently-failed job.
812
- */
807
+ * `GET /dead` — list the dead-letter records: jobs that exhausted their
808
+ * retry budget ({@link recordRetry}) and were parked under `dead:&lt;id>`
809
+ * instead of being silently dropped. These never appear in `/list` (their
810
+ * `id:` header is deleted on park), so this is the ONLY way the studio can
811
+ * surface — and recover — a permanently-failed job.
812
+ */
813
813
  private handleDeadList;
814
814
  /**
815
- * `POST /dead/retry { id }` — resurrect a dead-letter record: reset its
816
- * exhausted attempt count to 0 (a fresh retry budget), re-arm it for
817
- * immediate dispatch via the standard time index, and drop the `dead:` row.
818
- * The new `id:` header makes it visible to `/list` and the live `/ws`
819
- * subscription again. A miss is a no-op (`{ retried: false }`).
820
- */
815
+ * `POST /dead/retry { id }` — resurrect a dead-letter record: reset its
816
+ * exhausted attempt count to 0 (a fresh retry budget), re-arm it for
817
+ * immediate dispatch via the standard time index, and drop the `dead:` row.
818
+ * The new `id:` header makes it visible to `/list` and the live `/ws`
819
+ * subscription again. A miss is a no-op (`{ retried: false }`).
820
+ */
821
821
  private handleDeadRetry;
822
822
  /**
823
- * `POST /dead/cancel { id }` — permanently drop a dead-letter record the
824
- * operator has decided not to recover. Returns `{ removed }` (false when
825
- * nothing matched). Idempotent: a repeated purge is a harmless no-op.
826
- */
823
+ * `POST /dead/cancel { id }` — permanently drop a dead-letter record the
824
+ * operator has decided not to recover. Returns `{ removed }` (false when
825
+ * nothing matched). Idempotent: a repeated purge is a harmless no-op.
826
+ */
827
827
  private handleDeadCancel;
828
828
  /**
829
- * Resolve a single pending job by id via a direct `id:&lt;id>` storage read —
830
- * O(1), versus scanning the whole `/list` view. Responds `{ record }` on a
831
- * hit and `{}` on a miss (an absent `record` field — JSON has no `undefined`
832
- * — which the client reads back as `null`).
833
- */
829
+ * Resolve a single pending job by id via a direct `id:&lt;id>` storage read —
830
+ * O(1), versus scanning the whole `/list` view. Responds `{ record }` on a
831
+ * hit and `{}` on a miss (an absent `record` field — JSON has no `undefined`
832
+ * — which the client reads back as `null`).
833
+ */
834
834
  private handleGet;
835
835
  private removeRecord;
836
836
  /**
837
- * Arm the alarm for `scheduledFor` only if it is sooner than the currently
838
- * set alarm (or none is set). Used on the schedule path: inserting a job
839
- * can only ever pull the earliest-pending time *earlier*, never later, so a
840
- * full `t:` rescan is unnecessary unless the new job is the new earliest.
841
- */
837
+ * Arm the alarm for `scheduledFor` only if it is sooner than the currently
838
+ * set alarm (or none is set). Used on the schedule path: inserting a job
839
+ * can only ever pull the earliest-pending time *earlier*, never later, so a
840
+ * full `t:` rescan is unnecessary unless the new job is the new earliest.
841
+ */
842
842
  private armAlarmIfEarlier;
843
843
  private rescheduleAlarm;
844
844
  }
845
845
  /** Standard cron expression (5- or 6-field) or a supported `@macro`, per `cron-parser`. */
846
846
  declare const isValidCronExpression: (schedule: string) => boolean;
847
847
  /**
848
- * Assert a raw cron expression is well-formed, throwing the same shaped error
849
- * both cron surfaces use. The `context` prefix lets callers name the offending
850
- * job (`cron job "send digest"`) vs. the bare trigger.
851
- */
848
+ * Assert a raw cron expression is well-formed, throwing the same shaped error
849
+ * both cron surfaces use. The `context` prefix lets callers name the offending
850
+ * job (`cron job "send digest"`) vs. the bare trigger.
851
+ */
852
852
  declare const assertValidCronExpression: (schedule: string, context?: string) => void;
853
853
  export { type ArgsOf, CRON_SCHEDULE_KINDS, type CronJob, type CronJobsBuilder, type CronScheduleKind, type CronTarget, type CronTriggerOptions, type CronTriggerSnippet, type DailySchedule, type DurableObjectIdLike, type DurableObjectJurisdiction, type DurableObjectNamespaceLike, type DurableObjectStubLike, type EnqueueOptions, type FunctionReference, type HttpDispatcherOptions, type IntervalSchedule, type LunoraSchedulerOptions, type MessageBatchLike, type MonthlySchedule, type QueueConsumerOptions, type QueueDispatch, type QueueEnqueueOptions, type QueueJob, type QueueLike, type QueueMessageLike, type QueueSendOptionsLike, type QueueSendRequestLike, type QueueWorkpool, type QueueWorkpoolOptions, type RetryPolicy, type RunOptions, type ScheduleRecord, type Scheduler, SchedulerDO, type SchedulerDOState, type SchedulerEnv, type SchedulerPoolStatus, type SchedulerStatus, type WeeklySchedule, type WorkflowReference, type Workpool, type WorkpoolOptions, assertValidCronExpression, compileCronSchedule, createCronTrigger, createQueueConsumer, createQueueWorkpool, createScheduler, createWorkpool, cronJobs, httpDispatcher, isValidCronExpression, isWorkflowReference };