@lunora/scheduler 1.0.0-alpha.1 → 1.0.0-alpha.10
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/LICENSE.md +6 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +89 -21
- package/dist/index.d.ts +89 -21
- package/dist/index.mjs +7 -7
- package/dist/packem_shared/{compileCronSchedule-BaLlXJiN.mjs → CRON_SCHEDULE_KINDS-C2FflEnq.mjs} +28 -11
- package/dist/packem_shared/{SchedulerDO-BNzXNnS4.mjs → SchedulerDO-DeJHI379.mjs} +80 -53
- package/dist/packem_shared/{assertValidCronExpression-BLfrDgmK.mjs → assertValidCronExpression-B9m75qU0.mjs} +5 -1
- package/dist/packem_shared/{createCronTrigger-Cq9IBcWQ.mjs → createCronTrigger-Dh4VLxD9.mjs} +3 -2
- package/dist/packem_shared/{createQueueConsumer-DWahNPfz.mjs → createQueueConsumer-Cy-Mp-El.mjs} +5 -3
- package/dist/packem_shared/createScheduler-Df9JUjQb.mjs +52 -0
- package/dist/packem_shared/createWorkpool-Ce0kNM2p.mjs +37 -0
- package/dist/packem_shared/do-client-CMJtLoHO.mjs +42 -0
- package/package.json +4 -3
- package/dist/packem_shared/createScheduler-KCso4_at.mjs +0 -63
- package/dist/packem_shared/createWorkpool-D5cWPQmH.mjs +0 -56
package/dist/index.d.mts
CHANGED
|
@@ -45,6 +45,15 @@ interface WorkflowReference<Params = Record<string, unknown>> {
|
|
|
45
45
|
type CronTarget = FunctionReference | WorkflowReference;
|
|
46
46
|
/** The arguments a cron's target accepts: a workflow's inferred `params`, else an open record (function args aren't inferred). */
|
|
47
47
|
type CronTargetArgs<T extends CronTarget> = T extends WorkflowReference<infer Params> ? Params : Record<string, unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* The arguments a one-shot schedule target ({@link Scheduler.runAfter} /
|
|
50
|
+
* {@link Scheduler.runAt}) accepts. Unlike {@link CronTargetArgs} it preserves a
|
|
51
|
+
* {@link FunctionReference}'s inferred `args` (via {@link ArgsOf}) as well as a
|
|
52
|
+
* {@link WorkflowReference}'s inferred `params`, so scheduling a plain function
|
|
53
|
+
* keeps its today's arg checking while scheduling a workflow/agent infers its
|
|
54
|
+
* `params`.
|
|
55
|
+
*/
|
|
56
|
+
type ScheduleTargetArgs<T extends CronTarget> = T extends WorkflowReference<infer Params> ? Params : T extends FunctionReference ? ArgsOf<T> : Record<string, unknown>;
|
|
48
57
|
/** Narrow a {@link CronTarget} to a {@link WorkflowReference} by its runtime brand. */
|
|
49
58
|
declare const isWorkflowReference: (target: unknown) => target is WorkflowReference;
|
|
50
59
|
/**
|
|
@@ -93,7 +102,12 @@ interface ScheduleRecord {
|
|
|
93
102
|
*/
|
|
94
103
|
attempts?: number;
|
|
95
104
|
enqueuedAt: number;
|
|
96
|
-
|
|
105
|
+
/**
|
|
106
|
+
* The `ns:fn` path of the function to dispatch on fire. Absent when the job
|
|
107
|
+
* targets a durable workflow/agent instead — see {@link ScheduleRecord.workflow}.
|
|
108
|
+
* Exactly one of `functionPath` / `workflow` is set.
|
|
109
|
+
*/
|
|
110
|
+
functionPath?: string;
|
|
97
111
|
id: string;
|
|
98
112
|
/**
|
|
99
113
|
* Scheduler/workpool instance name the job was enqueued through. Echoed in
|
|
@@ -113,6 +127,14 @@ interface ScheduleRecord {
|
|
|
113
127
|
retry?: RetryPolicy;
|
|
114
128
|
scheduledFor: number;
|
|
115
129
|
shardKey?: string;
|
|
130
|
+
/**
|
|
131
|
+
* The `WORKFLOW_*`/`AGENT_*` binding name to start a fresh durable instance
|
|
132
|
+
* of on fire (the {@link ScheduleRecord.args} become its `params`). Set
|
|
133
|
+
* instead of {@link ScheduleRecord.functionPath} when the job targets a
|
|
134
|
+
* workflow/agent {@link WorkflowReference}. The runtime — not the DO — owns
|
|
135
|
+
* the binding, so the dispatch payload carries this through to the Worker.
|
|
136
|
+
*/
|
|
137
|
+
workflow?: string;
|
|
116
138
|
}
|
|
117
139
|
interface Scheduler {
|
|
118
140
|
cancel: (id: string) => Promise<{
|
|
@@ -122,19 +144,39 @@ interface Scheduler {
|
|
|
122
144
|
get: (id: string) => Promise<ScheduleRecord | null>;
|
|
123
145
|
/** All pending scheduled jobs (the DO's `/list` view). */
|
|
124
146
|
list: () => Promise<ScheduleRecord[]>;
|
|
125
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Schedule `target` to run once, `delayMs` from now. `target` is a function
|
|
149
|
+
* {@link FunctionReference} (dispatched as a one-shot) or a durable
|
|
150
|
+
* {@link WorkflowReference} — the generated `workflows.<name>` /
|
|
151
|
+
* `agents.<name>` ref — which starts a fresh instance on fire (args become
|
|
152
|
+
* its `params`). {@link ScheduleTargetArgs} infers the accepted args from
|
|
153
|
+
* whichever target was passed.
|
|
154
|
+
*/
|
|
155
|
+
runAfter: <T extends CronTarget>(delayMs: number, target: T, args: ScheduleTargetArgs<T>, options?: RunOptions) => Promise<{
|
|
126
156
|
id: string;
|
|
127
157
|
scheduledFor: number;
|
|
128
158
|
}>;
|
|
129
|
-
|
|
159
|
+
/** Like {@link Scheduler.runAfter} but fires at an absolute `date`/timestamp. */
|
|
160
|
+
runAt: <T extends CronTarget>(date: Date | number, target: T, args: ScheduleTargetArgs<T>, options?: RunOptions) => Promise<{
|
|
130
161
|
id: string;
|
|
131
162
|
scheduledFor: number;
|
|
132
163
|
}>;
|
|
133
164
|
}
|
|
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
|
+
*/
|
|
170
|
+
type DurableObjectJurisdiction = "eu" | "fedramp" | "us";
|
|
134
171
|
/** Subset of `DurableObjectNamespace` the package consumes. */
|
|
135
172
|
interface DurableObjectNamespaceLike {
|
|
136
173
|
get: (id: DurableObjectIdLike) => DurableObjectStubLike;
|
|
137
174
|
idFromName: (name: string) => DurableObjectIdLike;
|
|
175
|
+
/**
|
|
176
|
+
* Derive a jurisdiction-restricted subnamespace. Optional because older
|
|
177
|
+
* workers-types releases (and test doubles) may not expose it.
|
|
178
|
+
*/
|
|
179
|
+
jurisdiction?: (jurisdiction: DurableObjectJurisdiction) => DurableObjectNamespaceLike;
|
|
138
180
|
}
|
|
139
181
|
interface DurableObjectIdLike {
|
|
140
182
|
toString: () => string;
|
|
@@ -145,6 +187,13 @@ interface DurableObjectStubLike {
|
|
|
145
187
|
interface LunoraSchedulerOptions {
|
|
146
188
|
/** Optional named instance — useful for tenant isolation. Default `default`. */
|
|
147
189
|
instanceName?: string;
|
|
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
|
+
*/
|
|
196
|
+
jurisdiction?: DurableObjectJurisdiction;
|
|
148
197
|
/** Binding to the `SchedulerDO` durable object namespace. */
|
|
149
198
|
namespace: DurableObjectNamespaceLike;
|
|
150
199
|
/**
|
|
@@ -599,6 +648,13 @@ declare class SchedulerDO {
|
|
|
599
648
|
* compatibility shim, not the hot path.
|
|
600
649
|
*/
|
|
601
650
|
private static releaseFirstSlot;
|
|
651
|
+
/**
|
|
652
|
+
* Normalize the mutually-exclusive dispatch target off an untrusted body: a
|
|
653
|
+
* one-shot function path (`functionPath`) or a durable workflow/agent
|
|
654
|
+
* instance (`workflow`, a `WORKFLOW_*`/`AGENT_*` binding). Returns `undefined`
|
|
655
|
+
* when neither is present so the caller can reject the schedule.
|
|
656
|
+
*/
|
|
657
|
+
private static resolveScheduleTarget;
|
|
602
658
|
protected readonly state: SchedulerDOState;
|
|
603
659
|
protected readonly env: SchedulerEnv;
|
|
604
660
|
constructor(state: SchedulerDOState, env: SchedulerEnv);
|
|
@@ -630,15 +686,17 @@ declare class SchedulerDO {
|
|
|
630
686
|
*
|
|
631
687
|
* Claims the job by deleting its time-index entry BEFORE dispatch (an alarm
|
|
632
688
|
* re-fire then won't pick it up again), runs {@link drainRecord}, and on a
|
|
633
|
-
* thrown storage op
|
|
689
|
+
* thrown storage op re-asserts the claim so the job stays re-fireable.
|
|
634
690
|
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
*
|
|
641
|
-
*
|
|
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.
|
|
642
700
|
*/
|
|
643
701
|
private drainRecordGuarded;
|
|
644
702
|
/**
|
|
@@ -648,16 +706,18 @@ declare class SchedulerDO {
|
|
|
648
706
|
* free slot is reserved durably before dispatch and released immediately if
|
|
649
707
|
* the kick fails (success holds it until the runtime reports completion).
|
|
650
708
|
* Success clears the `id:`/`retry:` rows; failure routes to
|
|
651
|
-
* {@link recordRetry}.
|
|
652
|
-
*
|
|
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.
|
|
653
717
|
* @returns `true` only when the record was successfully dispatched (a 2xx
|
|
654
|
-
* kick)
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
* an already-kicked job would break idempotency. A `false` return (pool
|
|
658
|
-
* backpressure or a failed dispatch) means the job is still re-fireable —
|
|
659
|
-
* either already re-armed here, or, if a throw escapes, re-claimed by the
|
|
660
|
-
* guard's catch.
|
|
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).
|
|
661
721
|
*/
|
|
662
722
|
private drainRecord;
|
|
663
723
|
/**
|
|
@@ -665,6 +725,14 @@ declare class SchedulerDO {
|
|
|
665
725
|
* job via {@link requeuePooled}) when the pool is at `maxConcurrency`;
|
|
666
726
|
* otherwise reserves a slot durably and returns `true`. Non-pooled records
|
|
667
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).
|
|
668
736
|
*/
|
|
669
737
|
private reservePoolSlot;
|
|
670
738
|
/**
|
|
@@ -782,4 +850,4 @@ declare const isValidCronExpression: (schedule: string) => boolean;
|
|
|
782
850
|
* job (`cron job "send digest"`) vs. the bare trigger.
|
|
783
851
|
*/
|
|
784
852
|
declare const assertValidCronExpression: (schedule: string, context?: string) => void;
|
|
785
|
-
export { type ArgsOf, CRON_SCHEDULE_KINDS, type CronJob, type CronJobsBuilder, type CronScheduleKind, type CronTarget, type CronTriggerOptions, type CronTriggerSnippet, type DailySchedule, type DurableObjectIdLike, 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 };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -45,6 +45,15 @@ interface WorkflowReference<Params = Record<string, unknown>> {
|
|
|
45
45
|
type CronTarget = FunctionReference | WorkflowReference;
|
|
46
46
|
/** The arguments a cron's target accepts: a workflow's inferred `params`, else an open record (function args aren't inferred). */
|
|
47
47
|
type CronTargetArgs<T extends CronTarget> = T extends WorkflowReference<infer Params> ? Params : Record<string, unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* The arguments a one-shot schedule target ({@link Scheduler.runAfter} /
|
|
50
|
+
* {@link Scheduler.runAt}) accepts. Unlike {@link CronTargetArgs} it preserves a
|
|
51
|
+
* {@link FunctionReference}'s inferred `args` (via {@link ArgsOf}) as well as a
|
|
52
|
+
* {@link WorkflowReference}'s inferred `params`, so scheduling a plain function
|
|
53
|
+
* keeps its today's arg checking while scheduling a workflow/agent infers its
|
|
54
|
+
* `params`.
|
|
55
|
+
*/
|
|
56
|
+
type ScheduleTargetArgs<T extends CronTarget> = T extends WorkflowReference<infer Params> ? Params : T extends FunctionReference ? ArgsOf<T> : Record<string, unknown>;
|
|
48
57
|
/** Narrow a {@link CronTarget} to a {@link WorkflowReference} by its runtime brand. */
|
|
49
58
|
declare const isWorkflowReference: (target: unknown) => target is WorkflowReference;
|
|
50
59
|
/**
|
|
@@ -93,7 +102,12 @@ interface ScheduleRecord {
|
|
|
93
102
|
*/
|
|
94
103
|
attempts?: number;
|
|
95
104
|
enqueuedAt: number;
|
|
96
|
-
|
|
105
|
+
/**
|
|
106
|
+
* The `ns:fn` path of the function to dispatch on fire. Absent when the job
|
|
107
|
+
* targets a durable workflow/agent instead — see {@link ScheduleRecord.workflow}.
|
|
108
|
+
* Exactly one of `functionPath` / `workflow` is set.
|
|
109
|
+
*/
|
|
110
|
+
functionPath?: string;
|
|
97
111
|
id: string;
|
|
98
112
|
/**
|
|
99
113
|
* Scheduler/workpool instance name the job was enqueued through. Echoed in
|
|
@@ -113,6 +127,14 @@ interface ScheduleRecord {
|
|
|
113
127
|
retry?: RetryPolicy;
|
|
114
128
|
scheduledFor: number;
|
|
115
129
|
shardKey?: string;
|
|
130
|
+
/**
|
|
131
|
+
* The `WORKFLOW_*`/`AGENT_*` binding name to start a fresh durable instance
|
|
132
|
+
* of on fire (the {@link ScheduleRecord.args} become its `params`). Set
|
|
133
|
+
* instead of {@link ScheduleRecord.functionPath} when the job targets a
|
|
134
|
+
* workflow/agent {@link WorkflowReference}. The runtime — not the DO — owns
|
|
135
|
+
* the binding, so the dispatch payload carries this through to the Worker.
|
|
136
|
+
*/
|
|
137
|
+
workflow?: string;
|
|
116
138
|
}
|
|
117
139
|
interface Scheduler {
|
|
118
140
|
cancel: (id: string) => Promise<{
|
|
@@ -122,19 +144,39 @@ interface Scheduler {
|
|
|
122
144
|
get: (id: string) => Promise<ScheduleRecord | null>;
|
|
123
145
|
/** All pending scheduled jobs (the DO's `/list` view). */
|
|
124
146
|
list: () => Promise<ScheduleRecord[]>;
|
|
125
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Schedule `target` to run once, `delayMs` from now. `target` is a function
|
|
149
|
+
* {@link FunctionReference} (dispatched as a one-shot) or a durable
|
|
150
|
+
* {@link WorkflowReference} — the generated `workflows.<name>` /
|
|
151
|
+
* `agents.<name>` ref — which starts a fresh instance on fire (args become
|
|
152
|
+
* its `params`). {@link ScheduleTargetArgs} infers the accepted args from
|
|
153
|
+
* whichever target was passed.
|
|
154
|
+
*/
|
|
155
|
+
runAfter: <T extends CronTarget>(delayMs: number, target: T, args: ScheduleTargetArgs<T>, options?: RunOptions) => Promise<{
|
|
126
156
|
id: string;
|
|
127
157
|
scheduledFor: number;
|
|
128
158
|
}>;
|
|
129
|
-
|
|
159
|
+
/** Like {@link Scheduler.runAfter} but fires at an absolute `date`/timestamp. */
|
|
160
|
+
runAt: <T extends CronTarget>(date: Date | number, target: T, args: ScheduleTargetArgs<T>, options?: RunOptions) => Promise<{
|
|
130
161
|
id: string;
|
|
131
162
|
scheduledFor: number;
|
|
132
163
|
}>;
|
|
133
164
|
}
|
|
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
|
+
*/
|
|
170
|
+
type DurableObjectJurisdiction = "eu" | "fedramp" | "us";
|
|
134
171
|
/** Subset of `DurableObjectNamespace` the package consumes. */
|
|
135
172
|
interface DurableObjectNamespaceLike {
|
|
136
173
|
get: (id: DurableObjectIdLike) => DurableObjectStubLike;
|
|
137
174
|
idFromName: (name: string) => DurableObjectIdLike;
|
|
175
|
+
/**
|
|
176
|
+
* Derive a jurisdiction-restricted subnamespace. Optional because older
|
|
177
|
+
* workers-types releases (and test doubles) may not expose it.
|
|
178
|
+
*/
|
|
179
|
+
jurisdiction?: (jurisdiction: DurableObjectJurisdiction) => DurableObjectNamespaceLike;
|
|
138
180
|
}
|
|
139
181
|
interface DurableObjectIdLike {
|
|
140
182
|
toString: () => string;
|
|
@@ -145,6 +187,13 @@ interface DurableObjectStubLike {
|
|
|
145
187
|
interface LunoraSchedulerOptions {
|
|
146
188
|
/** Optional named instance — useful for tenant isolation. Default `default`. */
|
|
147
189
|
instanceName?: string;
|
|
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
|
+
*/
|
|
196
|
+
jurisdiction?: DurableObjectJurisdiction;
|
|
148
197
|
/** Binding to the `SchedulerDO` durable object namespace. */
|
|
149
198
|
namespace: DurableObjectNamespaceLike;
|
|
150
199
|
/**
|
|
@@ -599,6 +648,13 @@ declare class SchedulerDO {
|
|
|
599
648
|
* compatibility shim, not the hot path.
|
|
600
649
|
*/
|
|
601
650
|
private static releaseFirstSlot;
|
|
651
|
+
/**
|
|
652
|
+
* Normalize the mutually-exclusive dispatch target off an untrusted body: a
|
|
653
|
+
* one-shot function path (`functionPath`) or a durable workflow/agent
|
|
654
|
+
* instance (`workflow`, a `WORKFLOW_*`/`AGENT_*` binding). Returns `undefined`
|
|
655
|
+
* when neither is present so the caller can reject the schedule.
|
|
656
|
+
*/
|
|
657
|
+
private static resolveScheduleTarget;
|
|
602
658
|
protected readonly state: SchedulerDOState;
|
|
603
659
|
protected readonly env: SchedulerEnv;
|
|
604
660
|
constructor(state: SchedulerDOState, env: SchedulerEnv);
|
|
@@ -630,15 +686,17 @@ declare class SchedulerDO {
|
|
|
630
686
|
*
|
|
631
687
|
* Claims the job by deleting its time-index entry BEFORE dispatch (an alarm
|
|
632
688
|
* re-fire then won't pick it up again), runs {@link drainRecord}, and on a
|
|
633
|
-
* thrown storage op
|
|
689
|
+
* thrown storage op re-asserts the claim so the job stays re-fireable.
|
|
634
690
|
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
*
|
|
641
|
-
*
|
|
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.
|
|
642
700
|
*/
|
|
643
701
|
private drainRecordGuarded;
|
|
644
702
|
/**
|
|
@@ -648,16 +706,18 @@ declare class SchedulerDO {
|
|
|
648
706
|
* free slot is reserved durably before dispatch and released immediately if
|
|
649
707
|
* the kick fails (success holds it until the runtime reports completion).
|
|
650
708
|
* Success clears the `id:`/`retry:` rows; failure routes to
|
|
651
|
-
* {@link recordRetry}.
|
|
652
|
-
*
|
|
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.
|
|
653
717
|
* @returns `true` only when the record was successfully dispatched (a 2xx
|
|
654
|
-
* kick)
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
* an already-kicked job would break idempotency. A `false` return (pool
|
|
658
|
-
* backpressure or a failed dispatch) means the job is still re-fireable —
|
|
659
|
-
* either already re-armed here, or, if a throw escapes, re-claimed by the
|
|
660
|
-
* guard's catch.
|
|
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).
|
|
661
721
|
*/
|
|
662
722
|
private drainRecord;
|
|
663
723
|
/**
|
|
@@ -665,6 +725,14 @@ declare class SchedulerDO {
|
|
|
665
725
|
* job via {@link requeuePooled}) when the pool is at `maxConcurrency`;
|
|
666
726
|
* otherwise reserves a slot durably and returns `true`. Non-pooled records
|
|
667
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).
|
|
668
736
|
*/
|
|
669
737
|
private reservePoolSlot;
|
|
670
738
|
/**
|
|
@@ -782,4 +850,4 @@ declare const isValidCronExpression: (schedule: string) => boolean;
|
|
|
782
850
|
* job (`cron job "send digest"`) vs. the bare trigger.
|
|
783
851
|
*/
|
|
784
852
|
declare const assertValidCronExpression: (schedule: string, context?: string) => void;
|
|
785
|
-
export { type ArgsOf, CRON_SCHEDULE_KINDS, type CronJob, type CronJobsBuilder, type CronScheduleKind, type CronTarget, type CronTriggerOptions, type CronTriggerSnippet, type DailySchedule, type DurableObjectIdLike, 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 };
|
|
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { default as createScheduler } from './packem_shared/createScheduler-
|
|
2
|
-
export { default as createWorkpool } from './packem_shared/createWorkpool-
|
|
3
|
-
export { createCronTrigger } from './packem_shared/createCronTrigger-
|
|
4
|
-
export { CRON_SCHEDULE_KINDS, compileCronSchedule, cronJobs } from './packem_shared/
|
|
5
|
-
export { createQueueConsumer, createQueueWorkpool, httpDispatcher } from './packem_shared/createQueueConsumer-
|
|
6
|
-
export { SchedulerDO } from './packem_shared/SchedulerDO-
|
|
1
|
+
export { default as createScheduler } from './packem_shared/createScheduler-Df9JUjQb.mjs';
|
|
2
|
+
export { default as createWorkpool } from './packem_shared/createWorkpool-Ce0kNM2p.mjs';
|
|
3
|
+
export { createCronTrigger } from './packem_shared/createCronTrigger-Dh4VLxD9.mjs';
|
|
4
|
+
export { CRON_SCHEDULE_KINDS, compileCronSchedule, cronJobs } from './packem_shared/CRON_SCHEDULE_KINDS-C2FflEnq.mjs';
|
|
5
|
+
export { createQueueConsumer, createQueueWorkpool, httpDispatcher } from './packem_shared/createQueueConsumer-Cy-Mp-El.mjs';
|
|
6
|
+
export { SchedulerDO } from './packem_shared/SchedulerDO-DeJHI379.mjs';
|
|
7
7
|
export { isWorkflowReference } from './packem_shared/isWorkflowReference-C9mQkMXt.mjs';
|
|
8
|
-
export { assertValidCronExpression, isValidCronExpression } from './packem_shared/assertValidCronExpression-
|
|
8
|
+
export { assertValidCronExpression, isValidCronExpression } from './packem_shared/assertValidCronExpression-B9m75qU0.mjs';
|
package/dist/packem_shared/{compileCronSchedule-BaLlXJiN.mjs → CRON_SCHEDULE_KINDS-C2FflEnq.mjs}
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
1
2
|
import { isWorkflowReference } from './isWorkflowReference-C9mQkMXt.mjs';
|
|
2
|
-
import { assertValidCronExpression } from './assertValidCronExpression-
|
|
3
|
+
import { assertValidCronExpression } from './assertValidCronExpression-B9m75qU0.mjs';
|
|
3
4
|
|
|
4
5
|
const WEEKDAY_INDEX = {
|
|
5
6
|
friday: 5,
|
|
@@ -12,24 +13,37 @@ const WEEKDAY_INDEX = {
|
|
|
12
13
|
};
|
|
13
14
|
const field = (value, label, min, max) => {
|
|
14
15
|
if (!Number.isInteger(value) || value < min || value > max) {
|
|
15
|
-
throw new
|
|
16
|
+
throw new LunoraError(
|
|
17
|
+
"INTERNAL",
|
|
18
|
+
`@lunora/scheduler: cronJobs ${label} must be an integer in [${min.toFixed(0)}, ${max.toFixed(0)}], got ${String(value)}`
|
|
19
|
+
);
|
|
16
20
|
}
|
|
17
21
|
return value.toFixed(0);
|
|
18
22
|
};
|
|
23
|
+
const stepField = (value, label, period) => {
|
|
24
|
+
const rendered = field(value, label, 1, period - 1);
|
|
25
|
+
if (period % value !== 0) {
|
|
26
|
+
throw new LunoraError(
|
|
27
|
+
"INTERNAL",
|
|
28
|
+
`@lunora/scheduler: ${label} must evenly divide ${period.toFixed(0)} for a fixed "every ${value.toFixed(0)}" interval — cron "*/${value.toFixed(0)}" means "at values divisible by ${value.toFixed(0)}", which wraps unevenly; pick a divisor of ${period.toFixed(0)}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return rendered;
|
|
32
|
+
};
|
|
19
33
|
const compileInterval = (schedule) => {
|
|
20
34
|
const units = ["seconds", "minutes", "hours"].filter((unit2) => schedule[unit2] !== void 0);
|
|
21
35
|
if (units.length !== 1) {
|
|
22
|
-
throw new
|
|
36
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: interval schedule must specify exactly one of { seconds, minutes, hours }`);
|
|
23
37
|
}
|
|
24
38
|
const unit = units[0];
|
|
25
39
|
const value = schedule[unit];
|
|
26
40
|
if (unit === "seconds") {
|
|
27
|
-
return `*/${
|
|
41
|
+
return `*/${stepField(value, "interval.seconds", 60)} * * * * *`;
|
|
28
42
|
}
|
|
29
43
|
if (unit === "minutes") {
|
|
30
|
-
return `*/${
|
|
44
|
+
return `*/${stepField(value, "interval.minutes", 60)} * * * *`;
|
|
31
45
|
}
|
|
32
|
-
return `0 */${
|
|
46
|
+
return `0 */${stepField(value, "interval.hours", 24)} * * *`;
|
|
33
47
|
};
|
|
34
48
|
const compileDaily = (schedule) => {
|
|
35
49
|
const minute = field(schedule.minuteUTC, "daily.minuteUTC", 0, 59);
|
|
@@ -39,7 +53,7 @@ const compileDaily = (schedule) => {
|
|
|
39
53
|
const compileWeekly = (schedule) => {
|
|
40
54
|
const index = WEEKDAY_INDEX[schedule.dayOfWeek];
|
|
41
55
|
if (index === void 0) {
|
|
42
|
-
throw new
|
|
56
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: weekly schedule has invalid dayOfWeek "${schedule.dayOfWeek}"`);
|
|
43
57
|
}
|
|
44
58
|
const minute = field(schedule.minuteUTC, "weekly.minuteUTC", 0, 59);
|
|
45
59
|
const hour = field(schedule.hourUTC, "weekly.hourUTC", 0, 23);
|
|
@@ -67,7 +81,7 @@ const compileCronSchedule = (kind, schedule) => {
|
|
|
67
81
|
return compileWeekly(schedule);
|
|
68
82
|
}
|
|
69
83
|
default: {
|
|
70
|
-
throw new
|
|
84
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: unknown cron schedule kind "${String(kind)}"`);
|
|
71
85
|
}
|
|
72
86
|
}
|
|
73
87
|
};
|
|
@@ -76,10 +90,10 @@ const cronJobs = () => {
|
|
|
76
90
|
const seen = /* @__PURE__ */ new Set();
|
|
77
91
|
const register = (name, cron, target, args) => {
|
|
78
92
|
if (typeof name !== "string" || name.trim() === "") {
|
|
79
|
-
throw new
|
|
93
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: cron job name must be a non-empty string`);
|
|
80
94
|
}
|
|
81
95
|
if (seen.has(name)) {
|
|
82
|
-
throw new
|
|
96
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: duplicate cron job name "${name}" — names must be unique within one cronJobs()`);
|
|
83
97
|
}
|
|
84
98
|
if (isWorkflowReference(target)) {
|
|
85
99
|
assertValidCronExpression(cron, `cron expression for job "${name}"`);
|
|
@@ -88,7 +102,10 @@ const cronJobs = () => {
|
|
|
88
102
|
return;
|
|
89
103
|
}
|
|
90
104
|
if (!target || typeof target.__lunoraRef !== "string") {
|
|
91
|
-
throw new
|
|
105
|
+
throw new LunoraError(
|
|
106
|
+
"INTERNAL",
|
|
107
|
+
`@lunora/scheduler: cron job "${name}" requires a function reference (e.g. internal.email.digest) or a workflow reference`
|
|
108
|
+
);
|
|
92
109
|
}
|
|
93
110
|
assertValidCronExpression(cron, `cron expression for job "${name}"`);
|
|
94
111
|
seen.add(name);
|