@lunora/scheduler 1.0.0-alpha.20 → 1.0.0-alpha.22

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
@@ -18,7 +18,7 @@ type ArgsOf<F extends FunctionReference> = F extends {
18
18
  } ? A : Record<string, unknown>;
19
19
  /**
20
20
  * Typed reference to a Lunora durable workflow — either the generated
21
- * `workflows.&lt;name>` reference object (`_generated/api.ts`, which carries the
21
+ * `workflows.<name>` reference object (`_generated/api.ts`, which carries the
22
22
  * `WORKFLOW_*` binding + export name) or, structurally, a `defineWorkflow()`
23
23
  * result imported directly. Both are matched by the `isLunoraWorkflow` brand and
24
24
  * carry the workflow's `params` in the phantom `__params`, so a `cronJobs()`
@@ -36,7 +36,7 @@ type ArgsOf<F extends FunctionReference> = F extends {
36
36
  interface WorkflowReference<Params = Record<string, unknown>> {
37
37
  /** Phantom carrier for the workflow's `params` type — drives `cronJobs()` arg inference. Never read at runtime. */
38
38
  readonly __params?: Params;
39
- /** The `WORKFLOW_*` binding name (present on a generated `workflows.&lt;name>` ref). */
39
+ /** The `WORKFLOW_*` binding name (present on a generated `workflows.<name>` ref). */
40
40
  readonly binding?: string;
41
41
  readonly isLunoraWorkflow: true;
42
42
  /** The workflow's export/stable name (present on a generated ref; a `defineWorkflow({ name })` override otherwise). */
