@helipod/scheduler 0.1.0
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.ts +711 -0
- package/dist/index.js +775 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
import { Driver, ComponentDefinition } from '@helipod/component';
|
|
2
|
+
import * as _helipod_values from '@helipod/values';
|
|
3
|
+
import { JSONValue } from '@helipod/values';
|
|
4
|
+
import { GuestDatabaseWriter, ActionApi } from '@helipod/executor';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A function reference as produced by codegen's `api`/`internal` proxy (see
|
|
8
|
+
* `@helipod/client`'s `FunctionReference`/`getFunctionPath`). Replicated here (rather than
|
|
9
|
+
* depending on `@helipod/client`, a client-facing SDK package) since a server component
|
|
10
|
+
* needs only this one-field shape.
|
|
11
|
+
*/
|
|
12
|
+
interface FunctionReference {
|
|
13
|
+
readonly __path: string;
|
|
14
|
+
}
|
|
15
|
+
type FnRef = FunctionReference | string;
|
|
16
|
+
/** Resolve a `fnRef` (string path or codegen ref) to its string path. Mirrors `@helipod/client`'s `getFunctionPath`. */
|
|
17
|
+
declare function getFunctionPath(ref: FnRef): string;
|
|
18
|
+
/**
|
|
19
|
+
* Task 4 design note — `parentId` threading (cascading cancel):
|
|
20
|
+
*
|
|
21
|
+
* The original design was for the driver to set an ambient "current job id" while running a job,
|
|
22
|
+
* so a job scheduling a child (`ctx.scheduler.runAfter(...)` called from inside a driver-run job)
|
|
23
|
+
* would have that child's `parentId` populated automatically via `currentJobId()` below. That
|
|
24
|
+
* requires the ambient to survive from `driver.ts`'s `runPass()` — which only has the string
|
|
25
|
+
* `fnPath`/`jobId`, and calls `ctx.runFunction(claimed.fnPath, claimed.args)` — through
|
|
26
|
+
* `DriverContext.runFunction` → `InlineUdfExecutor.run` → this component's `context` builder,
|
|
27
|
+
* none of which currently carry a "who's calling" field. Wiring it soundly means extending
|
|
28
|
+
* `DriverContext.runFunction`'s signature, `RunOptions` (`packages/executor/src/executor.ts`),
|
|
29
|
+
* and `ComponentContext` (used by every `context:` facade, not just this one) — a cross-package
|
|
30
|
+
* change well outside `components/scheduler/*` with blast radius on every component, for a single
|
|
31
|
+
* driver's benefit.
|
|
32
|
+
*
|
|
33
|
+
* Chosen instead: cascading cancel is implemented generically over whatever `parentId` a job
|
|
34
|
+
* happens to have (`cancel()` below walks `by_parent` regardless of how `parentId` got set), and
|
|
35
|
+
* `currentJobId()` stays a stub returning `undefined`. Known limitation: a child scheduled from
|
|
36
|
+
* *inside* a driver-run job today gets `parentId: undefined` (not chained) — cascading cancel
|
|
37
|
+
* only reaches jobs whose `parentId` was set explicitly. The test suite (`test/reliability.test.ts`)
|
|
38
|
+
* exercises the cascade via the test-only `_system:insertJob` escape hatch, which sets `parentId`
|
|
39
|
+
* directly. Revisit if/when a later slice needs real parent/child chaining from driver-run jobs.
|
|
40
|
+
*/
|
|
41
|
+
type JobState = "pending" | "inProgress" | "success" | "failed" | "canceled";
|
|
42
|
+
interface EnqueueOpts {
|
|
43
|
+
/** Relative delay in ms from the enqueueing call's `now()`. `runAt` wins when both are set. */
|
|
44
|
+
runAfter?: number;
|
|
45
|
+
/** Absolute due time (epoch ms). Takes precedence over `runAfter`. */
|
|
46
|
+
runAt?: number;
|
|
47
|
+
retry?: {
|
|
48
|
+
maxFailures: number;
|
|
49
|
+
};
|
|
50
|
+
name?: string;
|
|
51
|
+
onComplete?: string;
|
|
52
|
+
context?: JSONValue;
|
|
53
|
+
idempotencyKey?: string;
|
|
54
|
+
}
|
|
55
|
+
interface SchedulerContext {
|
|
56
|
+
runAfter(delayMs: number, fnRef: FnRef, args: JSONValue): Promise<string>;
|
|
57
|
+
runAt(ts: number | Date, fnRef: FnRef, args: JSONValue): Promise<string>;
|
|
58
|
+
cancel(id: string): Promise<void>;
|
|
59
|
+
/** Internal: the general enqueue path (workflow-style callers pass `opts` directly). */
|
|
60
|
+
enqueue(fnRef: FnRef, args: JSONValue, opts?: EnqueueOpts): Promise<string>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The two `jobs`/`job_args` table names `enqueueInternal` writes to — bare (`"jobs"`) when
|
|
64
|
+
* called from this file's own namespaced `ctx.scheduler` facade, or fully qualified
|
|
65
|
+
* (`"scheduler/jobs"`) when called from a privileged context (`_cronTick` in `./modules.ts`, and
|
|
66
|
+
* the boot-time cron reconciler in `./crons.ts`) that bypasses namespace prefixing entirely —
|
|
67
|
+
* see `./modules.ts`'s module doc comment for why those callers must use fully-qualified names.
|
|
68
|
+
*/
|
|
69
|
+
interface EnqueueTables {
|
|
70
|
+
jobs: string;
|
|
71
|
+
jobArgs: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The shared enqueue path: inserts a `pending` `jobs` row (+ `job_args`), honoring the
|
|
75
|
+
* born-canceled check and, since Task 5, an idempotent insert-or-noop on `opts.idempotencyKey`
|
|
76
|
+
* (looked up via `by_idempotency`) — if a job with that key already exists, its id is returned
|
|
77
|
+
* unchanged and nothing new is inserted. This dedup is what makes the cron cadence's occurrence
|
|
78
|
+
* key (`${cronName}:${fireTs}`, `_cronTick` in `./modules.ts`) safe to call more than once for
|
|
79
|
+
* the same occurrence (e.g. a reclaim-driven re-run of a cadence job).
|
|
80
|
+
*
|
|
81
|
+
* Factored out of `schedulerContext` (which calls it with bare table names, scoped to the calling
|
|
82
|
+
* mutation's own transaction via `db`) so `_cronTick` and the boot-time cron reconciler — both of
|
|
83
|
+
* which write with fully-qualified table names outside a normal namespaced call — can reuse the
|
|
84
|
+
* exact same logic rather than a parallel reimplementation drifting out of sync.
|
|
85
|
+
*/
|
|
86
|
+
declare function enqueueInternal(db: GuestDatabaseWriter, now: () => number, tables: EnqueueTables, fnRef: FnRef, args: JSONValue, opts: EnqueueOpts,
|
|
87
|
+
/**
|
|
88
|
+
* Resolves the target's real kind for the job's `kind` column. Defaults to always-"mutation"
|
|
89
|
+
* — correct for every caller EXCEPT `schedulerContext`'s facade methods below, which pass a
|
|
90
|
+
* closure over `cctx.functionKind` (the only caller whose target might actually be an action;
|
|
91
|
+
* `_cronTick`'s cadence/work jobs and `fireOnComplete`'s callback are always mutations).
|
|
92
|
+
*/
|
|
93
|
+
kindOf?: (fnPath: string) => "mutation" | "action"): Promise<string>;
|
|
94
|
+
/**
|
|
95
|
+
* The terminal outcome an `onComplete` callback is invoked with — a strict superset of
|
|
96
|
+
* `./modules.ts`'s `JobResult` (which only ever carries `success`/`failed`, the two outcomes
|
|
97
|
+
* `_complete` itself produces) plus `canceled`, produced by `cancel()` below. Defined here (not
|
|
98
|
+
* `./modules.ts`) so `./modules.ts` — which already imports from this file — can reuse it without
|
|
99
|
+
* a circular import back the other way.
|
|
100
|
+
*/
|
|
101
|
+
type OnCompleteResult = {
|
|
102
|
+
kind: "success";
|
|
103
|
+
value: unknown;
|
|
104
|
+
} | {
|
|
105
|
+
kind: "failed";
|
|
106
|
+
error: string;
|
|
107
|
+
} | {
|
|
108
|
+
kind: "canceled";
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Task 6 — the workflow-ready `onComplete`/`context` round-trip: if `job.onComplete` (a mutation
|
|
112
|
+
* path) is set, enqueue it with `{ jobId, context, result }`, where `context` is `job_args`'s
|
|
113
|
+
* `context` for THIS job read back verbatim (opaque to the scheduler — never interpreted, just
|
|
114
|
+
* round-tripped) and `result` is the terminal outcome. Enqueued via `runAt: now()` (the `runAfter:
|
|
115
|
+
* 0` semantics) so it's immediately due — the reactive wake picks it up on the very next
|
|
116
|
+
* commit-driven iteration, no extra latency beyond one dispatch pass.
|
|
117
|
+
*
|
|
118
|
+
* A no-op when `onComplete` is `undefined` (the common case — most jobs don't register a
|
|
119
|
+
* completion callback). Called from exactly two terminal transitions, never a retry:
|
|
120
|
+
* - `./modules.ts`'s `_complete`, on `state:"success"` and on dead-lettered `state:"failed"`
|
|
121
|
+
* (NOT on the back-to-`"pending"` retry branch — the job isn't actually done yet).
|
|
122
|
+
* - `cancel()` below, for both the directly-canceled job and any cascaded-canceled descendant.
|
|
123
|
+
*/
|
|
124
|
+
declare function fireOnComplete(db: GuestDatabaseWriter, now: () => number, tables: EnqueueTables, jobId: string, onComplete: string | undefined, result: OnCompleteResult): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* The action-mode counterpart to `SchedulerContext` — same `runAfter`/`runAt`/`cancel` method
|
|
127
|
+
* signatures (so a function body scheduling work is portable between a mutation and an action),
|
|
128
|
+
* minus `enqueue` (the general opts-carrying path stays a mutation-only internal primitive; no
|
|
129
|
+
* action caller needs it yet). Deliberately NOT structurally assignable to `SchedulerContext` as a
|
|
130
|
+
* type (no `enqueue`) even though the three shared methods match exactly.
|
|
131
|
+
*/
|
|
132
|
+
interface SchedulerActionContext {
|
|
133
|
+
runAfter(delayMs: number, fnRef: FnRef, args: JSONValue): Promise<string>;
|
|
134
|
+
runAt(ts: number | Date, fnRef: FnRef, args: JSONValue): Promise<string>;
|
|
135
|
+
cancel(id: string): Promise<void>;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Builds the action-mode `ctx.scheduler` — wired as `defineScheduler()`'s `buildAction` (see
|
|
139
|
+
* `./index.ts`). An action has no `db`, so `runAfter`/`runAt`/`cancel` can't write a `jobs` row
|
|
140
|
+
* directly the way `schedulerContext` above does; instead each delegates to `api.runMutation` of
|
|
141
|
+
* the internal `scheduler:_enqueue`/`scheduler:_cancel` mutations (`./modules.ts`), which run the
|
|
142
|
+
* SAME `enqueueInternal`/`cancel` logic inside their own fresh top-level transaction.
|
|
143
|
+
*
|
|
144
|
+
* `Date.now()` below (converting `runAfter`'s relative `delayMs` to an absolute `runAtMs`) is fine
|
|
145
|
+
* here even though queries/mutations must stay deterministic: an action is non-deterministic by
|
|
146
|
+
* design (see `ActionCtx`'s doc comment in `@helipod/executor`), and the scheduler never
|
|
147
|
+
* recomputes anything from this timestamp — `scheduler:_enqueue` just stores it as `nextTs`.
|
|
148
|
+
*/
|
|
149
|
+
declare function schedulerActionContext(api: ActionApi): SchedulerActionContext;
|
|
150
|
+
|
|
151
|
+
type CatchUpPolicy = "skip" | "fireOnce" | "fireAll";
|
|
152
|
+
/** The two spec shapes `computeNextRun` understands. Stored on the `crons` row JSON-serialized (`spec: v.string()`). */
|
|
153
|
+
type CronSpec = {
|
|
154
|
+
kind: "interval";
|
|
155
|
+
ms: number;
|
|
156
|
+
} | {
|
|
157
|
+
kind: "cron";
|
|
158
|
+
expr: string;
|
|
159
|
+
};
|
|
160
|
+
interface CronRegistryEntry {
|
|
161
|
+
name: string;
|
|
162
|
+
spec: CronSpec;
|
|
163
|
+
tz: string;
|
|
164
|
+
catchUp: CatchUpPolicy;
|
|
165
|
+
workFnPath: string;
|
|
166
|
+
workArgs: JSONValue;
|
|
167
|
+
}
|
|
168
|
+
interface IntervalPeriod {
|
|
169
|
+
seconds?: number;
|
|
170
|
+
minutes?: number;
|
|
171
|
+
hours?: number;
|
|
172
|
+
}
|
|
173
|
+
interface DailyAt {
|
|
174
|
+
hourUTC: number;
|
|
175
|
+
minuteUTC: number;
|
|
176
|
+
}
|
|
177
|
+
interface HourlyAt {
|
|
178
|
+
minuteUTC: number;
|
|
179
|
+
}
|
|
180
|
+
type DayOfWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday";
|
|
181
|
+
interface WeeklyAt {
|
|
182
|
+
dayOfWeek: DayOfWeek;
|
|
183
|
+
hourUTC: number;
|
|
184
|
+
minuteUTC: number;
|
|
185
|
+
}
|
|
186
|
+
interface MonthlyAt {
|
|
187
|
+
day: number;
|
|
188
|
+
hourUTC: number;
|
|
189
|
+
minuteUTC: number;
|
|
190
|
+
}
|
|
191
|
+
/** `tz`/`catchUp` are additive Helipod extensions (absent on Convex) — see the design spec §5.2. */
|
|
192
|
+
interface CronOpts {
|
|
193
|
+
tz?: string;
|
|
194
|
+
catchUp?: CatchUpPolicy;
|
|
195
|
+
}
|
|
196
|
+
/** `.daily`/`.hourly`/`.weekly`/`.monthly` take *UTC fields by name — `tz` isn't accepted (it'd be ambiguous which field it applies to); only `catchUp` is overridable. */
|
|
197
|
+
type CronUtcOpts = Pick<CronOpts, "catchUp">;
|
|
198
|
+
interface CronJobs {
|
|
199
|
+
interval(name: string, period: IntervalPeriod, fnRef: FnRef, args: JSONValue, opts?: CronOpts): void;
|
|
200
|
+
cron(name: string, expr: string, fnRef: FnRef, args: JSONValue, opts?: CronOpts): void;
|
|
201
|
+
daily(name: string, at: DailyAt, fnRef: FnRef, args: JSONValue, opts?: CronUtcOpts): void;
|
|
202
|
+
hourly(name: string, at: HourlyAt, fnRef: FnRef, args: JSONValue, opts?: CronUtcOpts): void;
|
|
203
|
+
weekly(name: string, at: WeeklyAt, fnRef: FnRef, args: JSONValue, opts?: CronUtcOpts): void;
|
|
204
|
+
monthly(name: string, at: MonthlyAt, fnRef: FnRef, args: JSONValue, opts?: CronUtcOpts): void;
|
|
205
|
+
/** Boot-reconciliation seam — not part of the public Convex-parity surface. */
|
|
206
|
+
__entries(): CronRegistryEntry[];
|
|
207
|
+
}
|
|
208
|
+
/** `cronJobs()` — see this file's module doc comment for the full `crons.ts` shape. */
|
|
209
|
+
declare function cronJobs(): CronJobs;
|
|
210
|
+
/**
|
|
211
|
+
* Computes the next fire time strictly AFTER `afterTs`, per `spec`. For `{kind:"interval"}`
|
|
212
|
+
* that's plain arithmetic; for `{kind:"cron"}` it's `cron-parser`'s `parseExpression(...).next()`
|
|
213
|
+
* against `afterTs` as `currentDate` in the given IANA `tz` — verified to return a date strictly
|
|
214
|
+
* greater than `afterTs` even when `afterTs` itself exactly matches the pattern (i.e. calling
|
|
215
|
+
* this again with the returned value as the new `afterTs` always advances — no infinite loop,
|
|
216
|
+
* no repeat).
|
|
217
|
+
*/
|
|
218
|
+
declare function computeNextRun(spec: CronSpec, tz: string, afterTs: number): number;
|
|
219
|
+
/**
|
|
220
|
+
* Computes the LAST fire time AT-OR-BEFORE `atOrBeforeTs`, per `spec` — the mirror of
|
|
221
|
+
* `computeNextRun`, used only by `_cronTick`'s bounded catch-up path (`./modules.ts`, the
|
|
222
|
+
* unbounded-catch-up-loop fix) to find the single most recent missed occurrence in O(1) without
|
|
223
|
+
* stepping through a potentially enormous backlog. Only meaningful for `{kind:"cron"}` — an
|
|
224
|
+
* interval spec's "previous occurrence" is plain reverse arithmetic from its own numeric anchor,
|
|
225
|
+
* which `_cronTick` computes inline without a cron-parser round-trip, so this throws for
|
|
226
|
+
* `{kind:"interval"}` rather than silently doing the wrong thing.
|
|
227
|
+
*
|
|
228
|
+
* Implemented via `cron-parser`'s `.prev()`, called with `currentDate` one ms PAST
|
|
229
|
+
* `atOrBeforeTs` so an occurrence landing EXACTLY on `atOrBeforeTs` is still included —
|
|
230
|
+
* `cron-parser`'s `.next()`/`.prev()` are both strictly exclusive of `currentDate` itself
|
|
231
|
+
* (verified empirically, matching `computeNextRun`'s own "strictly after" contract on the other
|
|
232
|
+
* side).
|
|
233
|
+
*/
|
|
234
|
+
declare function computePrevRun(spec: CronSpec, tz: string, atOrBeforeTs: number): number;
|
|
235
|
+
/**
|
|
236
|
+
* Enqueues (or re-enqueues) a cron's CADENCE job, embedding the resulting job's own id into its
|
|
237
|
+
* args (`{cronName, jobId}`) — the duplicate-cadence-chain fix's belt-and-braces half: a future
|
|
238
|
+
* `_cronTick` invocation can then recognize whether it's still the CURRENT canonical chain for
|
|
239
|
+
* this cron (by comparing its own `args.jobId` against `crons.cadenceJobId`) and self-terminate
|
|
240
|
+
* without rescheduling if not — see `_cronTick`'s doc comment in `./modules.ts`. This convergence
|
|
241
|
+
* check is a backstop even if `hasLiveCadence` (below) somehow still let two chains coexist.
|
|
242
|
+
*
|
|
243
|
+
* Two writes (insert, then patch `job_args`) rather than one, because the job's id isn't known
|
|
244
|
+
* until after the insert — self-referential args can't be supplied up front. This runs at most
|
|
245
|
+
* once per cadence tick (or once per boot-time reconcile), so the extra write is negligible.
|
|
246
|
+
*/
|
|
247
|
+
declare function enqueueCadenceJob(db: GuestDatabaseWriter, now: () => number, tables: EnqueueTables, cronName: string, runAt: number): Promise<string>;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The `@helipod/scheduler` component schema (namespaced `scheduler/*` when composed).
|
|
251
|
+
*
|
|
252
|
+
* - `jobs` / `job_args`: split so a job's identity/state (small, hot — scanned by the driver's
|
|
253
|
+
* `by_next_ts` index) never carries the (possibly large) `args`/`context` payload.
|
|
254
|
+
* - `crons`: declared here for the schema to be stable across Task 2→5; only Task 5's cron
|
|
255
|
+
* scheduler reads/writes it (`cadenceJobId` links a cron to its currently-pending job).
|
|
256
|
+
*/
|
|
257
|
+
declare const schedulerSchema: _helipod_values.SchemaDefinition<{
|
|
258
|
+
jobs: _helipod_values.TableDefinition<{
|
|
259
|
+
fnPath: {
|
|
260
|
+
readonly kind: "string";
|
|
261
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
262
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
263
|
+
readonly _output: string;
|
|
264
|
+
readonly isOptional: "required";
|
|
265
|
+
};
|
|
266
|
+
kind: {
|
|
267
|
+
readonly kind: "union";
|
|
268
|
+
readonly members: [{
|
|
269
|
+
readonly value: "mutation";
|
|
270
|
+
readonly kind: "literal";
|
|
271
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
272
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
273
|
+
readonly _output: "mutation";
|
|
274
|
+
readonly isOptional: "required";
|
|
275
|
+
}, {
|
|
276
|
+
readonly value: "action";
|
|
277
|
+
readonly kind: "literal";
|
|
278
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
279
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
280
|
+
readonly _output: "action";
|
|
281
|
+
readonly isOptional: "required";
|
|
282
|
+
}];
|
|
283
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
284
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
285
|
+
readonly _output: "mutation" | "action";
|
|
286
|
+
readonly isOptional: "required";
|
|
287
|
+
};
|
|
288
|
+
state: {
|
|
289
|
+
readonly kind: "union";
|
|
290
|
+
readonly members: [{
|
|
291
|
+
readonly value: "pending";
|
|
292
|
+
readonly kind: "literal";
|
|
293
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
294
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
295
|
+
readonly _output: "pending";
|
|
296
|
+
readonly isOptional: "required";
|
|
297
|
+
}, {
|
|
298
|
+
readonly value: "inProgress";
|
|
299
|
+
readonly kind: "literal";
|
|
300
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
301
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
302
|
+
readonly _output: "inProgress";
|
|
303
|
+
readonly isOptional: "required";
|
|
304
|
+
}, {
|
|
305
|
+
readonly value: "success";
|
|
306
|
+
readonly kind: "literal";
|
|
307
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
308
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
309
|
+
readonly _output: "success";
|
|
310
|
+
readonly isOptional: "required";
|
|
311
|
+
}, {
|
|
312
|
+
readonly value: "failed";
|
|
313
|
+
readonly kind: "literal";
|
|
314
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
315
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
316
|
+
readonly _output: "failed";
|
|
317
|
+
readonly isOptional: "required";
|
|
318
|
+
}, {
|
|
319
|
+
readonly value: "canceled";
|
|
320
|
+
readonly kind: "literal";
|
|
321
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
322
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
323
|
+
readonly _output: "canceled";
|
|
324
|
+
readonly isOptional: "required";
|
|
325
|
+
}];
|
|
326
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
327
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
328
|
+
readonly _output: "pending" | "inProgress" | "success" | "failed" | "canceled";
|
|
329
|
+
readonly isOptional: "required";
|
|
330
|
+
};
|
|
331
|
+
nextTs: {
|
|
332
|
+
readonly kind: "number";
|
|
333
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
334
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
335
|
+
readonly _output: number;
|
|
336
|
+
readonly isOptional: "required";
|
|
337
|
+
};
|
|
338
|
+
attempts: {
|
|
339
|
+
readonly kind: "number";
|
|
340
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
341
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
342
|
+
readonly _output: number;
|
|
343
|
+
readonly isOptional: "required";
|
|
344
|
+
};
|
|
345
|
+
maxFailures: {
|
|
346
|
+
readonly kind: "number";
|
|
347
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
348
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
349
|
+
readonly _output: number;
|
|
350
|
+
readonly isOptional: "required";
|
|
351
|
+
};
|
|
352
|
+
leaseHolder: {
|
|
353
|
+
readonly inner: _helipod_values.Validator<string, _helipod_values.OptionalProperty>;
|
|
354
|
+
readonly kind: "optional";
|
|
355
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
356
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
357
|
+
readonly _output: string | undefined;
|
|
358
|
+
readonly isOptional: "optional";
|
|
359
|
+
};
|
|
360
|
+
leaseExpiresAt: {
|
|
361
|
+
readonly inner: _helipod_values.Validator<number, _helipod_values.OptionalProperty>;
|
|
362
|
+
readonly kind: "optional";
|
|
363
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
364
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
365
|
+
readonly _output: number | undefined;
|
|
366
|
+
readonly isOptional: "optional";
|
|
367
|
+
};
|
|
368
|
+
idempotencyKey: {
|
|
369
|
+
readonly inner: _helipod_values.Validator<string, _helipod_values.OptionalProperty>;
|
|
370
|
+
readonly kind: "optional";
|
|
371
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
372
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
373
|
+
readonly _output: string | undefined;
|
|
374
|
+
readonly isOptional: "optional";
|
|
375
|
+
};
|
|
376
|
+
appVersion: {
|
|
377
|
+
readonly inner: _helipod_values.Validator<string, _helipod_values.OptionalProperty>;
|
|
378
|
+
readonly kind: "optional";
|
|
379
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
380
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
381
|
+
readonly _output: string | undefined;
|
|
382
|
+
readonly isOptional: "optional";
|
|
383
|
+
};
|
|
384
|
+
name: {
|
|
385
|
+
readonly inner: _helipod_values.Validator<string, _helipod_values.OptionalProperty>;
|
|
386
|
+
readonly kind: "optional";
|
|
387
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
388
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
389
|
+
readonly _output: string | undefined;
|
|
390
|
+
readonly isOptional: "optional";
|
|
391
|
+
};
|
|
392
|
+
hasArgs: {
|
|
393
|
+
readonly kind: "boolean";
|
|
394
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
395
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
396
|
+
readonly _output: boolean;
|
|
397
|
+
readonly isOptional: "required";
|
|
398
|
+
};
|
|
399
|
+
onComplete: {
|
|
400
|
+
readonly inner: _helipod_values.Validator<string, _helipod_values.OptionalProperty>;
|
|
401
|
+
readonly kind: "optional";
|
|
402
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
403
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
404
|
+
readonly _output: string | undefined;
|
|
405
|
+
readonly isOptional: "optional";
|
|
406
|
+
};
|
|
407
|
+
parentId: {
|
|
408
|
+
readonly inner: _helipod_values.Validator<string, _helipod_values.OptionalProperty>;
|
|
409
|
+
readonly kind: "optional";
|
|
410
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
411
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
412
|
+
readonly _output: string | undefined;
|
|
413
|
+
readonly isOptional: "optional";
|
|
414
|
+
};
|
|
415
|
+
completedTs: {
|
|
416
|
+
readonly inner: _helipod_values.Validator<number, _helipod_values.OptionalProperty>;
|
|
417
|
+
readonly kind: "optional";
|
|
418
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
419
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
420
|
+
readonly _output: number | undefined;
|
|
421
|
+
readonly isOptional: "optional";
|
|
422
|
+
};
|
|
423
|
+
/** The most recent failure's `String(error)` — set on retry AND on dead-letter (Task 4). */
|
|
424
|
+
lastError: {
|
|
425
|
+
readonly inner: _helipod_values.Validator<string, _helipod_values.OptionalProperty>;
|
|
426
|
+
readonly kind: "optional";
|
|
427
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
428
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
429
|
+
readonly _output: string | undefined;
|
|
430
|
+
readonly isOptional: "optional";
|
|
431
|
+
};
|
|
432
|
+
}>;
|
|
433
|
+
job_args: _helipod_values.TableDefinition<{
|
|
434
|
+
jobId: {
|
|
435
|
+
readonly kind: "string";
|
|
436
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
437
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
438
|
+
readonly _output: string;
|
|
439
|
+
readonly isOptional: "required";
|
|
440
|
+
};
|
|
441
|
+
args: {
|
|
442
|
+
readonly kind: "any";
|
|
443
|
+
check(): void;
|
|
444
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
445
|
+
readonly _output: any;
|
|
446
|
+
readonly isOptional: "required";
|
|
447
|
+
};
|
|
448
|
+
context: {
|
|
449
|
+
readonly inner: _helipod_values.Validator<any, _helipod_values.OptionalProperty>;
|
|
450
|
+
readonly kind: "optional";
|
|
451
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
452
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
453
|
+
readonly _output: any;
|
|
454
|
+
readonly isOptional: "optional";
|
|
455
|
+
};
|
|
456
|
+
}>;
|
|
457
|
+
crons: _helipod_values.TableDefinition<{
|
|
458
|
+
name: {
|
|
459
|
+
readonly kind: "string";
|
|
460
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
461
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
462
|
+
readonly _output: string;
|
|
463
|
+
readonly isOptional: "required";
|
|
464
|
+
};
|
|
465
|
+
spec: {
|
|
466
|
+
readonly kind: "string";
|
|
467
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
468
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
469
|
+
readonly _output: string;
|
|
470
|
+
readonly isOptional: "required";
|
|
471
|
+
};
|
|
472
|
+
tz: {
|
|
473
|
+
readonly kind: "string";
|
|
474
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
475
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
476
|
+
readonly _output: string;
|
|
477
|
+
readonly isOptional: "required";
|
|
478
|
+
};
|
|
479
|
+
catchUp: {
|
|
480
|
+
readonly kind: "union";
|
|
481
|
+
readonly members: [{
|
|
482
|
+
readonly value: "skip";
|
|
483
|
+
readonly kind: "literal";
|
|
484
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
485
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
486
|
+
readonly _output: "skip";
|
|
487
|
+
readonly isOptional: "required";
|
|
488
|
+
}, {
|
|
489
|
+
readonly value: "fireOnce";
|
|
490
|
+
readonly kind: "literal";
|
|
491
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
492
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
493
|
+
readonly _output: "fireOnce";
|
|
494
|
+
readonly isOptional: "required";
|
|
495
|
+
}, {
|
|
496
|
+
readonly value: "fireAll";
|
|
497
|
+
readonly kind: "literal";
|
|
498
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
499
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
500
|
+
readonly _output: "fireAll";
|
|
501
|
+
readonly isOptional: "required";
|
|
502
|
+
}];
|
|
503
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
504
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
505
|
+
readonly _output: "skip" | "fireOnce" | "fireAll";
|
|
506
|
+
readonly isOptional: "required";
|
|
507
|
+
};
|
|
508
|
+
lastScheduledTs: {
|
|
509
|
+
readonly inner: _helipod_values.Validator<number, _helipod_values.OptionalProperty>;
|
|
510
|
+
readonly kind: "optional";
|
|
511
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
512
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
513
|
+
readonly _output: number | undefined;
|
|
514
|
+
readonly isOptional: "optional";
|
|
515
|
+
};
|
|
516
|
+
workFnPath: {
|
|
517
|
+
readonly kind: "string";
|
|
518
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
519
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
520
|
+
readonly _output: string;
|
|
521
|
+
readonly isOptional: "required";
|
|
522
|
+
};
|
|
523
|
+
workArgs: {
|
|
524
|
+
readonly kind: "any";
|
|
525
|
+
check(): void;
|
|
526
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
527
|
+
readonly _output: any;
|
|
528
|
+
readonly isOptional: "required";
|
|
529
|
+
};
|
|
530
|
+
cadenceJobId: {
|
|
531
|
+
readonly inner: _helipod_values.Validator<string, _helipod_values.OptionalProperty>;
|
|
532
|
+
readonly kind: "optional";
|
|
533
|
+
check(value: _helipod_values.Value, path: string, out: _helipod_values.ValidationFailure[]): void;
|
|
534
|
+
toJSON(): _helipod_values.ValidatorJSON;
|
|
535
|
+
readonly _output: string | undefined;
|
|
536
|
+
readonly isOptional: "optional";
|
|
537
|
+
};
|
|
538
|
+
}>;
|
|
539
|
+
}>;
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Internal modules for `@helipod/scheduler` — registered on `defineScheduler()`'s `modules` map
|
|
543
|
+
* (so they're reachable as `scheduler:_peekDue` / `scheduler:_claim` / `scheduler:_complete`),
|
|
544
|
+
* consumed ONLY by the Task 3 driver loop (`./driver.ts`) via `DriverContext.runFunction`, which
|
|
545
|
+
* always calls privileged (`runtime-embedded/src/runtime.ts`'s `driverCtx.runFunction` sets
|
|
546
|
+
* `privileged: true`). Privileged calls bypass namespace prefixing entirely (`kernel.ts`'s
|
|
547
|
+
* `requireTable`), so — unlike `facade.ts`, which runs namespaced and uses bare table names
|
|
548
|
+
* (`"jobs"`, `"job_args"`) — these modules must use the fully-qualified names
|
|
549
|
+
* (`"scheduler/jobs"`, `"scheduler/job_args"`).
|
|
550
|
+
*
|
|
551
|
+
* `_peekDue`/`_claim`/`_complete` are internal by convention (the `_` prefix + being paired only
|
|
552
|
+
* with the driver), not by enforced access control — see Task 3's research notes. That's an
|
|
553
|
+
* accepted gap for this slice (nothing else in the codebase enforces "driver-only" beyond
|
|
554
|
+
* `_system:*`/`_admin:*`'s separate privileged registries).
|
|
555
|
+
*/
|
|
556
|
+
/** Cap on how many due jobs a single `_peekDue` batch returns, so one loop iteration can't run unbounded. */
|
|
557
|
+
declare const BATCH_CAP = 64;
|
|
558
|
+
/**
|
|
559
|
+
* Hard ceiling on how many missed occurrences `_cronTick`'s `catchUp:"fireAll"` path will
|
|
560
|
+
* materialize (and fire a work job for) in a single tick — see `_cronTick`'s doc comment. Without
|
|
561
|
+
* this, a `"fireAll"` cron down for a long time on a fast interval could try to enqueue an
|
|
562
|
+
* unbounded number of work jobs synchronously inside one mutation. Occurrences beyond the cap are
|
|
563
|
+
* discarded (logged), not deferred to a later tick — the cron's cadence always re-anchors past
|
|
564
|
+
* the ENTIRE true backlog on the SAME tick, regardless of how much of it actually got fired.
|
|
565
|
+
*/
|
|
566
|
+
declare const CATCHUP_CAP = 1000;
|
|
567
|
+
/** How long a claim's lease is valid before it could be reclaimed by the sweep below. */
|
|
568
|
+
declare const LEASE_MS = 30000;
|
|
569
|
+
/**
|
|
570
|
+
* The driver's ONLY periodic timer: how often `scheduler:_reclaim` runs to sweep `inProgress`
|
|
571
|
+
* jobs whose lease has expired (an infra kill mid-run — the process that claimed the job died
|
|
572
|
+
* before completing it). Normal dispatch stays fully reactive/event-driven; this is a backstop.
|
|
573
|
+
*/
|
|
574
|
+
declare const SWEEP_MS = 30000;
|
|
575
|
+
interface DueJob {
|
|
576
|
+
_id: string;
|
|
577
|
+
fnPath: string;
|
|
578
|
+
kind: "mutation" | "action";
|
|
579
|
+
state: JobState;
|
|
580
|
+
nextTs: number;
|
|
581
|
+
[key: string]: unknown;
|
|
582
|
+
}
|
|
583
|
+
interface PeekDueResult {
|
|
584
|
+
due: DueJob[];
|
|
585
|
+
earliestFutureTs: number | null;
|
|
586
|
+
}
|
|
587
|
+
interface ClaimResult {
|
|
588
|
+
jobId: string;
|
|
589
|
+
fnPath: string;
|
|
590
|
+
kind: "mutation" | "action";
|
|
591
|
+
args: JSONValue;
|
|
592
|
+
context: JSONValue | undefined;
|
|
593
|
+
onComplete: string | undefined;
|
|
594
|
+
}
|
|
595
|
+
type JobResult = {
|
|
596
|
+
kind: "success";
|
|
597
|
+
value: unknown;
|
|
598
|
+
} | {
|
|
599
|
+
kind: "failed";
|
|
600
|
+
error: string;
|
|
601
|
+
retryable?: boolean;
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* A `schedulerDriver()` also exposes:
|
|
606
|
+
* - `__tick`: a deterministic test seam for one loop iteration (no real timers).
|
|
607
|
+
* - `__wake`: the same fire-and-forget signal `DriverContext.onCommit`/timers use internally,
|
|
608
|
+
* exposed so a test can simulate a commit notification landing at a precise moment (e.g. from
|
|
609
|
+
* inside a job's own mutation body, to interleave with an in-flight `__tick()`) — the reactive
|
|
610
|
+
* `onCommit` path itself can't be driven precisely from a test, since it fires off the real
|
|
611
|
+
* commit fan-out on whatever schedule the runtime gives it.
|
|
612
|
+
* - `__sweep`: runs the lease-reclaim sweep (`scheduler:_reclaim`) exactly once, without arming
|
|
613
|
+
* (or waiting on) the real `SWEEP_MS`-interval timer — the deterministic test seam for Task 4's
|
|
614
|
+
* infra-kill reclaim path.
|
|
615
|
+
*/
|
|
616
|
+
interface SchedulerDriver extends Driver {
|
|
617
|
+
__tick: () => Promise<void>;
|
|
618
|
+
__wake: () => void;
|
|
619
|
+
__sweep: () => Promise<void>;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* `@helipod/scheduler`'s driver — the event-driven loop that actually RUNS due jobs.
|
|
623
|
+
*
|
|
624
|
+
* Two wake sources, NO fixed-interval polling:
|
|
625
|
+
* - **Reactive**: taps the runtime's commit fan-out (`DriverContext.onCommit`) and re-runs
|
|
626
|
+
* `iterate()` whenever a commit touches any `scheduler/*` table (an `enqueue`/`cancel`/
|
|
627
|
+
* `complete` write) — so a freshly-enqueued due job gets picked up with ~0 latency.
|
|
628
|
+
* - **Timer**: re-arms a single wall-clock timer to `earliestFutureTs` (the soonest still-pending
|
|
629
|
+
* job) after every iteration, so a job scheduled for later still fires once its time arrives
|
|
630
|
+
* without anything scanning `jobs` in between.
|
|
631
|
+
*
|
|
632
|
+
* Single-owner: an in-process `running` flag collapses overlapping wake-ups (two commits, or a
|
|
633
|
+
* commit racing a timer) into a single iteration at a time. Because `running` is set
|
|
634
|
+
* synchronously — before the first `await` — two `iterate()` calls issued back-to-back in the
|
|
635
|
+
* same synchronous turn can never both proceed; the second observes `running === true` and
|
|
636
|
+
* returns immediately. That said, the in-process flag is only a throughput optimization, NOT the
|
|
637
|
+
* correctness guarantee: the AUTHORITATIVE double-run guard is `scheduler:_claim`'s snapshot-read
|
|
638
|
+
* + exact `state === "pending"` check (`./modules.ts`), serialized by the single-writer OCC
|
|
639
|
+
* transactor — even if two iterations somehow ran concurrently (e.g. two runtimes sharing a
|
|
640
|
+
* store), at most one `_claim` call per job ever observes `"pending"`.
|
|
641
|
+
*
|
|
642
|
+
* A wake that arrives while `running` is already true is NOT dropped: it sets a coalesced
|
|
643
|
+
* `pendingWake` bit that the in-flight iteration checks at the end of every pass, looping for one
|
|
644
|
+
* more fresh peek/claim/complete pass instead of exiting. Without this, a commit that lands
|
|
645
|
+
* mid-iteration (an app mutation enqueuing a due-now job between the loop's awaits) would be
|
|
646
|
+
* silently swallowed — the timer re-arm at the end of the pass would use the `earliestFutureTs`
|
|
647
|
+
* that pass captured, which doesn't account for the new job, and the job would sit `pending`
|
|
648
|
+
* until some unrelated future wake.
|
|
649
|
+
*
|
|
650
|
+
* A job that throws while running is caught per-job (not allowed to escape the loop), so one bad
|
|
651
|
+
* job can't wedge the whole batch or leave `running` stuck `true` — `_complete` is always called
|
|
652
|
+
* (with a `failed` result) and the outer `try/finally` always clears `running`.
|
|
653
|
+
*
|
|
654
|
+
* `iterate()` always returns a promise that settles when the due set it's responsible for has
|
|
655
|
+
* actually been drained — including a coalesced call. A caller that arrives while a pass is
|
|
656
|
+
* already in flight (setting `pendingWake` per above) gets back the SAME promise as the in-flight
|
|
657
|
+
* pass, not an already-resolved one: with the reactive `onCommit` wake now real (not dead code —
|
|
658
|
+
* see `packages/runtime-embedded/src/runtime.ts`), a test's `__tick()` frequently races an
|
|
659
|
+
* app-mutation's own commit, which fires `wake()` before `__tick()` gets a turn. If `__tick()`
|
|
660
|
+
* merely no-op'd on a coalesced call, tests would observe results before the real work finished.
|
|
661
|
+
* `wake()`'s callers (reactive `onCommit`/timer) never await this return value, so they're
|
|
662
|
+
* unaffected — this only changes behavior for synchronous callers like `__tick()`.
|
|
663
|
+
*/
|
|
664
|
+
declare function schedulerDriver(): SchedulerDriver;
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Exponential backoff (with jitter) for a scheduler job's Nth failure. Pure and side-effect-free
|
|
668
|
+
* so it's trivially unit-testable and safe to call from a deterministic mutation (`_complete` in
|
|
669
|
+
* `./modules.ts`) — it takes its randomness as an injected `rng`, never touching `Math.random`
|
|
670
|
+
* directly unless the caller passes none (the default, for callers outside a UDF's determinism
|
|
671
|
+
* boundary — there are none in this codebase yet, but this keeps the function usable standalone).
|
|
672
|
+
*
|
|
673
|
+
* `_complete` passes `ctx.random` (the query/mutation guest ctx's seeded PRNG — see
|
|
674
|
+
* `@helipod/executor`'s `seeded-random.ts`), which is what actually gives the retry jitter its
|
|
675
|
+
* determinism-for-replay property: a mutation and its OCC-conflict replay call `ctx.random()` in
|
|
676
|
+
* the same sequence and get the same numbers, and tests get a computable, non-flaky result.
|
|
677
|
+
*/
|
|
678
|
+
interface BackoffOptions {
|
|
679
|
+
/** Backoff for the first retry (attempts=1), before jitter. */
|
|
680
|
+
initialBackoffMs: number;
|
|
681
|
+
/** Multiplier applied per additional attempt. */
|
|
682
|
+
base: number;
|
|
683
|
+
}
|
|
684
|
+
declare const DEFAULT_BACKOFF_OPTIONS: BackoffOptions;
|
|
685
|
+
/**
|
|
686
|
+
* `attempts` is the failure count AFTER this failure is recorded (i.e. call with the
|
|
687
|
+
* already-incremented `attempts`, not the pre-failure count) — so the first retry (attempts=1)
|
|
688
|
+
* backs off `initialBackoffMs * base^2`, jittered to 50–100% of that.
|
|
689
|
+
*/
|
|
690
|
+
declare function computeBackoff(attempts: number, rng?: () => number, o?: BackoffOptions): number;
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* `defineScheduler()` — the `@helipod/scheduler` component: the `jobs`/`job_args`/`crons`
|
|
694
|
+
* schema, the `ctx.scheduler` facade (`runAfter`/`runAt`/`cancel`/`enqueue`), the
|
|
695
|
+
* internal `_peekDue`/`_claim`/`_complete`/`_cronTick` modules, and the `schedulerDriver`
|
|
696
|
+
* event-loop that actually RUNS due jobs — reactive on commits touching `scheduler/*` plus a
|
|
697
|
+
* wall-clock timer re-armed to the earliest future job (see `./driver.ts`).
|
|
698
|
+
*
|
|
699
|
+
* `contextWrite: true` is load-bearing: it's what lets the facade write (via the calling
|
|
700
|
+
* mutation's own transaction) instead of only reading — see `schedulerContext` in `./facade.ts`
|
|
701
|
+
* and the `ContextProvider.write` opt-in on `@helipod/executor`.
|
|
702
|
+
*
|
|
703
|
+
* `opts.crons` — an app's `crons.ts` (`export default crons` from `cronJobs()` + `.interval()`/
|
|
704
|
+
* `.cron()`/etc.) — is reconciled into the `crons` table once at boot (`reconcileCrons`, see its
|
|
705
|
+
* doc comment in `./crons.ts` for why this is a config value rather than file-discovery magic).
|
|
706
|
+
*/
|
|
707
|
+
declare function defineScheduler(opts?: {
|
|
708
|
+
crons?: CronJobs;
|
|
709
|
+
}): ComponentDefinition;
|
|
710
|
+
|
|
711
|
+
export { BATCH_CAP, type BackoffOptions, CATCHUP_CAP, type CatchUpPolicy, type ClaimResult, type CronJobs, type CronOpts, type CronRegistryEntry, type CronSpec, type CronUtcOpts, DEFAULT_BACKOFF_OPTIONS, type DailyAt, type DayOfWeek, type DueJob, type EnqueueOpts, type EnqueueTables, type FnRef, type FunctionReference, type HourlyAt, type IntervalPeriod, type JobResult, type JobState, LEASE_MS, type MonthlyAt, type OnCompleteResult, type PeekDueResult, SWEEP_MS, type SchedulerActionContext, type SchedulerContext, type SchedulerDriver, type WeeklyAt, computeBackoff, computeNextRun, computePrevRun, cronJobs, defineScheduler, enqueueCadenceJob, enqueueInternal, fireOnComplete, getFunctionPath, schedulerActionContext, schedulerDriver, schedulerSchema };
|