effect-mq 0.4.2 → 0.6.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/README.md +85 -17
- package/dist/Flow.d.ts +381 -0
- package/dist/Flow.d.ts.map +1 -0
- package/dist/Flow.js +340 -0
- package/dist/Flow.js.map +1 -0
- package/dist/Job.d.ts +37 -6
- package/dist/Job.d.ts.map +1 -1
- package/dist/Job.js +17 -2
- package/dist/Job.js.map +1 -1
- package/dist/JobSchedules.d.ts +112 -0
- package/dist/JobSchedules.d.ts.map +1 -0
- package/dist/JobSchedules.js +106 -0
- package/dist/JobSchedules.js.map +1 -0
- package/dist/JobStore.d.ts +320 -10
- package/dist/JobStore.d.ts.map +1 -1
- package/dist/JobStore.js.map +1 -1
- package/dist/MemoryJobStore.d.ts.map +1 -1
- package/dist/MemoryJobStore.js +336 -8
- package/dist/MemoryJobStore.js.map +1 -1
- package/dist/Metrics.d.ts +31 -0
- package/dist/Metrics.d.ts.map +1 -1
- package/dist/Metrics.js +39 -0
- package/dist/Metrics.js.map +1 -1
- package/dist/Worker.d.ts +120 -11
- package/dist/Worker.d.ts.map +1 -1
- package/dist/Worker.js +452 -26
- package/dist/Worker.js.map +1 -1
- package/dist/drizzle-postgres/DrizzleJobStore.d.ts +19 -1
- package/dist/drizzle-postgres/DrizzleJobStore.d.ts.map +1 -1
- package/dist/drizzle-postgres/DrizzleJobStore.js +662 -81
- package/dist/drizzle-postgres/DrizzleJobStore.js.map +1 -1
- package/dist/drizzle-postgres/schema.d.ts +310 -3
- package/dist/drizzle-postgres/schema.d.ts.map +1 -1
- package/dist/drizzle-postgres/schema.js +68 -1
- package/dist/drizzle-postgres/schema.js.map +1 -1
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/redis/RedisJobStore.d.ts.map +1 -1
- package/dist/redis/RedisJobStore.js +221 -19
- package/dist/redis/RedisJobStore.js.map +1 -1
- package/dist/redis/scripts.d.ts +118 -11
- package/dist/redis/scripts.d.ts.map +1 -1
- package/dist/redis/scripts.js +497 -28
- package/dist/redis/scripts.js.map +1 -1
- package/dist/testing/conformance.d.ts +6 -0
- package/dist/testing/conformance.d.ts.map +1 -1
- package/dist/testing/conformance.js +765 -1
- package/dist/testing/conformance.js.map +1 -1
- package/package.json +1 -1
- package/src/Flow.ts +778 -0
- package/src/Job.ts +42 -11
- package/src/JobSchedules.ts +223 -0
- package/src/JobStore.ts +347 -9
- package/src/MemoryJobStore.ts +372 -8
- package/src/Metrics.ts +43 -0
- package/src/Worker.ts +726 -37
- package/src/drizzle-postgres/DrizzleJobStore.ts +827 -82
- package/src/drizzle-postgres/schema.ts +94 -0
- package/src/index.ts +16 -0
- package/src/redis/RedisJobStore.ts +291 -8
- package/src/redis/scripts.ts +529 -26
- package/src/testing/conformance.ts +989 -1
package/src/JobStore.ts
CHANGED
|
@@ -67,6 +67,8 @@ export const ScheduleKey: Brand.Constructor<ScheduleKey> = Brand.nominal<Schedul
|
|
|
67
67
|
* - `waiting`: runnable now, ordered by (priority desc, enqueue order asc)
|
|
68
68
|
* - `delayed`: must not run before `runAt`
|
|
69
69
|
* - `active`: claimed by a worker holding a lock token
|
|
70
|
+
* - `waiting-children`: a flow parent parked until its children settle (see
|
|
71
|
+
* `AckOutcome`'s `FanOut`); never claimable, not terminal
|
|
70
72
|
* - `completed` / `failed`: terminal, with an encoded `Exit` stored
|
|
71
73
|
*
|
|
72
74
|
* @since 0.1.0
|
|
@@ -75,6 +77,7 @@ export type JobState =
|
|
|
75
77
|
| "waiting"
|
|
76
78
|
| "delayed"
|
|
77
79
|
| "active"
|
|
80
|
+
| "waiting-children"
|
|
78
81
|
| "completed"
|
|
79
82
|
| "failed"
|
|
80
83
|
| "cancelled"
|
|
@@ -205,8 +208,12 @@ export interface AttemptRecord {
|
|
|
205
208
|
readonly startedAt: number | undefined
|
|
206
209
|
/** Ack/recovery time of this run (epoch millis). */
|
|
207
210
|
readonly finishedAt: number
|
|
208
|
-
readonly outcome: "completed" | "retried" | "failed" | "stalled" | "cancelled"
|
|
209
|
-
/**
|
|
211
|
+
readonly outcome: "completed" | "retried" | "failed" | "stalled" | "cancelled" | "fanned-out"
|
|
212
|
+
/**
|
|
213
|
+
* Schema-encoded `Exit`; undefined for `stalled`, `cancelled`, and
|
|
214
|
+
* `fanned-out` entries, and for `failed` entries written by a store-side
|
|
215
|
+
* settle (a fail-fast flow parent) rather than a handler run.
|
|
216
|
+
*/
|
|
210
217
|
readonly exit: unknown
|
|
211
218
|
}
|
|
212
219
|
|
|
@@ -244,6 +251,10 @@ export interface JobRecord {
|
|
|
244
251
|
readonly dedupeKey: string | undefined
|
|
245
252
|
/** The producer's span context, restored as the handler span's parent. */
|
|
246
253
|
readonly trace: TraceContext | undefined
|
|
254
|
+
/** Present on flow children: the link back to their parent flow. */
|
|
255
|
+
readonly parent: ParentEnvelope | undefined
|
|
256
|
+
/** Present on flow parents once their manifest landed (see `FlowState`). */
|
|
257
|
+
readonly flow: FlowState | undefined
|
|
247
258
|
/** Epoch millis before which the job must not be claimed. */
|
|
248
259
|
readonly runAt: number
|
|
249
260
|
readonly enqueuedAt: number
|
|
@@ -304,6 +315,171 @@ export interface TraceContext {
|
|
|
304
315
|
readonly delayed: boolean
|
|
305
316
|
}
|
|
306
317
|
|
|
318
|
+
/**
|
|
319
|
+
* The persisted link from a flow child job to its parent flow. Attached by
|
|
320
|
+
* the flow runtime at fan-out (never by producers). Its presence puts the
|
|
321
|
+
* job under the outbox invariant: the child's store appends its report to
|
|
322
|
+
* the outbox with every terminal transition (see `OutboxEntry`), and a
|
|
323
|
+
* worker's relay delivers it into the parent's store (see
|
|
324
|
+
* `Worker.layer({ flows })`).
|
|
325
|
+
*
|
|
326
|
+
* @since 0.6.0
|
|
327
|
+
*/
|
|
328
|
+
export interface ParentEnvelope {
|
|
329
|
+
/** The flow definition's name (`Flow.make(name, ...)`). */
|
|
330
|
+
readonly flowName: string
|
|
331
|
+
/** The parent job's id in the parent store. */
|
|
332
|
+
readonly flowId: JobId
|
|
333
|
+
/** This child's key, unique within the flow (the idempotency mechanism). */
|
|
334
|
+
readonly childKey: string
|
|
335
|
+
/** The parent store's context-key string, for cross-store report routing. */
|
|
336
|
+
readonly parentStoreKey: string
|
|
337
|
+
/**
|
|
338
|
+
* This child's nesting level: 1 for children of a top-level flow, one
|
|
339
|
+
* more per level of nesting. Carried explicitly (never parsed out of
|
|
340
|
+
* ids — user keys are arbitrary strings) so the fan-out depth cap can
|
|
341
|
+
* catch cyclic definitions.
|
|
342
|
+
*/
|
|
343
|
+
readonly depth: number
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Flow bookkeeping persisted on a parent job by the `FanOut` ack. Its
|
|
348
|
+
* presence IS the phase marker: absent means the parent has not fanned out
|
|
349
|
+
* yet (a claim dispatches `fanOut`); present means the manifest landed (a
|
|
350
|
+
* claim dispatches `collect`, and a re-run can never fan out twice).
|
|
351
|
+
*
|
|
352
|
+
* The four counters always sum to the manifest size: applied reports move
|
|
353
|
+
* one child from `pending` to its outcome bucket, and settle-time marking
|
|
354
|
+
* (fail-fast, parent cancel) moves every remaining `pending` child to
|
|
355
|
+
* `cancelled` in the same atomic op. `collect` reads its tallies from here
|
|
356
|
+
* without touching a single dependency row.
|
|
357
|
+
*
|
|
358
|
+
* @since 0.6.0
|
|
359
|
+
*/
|
|
360
|
+
export interface FlowState {
|
|
361
|
+
/** When true, the first failed child report settles the parent as failed. */
|
|
362
|
+
readonly failFast: boolean
|
|
363
|
+
/** Children whose result has not been recorded yet. */
|
|
364
|
+
readonly pending: number
|
|
365
|
+
readonly completed: number
|
|
366
|
+
readonly failed: number
|
|
367
|
+
readonly cancelled: number
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* One child of a flow fan-out, persisted on its dependency row so the flow
|
|
372
|
+
* sweeper can (re-)enqueue the child from storage alone after any crash.
|
|
373
|
+
* `request.id` is the deterministic flow child id — derived from the parent
|
|
374
|
+
* store key, flow id, and child key, so re-enqueues are idempotent — and
|
|
375
|
+
* `request.parent` carries the envelope.
|
|
376
|
+
*
|
|
377
|
+
* @since 0.6.0
|
|
378
|
+
*/
|
|
379
|
+
export interface FlowChildSpec {
|
|
380
|
+
readonly childKey: string
|
|
381
|
+
/** The CHILD store's context-key string (children may live elsewhere). */
|
|
382
|
+
readonly storeKey: string
|
|
383
|
+
readonly request: EnqueueRequest
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* A dependency row: one child's status and result as recorded in the PARENT
|
|
388
|
+
* store. `exit` is the child's schema-encoded exit; `failedReason` carries
|
|
389
|
+
* store-side child failures (stall exhaustion, a nested parent's fail-fast
|
|
390
|
+
* settle) that never produced an exit. `cascaded` marks that a cancel no
|
|
391
|
+
* longer needs to be delivered into the child's store (set by
|
|
392
|
+
* `markChildrenCascaded`, or immediately when the recorded outcome came
|
|
393
|
+
* FROM the child's store).
|
|
394
|
+
*
|
|
395
|
+
* @since 0.6.0
|
|
396
|
+
*/
|
|
397
|
+
export interface FlowChildRecord {
|
|
398
|
+
readonly flowId: JobId
|
|
399
|
+
readonly childKey: string
|
|
400
|
+
readonly name: string
|
|
401
|
+
readonly storeKey: string
|
|
402
|
+
readonly childJobId: JobId
|
|
403
|
+
readonly status: "pending" | "completed" | "failed" | "cancelled"
|
|
404
|
+
readonly exit: unknown
|
|
405
|
+
readonly failedReason: string | undefined
|
|
406
|
+
readonly cascaded: boolean
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* An idempotent child-result report delivered into the parent store — by a
|
|
411
|
+
* worker's outbox relay (the push path) or synthesized by the flow sweeper
|
|
412
|
+
* from child-store state (the reconcile path). Both may deliver the same
|
|
413
|
+
* report; the dependency row's state dedups them.
|
|
414
|
+
*
|
|
415
|
+
* @since 0.6.0
|
|
416
|
+
*/
|
|
417
|
+
export interface FlowChildReport {
|
|
418
|
+
readonly flowId: JobId
|
|
419
|
+
readonly childKey: string
|
|
420
|
+
readonly outcome: "completed" | "failed" | "cancelled"
|
|
421
|
+
/** The child's schema-encoded exit; undefined for store-side failures. */
|
|
422
|
+
readonly exit: unknown
|
|
423
|
+
/** Present when the child was failed store-side (no exit exists). */
|
|
424
|
+
readonly failedReason: string | undefined
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* One undelivered child-result report in a CHILD store's outbox.
|
|
429
|
+
*
|
|
430
|
+
* The outbox invariant every driver must uphold: whenever a store operation
|
|
431
|
+
* moves a job carrying a `parent` envelope INTO a terminal state — a
|
|
432
|
+
* `Complete`/`Fail`/`Cancelled` ack, stall exhaustion, a direct `cancel` of
|
|
433
|
+
* a waiting/delayed child, a cancel honoured during release or stall
|
|
434
|
+
* recovery, or a fail-fast settle of a NESTED flow parent (its terminal
|
|
435
|
+
* transition happens store-side, with no ack) — the same atomic operation
|
|
436
|
+
* appends the corresponding report here. The worker's relay then drains
|
|
437
|
+
* the outbox into the parent store in batches and deletes what it
|
|
438
|
+
* delivered. Because dependency rows dedup redelivery, the relay needs no
|
|
439
|
+
* leases: crash anywhere and the entries are simply delivered again.
|
|
440
|
+
*
|
|
441
|
+
* `remove` appends nothing (an operator override), and a `FanOut` ack
|
|
442
|
+
* appends nothing (`waiting-children` is not terminal).
|
|
443
|
+
*
|
|
444
|
+
* @since 0.6.0
|
|
445
|
+
*/
|
|
446
|
+
export interface OutboxEntry {
|
|
447
|
+
/** Store-assigned, opaque; pass back to `deleteOutbox` verbatim. */
|
|
448
|
+
readonly id: string
|
|
449
|
+
readonly flowName: string
|
|
450
|
+
/** The parent store's context-key string, for relay routing. */
|
|
451
|
+
readonly parentStoreKey: string
|
|
452
|
+
readonly report: FlowChildReport
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* The flow sweeper's work list, scoped by parent state so settled flows
|
|
457
|
+
* never re-drive work:
|
|
458
|
+
*
|
|
459
|
+
* - `reconcile`: for parents still in `waiting-children`, dependency rows
|
|
460
|
+
* `pending` longer than the caller's threshold, with their stored specs —
|
|
461
|
+
* the sweeper checks the child's store and either enqueues the child
|
|
462
|
+
* (missing) or synthesizes its report (terminal).
|
|
463
|
+
* - `cascade`: rows marked `cancelled` by a settle but not yet delivered as
|
|
464
|
+
* real cancels into their child store (any parent state).
|
|
465
|
+
*
|
|
466
|
+
* @since 0.6.0
|
|
467
|
+
*/
|
|
468
|
+
export interface FlowSweepWork {
|
|
469
|
+
readonly reconcile: ReadonlyArray<{
|
|
470
|
+
readonly flowId: JobId
|
|
471
|
+
readonly children: ReadonlyArray<FlowChildSpec>
|
|
472
|
+
}>
|
|
473
|
+
readonly cascade: ReadonlyArray<{
|
|
474
|
+
readonly flowId: JobId
|
|
475
|
+
readonly children: ReadonlyArray<{
|
|
476
|
+
readonly childKey: string
|
|
477
|
+
readonly storeKey: string
|
|
478
|
+
readonly childJobId: JobId
|
|
479
|
+
}>
|
|
480
|
+
}>
|
|
481
|
+
}
|
|
482
|
+
|
|
307
483
|
/**
|
|
308
484
|
* @since 0.1.0
|
|
309
485
|
*/
|
|
@@ -327,6 +503,8 @@ export interface EnqueueRequest {
|
|
|
327
503
|
readonly dedupe: DedupePolicy | undefined
|
|
328
504
|
/** The producer's span context, for cross-process trace propagation. */
|
|
329
505
|
readonly trace: TraceContext | undefined
|
|
506
|
+
/** Flow-parent link, set only by the flow runtime (opaque to the store). */
|
|
507
|
+
readonly parent: ParentEnvelope | undefined
|
|
330
508
|
readonly delayMs: number
|
|
331
509
|
}
|
|
332
510
|
|
|
@@ -389,7 +567,18 @@ export type ClaimResult =
|
|
|
389
567
|
* attempts accounting) is computed by the worker; the store only applies it.
|
|
390
568
|
*
|
|
391
569
|
* Every outcome appends an `AttemptRecord` to the job's ledger (`Complete` →
|
|
392
|
-
* completed, `Retry` → retried, `Fail` → failed).
|
|
570
|
+
* completed, `Retry` → retried, `Fail` → failed, `FanOut` → fanned-out).
|
|
571
|
+
*
|
|
572
|
+
* `FanOut` (flow parents only) atomically: persists `FlowState` (phase
|
|
573
|
+
* marker + policy + `pending = children.length`), inserts one `pending`
|
|
574
|
+
* dependency row per child (spec stored, so crash recovery needs only the
|
|
575
|
+
* parent store), and parks the parent in `waiting-children` — or, for an
|
|
576
|
+
* empty spec, settles it straight to `waiting`. It does NOT consume an
|
|
577
|
+
* attempt (a phase transition is not a completed run), and it does NOT
|
|
578
|
+
* enqueue the children (the flow runtime and sweeper own that). A parent
|
|
579
|
+
* whose `flow` is already present keeps its persisted manifest untouched —
|
|
580
|
+
* the new children are ignored and the state transition follows the existing
|
|
581
|
+
* `pending` count, so a double fan-out cannot duplicate children.
|
|
393
582
|
*
|
|
394
583
|
* @since 0.1.0
|
|
395
584
|
*/
|
|
@@ -398,6 +587,11 @@ export type AckOutcome =
|
|
|
398
587
|
| { readonly _tag: "Retry"; readonly delayMs: number; readonly exit: unknown }
|
|
399
588
|
| { readonly _tag: "Fail"; readonly exit: unknown }
|
|
400
589
|
| { readonly _tag: "Cancelled" }
|
|
590
|
+
| {
|
|
591
|
+
readonly _tag: "FanOut"
|
|
592
|
+
readonly failFast: boolean
|
|
593
|
+
readonly children: ReadonlyArray<FlowChildSpec>
|
|
594
|
+
}
|
|
401
595
|
|
|
402
596
|
/**
|
|
403
597
|
* Filters and pagination for `list`. Results are ordered newest-first
|
|
@@ -450,6 +644,12 @@ export interface ScheduleRecord {
|
|
|
450
644
|
readonly backoff: BackoffPolicy | undefined
|
|
451
645
|
readonly keep: KeepPolicy | undefined
|
|
452
646
|
readonly timeoutMs: number | undefined
|
|
647
|
+
/**
|
|
648
|
+
* Ownership label for declarative reconciliation (`JobSchedules.layer`):
|
|
649
|
+
* a reconciler only ever prunes schedules carrying ITS group. Unlabeled
|
|
650
|
+
* schedules (plain `.schedule()` calls) are never pruned.
|
|
651
|
+
*/
|
|
652
|
+
readonly group: string | undefined
|
|
453
653
|
/** Epoch millis of the next occurrence to enqueue. */
|
|
454
654
|
readonly nextRunAt: number
|
|
455
655
|
}
|
|
@@ -657,7 +857,8 @@ export interface Service {
|
|
|
657
857
|
* Acknowledge a claimed job. Verifies the lock token, releases the lock,
|
|
658
858
|
* increments `attemptsMade`, appends to the attempts ledger, then applies
|
|
659
859
|
* the outcome (`Complete`/`Fail` are terminal and apply the record's `keep`
|
|
660
|
-
* policy; `Retry` re-queues after `delayMs`).
|
|
860
|
+
* policy; `Retry` re-queues after `delayMs`). Terminal outcomes for jobs
|
|
861
|
+
* carrying a `parent` envelope also append an `OutboxEntry` atomically.
|
|
661
862
|
*/
|
|
662
863
|
readonly ack: (
|
|
663
864
|
id: JobId,
|
|
@@ -687,7 +888,8 @@ export interface Service {
|
|
|
687
888
|
* Sweep active jobs whose lock has expired. Each recovered job gets
|
|
688
889
|
* `stalledCount + 1` and a `stalled` ledger entry; jobs exceeding
|
|
689
890
|
* `maxStalledCount` are failed (`failed: true` in the result), the rest
|
|
690
|
-
* return to `waiting`.
|
|
891
|
+
* return to `waiting`. Stall-exhausting a job that carries a `parent`
|
|
892
|
+
* envelope appends its failed report to the outbox atomically.
|
|
691
893
|
*/
|
|
692
894
|
readonly recoverStalled: (options: {
|
|
693
895
|
readonly maxStalledCount: number
|
|
@@ -724,7 +926,10 @@ export interface Service {
|
|
|
724
926
|
/**
|
|
725
927
|
* Re-run a failed job: back to `waiting` with a fresh attempt budget
|
|
726
928
|
* (`attemptsMade`/`stalledCount` reset, terminal fields cleared). The
|
|
727
|
-
* attempts ledger is preserved and keeps numbering monotonically.
|
|
929
|
+
* attempts ledger is preserved and keeps numbering monotonically. A flow
|
|
930
|
+
* parent's `flow` field and dependency rows survive: a retried fail-fast
|
|
931
|
+
* flow re-enters the `collect` phase with its recorded (mixed) results —
|
|
932
|
+
* it can never fan out twice.
|
|
728
933
|
*/
|
|
729
934
|
readonly retry: (
|
|
730
935
|
id: JobId
|
|
@@ -736,8 +941,13 @@ export interface Service {
|
|
|
736
941
|
/**
|
|
737
942
|
* Cancel a job. Waiting/delayed jobs become terminal (`cancelled`)
|
|
738
943
|
* immediately; active jobs get `cancelRequested` set, and the owning
|
|
739
|
-
* worker interrupts the handler on its next heartbeat.
|
|
740
|
-
*
|
|
944
|
+
* worker interrupts the handler on its next heartbeat. A `waiting-children`
|
|
945
|
+
* flow parent settles to `cancelled` AND flips its remaining `pending`
|
|
946
|
+
* dependency rows to `cancelled` (not `cascaded`) in the same atomic op —
|
|
947
|
+
* the flow sweeper then delivers real cancels into the child stores, and
|
|
948
|
+
* late child reports find their row terminal and drop. Cancelling a job
|
|
949
|
+
* that itself carries a `parent` envelope appends its cancelled report to
|
|
950
|
+
* the outbox atomically. Terminal jobs fail with `JobNotCancellableError`.
|
|
741
951
|
*/
|
|
742
952
|
readonly cancel: (
|
|
743
953
|
id: JobId
|
|
@@ -796,6 +1006,8 @@ export interface Service {
|
|
|
796
1006
|
readonly listSchedules: (options?: {
|
|
797
1007
|
readonly jobName?: string | undefined
|
|
798
1008
|
readonly queue?: QueueName | undefined
|
|
1009
|
+
/** Only schedules carrying this ownership group. */
|
|
1010
|
+
readonly group?: string | undefined
|
|
799
1011
|
}) => Effect.Effect<ReadonlyArray<ScheduleRecord>, JobStoreError>
|
|
800
1012
|
|
|
801
1013
|
/** Schedules whose `nextRunAt` is due (per the Effect `Clock`). */
|
|
@@ -830,11 +1042,137 @@ export interface Service {
|
|
|
830
1042
|
nextRunAt: number
|
|
831
1043
|
) => Effect.Effect<void, JobStoreError>
|
|
832
1044
|
|
|
1045
|
+
/**
|
|
1046
|
+
* Record a batch of child outcomes on their dependency rows — idempotent
|
|
1047
|
+
* and atomic, results positional. Each report applies only when its row
|
|
1048
|
+
* is still `pending` (`applied: false` for duplicate, late, or unknown
|
|
1049
|
+
* reports); an applied report moves the child from the parent's `pending`
|
|
1050
|
+
* counter to its outcome counter and marks the row `cascaded` (the
|
|
1051
|
+
* outcome came from the child's store — no cancel needs delivering). A
|
|
1052
|
+
* batch may span flows.
|
|
1053
|
+
*
|
|
1054
|
+
* Per flow, all row updates apply BEFORE the settle decision, and the
|
|
1055
|
+
* parent settles at most once per batch (`parentSettled: true` on the
|
|
1056
|
+
* report that decided it): when `pending` hits zero, `waiting-children` →
|
|
1057
|
+
* `waiting` (runnable now, phase `collect`) — or on the first applied
|
|
1058
|
+
* `failed` report in batch order under the fail-fast policy, which
|
|
1059
|
+
* instead settles the parent terminally `failed` (store-side,
|
|
1060
|
+
* `failedReason` set, no exit — like stall exhaustion) and flips every
|
|
1061
|
+
* remaining `pending` row to `cancelled`/not-`cascaded` in the same
|
|
1062
|
+
* atomic op. Fail-fast wins the tie when one report triggers both rules.
|
|
1063
|
+
*
|
|
1064
|
+
* Lock ordering (drivers MUST follow it): dependency rows first, the
|
|
1065
|
+
* parent row second — reports, fail-fast marking, and cancel marking all
|
|
1066
|
+
* take locks in this order, so report-vs-settle cannot deadlock.
|
|
1067
|
+
*
|
|
1068
|
+
* Drivers may process very large batches in atomic sub-batches (the
|
|
1069
|
+
* Redis driver chunks at 500); the apply-all-before-settle rule then
|
|
1070
|
+
* holds per sub-batch. Worker relays never exceed one page (500), so
|
|
1071
|
+
* this only shows on direct store calls with larger batches.
|
|
1072
|
+
*
|
|
1073
|
+
* @since 0.6.0
|
|
1074
|
+
*/
|
|
1075
|
+
readonly recordChildResults: (
|
|
1076
|
+
reports: ReadonlyArray<FlowChildReport>
|
|
1077
|
+
) => Effect.Effect<
|
|
1078
|
+
ReadonlyArray<{ readonly applied: boolean; readonly parentSettled: boolean }>,
|
|
1079
|
+
JobStoreError
|
|
1080
|
+
>
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* The oldest undelivered outbox entries, up to `limit` (see
|
|
1084
|
+
* `OutboxEntry` for the append invariant). The relay peeks, delivers via
|
|
1085
|
+
* `recordChildResults` on the parent store, then deletes — redelivery
|
|
1086
|
+
* after a crash is safe because dependency rows dedup.
|
|
1087
|
+
*
|
|
1088
|
+
* `after` pages past a previously-returned entry id (exclusive), whether
|
|
1089
|
+
* or not that entry still exists — the relay walks the whole outbox this
|
|
1090
|
+
* way, so entries it cannot route (their parent store is not provided
|
|
1091
|
+
* here) never blockade the ones behind them. Anything other than an id
|
|
1092
|
+
* this store issued may be treated as unset.
|
|
1093
|
+
*
|
|
1094
|
+
* @since 0.6.0
|
|
1095
|
+
*/
|
|
1096
|
+
readonly peekOutbox: (options: {
|
|
1097
|
+
readonly limit: number
|
|
1098
|
+
readonly after?: string | undefined
|
|
1099
|
+
}) => Effect.Effect<ReadonlyArray<OutboxEntry>, JobStoreError>
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Delete delivered outbox entries by id. Idempotent; unknown ids are
|
|
1103
|
+
* ignored.
|
|
1104
|
+
*
|
|
1105
|
+
* @since 0.6.0
|
|
1106
|
+
*/
|
|
1107
|
+
readonly deleteOutbox: (
|
|
1108
|
+
ids: ReadonlyArray<string>
|
|
1109
|
+
) => Effect.Effect<void, JobStoreError>
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* A flow's dependency rows, ordered by child key; feeds `collect` and
|
|
1113
|
+
* dashboards. Pass the returned `cursor` back for the next page.
|
|
1114
|
+
*
|
|
1115
|
+
* @since 0.6.0
|
|
1116
|
+
*/
|
|
1117
|
+
readonly listChildResults: (
|
|
1118
|
+
flowId: JobId,
|
|
1119
|
+
options?: {
|
|
1120
|
+
readonly cursor?: string | undefined
|
|
1121
|
+
/** Page size; default 1000. */
|
|
1122
|
+
readonly limit?: number | undefined
|
|
1123
|
+
} | undefined
|
|
1124
|
+
) => Effect.Effect<
|
|
1125
|
+
{
|
|
1126
|
+
readonly items: ReadonlyArray<FlowChildRecord>
|
|
1127
|
+
readonly cursor: string | undefined
|
|
1128
|
+
},
|
|
1129
|
+
JobStoreError
|
|
1130
|
+
>
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* The flow sweeper's work list (see `FlowSweepWork`). `pendingAgeMs`
|
|
1134
|
+
* scopes reconciliation to rows whose eligibility timestamp is at least
|
|
1135
|
+
* this old (giving the push path time); `limit` bounds the rows returned
|
|
1136
|
+
* per class per sweep.
|
|
1137
|
+
*
|
|
1138
|
+
* Returning a row for reconciliation re-arms its eligibility timestamp
|
|
1139
|
+
* (it is not returned again until another `pendingAgeMs` elapses), so a
|
|
1140
|
+
* full page ROTATES across sweeps: healthy in-flight children and rows
|
|
1141
|
+
* this sweeper cannot act on never pin the head of the page and starve
|
|
1142
|
+
* the rows behind them.
|
|
1143
|
+
*
|
|
1144
|
+
* @since 0.6.0
|
|
1145
|
+
*/
|
|
1146
|
+
readonly flowSweepWork: (options: {
|
|
1147
|
+
readonly pendingAgeMs: number
|
|
1148
|
+
readonly limit?: number | undefined
|
|
1149
|
+
}) => Effect.Effect<FlowSweepWork, JobStoreError>
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* Mark dependency rows as `cascaded` after their cancels were delivered
|
|
1153
|
+
* into (or confirmed unnecessary by) the child's store. Idempotent.
|
|
1154
|
+
*
|
|
1155
|
+
* @since 0.6.0
|
|
1156
|
+
*/
|
|
1157
|
+
readonly markChildrenCascaded: (
|
|
1158
|
+
flowId: JobId,
|
|
1159
|
+
childKeys: ReadonlyArray<string>
|
|
1160
|
+
) => Effect.Effect<void, JobStoreError>
|
|
1161
|
+
|
|
833
1162
|
readonly counts: (
|
|
834
1163
|
queue?: QueueName
|
|
835
1164
|
) => Effect.Effect<Record<JobState, number>, JobStoreError>
|
|
836
1165
|
|
|
837
|
-
/**
|
|
1166
|
+
/**
|
|
1167
|
+
* Remove a job (and its ledger; a flow parent's dependency rows go with
|
|
1168
|
+
* it). Refuses (returns false) when active or `waiting-children`.
|
|
1169
|
+
*
|
|
1170
|
+
* Note the retention asymmetry for flows: AUTOMATIC pruning (`keep`
|
|
1171
|
+
* policies, the `historyTtl` sweep) must skip a settled flow parent whose
|
|
1172
|
+
* rows still owe cascade cancels (`cancelled` and not `cascaded`) — those
|
|
1173
|
+
* rows are the only record that real cancels are still due in the child
|
|
1174
|
+
* stores. `remove` is the explicit operator override and deletes anyway.
|
|
1175
|
+
*/
|
|
838
1176
|
readonly remove: (id: JobId) => Effect.Effect<boolean, JobStoreError>
|
|
839
1177
|
}
|
|
840
1178
|
|