@@ -161,8 +161,8 @@ interface Scheduler {
161
161
  /**
162
162
  * Schedule `target` to run once, `delayMs` from now. `target` is a function
163
163
  * {@link FunctionReference} (dispatched as a one-shot) or a durable
164
- * {@link WorkflowReference} — the generated `workflows.&lt;name>` /
165
- * `agents.&lt;name>` ref — which starts a fresh instance on fire (args become
164
+ * {@link WorkflowReference} — the generated `workflows.<name>` /
165
+ * `agents.<name>` ref — which starts a fresh instance on fire (args become
166
166
  * its `params`). {@link ScheduleTargetArgs} infers the accepted args from
167
167
  * whichever target was passed.
168
168
  */
@@ -241,7 +241,7 @@ interface WorkpoolOptions extends LunoraSchedulerOptions {
241
241
  maxConcurrency: number;
242
242
  /**
243
243
  * Pool name — the concurrency counter is keyed by this inside the
244
- * SchedulerDO storage (`pool:&lt;name>`). Default `default`.
244
+ * SchedulerDO storage (`pool:<name>`). Default `default`.
245
245
  */
246
246
  name?: string;
247
247
  }
@@ -265,7 +265,7 @@ interface Workpool {
265
265
  id: string;
266
266
  scheduledFor: number;
267
267
  }>;
268
- /** The pool's name (the `pool:&lt;name>` storage key suffix). */
268
+ /** The pool's name (the `pool:<name>` storage key suffix). */
269
269
  readonly name: string;
270
270
  /** Inspect the pool's current state — `inFlight` slots used and the configured `maxConcurrency`. */
271
271
  status: () => Promise<{
@@ -376,7 +376,7 @@ declare const createScheduler: (options: LunoraSchedulerOptions) => Scheduler;
376
376
  * `@convex-dev/workpool`. Mirrors `createScheduler`'s `namespace` /
377
377
  * `originUrl` / `instanceName` options and is built on the SAME `SchedulerDO`:
378
378
  * a workpool is just a NAMED logical pool inside that DO (concurrency counter
379
- * keyed by {@link WorkpoolOptions.name} under the `pool:&lt;name>` storage key).
379
+ * keyed by {@link WorkpoolOptions.name} under the `pool:<name>` storage key).
380
380
  * It needs no extra Durable Object or wrangler binding beyond the SchedulerDO
381
381
  * the scheduler already uses.
382
382
  *
@@ -511,20 +511,20 @@ interface CronJobsBuilder {
511
511
  /**
512
512
  * Raw cron expression escape hatch (5- or 6-field, full cron-parser grammar).
513
513
  * The target may be a function (`internal.file.fn`) or a durable workflow
514
- * (`workflows.&lt;name>`); a workflow's `args` are inferred from its `params`.
514
+ * (`workflows.<name>`); a workflow's `args` are inferred from its `params`.
515
515
  */
516
516
  cron: <T extends CronTarget>(name: string, cronExpr: string, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
517
- /** Daily at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.&lt;name>`). */
517
+ /** Daily at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.<name>`). */
518
518
  daily: <T extends CronTarget>(name: string, schedule: DailySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
519
- /** Hourly at `minuteUTC` past the hour. The target may be a function or a durable workflow (`workflows.&lt;name>`). */
519
+ /** Hourly at `minuteUTC` past the hour. The target may be a function or a durable workflow (`workflows.<name>`). */
520
520
  hourly: <T extends CronTarget>(name: string, schedule: HourlySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
521
- /** Every `{ seconds | minutes | hours }`. The target may be a function or a durable workflow (`workflows.&lt;name>`). */
521
+ /** Every `{ seconds | minutes | hours }`. The target may be a function or a durable workflow (`workflows.<name>`). */
522
522
  interval: <T extends CronTarget>(name: string, schedule: IntervalSchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
523
523
  /** Snapshot of the registered jobs, in declaration order. */
524
524
  jobs: () => ReadonlyArray<CronJob>;
525
- /** Monthly on `day` at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.&lt;name>`). */
525
+ /** Monthly on `day` at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.<name>`). */
526
526
  monthly: <T extends CronTarget>(name: string, schedule: MonthlySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
527
- /** Weekly on `dayOfWeek` at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.&lt;name>`). */
527
+ /** Weekly on `dayOfWeek` at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.<name>`). */
528
528
  weekly: <T extends CronTarget>(name: string, schedule: WeeklySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
529
529
  }
530
530
  /**
@@ -597,7 +597,7 @@ interface SchedulerEnv {
597
597
  /**
598
598
  * Fallback bearer token attached to the dispatch when
599
599
  * {@link SchedulerEnv.LUNORA_SCHEDULER_SECRET} is not configured. Sent as
600
- * `authorization: Bearer &lt;token>`.
600
+ * `authorization: Bearer <token>`.
601
601
  */
602
602
  LUNORA_ADMIN_TOKEN?: string;
603
603
  /**
@@ -624,7 +624,7 @@ interface SchedulerPoolStatus {
624
624
  inFlight: number;
625
625
  /** The pool's concurrency cap. */
626
626
  maxConcurrency: number;
627
- /** The logical workpool name (the `pool:&lt;name>` suffix). */
627
+ /** The logical workpool name (the `pool:<name>` suffix). */
628
628
  name: string;
629
629
  /** Pending jobs routed to this pool but not yet dispatched. */
630
630
  queued: number;
@@ -639,13 +639,13 @@ interface SchedulerStatus {
639
639
  backlog: number;
640
640
  /** Sum of every pool's `inFlight` count — the total held concurrency slots. */
641
641
  inFlight: number;
642
- /** Per-pool backlog breakdown, one entry per `pool:&lt;name>` record. */
642
+ /** Per-pool backlog breakdown, one entry per `pool:<name>` record. */
643
643
  pools: SchedulerPoolStatus[];
644
644
  }
645
645
  /**
646
646
  * Durable Object that stores pending scheduled invocations sorted by their
647
647
  * `scheduledFor` time and fires them via HTTP on alarm. Storage layout:
648
- * `id:&lt;id>` maps to {@link ScheduleRecord}; `t:&lt;paddedTime>:&lt;id>` maps to the
648
+ * `id:<id>` maps to {@link ScheduleRecord}; `t:<paddedTime>:<id>` maps to the
649
649
  * id (used as a sorted index).
650
650
  *
651
651
  * On every mutation the DO recomputes the earliest pending task and updates
@@ -835,7 +835,7 @@ declare class SchedulerDO {
835
835
  * under a `dead:` key for manual inspection.
836
836
  */
837
837
  private recordRetry;
838
- /** Read the durable `pool:&lt;name>` row, defaulting to a fresh `inFlight: 0` pool. */
838
+ /** Read the durable `pool:<name>` row, defaulting to a fresh `inFlight: 0` pool. */
839
839
  private loadPool;
840
840
  private savePool;
841
841
  /**
@@ -857,7 +857,7 @@ declare class SchedulerDO {
857
857
  private handlePoolStatus;
858
858
  /**
859
859
  * `GET /status` — the app-level backlog signal that powers the studio's
860
- * SLO view. Enumerates every durable `pool:&lt;name>` row for its `inFlight`/
860
+ * SLO view. Enumerates every durable `pool:<name>` row for its `inFlight`/
861
861
  * `maxConcurrency` semaphore, counts the pending (not-yet-dispatched) jobs
862
862
  * routed to each pool with the same single-pass scan {@link handlePoolStatus}
863
863
  * uses, and rolls those up into app-wide `backlog` (sum of `queued`) and
@@ -866,7 +866,7 @@ declare class SchedulerDO {
866
866
  * Pools that have rows but no queued jobs still appear (with `queued: 0`) so
867
867
  * a saturated-but-idle pool stays visible; a pool that only ever existed as
868
868
  * queued jobs without a persisted row is unreachable here (the schedule path
869
- * always writes a `pool:&lt;name>` row before the job's header), so a single
869
+ * always writes a `pool:<name>` row before the job's header), so a single
870
870
  * scan over `pool:` plus a cursor loop over `id:` is sufficient.
871
871
  */
872
872
  private handleStatus;
@@ -875,7 +875,7 @@ declare class SchedulerDO {
875
875
  private handleList;
876
876
  /**
877
877
  * `GET /dead` — list the dead-letter records: jobs that exhausted their
878
- * retry budget ({@link recordRetry}) and were parked under `dead:&lt;id>`
878
+ * retry budget ({@link recordRetry}) and were parked under `dead:<id>`
879
879
  * instead of being silently dropped. These never appear in `/list` (their
880
880
  * `id:` header is deleted on park), so this is the ONLY way the studio can
881
881
  * surface — and recover — a permanently-failed job.
@@ -896,7 +896,7 @@ declare class SchedulerDO {
896
896
  */
897
897
  private handleDeadCancel;
898
898
  /**
899
- * Resolve a single pending job by id via a direct `id:&lt;id>` storage read —
899
+ * Resolve a single pending job by id via a direct `id:<id>` storage read —
900
900
  * O(1), versus scanning the whole `/list` view. Responds `{ record }` on a
901
901
  * hit and `{}` on a miss (an absent `record` field — JSON has no `undefined`
902
902
  * — which the client reads back as `null`).
package/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@ type ArgsOf<F extends FunctionReference> = F extends {
18
18
  } ? A : Record<string, unknown>;
19
19
  /**
20
20
  * Typed reference to a Lunora durable workflow — either the generated
21
- * `workflows.&lt;name>` reference object (`_generated/api.ts`, which carries the
21
+ * `workflows.<name>` reference object (`_generated/api.ts`, which carries the
22
22
  * `WORKFLOW_*` binding + export name) or, structurally, a `defineWorkflow()`
23
23
  * result imported directly. Both are matched by the `isLunoraWorkflow` brand and
24
24
  * carry the workflow's `params` in the phantom `__params`, so a `cronJobs()`
@@ -36,7 +36,7 @@ type ArgsOf<F extends FunctionReference> = F extends {
36
36
  interface WorkflowReference<Params = Record<string, unknown>> {
37
37
  /** Phantom carrier for the workflow's `params` type — drives `cronJobs()` arg inference. Never read at runtime. */
38
38
  readonly __params?: Params;
39
- /** The `WORKFLOW_*` binding name (present on a generated `workflows.&lt;name>` ref). */
39
+ /** The `WORKFLOW_*` binding name (present on a generated `workflows.<name>` ref). */
40
40
  readonly binding?: string;
41
41
  readonly isLunoraWorkflow: true;
42
42
  /** The workflow's export/stable name (present on a generated ref; a `defineWorkflow({ name })` override otherwise). */
@@ -161,8 +161,8 @@ interface Scheduler {
161
161
  /**
162
162
  * Schedule `target` to run once, `delayMs` from now. `target` is a function
163
163
  * {@link FunctionReference} (dispatched as a one-shot) or a durable
164
- * {@link WorkflowReference} — the generated `workflows.&lt;name>` /
165
- * `agents.&lt;name>` ref — which starts a fresh instance on fire (args become
164
+ * {@link WorkflowReference} — the generated `workflows.<name>` /
165
+ * `agents.<name>` ref — which starts a fresh instance on fire (args become
166
166
  * its `params`). {@link ScheduleTargetArgs} infers the accepted args from
167
167
  * whichever target was passed.
168
168
  */
@@ -241,7 +241,7 @@ interface WorkpoolOptions extends LunoraSchedulerOptions {
241
241
  maxConcurrency: number;
242
242
  /**
243
243
  * Pool name — the concurrency counter is keyed by this inside the
244
- * SchedulerDO storage (`pool:&lt;name>`). Default `default`.
244
+ * SchedulerDO storage (`pool:<name>`). Default `default`.
245
245
  */
246
246
  name?: string;
247
247
  }
@@ -265,7 +265,7 @@ interface Workpool {
265
265
  id: string;
266
266
  scheduledFor: number;
267
267
  }>;
268
- /** The pool's name (the `pool:&lt;name>` storage key suffix). */
268
+ /** The pool's name (the `pool:<name>` storage key suffix). */
269
269
  readonly name: string;
270
270
  /** Inspect the pool's current state — `inFlight` slots used and the configured `maxConcurrency`. */
271
271
  status: () => Promise<{
@@ -376,7 +376,7 @@ declare const createScheduler: (options: LunoraSchedulerOptions) => Scheduler;
376
376
  * `@convex-dev/workpool`. Mirrors `createScheduler`'s `namespace` /
377
377
  * `originUrl` / `instanceName` options and is built on the SAME `SchedulerDO`:
378
378
  * a workpool is just a NAMED logical pool inside that DO (concurrency counter
379
- * keyed by {@link WorkpoolOptions.name} under the `pool:&lt;name>` storage key).
379
+ * keyed by {@link WorkpoolOptions.name} under the `pool:<name>` storage key).
380
380
  * It needs no extra Durable Object or wrangler binding beyond the SchedulerDO
381
381
  * the scheduler already uses.
382
382
  *
@@ -511,20 +511,20 @@ interface CronJobsBuilder {
511
511
  /**
512
512
  * Raw cron expression escape hatch (5- or 6-field, full cron-parser grammar).
513
513
  * The target may be a function (`internal.file.fn`) or a durable workflow
514
- * (`workflows.&lt;name>`); a workflow's `args` are inferred from its `params`.
514
+ * (`workflows.<name>`); a workflow's `args` are inferred from its `params`.
515
515
  */
516
516
  cron: <T extends CronTarget>(name: string, cronExpr: string, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
517
- /** Daily at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.&lt;name>`). */
517
+ /** Daily at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.<name>`). */
518
518
  daily: <T extends CronTarget>(name: string, schedule: DailySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
519
- /** Hourly at `minuteUTC` past the hour. The target may be a function or a durable workflow (`workflows.&lt;name>`). */
519
+ /** Hourly at `minuteUTC` past the hour. The target may be a function or a durable workflow (`workflows.<name>`). */
520
520
  hourly: <T extends CronTarget>(name: string, schedule: HourlySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
521
- /** Every `{ seconds | minutes | hours }`. The target may be a function or a durable workflow (`workflows.&lt;name>`). */
521
+ /** Every `{ seconds | minutes | hours }`. The target may be a function or a durable workflow (`workflows.<name>`). */
522
522
  interval: <T extends CronTarget>(name: string, schedule: IntervalSchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
523
523
  /** Snapshot of the registered jobs, in declaration order. */
524
524
  jobs: () => ReadonlyArray<CronJob>;
525
- /** Monthly on `day` at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.&lt;name>`). */
525
+ /** Monthly on `day` at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.<name>`). */
526
526
  monthly: <T extends CronTarget>(name: string, schedule: MonthlySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
527
- /** Weekly on `dayOfWeek` at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.&lt;name>`). */
527
+ /** Weekly on `dayOfWeek` at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.<name>`). */
528
528
  weekly: <T extends CronTarget>(name: string, schedule: WeeklySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
529
529
  }
530
530
  /**
@@ -597,7 +597,7 @@ interface SchedulerEnv {
597
597
  /**
598
598
  * Fallback bearer token attached to the dispatch when
599
599
  * {@link SchedulerEnv.LUNORA_SCHEDULER_SECRET} is not configured. Sent as
600
- * `authorization: Bearer &lt;token>`.
600
+ * `authorization: Bearer <token>`.
601
601
  */
602
602
  LUNORA_ADMIN_TOKEN?: string;
603
603
  /**
@@ -624,7 +624,7 @@ interface SchedulerPoolStatus {
624
624
  inFlight: number;
625
625
  /** The pool's concurrency cap. */
626
626
  maxConcurrency: number;
627
- /** The logical workpool name (the `pool:&lt;name>` suffix). */
627
+ /** The logical workpool name (the `pool:<name>` suffix). */
628
628
  name: string;
629
629
  /** Pending jobs routed to this pool but not yet dispatched. */
630
630
  queued: number;
@@ -639,13 +639,13 @@ interface SchedulerStatus {
639
639
  backlog: number;
640
640
  /** Sum of every pool's `inFlight` count — the total held concurrency slots. */
641
641
  inFlight: number;
642
- /** Per-pool backlog breakdown, one entry per `pool:&lt;name>` record. */
642
+ /** Per-pool backlog breakdown, one entry per `pool:<name>` record. */
643
643
  pools: SchedulerPoolStatus[];
644
644
  }
645
645
  /**
646
646
  * Durable Object that stores pending scheduled invocations sorted by their
647
647
  * `scheduledFor` time and fires them via HTTP on alarm. Storage layout:
648
- * `id:&lt;id>` maps to {@link ScheduleRecord}; `t:&lt;paddedTime>:&lt;id>` maps to the
648
+ * `id:<id>` maps to {@link ScheduleRecord}; `t:<paddedTime>:<id>` maps to the
649
649
  * id (used as a sorted index).
650
650
  *
651
651
  * On every mutation the DO recomputes the earliest pending task and updates
@@ -835,7 +835,7 @@ declare class SchedulerDO {
835
835
  * under a `dead:` key for manual inspection.
836
836
  */
837
837
  private recordRetry;
838
- /** Read the durable `pool:&lt;name>` row, defaulting to a fresh `inFlight: 0` pool. */
838
+ /** Read the durable `pool:<name>` row, defaulting to a fresh `inFlight: 0` pool. */
839
839
  private loadPool;
840
840
  private savePool;
841
841
  /**
@@ -857,7 +857,7 @@ declare class SchedulerDO {
857
857
  private handlePoolStatus;
858
858
  /**
859
859
  * `GET /status` — the app-level backlog signal that powers the studio's
860
- * SLO view. Enumerates every durable `pool:&lt;name>` row for its `inFlight`/
860
+ * SLO view. Enumerates every durable `pool:<name>` row for its `inFlight`/
861
861
  * `maxConcurrency` semaphore, counts the pending (not-yet-dispatched) jobs
862
862
  * routed to each pool with the same single-pass scan {@link handlePoolStatus}
863
863
  * uses, and rolls those up into app-wide `backlog` (sum of `queued`) and
@@ -866,7 +866,7 @@ declare class SchedulerDO {
866
866
  * Pools that have rows but no queued jobs still appear (with `queued: 0`) so
867
867
  * a saturated-but-idle pool stays visible; a pool that only ever existed as
868
868
  * queued jobs without a persisted row is unreachable here (the schedule path
869
- * always writes a `pool:&lt;name>` row before the job's header), so a single
869
+ * always writes a `pool:<name>` row before the job's header), so a single
870
870
  * scan over `pool:` plus a cursor loop over `id:` is sufficient.
871
871
  */
872
872
  private handleStatus;
@@ -875,7 +875,7 @@ declare class SchedulerDO {
875
875
  private handleList;
876
876
  /**
877
877
  * `GET /dead` — list the dead-letter records: jobs that exhausted their
878
- * retry budget ({@link recordRetry}) and were parked under `dead:&lt;id>`
878
+ * retry budget ({@link recordRetry}) and were parked under `dead:<id>`
879
879
  * instead of being silently dropped. These never appear in `/list` (their
880
880
  * `id:` header is deleted on park), so this is the ONLY way the studio can
881
881
  * surface — and recover — a permanently-failed job.
@@ -896,7 +896,7 @@ declare class SchedulerDO {
896
896
  */
897
897
  private handleDeadCancel;
898
898
  /**
899
- * Resolve a single pending job by id via a direct `id:&lt;id>` storage read —
899
+ * Resolve a single pending job by id via a direct `id:<id>` storage read —
900
900
  * O(1), versus scanning the whole `/list` view. Responds `{ record }` on a
901
901
  * hit and `{}` on a miss (an absent `record` field — JSON has no `undefined`
902
902
  * — which the client reads back as `null`).
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{default as o}from"./packem_shared/createScheduler-CsRAEtdb.mjs";import{default as a}from"./packem_shared/createWorkpool-DyVdhB6o.mjs";import{createCronTrigger as c}from"./packem_shared/createCronTrigger-C4iUW_zz.mjs";import{CRON_SCHEDULE_KINDS as f,compileCronSchedule as l,cronJobs as n}from"./packem_shared/CRON_SCHEDULE_KINDS-DMu4DfhG.mjs";import{createQueueConsumer as u,createQueueWorkpool as x,httpDispatcher as d}from"./packem_shared/createQueueConsumer-BnX7wkJO.mjs";import{SchedulerDO as C}from"./packem_shared/SchedulerDO-CCP9LV5x.mjs";import{createSchedulerHost as h}from"./packem_shared/createSchedulerHost-C5XacwXv.mjs";import{isWorkflowReference as E}from"./packem_shared/isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as k,isValidCronExpression as W,warnIfSecondsLeading as w}from"./packem_shared/assertValidCronExpression-B2lN8n3O.mjs";export{f as CRON_SCHEDULE_KINDS,C as SchedulerDO,k as assertValidCronExpression,l as compileCronSchedule,c as createCronTrigger,u as createQueueConsumer,x as createQueueWorkpool,o as createScheduler,h as createSchedulerHost,a as createWorkpool,n as cronJobs,d as httpDispatcher,W as isValidCronExpression,E as isWorkflowReference,w as warnIfSecondsLeading};
1
+ import{default as o}from"./packem_shared/createScheduler-w8KwN6aR.mjs";import{default as a}from"./packem_shared/createWorkpool-CqhYmK3r.mjs";import{createCronTrigger as c}from"./packem_shared/createCronTrigger-C4iUW_zz.mjs";import{CRON_SCHEDULE_KINDS as f,compileCronSchedule as l,cronJobs as n}from"./packem_shared/CRON_SCHEDULE_KINDS-Bi-0IEbF.mjs";import{createQueueConsumer as u,createQueueWorkpool as x,httpDispatcher as d}from"./packem_shared/createQueueConsumer-BnX7wkJO.mjs";import{SchedulerDO as C}from"./packem_shared/SchedulerDO-UqT2X44b.mjs";import{createSchedulerHost as h}from"./packem_shared/createSchedulerHost-yZW681fB.mjs";import{isWorkflowReference as E}from"./packem_shared/isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as k,isValidCronExpression as W,warnIfSecondsLeading as w}from"./packem_shared/assertValidCronExpression-B2lN8n3O.mjs";export{f as CRON_SCHEDULE_KINDS,C as SchedulerDO,k as assertValidCronExpression,l as compileCronSchedule,c as createCronTrigger,u as createQueueConsumer,x as createQueueWorkpool,o as createScheduler,h as createSchedulerHost,a as createWorkpool,n as cronJobs,d as httpDispatcher,W as isValidCronExpression,E as isWorkflowReference,w as warnIfSecondsLeading};
@@ -0,0 +1 @@
1
+ import{LunoraError as a}from"@lunora/errors";import{isWorkflowReference as m}from"./isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as f}from"./assertValidCronExpression-B2lN8n3O.mjs";const w={friday:5,monday:1,saturday:6,sunday:0,thursday:4,tuesday:2,wednesday:3},$=24,h=" For a daily or longer schedule use crons.daily(name, { hourUTC, minuteUTC }, …), crons.weekly(name, { dayOfWeek, hourUTC, minuteUTC }, …) or crons.monthly(name, { day, hourUTC, minuteUTC }, …).",l=(e,o,n,i)=>{if(!Number.isInteger(e)||e<n||e>i)throw new a("INTERNAL",`@lunora/scheduler: cronJobs ${o} must be an integer in [${n.toFixed(0)}, ${i.toFixed(0)}], got ${String(e)}`);return e.toFixed(0)},y=(e,o,n)=>{const i=l(e,o,1,n-1);if(n%e!==0)throw new a("INTERNAL",`@lunora/scheduler: ${o} must evenly divide ${n.toFixed(0)} for a fixed "every ${e.toFixed(0)}" interval — cron "*/${e.toFixed(0)}" means "at values divisible by ${e.toFixed(0)}", which wraps unevenly; pick a divisor of ${n.toFixed(0)}`);return i},T=(e,o)=>{const n=["seconds","minutes","hours"].filter(s=>e[s]!==void 0);if(n.length!==1){const s=Object.entries(e).filter(([,t])=>t!==void 0).map(([t])=>t);throw new a("INTERNAL",`@lunora/scheduler: interval schedule must specify exactly one of { seconds, minutes, hours }, got { ${s.join(", ")} }.${h}`)}const i=n[0],r=e[i];if(i==="hours"&&r>=$)throw new a("INTERNAL",`@lunora/scheduler: interval.hours is capped at 23, got ${String(r)} — an interval repeats WITHIN a day rather than spanning one.${h}`);if(i==="seconds")throw new a("INTERNAL",`@lunora/scheduler: cron job${o?` "${o}"`:""} uses interval.seconds (${String(r)}) — Cloudflare Cron Triggers have a one-minute floor and cannot run sub-minute schedules; \`wrangler deploy\` would reject the resulting 6-field cron expression. Use ctx.scheduler.runAfter/runAt (optionally via a workpool for bounded concurrency) for sub-minute recurrence, or crons.interval(name, { minutes: 1 }, …) for the fastest cron-native cadence.`);return i==="minutes"?`*/${y(r,"interval.minutes",60)} * * * *`:`0 */${y(r,"interval.hours",24)} * * *`},C=e=>`${l(e.minuteUTC,"hourly.minuteUTC",0,59)} * * * *`,p=e=>{const o=l(e.minuteUTC,"daily.minuteUTC",0,59),n=l(e.hourUTC,"daily.hourUTC",0,23);return`${o} ${n} * * *`},v=e=>{const o=w[e.dayOfWeek];if(o===void 0)throw new a("INTERNAL",`@lunora/scheduler: weekly schedule has invalid dayOfWeek "${e.dayOfWeek}"`);const n=l(e.minuteUTC,"weekly.minuteUTC",0,59),i=l(e.hourUTC,"weekly.hourUTC",0,23);return`${n} ${i} * * ${o.toFixed(0)}`},N=e=>{const o=l(e.day,"monthly.day",1,31),n=l(e.minuteUTC,"monthly.minuteUTC",0,59),i=l(e.hourUTC,"monthly.hourUTC",0,23);return`${n} ${i} ${o} * *`},k=new Set(["daily","hourly","interval","monthly","weekly"]),d=(e,o,n)=>{switch(e){case"daily":return p(o);case"hourly":return C(o);case"interval":return T(o,n);case"monthly":return N(o);case"weekly":return v(o);default:throw new a("INTERNAL",`@lunora/scheduler: unknown cron schedule kind "${String(e)}"`)}},x=()=>{const e=[],o=new Set,n=(r,s,t,u)=>{if(typeof r!="string"||r.trim()==="")throw new a("INTERNAL","@lunora/scheduler: cron job name must be a non-empty string");if(o.has(r))throw new a("INTERNAL",`@lunora/scheduler: duplicate cron job name "${r}" — names must be unique within one cronJobs()`);let c;if(m(t))c={workflow:typeof t.name=="string"?t.name:""};else if(t&&typeof t.__lunoraRef=="string")c={functionPath:t.__lunoraRef};else throw new a("INTERNAL",`@lunora/scheduler: cron job "${r}" requires a function reference (e.g. internal.email.digest) or a workflow reference`);f(s,`cron expression for job "${r}"`),o.add(r),e.push({args:u??{},cron:s,name:r,...c})},i={cron(r,s,t,u){return n(r,s,t,u),i},daily(r,s,t,u){return n(r,d("daily",s),t,u),i},hourly(r,s,t,u){return n(r,d("hourly",s),t,u),i},interval(r,s,t,u){return n(r,d("interval",s,r),t,u),i},jobs:()=>[...e],monthly(r,s,t,u){return n(r,d("monthly",s),t,u),i},weekly(r,s,t,u){return n(r,d("weekly",s),t,u),i}};return i};export{k as CRON_SCHEDULE_KINDS,d as compileCronSchedule,x as cronJobs};
@@ -0,0 +1 @@
1
+ const f=(u,t=200,e)=>Response.json(u,{headers:{"content-type":"application/json",...e},status:t}),b="lunora-ping",I="lunora-pong";const m="retry:",g="dead:",p="pool:";const y=u=>String(u).padStart(15,"0"),w=u=>{let t="";for(const e of u)t+=String.fromCodePoint(e);return btoa(t).replaceAll("+","-").replaceAll("/","_").replaceAll("=","")},v=()=>w(crypto.getRandomValues(new Uint8Array(12)));class r{static indexKey(t,e){return`t:${y(t)}:${e}`}static json(t,e=200){return f(t,e)}static error(t,e,s){return r.json({error:{code:e,message:s}},t)}static resolveRetry(t){const e=t.retry,s=typeof e?.maxAttempts=="number"&&Number.isInteger(e.maxAttempts)&&e.maxAttempts>0?e.maxAttempts:5,a=typeof e?.baseMs=="number"&&Number.isFinite(e.baseMs)&&e.baseMs>=0?e.baseMs:3e4,n=e?.backoff==="linear"?"linear":"exponential",i=typeof e?.maxMs=="number"&&Number.isFinite(e.maxMs)&&e.maxMs>=0?e.maxMs:void 0;return{backoff:n,baseMs:a,maxAttempts:s,maxMs:i}}static normalizeConcurrency(t,e){return typeof t=="number"&&Number.isInteger(t)&&t>0?t:e}static normalizeRetry(t){if(typeof t!="object"||t===null)return;const e=t,s={};return typeof e.maxAttempts=="number"&&Number.isInteger(e.maxAttempts)&&e.maxAttempts>0&&(s.maxAttempts=e.maxAttempts),typeof e.baseMs=="number"&&Number.isFinite(e.baseMs)&&e.baseMs>=0&&(s.baseMs=e.baseMs),(e.backoff==="exponential"||e.backoff==="linear")&&(s.backoff=e.backoff),typeof e.maxMs=="number"&&Number.isFinite(e.maxMs)&&e.maxMs>=0&&(s.maxMs=e.maxMs),Object.keys(s).length===0?void 0:s}static releaseSlot(t,e){if(t.inFlightIds===void 0)return{...t,inFlight:Math.max(0,t.inFlight-1)};const s=t.inFlightIds.filter(a=>a!==e);return{...t,inFlight:s.length,inFlightIds:s}}static releaseFirstSlot(t){if(t.inFlightIds===void 0)return{...t,inFlight:Math.max(0,t.inFlight-1)};const e=t.inFlightIds.slice(0,Math.max(0,t.inFlightIds.length-1));return{...t,inFlight:e.length,inFlightIds:e}}static resolveScheduleTarget(t){const e=typeof t?.functionPath=="string"&&t.functionPath.length>0?t.functionPath:void 0,s=typeof t?.workflow=="string"&&t.workflow.length>0?t.workflow:void 0;if(!(e===void 0&&s===void 0))return{functionPath:e,workflow:s}}state;env;constructor(t,e){this.state=t,this.env=e,this.armWebSocketKeepalive()}async fetch(t){const e=new URL(t.url);if(e.pathname==="/ws"&&t.headers.get("Upgrade")==="websocket")return this.handleWebSocketUpgrade();switch(`${t.method} ${e.pathname}`){case"GET /dead":return this.handleDeadList();case"GET /get":return this.handleGet(e);case"GET /list":return this.handleList();case"GET /pool":return this.handlePoolStatus(e);case"GET /status":return this.handleStatus();case"POST /cancel":return this.handleCancel(t);case"POST /complete":return this.handleComplete(t);case"POST /dead/cancel":return this.handleDeadCancel(t);case"POST /dead/retry":return this.handleDeadRetry(t);case"POST /schedule":return this.handleSchedule(t)}return f({error:{code:"NOT_FOUND"}},404)}async alarm(){const t=Date.now(),e=[],s=await this.state.storage.list({end:`t:${y(t)}:~`,limit:100,prefix:"t:"});for(const[a,n]of s.entries()){const i=Number.parseInt(a.slice(2,a.indexOf(":",2)),10);if(Number.isFinite(i)&&i<=t){const o=await this.state.storage.get(`id:${n}`);o?e.push(o):await this.state.storage.delete(a)}}try{for(const a of e)await this.drainRecordGuarded(a)}finally{await this.rescheduleAlarm()}e.length>0&&await this.broadcastChange()}async dispatch(t){const e=typeof this.env.LUNORA_ORIGIN_URL=="string"&&this.env.LUNORA_ORIGIN_URL.length>0?this.env.LUNORA_ORIGIN_URL:void 0;if(!e)return!1;const s=JSON.stringify({args:t.args,functionPath:t.functionPath,id:t.id,instanceName:t.instanceName,pool:t.pool,scheduledFor:t.scheduledFor,shardKey:t.shardKey,workflow:t.workflow});try{const a={"content-type":"application/json"},n=await this.signDispatch(s);return n!==void 0?a["x-lunora-scheduler-signature"]=n:typeof this.env.LUNORA_ADMIN_TOKEN=="string"&&this.env.LUNORA_ADMIN_TOKEN.length>0&&(a.authorization=`Bearer ${this.env.LUNORA_ADMIN_TOKEN}`),(await fetch(`${e}/_lunora/scheduler/dispatch`,{body:s,headers:a,method:"POST"})).ok}catch{return!1}}armWebSocketKeepalive(){const t=this.state.setWebSocketAutoResponse;typeof t!="function"||typeof WebSocketRequestResponsePair>"u"||t.call(this.state,new WebSocketRequestResponsePair(b,I))}async drainRecordGuarded(t){try{await this.state.storage.delete(r.indexKey(t.scheduledFor,t.id)),await this.drainRecord(t)}catch{try{await this.state.storage.put(r.indexKey(t.scheduledFor,t.id),t.id)}catch{}}}async drainRecord(t){if(!await this.reservePoolSlot(t))return!1;const e=await this.dispatch(t);if(!e&&t.pool!==void 0){const s=await this.loadPool(t.pool),a=r.releaseSlot(s,t.id);await this.savePool(t.pool,a)}if(e){try{await this.state.storage.delete([`id:${t.id}`,`${m}${t.id}`])}catch{}return!0}return await this.recordRetry(t),!1}async reservePoolSlot(t){if(t.pool===void 0)return!0;const e=await this.loadPool(t.pool);if(e.inFlight>=e.maxConcurrency)return await this.requeuePooled(t),!1;const s=e.inFlightIds??[];return s.includes(t.id)||s.push(t.id),e.inFlightIds=s,e.inFlight=s.length,await this.savePool(t.pool,e),!0}async handleWebSocketUpgrade(){if(this.state.acceptWebSocket===void 0)return r.error(501,"WS_UNSUPPORTED","WebSocket subscriptions are not supported in this runtime");const t=new WebSocketPair,e=t[0],s=t[1];this.state.acceptWebSocket(s);const a=await this.listRecords();return s.send(JSON.stringify({records:a.records,truncated:a.truncated,type:"jobs"})),new Response(null,{status:101,webSocket:e})}async broadcastChange(){const t=this.state.getWebSockets?.();if(t===void 0||t.length===0)return;const{records:e,truncated:s}=await this.listRecords(),a=JSON.stringify({records:e,truncated:s,type:"jobs"});for(const n of t)try{n.send(a)}catch{}}async listRecords(t=100){const e=[...(await this.state.storage.list({limit:t+1,prefix:"id:"})).values()],s=e.length>t;return{records:s?e.slice(0,t):e,truncated:s}}async countHeaders(t,e=100){let s;for(;;){const a=await this.state.storage.list(s===void 0?{limit:e,prefix:"id:"}:{limit:e,prefix:"id:",startAfter:s});if(a.size===0)break;for(const n of a.values())t(n);if(s=[...a.keys()].at(-1),a.size<e)break}}async signDispatch(t){const e=typeof this.env.LUNORA_SCHEDULER_SECRET=="string"?this.env.LUNORA_SCHEDULER_SECRET:void 0;if(!e||e.length===0)return;const s=new TextEncoder,a=await crypto.subtle.importKey("raw",s.encode(e),{hash:"SHA-256",name:"HMAC"},!1,["sign"]),n=await crypto.subtle.sign("HMAC",a,s.encode(t));return w(new Uint8Array(n))}async recordRetry(t){const e=(t.attempts??0)+1,{backoff:s,baseMs:a,maxAttempts:n,maxMs:i}=r.resolveRetry(t);if(e>n){await this.state.storage.put(`${g}${t.id}`,{...t,attempts:e}),await this.state.storage.delete([`${m}${t.id}`,`id:${t.id}`]),console.warn(`@lunora/scheduler: job "${t.id}" (${t.functionPath??t.workflow??"unknown"}) parked in dead-letter after ${String(e)} attempts`);return}const o=s==="linear"?a*e:a*2**(e-1),l=i===void 0?o:Math.min(o,i),c=Date.now()+l,d={...t,attempts:e,scheduledFor:c};await this.state.storage.put(`${m}${t.id}`,d),await this.state.storage.put(`id:${t.id}`,d),await this.state.storage.put(r.indexKey(c,t.id),t.id)}async loadPool(t,e){const s=await this.state.storage.get(`${p}${t}`);return s!==void 0?Array.isArray(s.inFlightIds)?{inFlight:s.inFlightIds.length,inFlightIds:[...s.inFlightIds],maxConcurrency:s.maxConcurrency}:{inFlight:Math.max(0,s.inFlight),maxConcurrency:s.maxConcurrency}:{inFlight:0,inFlightIds:[],maxConcurrency:r.normalizeConcurrency(e,1)}}async savePool(t,e){await this.state.storage.put(`${p}${t}`,e)}async requeuePooled(t){const e=Date.now()+1e3,s={...t,scheduledFor:e};await this.state.storage.put(`id:${t.id}`,s),await this.state.storage.put(r.indexKey(e,t.id),t.id)}async handleComplete(t){const e=await t.json().catch(()=>{}),s=typeof e?.pool=="string"&&e.pool.length>0?e.pool:void 0,a=typeof e?.id=="string"&&e.id.length>0?e.id:void 0;if(s===void 0)return r.error(400,"INVALID_INPUT","pool is required");const n=await this.loadPool(s),i=a===void 0?r.releaseFirstSlot(n):r.releaseSlot(n,a);return await this.savePool(s,i),await this.armAlarmIfEarlier(Date.now()),r.json({inFlight:i.inFlight})}async handlePoolStatus(t){const e=t.searchParams.get("name");if(e===null||e.length===0)return r.error(400,"INVALID_INPUT","name is required");const s=await this.loadPool(e);let a=0;return await this.countHeaders(n=>{n.pool===e&&(a+=1)}),r.json({inFlight:s.inFlight,maxConcurrency:s.maxConcurrency,queued:a})}async handleStatus(){const t=await this.state.storage.list({prefix:p}),e=new Map;await this.countHeaders(o=>{o.pool!==void 0&&e.set(o.pool,(e.get(o.pool)??0)+1)});const s=[];let a=0,n=0;for(const[o,l]of t.entries()){const c=o.slice(p.length),d=Math.max(0,l.inFlight),h=e.get(c)??0;s.push({inFlight:d,maxConcurrency:l.maxConcurrency,name:c,queued:h}),a+=h,n+=d}const i={backlog:a,inFlight:n,pools:s};return r.json(i)}async handleSchedule(t){const e=await t.json().catch(()=>{}),s=r.resolveScheduleTarget(e);if(!e||s===void 0)return r.error(400,"INVALID_INPUT","functionPath or workflow is required");const{functionPath:a,workflow:n}=s;if(typeof e.scheduledFor!="number"||!Number.isInteger(e.scheduledFor)||e.scheduledFor<=0||e.scheduledFor>999999999999999)return r.error(400,"INVALID_INPUT","scheduledFor must be a positive integer epoch-millisecond number no greater than 999999999999999");if(typeof this.env.LUNORA_ORIGIN_URL!="string"||this.env.LUNORA_ORIGIN_URL.length===0)return r.error(500,"ORIGIN_NOT_CONFIGURED","LUNORA_ORIGIN_URL env binding must be set on the SchedulerDO");const i=typeof e.pool=="string"&&e.pool.length>0?e.pool:void 0,o=typeof e.instanceName=="string"&&e.instanceName.length>0?e.instanceName:void 0,l=r.normalizeRetry(e.retry),c=v(),d={args:e.args??{},enqueuedAt:Date.now(),id:c,...a===void 0?{}:{functionPath:a},...o===void 0?{}:{instanceName:o},...i===void 0?{}:{pool:i},...l===void 0?{}:{retry:l},scheduledFor:e.scheduledFor,shardKey:e.shardKey,...n===void 0?{}:{workflow:n}};if(i!==void 0){const h=await this.loadPool(i,e.maxConcurrency);await this.savePool(i,{inFlight:h.inFlight,...h.inFlightIds===void 0?{}:{inFlightIds:h.inFlightIds},maxConcurrency:r.normalizeConcurrency(e.maxConcurrency,h.maxConcurrency)})}return await this.state.storage.put(`id:${c}`,d),await this.state.storage.put(r.indexKey(d.scheduledFor,c),c),await this.armAlarmIfEarlier(d.scheduledFor),await this.broadcastChange(),r.json({id:c,scheduledFor:d.scheduledFor})}async handleCancel(t){const e=await t.json().catch(()=>{});if(!e?.id)return r.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`id:${e.id}`);return s?(await this.removeRecord(s),await this.rescheduleAlarm(),await this.broadcastChange(),r.json({cancelled:!0})):r.json({cancelled:!1})}async handleList(){const{records:t,truncated:e}=await this.listRecords();return r.json({records:t,truncated:e})}async handleDeadList(){const t=await this.state.storage.list({prefix:g});return r.json({records:[...t.values()]})}async handleDeadRetry(t){const e=await t.json().catch(()=>{});if(typeof e?.id!="string"||e.id.length===0)return r.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`${g}${e.id}`);if(s===void 0)return r.json({retried:!1});const a=Date.now(),n={...s,attempts:0,scheduledFor:a};return await this.state.storage.put(`id:${s.id}`,n),await this.state.storage.put(r.indexKey(a,s.id),s.id),await this.state.storage.delete(`${g}${s.id}`),await this.armAlarmIfEarlier(a),await this.broadcastChange(),r.json({id:s.id,retried:!0,scheduledFor:a})}async handleDeadCancel(t){const e=await t.json().catch(()=>{});if(typeof e?.id!="string"||e.id.length===0)return r.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.delete(`${g}${e.id}`);return r.json({removed:!!s})}async handleGet(t){const e=t.searchParams.get("id");if(e===null||e.length===0)return r.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`id:${e}`);return r.json(s===void 0?{}:{record:s})}async removeRecord(t){await this.state.storage.delete([`id:${t.id}`,r.indexKey(t.scheduledFor,t.id),`${m}${t.id}`])}async armAlarmIfEarlier(t){const e=await this.state.storage.getAlarm();(e===null||t<e)&&await this.state.storage.setAlarm(t)}async rescheduleAlarm(){const t=(await this.state.storage.list({limit:1,prefix:"t:"})).entries().next();if(t.done){await this.state.storage.deleteAlarm();return}const[e]=t.value,s=Number.parseInt(e.slice(2,e.indexOf(":",2)),10);Number.isFinite(s)&&await this.state.storage.setAlarm(s)}}export{r as SchedulerDO};
@@ -0,0 +1 @@
1
+ import{LunoraError as c}from"@lunora/errors";import{u as f,l as i,d as a}from"./do-client-DvdRxZzc.mjs";import{isWorkflowReference as y}from"./isWorkflowReference-CT3tdefh.mjs";const h=t=>{f(t);const s=async(e,r,o,n={})=>{const l=e instanceof Date?e.getTime():e,d={args:o,originUrl:t.originUrl,pool:n.pool,retry:n.retry,scheduledFor:l,shardKey:n.shardKey};if(y(r)){if(typeof r.binding!="string"||r.binding.length===0)throw new c("INTERNAL","@lunora/scheduler: workflow/agent schedule target is missing its `binding` — pass the generated `workflows.<name>` / `agents.<name>` reference");return a(t,"/schedule",{...d,workflow:r.binding})}const u=typeof r=="string"?r:r.__lunoraRef;return a(t,"/schedule",{...d,functionPath:u})};return{cancel:async e=>a(t,"/cancel",{id:e}),dead:async()=>{const e=await i(t,"/dead");return Array.isArray(e.records)?e.records:[]},deadRetry:async e=>{const{retried:r}=await a(t,"/dead/retry",{id:e});return r===!0},get:async e=>(await i(t,`/get?id=${encodeURIComponent(e)}`)).record??null,list:async()=>{const e=await i(t,"/list");return Array.isArray(e.records)?e.records:[]},runAfter:async(e,r,o,n={})=>{if(!Number.isFinite(e)||e<0)throw new c("INTERNAL","@lunora/scheduler: `delayMs` must be a non-negative finite number");return s(Date.now()+e,r,o,n)},runAt:s}};export{h as default};
@@ -1 +1 @@
1
- import i from"./createScheduler-CsRAEtdb.mjs";const o=a=>a?.at!==void 0?typeof a.at=="number"?a.at:a.at.getTime():Date.now()+(a?.delayMs??0),u=a=>{const t=i({instanceName:a.instanceName,jurisdiction:a.jurisdiction,namespace:a.namespace,originUrl:a.originUrl}),c=e=>({attempts:e.attempts??0,functionPath:e.functionPath??e.workflow??"",id:e.id,scheduledFor:e.scheduledFor});return{cancel:async e=>{const{cancelled:r}=await t.cancel(e);return r},deadLetter:{list:async()=>(await t.dead()).map(e=>c(e)),requeue:async e=>t.deadRetry(e)},list:async()=>(await t.list()).map(e=>c(e)),schedule:async(e,r,n)=>{const s=t.runAt;return s(o(n),e,r,{retry:n?.retry,shardKey:n?.shardKey})}}};export{u as createSchedulerHost};
1
+ import i from"./createScheduler-w8KwN6aR.mjs";const o=a=>a?.at!==void 0?typeof a.at=="number"?a.at:a.at.getTime():Date.now()+(a?.delayMs??0),u=a=>{const t=i({instanceName:a.instanceName,jurisdiction:a.jurisdiction,namespace:a.namespace,originUrl:a.originUrl}),c=e=>({attempts:e.attempts??0,functionPath:e.functionPath??e.workflow??"",id:e.id,scheduledFor:e.scheduledFor});return{cancel:async e=>{const{cancelled:r}=await t.cancel(e);return r},deadLetter:{list:async()=>(await t.dead()).map(e=>c(e)),requeue:async e=>t.deadRetry(e)},list:async()=>(await t.list()).map(e=>c(e)),schedule:async(e,r,n)=>{const s=t.runAt;return s(o(n),e,r,{retry:n?.retry,shardKey:n?.shardKey})}}};export{u as createSchedulerHost};
@@ -0,0 +1 @@
1
+ import{LunoraError as t}from"@lunora/errors";import{u,l as i,d as c}from"./do-client-DvdRxZzc.mjs";const d=n=>{if(u(n),!Number.isInteger(n.maxConcurrency)||n.maxConcurrency<=0)throw new t("INTERNAL","@lunora/scheduler: `maxConcurrency` must be a positive integer");const e=typeof n.name=="string"&&n.name.length>0?n.name:"default";return{cancel:async r=>c(n,"/cancel",{id:r}),enqueue:async(r,s,a={})=>{const o=a.delayMs??0;if(!Number.isFinite(o)||o<0)throw new t("INTERNAL","@lunora/scheduler: `delayMs` must be a non-negative finite number");return c(n,"/schedule",{args:s,functionPath:r.__lunoraRef,instanceName:n.instanceName??"default",maxConcurrency:n.maxConcurrency,originUrl:n.originUrl,pool:e,retry:a.retry,scheduledFor:Date.now()+o,shardKey:a.shardKey})},name:e,status:async()=>i(n,`/pool?name=${encodeURIComponent(e)}`)}};export{d as default};
@@ -0,0 +1 @@
1
+ import{LunoraError as n}from"@lunora/errors";const s=(e,r)=>{if(r===void 0)return e;if(typeof e.jurisdiction!="function")throw new TypeError(`@lunora/scheduler: Durable Object namespace does not support jurisdiction("${r}") — update @cloudflare/workers-types or remove the jurisdiction option`);return e.jurisdiction(r)},d=e=>{if(!e.namespace)throw new n("INTERNAL","@lunora/scheduler: `namespace` (SchedulerDO binding) is required");if(!e.originUrl)throw new n("INTERNAL","@lunora/scheduler: `originUrl` is required so the DO can dispatch back to the Worker")},c=e=>{const r=s(e.namespace,e.jurisdiction);return r.get(r.idFromName(e.instanceName??"default"))},i=async(e,r,o)=>{const t=await c(e).fetch(`https://scheduler.internal${r}`,o);if(!t.ok){const a=await t.text();throw new n("INTERNAL",`@lunora/scheduler: SchedulerDO ${r} failed (${String(t.status)}): ${a}`)}return await t.json()},l=async(e,r,o)=>i(e,r,{body:JSON.stringify(o),headers:{"content-type":"application/json"},method:"POST"}),h=async(e,r)=>i(e,r,{method:"GET"});export{l as d,h as l,d as u};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/scheduler",
3
- "version": "1.0.0-alpha.20",
3
+ "version": "1.0.0-alpha.22",
4
4
  "description": "Scheduling for Lunora: runAfter / runAt and Cron Triggers via SchedulerDO",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,8 +46,8 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/errors": "1.0.0-alpha.13",
50
- "@lunora/platform": "1.0.0-alpha.5",
49
+ "@lunora/errors": "1.0.0-alpha.15",
50
+ "@lunora/platform": "1.0.0-alpha.6",
51
51
  "cron-parser": "5.6.2"
52
52
  },
53
53
  "engines": {
@@ -1 +0,0 @@
1
- import{LunoraError as a}from"@lunora/errors";import{isWorkflowReference as y}from"./isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as c}from"./assertValidCronExpression-B2lN8n3O.mjs";const f={friday:5,monday:1,saturday:6,sunday:0,thursday:4,tuesday:2,wednesday:3},w=24,h=" For a daily or longer schedule use crons.daily(name, { hourUTC, minuteUTC }, …), crons.weekly(name, { dayOfWeek, hourUTC, minuteUTC }, …) or crons.monthly(name, { day, hourUTC, minuteUTC }, …).",l=(e,o,r,s)=>{if(!Number.isInteger(e)||e<r||e>s)throw new a("INTERNAL",`@lunora/scheduler: cronJobs ${o} must be an integer in [${r.toFixed(0)}, ${s.toFixed(0)}], got ${String(e)}`);return e.toFixed(0)},m=(e,o,r)=>{const s=l(e,o,1,r-1);if(r%e!==0)throw new a("INTERNAL",`@lunora/scheduler: ${o} must evenly divide ${r.toFixed(0)} for a fixed "every ${e.toFixed(0)}" interval — cron "*/${e.toFixed(0)}" means "at values divisible by ${e.toFixed(0)}", which wraps unevenly; pick a divisor of ${r.toFixed(0)}`);return s},$=(e,o)=>{const r=["seconds","minutes","hours"].filter(i=>e[i]!==void 0);if(r.length!==1){const i=Object.entries(e).filter(([,t])=>t!==void 0).map(([t])=>t);throw new a("INTERNAL",`@lunora/scheduler: interval schedule must specify exactly one of { seconds, minutes, hours }, got { ${i.join(", ")} }.${h}`)}const s=r[0],n=e[s];if(s==="hours"&&n>=w)throw new a("INTERNAL",`@lunora/scheduler: interval.hours is capped at 23, got ${String(n)} — an interval repeats WITHIN a day rather than spanning one.${h}`);if(s==="seconds")throw new a("INTERNAL",`@lunora/scheduler: cron job${o?` "${o}"`:""} uses interval.seconds (${String(n)}) — Cloudflare Cron Triggers have a one-minute floor and cannot run sub-minute schedules; \`wrangler deploy\` would reject the resulting 6-field cron expression. Use ctx.scheduler.runAfter/runAt (optionally via a workpool for bounded concurrency) for sub-minute recurrence, or crons.interval(name, { minutes: 1 }, …) for the fastest cron-native cadence.`);return s==="minutes"?`*/${m(n,"interval.minutes",60)} * * * *`:`0 */${m(n,"interval.hours",24)} * * *`},T=e=>`${l(e.minuteUTC,"hourly.minuteUTC",0,59)} * * * *`,p=e=>{const o=l(e.minuteUTC,"daily.minuteUTC",0,59),r=l(e.hourUTC,"daily.hourUTC",0,23);return`${o} ${r} * * *`},C=e=>{const o=f[e.dayOfWeek];if(o===void 0)throw new a("INTERNAL",`@lunora/scheduler: weekly schedule has invalid dayOfWeek "${e.dayOfWeek}"`);const r=l(e.minuteUTC,"weekly.minuteUTC",0,59),s=l(e.hourUTC,"weekly.hourUTC",0,23);return`${r} ${s} * * ${o.toFixed(0)}`},v=e=>{const o=l(e.day,"monthly.day",1,31),r=l(e.minuteUTC,"monthly.minuteUTC",0,59),s=l(e.hourUTC,"monthly.hourUTC",0,23);return`${r} ${s} ${o} * *`},b=new Set(["daily","hourly","interval","monthly","weekly"]),d=(e,o,r)=>{switch(e){case"daily":return p(o);case"hourly":return T(o);case"interval":return $(o,r);case"monthly":return v(o);case"weekly":return C(o);default:throw new a("INTERNAL",`@lunora/scheduler: unknown cron schedule kind "${String(e)}"`)}},k=()=>{const e=[],o=new Set,r=(n,i,t,u)=>{if(typeof n!="string"||n.trim()==="")throw new a("INTERNAL","@lunora/scheduler: cron job name must be a non-empty string");if(o.has(n))throw new a("INTERNAL",`@lunora/scheduler: duplicate cron job name "${n}" — names must be unique within one cronJobs()`);if(y(t)){c(i,`cron expression for job "${n}"`),o.add(n),e.push({args:u??{},cron:i,name:n,workflow:typeof t.name=="string"?t.name:""});return}if(!t||typeof t.__lunoraRef!="string")throw new a("INTERNAL",`@lunora/scheduler: cron job "${n}" requires a function reference (e.g. internal.email.digest) or a workflow reference`);c(i,`cron expression for job "${n}"`),o.add(n),e.push({args:u??{},cron:i,functionPath:t.__lunoraRef,name:n})},s={cron(n,i,t,u){return r(n,i,t,u),s},daily(n,i,t,u){return r(n,d("daily",i),t,u),s},hourly(n,i,t,u){return r(n,d("hourly",i),t,u),s},interval(n,i,t,u){return r(n,d("interval",i,n),t,u),s},jobs:()=>[...e],monthly(n,i,t,u){return r(n,d("monthly",i),t,u),s},weekly(n,i,t,u){return r(n,d("weekly",i),t,u),s}};return s};export{b as CRON_SCHEDULE_KINDS,d as compileCronSchedule,k as cronJobs};
@@ -1 +0,0 @@
1
- const f=(u,t=200,e)=>Response.json(u,{headers:{"content-type":"application/json",...e},status:t}),w="lunora-ping",b="lunora-pong";const p="retry:",g="dead:",m="pool:";const y=u=>String(u).padStart(15,"0"),v=()=>{const u=crypto.getRandomValues(new Uint8Array(12));let t="";for(const e of u)t+=String.fromCodePoint(e);return btoa(t).replaceAll("+","-").replaceAll("/","_").replaceAll("=","")};class r{static indexKey(t,e){return`t:${y(t)}:${e}`}static json(t,e=200){return f(t,e)}static error(t,e,s){return r.json({error:{code:e,message:s}},t)}static resolveRetry(t){const e=t.retry,s=typeof e?.maxAttempts=="number"&&Number.isInteger(e.maxAttempts)&&e.maxAttempts>0?e.maxAttempts:5,a=typeof e?.baseMs=="number"&&Number.isFinite(e.baseMs)&&e.baseMs>=0?e.baseMs:3e4,n=e?.backoff==="linear"?"linear":"exponential",i=typeof e?.maxMs=="number"&&Number.isFinite(e.maxMs)&&e.maxMs>=0?e.maxMs:void 0;return{backoff:n,baseMs:a,maxAttempts:s,maxMs:i}}static normalizeConcurrency(t,e){return typeof t=="number"&&Number.isInteger(t)&&t>0?t:e}static normalizeRetry(t){if(typeof t!="object"||t===null)return;const e=t,s={};return typeof e.maxAttempts=="number"&&Number.isInteger(e.maxAttempts)&&e.maxAttempts>0&&(s.maxAttempts=e.maxAttempts),typeof e.baseMs=="number"&&Number.isFinite(e.baseMs)&&e.baseMs>=0&&(s.baseMs=e.baseMs),(e.backoff==="exponential"||e.backoff==="linear")&&(s.backoff=e.backoff),typeof e.maxMs=="number"&&Number.isFinite(e.maxMs)&&e.maxMs>=0&&(s.maxMs=e.maxMs),Object.keys(s).length===0?void 0:s}static releaseSlot(t,e){if(t.inFlightIds===void 0)return{...t,inFlight:Math.max(0,t.inFlight-1)};const s=t.inFlightIds.filter(a=>a!==e);return{...t,inFlight:s.length,inFlightIds:s}}static releaseFirstSlot(t){if(t.inFlightIds===void 0)return{...t,inFlight:Math.max(0,t.inFlight-1)};const e=t.inFlightIds.slice(0,Math.max(0,t.inFlightIds.length-1));return{...t,inFlight:e.length,inFlightIds:e}}static resolveScheduleTarget(t){const e=typeof t?.functionPath=="string"&&t.functionPath.length>0?t.functionPath:void 0,s=typeof t?.workflow=="string"&&t.workflow.length>0?t.workflow:void 0;if(!(e===void 0&&s===void 0))return{functionPath:e,workflow:s}}state;env;constructor(t,e){this.state=t,this.env=e,this.armWebSocketKeepalive()}async fetch(t){const e=new URL(t.url);if(e.pathname==="/ws"&&t.headers.get("Upgrade")==="websocket")return this.handleWebSocketUpgrade();switch(`${t.method} ${e.pathname}`){case"GET /dead":return this.handleDeadList();case"GET /get":return this.handleGet(e);case"GET /list":return this.handleList();case"GET /pool":return this.handlePoolStatus(e);case"GET /status":return this.handleStatus();case"POST /cancel":return this.handleCancel(t);case"POST /complete":return this.handleComplete(t);case"POST /dead/cancel":return this.handleDeadCancel(t);case"POST /dead/retry":return this.handleDeadRetry(t);case"POST /schedule":return this.handleSchedule(t)}return f({error:{code:"NOT_FOUND"}},404)}async alarm(){const t=Date.now(),e=[],s=await this.state.storage.list({end:`t:${y(t)}:~`,limit:100,prefix:"t:"});for(const[a,n]of s.entries()){const i=Number.parseInt(a.slice(2,a.indexOf(":",2)),10);if(Number.isFinite(i)&&i<=t){const o=await this.state.storage.get(`id:${n}`);o?e.push(o):await this.state.storage.delete(a)}}try{for(const a of e)await this.drainRecordGuarded(a)}finally{await this.rescheduleAlarm()}e.length>0&&await this.broadcastChange()}async dispatch(t){const e=typeof this.env.LUNORA_ORIGIN_URL=="string"&&this.env.LUNORA_ORIGIN_URL.length>0?this.env.LUNORA_ORIGIN_URL:void 0;if(!e)return!1;const s=JSON.stringify({args:t.args,functionPath:t.functionPath,id:t.id,instanceName:t.instanceName,pool:t.pool,scheduledFor:t.scheduledFor,shardKey:t.shardKey,workflow:t.workflow});try{const a={"content-type":"application/json"},n=await this.signDispatch(s);return n!==void 0?a["x-lunora-scheduler-signature"]=n:typeof this.env.LUNORA_ADMIN_TOKEN=="string"&&this.env.LUNORA_ADMIN_TOKEN.length>0&&(a.authorization=`Bearer ${this.env.LUNORA_ADMIN_TOKEN}`),(await fetch(`${e}/_lunora/scheduler/dispatch`,{body:s,headers:a,method:"POST"})).ok}catch{return!1}}armWebSocketKeepalive(){const t=this.state.setWebSocketAutoResponse;typeof t!="function"||typeof WebSocketRequestResponsePair>"u"||t.call(this.state,new WebSocketRequestResponsePair(w,b))}async drainRecordGuarded(t){try{await this.state.storage.delete(r.indexKey(t.scheduledFor,t.id)),await this.drainRecord(t)}catch{try{await this.state.storage.put(r.indexKey(t.scheduledFor,t.id),t.id)}catch{}}}async drainRecord(t){if(!await this.reservePoolSlot(t))return!1;const e=await this.dispatch(t);if(!e&&t.pool!==void 0){const s=await this.loadPool(t.pool),a=r.releaseSlot(s,t.id);await this.savePool(t.pool,a)}if(e){try{await this.state.storage.delete([`id:${t.id}`,`${p}${t.id}`])}catch{}return!0}return await this.recordRetry(t),!1}async reservePoolSlot(t){if(t.pool===void 0)return!0;const e=await this.loadPool(t.pool);if(e.inFlight>=e.maxConcurrency)return await this.requeuePooled(t),!1;const s=e.inFlightIds??[];return s.includes(t.id)||s.push(t.id),e.inFlightIds=s,e.inFlight=s.length,await this.savePool(t.pool,e),!0}async handleWebSocketUpgrade(){if(this.state.acceptWebSocket===void 0)return r.error(501,"WS_UNSUPPORTED","WebSocket subscriptions are not supported in this runtime");const t=new WebSocketPair,e=t[0],s=t[1];this.state.acceptWebSocket(s);const a=await this.listRecords();return s.send(JSON.stringify({records:a.records,truncated:a.truncated,type:"jobs"})),new Response(null,{status:101,webSocket:e})}async broadcastChange(){const t=this.state.getWebSockets?.();if(t===void 0||t.length===0)return;const{records:e,truncated:s}=await this.listRecords(),a=JSON.stringify({records:e,truncated:s,type:"jobs"});for(const n of t)try{n.send(a)}catch{}}async listRecords(t=100){const e=[...(await this.state.storage.list({limit:t+1,prefix:"id:"})).values()],s=e.length>t;return{records:s?e.slice(0,t):e,truncated:s}}async countHeaders(t,e=100){let s;for(;;){const a=await this.state.storage.list(s===void 0?{limit:e,prefix:"id:"}:{limit:e,prefix:"id:",startAfter:s});if(a.size===0)break;for(const n of a.values())t(n);if(s=[...a.keys()].at(-1),a.size<e)break}}async signDispatch(t){const e=typeof this.env.LUNORA_SCHEDULER_SECRET=="string"?this.env.LUNORA_SCHEDULER_SECRET:void 0;if(!e||e.length===0)return;const s=new TextEncoder,a=await crypto.subtle.importKey("raw",s.encode(e),{hash:"SHA-256",name:"HMAC"},!1,["sign"]),n=await crypto.subtle.sign("HMAC",a,s.encode(t)),i=new Uint8Array(n);let o="";for(const d of i)o+=String.fromCodePoint(d);return btoa(o).replaceAll("+","-").replaceAll("/","_").replaceAll("=","")}async recordRetry(t){const e=(t.attempts??0)+1,{backoff:s,baseMs:a,maxAttempts:n,maxMs:i}=r.resolveRetry(t);if(e>n){await this.state.storage.put(`${g}${t.id}`,{...t,attempts:e}),await this.state.storage.delete([`${p}${t.id}`,`id:${t.id}`]),console.warn(`@lunora/scheduler: job "${t.id}" (${t.functionPath??t.workflow??"unknown"}) parked in dead-letter after ${String(e)} attempts`);return}const o=s==="linear"?a*e:a*2**(e-1),d=i===void 0?o:Math.min(o,i),c=Date.now()+d,l={...t,attempts:e,scheduledFor:c};await this.state.storage.put(`${p}${t.id}`,l),await this.state.storage.put(`id:${t.id}`,l),await this.state.storage.put(r.indexKey(c,t.id),t.id)}async loadPool(t,e){const s=await this.state.storage.get(`${m}${t}`);return s!==void 0?Array.isArray(s.inFlightIds)?{inFlight:s.inFlightIds.length,inFlightIds:[...s.inFlightIds],maxConcurrency:s.maxConcurrency}:{inFlight:Math.max(0,s.inFlight),maxConcurrency:s.maxConcurrency}:{inFlight:0,inFlightIds:[],maxConcurrency:r.normalizeConcurrency(e,1)}}async savePool(t,e){await this.state.storage.put(`${m}${t}`,e)}async requeuePooled(t){const e=Date.now()+1e3,s={...t,scheduledFor:e};await this.state.storage.put(`id:${t.id}`,s),await this.state.storage.put(r.indexKey(e,t.id),t.id)}async handleComplete(t){const e=await t.json().catch(()=>{}),s=typeof e?.pool=="string"&&e.pool.length>0?e.pool:void 0,a=typeof e?.id=="string"&&e.id.length>0?e.id:void 0;if(s===void 0)return r.error(400,"INVALID_INPUT","pool is required");const n=await this.loadPool(s),i=a===void 0?r.releaseFirstSlot(n):r.releaseSlot(n,a);return await this.savePool(s,i),await this.armAlarmIfEarlier(Date.now()),r.json({inFlight:i.inFlight})}async handlePoolStatus(t){const e=t.searchParams.get("name");if(e===null||e.length===0)return r.error(400,"INVALID_INPUT","name is required");const s=await this.loadPool(e);let a=0;return await this.countHeaders(n=>{n.pool===e&&(a+=1)}),r.json({inFlight:s.inFlight,maxConcurrency:s.maxConcurrency,queued:a})}async handleStatus(){const t=await this.state.storage.list({prefix:m}),e=new Map;await this.countHeaders(o=>{o.pool!==void 0&&e.set(o.pool,(e.get(o.pool)??0)+1)});const s=[];let a=0,n=0;for(const[o,d]of t.entries()){const c=o.slice(m.length),l=Math.max(0,d.inFlight),h=e.get(c)??0;s.push({inFlight:l,maxConcurrency:d.maxConcurrency,name:c,queued:h}),a+=h,n+=l}const i={backlog:a,inFlight:n,pools:s};return r.json(i)}async handleSchedule(t){const e=await t.json().catch(()=>{}),s=r.resolveScheduleTarget(e);if(!e||s===void 0)return r.error(400,"INVALID_INPUT","functionPath or workflow is required");const{functionPath:a,workflow:n}=s;if(typeof e.scheduledFor!="number"||!Number.isInteger(e.scheduledFor)||e.scheduledFor<=0||e.scheduledFor>999999999999999)return r.error(400,"INVALID_INPUT","scheduledFor must be a positive integer epoch-millisecond number no greater than 999999999999999");if(typeof this.env.LUNORA_ORIGIN_URL!="string"||this.env.LUNORA_ORIGIN_URL.length===0)return r.error(500,"ORIGIN_NOT_CONFIGURED","LUNORA_ORIGIN_URL env binding must be set on the SchedulerDO");const i=typeof e.pool=="string"&&e.pool.length>0?e.pool:void 0,o=typeof e.instanceName=="string"&&e.instanceName.length>0?e.instanceName:void 0,d=r.normalizeRetry(e.retry),c=v(),l={args:e.args??{},enqueuedAt:Date.now(),id:c,...a===void 0?{}:{functionPath:a},...o===void 0?{}:{instanceName:o},...i===void 0?{}:{pool:i},...d===void 0?{}:{retry:d},scheduledFor:e.scheduledFor,shardKey:e.shardKey,...n===void 0?{}:{workflow:n}};if(i!==void 0){const h=await this.loadPool(i,e.maxConcurrency);await this.savePool(i,{inFlight:h.inFlight,...h.inFlightIds===void 0?{}:{inFlightIds:h.inFlightIds},maxConcurrency:r.normalizeConcurrency(e.maxConcurrency,h.maxConcurrency)})}return await this.state.storage.put(`id:${c}`,l),await this.state.storage.put(r.indexKey(l.scheduledFor,c),c),await this.armAlarmIfEarlier(l.scheduledFor),await this.broadcastChange(),r.json({id:c,scheduledFor:l.scheduledFor})}async handleCancel(t){const e=await t.json().catch(()=>{});if(!e?.id)return r.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`id:${e.id}`);return s?(await this.removeRecord(s),await this.rescheduleAlarm(),await this.broadcastChange(),r.json({cancelled:!0})):r.json({cancelled:!1})}async handleList(){const{records:t,truncated:e}=await this.listRecords();return r.json({records:t,truncated:e})}async handleDeadList(){const t=await this.state.storage.list({prefix:g});return r.json({records:[...t.values()]})}async handleDeadRetry(t){const e=await t.json().catch(()=>{});if(typeof e?.id!="string"||e.id.length===0)return r.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`${g}${e.id}`);if(s===void 0)return r.json({retried:!1});const a=Date.now(),n={...s,attempts:0,scheduledFor:a};return await this.state.storage.put(`id:${s.id}`,n),await this.state.storage.put(r.indexKey(a,s.id),s.id),await this.state.storage.delete(`${g}${s.id}`),await this.armAlarmIfEarlier(a),await this.broadcastChange(),r.json({id:s.id,retried:!0,scheduledFor:a})}async handleDeadCancel(t){const e=await t.json().catch(()=>{});if(typeof e?.id!="string"||e.id.length===0)return r.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.delete(`${g}${e.id}`);return r.json({removed:!!s})}async handleGet(t){const e=t.searchParams.get("id");if(e===null||e.length===0)return r.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`id:${e}`);return r.json(s===void 0?{}:{record:s})}async removeRecord(t){await this.state.storage.delete([`id:${t.id}`,r.indexKey(t.scheduledFor,t.id),`${p}${t.id}`])}async armAlarmIfEarlier(t){const e=await this.state.storage.getAlarm();(e===null||t<e)&&await this.state.storage.setAlarm(t)}async rescheduleAlarm(){const t=(await this.state.storage.list({limit:1,prefix:"t:"})).entries().next();if(t.done){await this.state.storage.deleteAlarm();return}const[e]=t.value,s=Number.parseInt(e.slice(2,e.indexOf(":",2)),10);Number.isFinite(s)&&await this.state.storage.setAlarm(s)}}export{r as SchedulerDO};
@@ -1 +0,0 @@
1
- import{LunoraError as i}from"@lunora/errors";import{l as s,u as a}from"./do-client-BFps7NIj.mjs";import{isWorkflowReference as g}from"./isWorkflowReference-CT3tdefh.mjs";const y=n=>{if(!n.namespace)throw new i("INTERNAL","@lunora/scheduler: `namespace` (SchedulerDO binding) is required");if(!n.originUrl)throw new i("INTERNAL","@lunora/scheduler: `originUrl` is required so the DO can dispatch back to the Worker");const c=async(e,r,o,t={})=>{const l=e instanceof Date?e.getTime():e,d={args:o,originUrl:n.originUrl,pool:t.pool,retry:t.retry,scheduledFor:l,shardKey:t.shardKey};if(g(r)){if(typeof r.binding!="string"||r.binding.length===0)throw new i("INTERNAL","@lunora/scheduler: workflow/agent schedule target is missing its `binding` — pass the generated `workflows.<name>` / `agents.<name>` reference");return a(n,"/schedule",{...d,workflow:r.binding})}const u=typeof r=="string"?r:r.__lunoraRef;return a(n,"/schedule",{...d,functionPath:u})};return{cancel:async e=>a(n,"/cancel",{id:e}),dead:async()=>{const e=await s(n,"/dead");return Array.isArray(e.records)?e.records:[]},deadRetry:async e=>{const{retried:r}=await a(n,"/dead/retry",{id:e});return r===!0},get:async e=>(await s(n,`/get?id=${encodeURIComponent(e)}`)).record??null,list:async()=>{const e=await s(n,"/list");return Array.isArray(e.records)?e.records:[]},runAfter:async(e,r,o,t={})=>{if(!Number.isFinite(e)||e<0)throw new i("INTERNAL","@lunora/scheduler: `delayMs` must be a non-negative finite number");return c(Date.now()+e,r,o,t)},runAt:c}};export{y as default};
@@ -1 +0,0 @@
1
- import{LunoraError as n}from"@lunora/errors";import{l as c,u as i}from"./do-client-BFps7NIj.mjs";const m=e=>{if(!e.namespace)throw new n("INTERNAL","@lunora/scheduler: `namespace` (SchedulerDO binding) is required");if(!e.originUrl)throw new n("INTERNAL","@lunora/scheduler: `originUrl` is required so the DO can dispatch back to the Worker");if(!Number.isInteger(e.maxConcurrency)||e.maxConcurrency<=0)throw new n("INTERNAL","@lunora/scheduler: `maxConcurrency` must be a positive integer");const r=typeof e.name=="string"&&e.name.length>0?e.name:"default";return{cancel:async a=>i(e,"/cancel",{id:a}),enqueue:async(a,u,o={})=>{const t=o.delayMs??0;if(!Number.isFinite(t)||t<0)throw new n("INTERNAL","@lunora/scheduler: `delayMs` must be a non-negative finite number");return i(e,"/schedule",{args:u,functionPath:a.__lunoraRef,instanceName:e.instanceName??"default",maxConcurrency:e.maxConcurrency,originUrl:e.originUrl,pool:r,retry:o.retry,scheduledFor:Date.now()+t,shardKey:o.shardKey})},name:r,status:async()=>c(e,`/pool?name=${encodeURIComponent(r)}`)}};export{m as default};
@@ -1 +0,0 @@
1
- import{LunoraError as o}from"@lunora/errors";const s=(e,t)=>{if(t===void 0)return e;if(typeof e.jurisdiction!="function")throw new TypeError(`@lunora/scheduler: Durable Object namespace does not support jurisdiction("${t}") — update @cloudflare/workers-types or remove the jurisdiction option`);return e.jurisdiction(t)},i=e=>{const t=s(e.namespace,e.jurisdiction);return t.get(t.idFromName(e.instanceName??"default"))},u=async(e,t,r)=>{const n=await i(e).fetch(`https://scheduler.internal${t}`,{body:JSON.stringify(r),headers:{"content-type":"application/json"},method:"POST"});if(!n.ok){const a=await n.text();throw new o("INTERNAL",`@lunora/scheduler: SchedulerDO ${t} failed (${String(n.status)}): ${a}`)}return await n.json()},d=async(e,t)=>{const r=await i(e).fetch(`https://scheduler.internal${t}`,{method:"GET"});if(!r.ok){const n=await r.text();throw new o("INTERNAL",`@lunora/scheduler: SchedulerDO ${t} failed (${String(r.status)}): ${n}`)}return await r.json()};export{d as l,u};