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.
Files changed (64) hide show
  1. package/README.md +85 -17
  2. package/dist/Flow.d.ts +381 -0
  3. package/dist/Flow.d.ts.map +1 -0
  4. package/dist/Flow.js +340 -0
  5. package/dist/Flow.js.map +1 -0
  6. package/dist/Job.d.ts +37 -6
  7. package/dist/Job.d.ts.map +1 -1
  8. package/dist/Job.js +17 -2
  9. package/dist/Job.js.map +1 -1
  10. package/dist/JobSchedules.d.ts +112 -0
  11. package/dist/JobSchedules.d.ts.map +1 -0
  12. package/dist/JobSchedules.js +106 -0
  13. package/dist/JobSchedules.js.map +1 -0
  14. package/dist/JobStore.d.ts +320 -10
  15. package/dist/JobStore.d.ts.map +1 -1
  16. package/dist/JobStore.js.map +1 -1
  17. package/dist/MemoryJobStore.d.ts.map +1 -1
  18. package/dist/MemoryJobStore.js +336 -8
  19. package/dist/MemoryJobStore.js.map +1 -1
  20. package/dist/Metrics.d.ts +31 -0
  21. package/dist/Metrics.d.ts.map +1 -1
  22. package/dist/Metrics.js +39 -0
  23. package/dist/Metrics.js.map +1 -1
  24. package/dist/Worker.d.ts +120 -11
  25. package/dist/Worker.d.ts.map +1 -1
  26. package/dist/Worker.js +452 -26
  27. package/dist/Worker.js.map +1 -1
  28. package/dist/drizzle-postgres/DrizzleJobStore.d.ts +19 -1
  29. package/dist/drizzle-postgres/DrizzleJobStore.d.ts.map +1 -1
  30. package/dist/drizzle-postgres/DrizzleJobStore.js +662 -81
  31. package/dist/drizzle-postgres/DrizzleJobStore.js.map +1 -1
  32. package/dist/drizzle-postgres/schema.d.ts +310 -3
  33. package/dist/drizzle-postgres/schema.d.ts.map +1 -1
  34. package/dist/drizzle-postgres/schema.js +68 -1
  35. package/dist/drizzle-postgres/schema.js.map +1 -1
  36. package/dist/index.d.ts +14 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +14 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/redis/RedisJobStore.d.ts.map +1 -1
  41. package/dist/redis/RedisJobStore.js +221 -19
  42. package/dist/redis/RedisJobStore.js.map +1 -1
  43. package/dist/redis/scripts.d.ts +118 -11
  44. package/dist/redis/scripts.d.ts.map +1 -1
  45. package/dist/redis/scripts.js +497 -28
  46. package/dist/redis/scripts.js.map +1 -1
  47. package/dist/testing/conformance.d.ts +6 -0
  48. package/dist/testing/conformance.d.ts.map +1 -1
  49. package/dist/testing/conformance.js +765 -1
  50. package/dist/testing/conformance.js.map +1 -1
  51. package/package.json +1 -1
  52. package/src/Flow.ts +778 -0
  53. package/src/Job.ts +42 -11
  54. package/src/JobSchedules.ts +223 -0
  55. package/src/JobStore.ts +347 -9
  56. package/src/MemoryJobStore.ts +372 -8
  57. package/src/Metrics.ts +43 -0
  58. package/src/Worker.ts +726 -37
  59. package/src/drizzle-postgres/DrizzleJobStore.ts +827 -82
  60. package/src/drizzle-postgres/schema.ts +94 -0
  61. package/src/index.ts +16 -0
  62. package/src/redis/RedisJobStore.ts +291 -8
  63. package/src/redis/scripts.ts +529 -26
  64. package/src/testing/conformance.ts +989 -1
@@ -28,18 +28,36 @@
28
28
  * - `p:schedules` / `p:schedule:<key>` ZSET by nextRunAt + HASH per record
29
29
  * - `p:dedupe:<name>\0<key>` HASH {jobId, expiresAt} + `p:dedupes` index
30
30
  * ZSET (score = window expiry, +inf = pending)
31
+ * - `p:flowchild:<flowId>\0<childKey>` HASH of one flow dependency row
32
+ * - `p:flowchildren:<flowId>` ZSET (score 0, member = childKey; ZRANGEBYLEX
33
+ * gives child-key order + cursor pagination)
34
+ * - `p:flowpending` ZSET, member `<flowId>\0<childKey>`, score =
35
+ * sweep-eligibility timestamp (pendingSince at
36
+ * staging, re-armed on sweep return)
37
+ * - `p:flowcascade` ZSET, score 0, member `<flowId>\0<childKey>`
38
+ * (cancels still owed to child stores)
39
+ * - `p:flowoutbox` ZSET, score = seq from `p:flowoutbox:seq`,
40
+ * member `<seq>\0<report json>` — undelivered
41
+ * child-result reports (see OutboxEntry). The
42
+ * seq prefix makes every member id a peek
43
+ * cursor that survives deletions (see
44
+ * peekOutbox in RedisJobStore)
45
+ *
46
+ * `waiting-children` parents live only in the job hash, `p:all`, and
47
+ * `p:counts` — never in a pending zset, so `claim` can never return them.
31
48
  *
32
49
  * @since 0.2.0
33
50
  */
34
51
  import { Redis } from "effect/unstable/persistence";
35
52
  /**
36
53
  * enqueue(prefix, idMode, id, name, queue, payloadJson, metadataJson,
37
- * priority, attemptsMax, backoffJson, keepJson, timeoutMs, delayMs, now)
54
+ * priority, attemptsMax, backoffJson, keepJson, timeoutMs, delayMs,
55
+ * now, dedupe..., traceJson, parentJson)
38
56
  * idMode: "user" (dedup no-op), "generated" (collision -> retry sentinel),
39
57
  * "auto" (j-<seq>, in-script collision loop).
40
58
  */
41
59
  export declare const enqueue: Redis.Script<{
42
- params: [prefix: string, idMode: string, id: string, name: string, queue: string, payloadJson: string, metadataJson: string, priority: number, attemptsMax: number, backoffJson: string, keepJson: string, timeoutMs: string, delayMs: number, now: number, dedupeKey: string, dedupeTtlMs: string, dedupeExtend: string, dedupeReplace: string, traceJson: string];
60
+ params: [prefix: string, idMode: string, id: string, name: string, queue: string, payloadJson: string, metadataJson: string, priority: number, attemptsMax: number, backoffJson: string, keepJson: string, timeoutMs: string, delayMs: number, now: number, dedupeKey: string, dedupeTtlMs: string, dedupeExtend: string, dedupeReplace: string, traceJson: string, parentJson: string];
43
61
  result: string;
44
62
  }>;
45
63
  /**
@@ -103,7 +121,7 @@ export declare const counts: Redis.Script<{
103
121
  params: [prefix: string];
104
122
  result: string;
105
123
  }>;
106
- /** remove(prefix, id) -> removed boolean (active jobs are refused). */
124
+ /** remove(prefix, id) -> removed boolean (active/waiting-children refused). */
107
125
  export declare const remove: Redis.Script<{
108
126
  params: [prefix: string, id: string];
109
127
  result: string;
@@ -114,8 +132,10 @@ export declare const retry: Redis.Script<{
114
132
  result: string;
115
133
  }>;
116
134
  /**
117
- * cancel(prefix, id, now) — waiting/delayed become terminal; active gets the
118
- * cancel-request flag; terminal states are refused.
135
+ * cancel(prefix, id, now) — waiting/delayed/waiting-children become terminal
136
+ * (a parked flow parent also flips its remaining pending rows to cancelled,
137
+ * handing them to the cascade sweep); active gets the cancel-request flag;
138
+ * terminal states are refused.
119
139
  */
120
140
  export declare const cancel: Redis.Script<{
121
141
  params: [prefix: string, id: string, now: number];
@@ -136,7 +156,7 @@ export declare const promote: Redis.Script<{
136
156
  * preserves the stored nextRunAt.
137
157
  */
138
158
  export declare const upsertSchedule: Redis.Script<{
139
- params: [prefix: string, key: string, jobName: string, queue: string, cron: string, tz: string, everyMs: string, payloadJson: string, metadataJson: string, priority: string, attemptsMax: string, backoffJson: string, keepJson: string, timeoutMs: string, nextRunAt: number];
159
+ params: [prefix: string, key: string, jobName: string, queue: string, cron: string, tz: string, everyMs: string, payloadJson: string, metadataJson: string, priority: string, attemptsMax: string, backoffJson: string, keepJson: string, timeoutMs: string, group: string, nextRunAt: number];
140
160
  result: string;
141
161
  }>;
142
162
  /** removeSchedule(prefix, key) -> existed boolean. */
@@ -162,21 +182,21 @@ export declare const advanceSchedule: Redis.Script<{
162
182
  /**
163
183
  * tickSchedule(prefix, key, expectedRunAt, nextRunAt, id, name, queue,
164
184
  * payloadJson, metadataJson, priority, attemptsMax, backoffJson, keepJson,
165
- * timeoutMs, traceJson, delayMs, now) -> "1" fired | "0"
185
+ * timeoutMs, traceJson, parentJson, delayMs, now) -> "1" fired | "0"
166
186
  * Atomic occurrence claim: the nextRunAt CAS and the tick job's insert run
167
187
  * in one script, so a stale sweeper can never re-fire a slot — even after
168
188
  * retention pruned the previous slot's job row.
169
189
  */
170
190
  export declare const tickSchedule: Redis.Script<{
171
- params: [prefix: string, key: string, expectedRunAt: number, nextRunAt: number, id: string, name: string, queue: string, payloadJson: string, metadataJson: string, priority: number, attemptsMax: number, backoffJson: string, keepJson: string, timeoutMs: string, traceJson: string, delayMs: number, now: number];
191
+ params: [prefix: string, key: string, expectedRunAt: number, nextRunAt: number, id: string, name: string, queue: string, payloadJson: string, metadataJson: string, priority: number, attemptsMax: number, backoffJson: string, keepJson: string, timeoutMs: string, traceJson: string, parentJson: string, delayMs: number, now: number];
172
192
  result: string;
173
193
  }>;
174
194
  /**
175
195
  * enqueueMany(prefix, now, count, ...items) -> JSON array of per-item results
176
- * ({id, duplicate} | {collision} | {error}). Items are 13-ARGV strides:
196
+ * ({id, duplicate} | {collision} | {error}). Items are 14-ARGV strides:
177
197
  * idMode, id, name, queue, payloadJson, metadataJson, priority, attemptsMax,
178
- * backoffJson, keepJson, timeoutMs, traceJson, delayMs. Plain (non-dedup)
179
- * items only — the caller routes dedup items through \`enqueue\`.
198
+ * backoffJson, keepJson, timeoutMs, traceJson, parentJson, delayMs. Plain
199
+ * (non-dedup) items only — the caller routes dedup items through \`enqueue\`.
180
200
  */
181
201
  export declare const enqueueMany: Redis.Script<{
182
202
  params: [prefix: string, now: number, count: number, items: readonly string[]];
@@ -201,4 +221,91 @@ export declare const sweepDedupes: Redis.Script<{
201
221
  params: [prefix: string, limit: number, now: number];
202
222
  result: string;
203
223
  }>;
224
+ /**
225
+ * fanOut(prefix, id, token, final, clearStaged, failFast, total, now, count,
226
+ * ...items) — the FanOut ack, chunked like enqueueMany. Items are 5-ARGV
227
+ * strides: childKey, storeKey, childJobId, name, specJson.
228
+ *
229
+ * Every chunk is lock-token-guarded. Non-final chunks ONLY stage dependency
230
+ * rows; the final chunk stages its rows, appends the "fanned-out" ledger
231
+ * entry (no attempt consumed), persists the manifest (flowFailFast,
232
+ * flowPending = total, zeroed outcome counters), and transitions the parent:
233
+ * pending > 0 parks it in
234
+ * waiting-children (no pending zset — never claimable), pending == 0 settles
235
+ * it straight to runnable collect. A parent whose manifest already landed
236
+ * keeps it untouched (rows are not re-created; the transition follows the
237
+ * persisted pending count), so a double fan-out cannot duplicate children.
238
+ * When staging starts with no manifest, the FIRST chunk clears previously
239
+ * staged rows — a crashed earlier attempt may have staged different keys. A
240
+ * raced cancelRequested wins: the parent settles cancelled and its pending
241
+ * rows flip to cancelled (cascade work for the flow sweeper).
242
+ */
243
+ export declare const fanOut: Redis.Script<{
244
+ params: [prefix: string, id: string, token: string, final: string, clearStaged: string, failFast: string, total: number, now: number, count: number, items: readonly string[]];
245
+ result: string;
246
+ }>;
247
+ /**
248
+ * recordChildResults(prefix, now, count, ...items) -> {results, wakes}.
249
+ * Items are 5-ARGV strides: flowId, childKey, outcome, exitJson,
250
+ * failedReason; results are positional {applied, parentSettled}; wakes names
251
+ * the queues of parents that resumed runnable. One atomic batch; reports may
252
+ * span flows.
253
+ *
254
+ * Phase 1 applies EVERY row update — idempotent, only while the row is
255
+ * still pending; an applied report moves the child from the parent's `pending`
256
+ * counter to its outcome counter and marks the row cascaded (the outcome
257
+ * came FROM the child's store). Phase 2 settles each touched flow at most
258
+ * once: the FIRST applied failed report in batch order under fail-fast
259
+ * (terminal store-side failure; remaining rows flip to cancelled; wins the
260
+ * tie over pending==0 — and this settle IS a nested parent's terminal
261
+ * transition, so its own report goes to the outbox here), else pending==0
262
+ * resumes the parent runnable at the flow's LAST applied report's index.
263
+ */
264
+ export declare const recordChildResults: Redis.Script<{
265
+ params: [prefix: string, now: number, count: number, items: readonly string[]];
266
+ result: string;
267
+ }>;
268
+ /**
269
+ * listChildResults(prefix, flowId, cursor, limit) — child-key order via
270
+ * ZRANGEBYLEX over the per-flow index; cursor = last childKey (exclusive).
271
+ * Items are positional HMGET tuples (the field list must stay in lockstep
272
+ * with the driver's `toChildRecord`) — the full spec JSON stays server-side.
273
+ */
274
+ export declare const listChildResults: Redis.Script<{
275
+ params: [prefix: string, flowId: string, cursor: string, limit: number];
276
+ result: string;
277
+ }>;
278
+ /**
279
+ * flowSweepWork(prefix, pendingAgeMs, limit, now) -> {reconcile, cascade}
280
+ * grouped by flowId. Reconcile scans the flowpending zset (score = the row's
281
+ * sweep-eligibility timestamp) and yields rows whose parent is still parked
282
+ * in waiting-children. Every scanned member is re-armed or purged so no
283
+ * member can pin the head of the page:
284
+ *
285
+ * - parent missing, or terminal with NO manifest: a crashed fan-out's
286
+ * staged orphan — purge the row, its index member, and the flowpending
287
+ * member (left alone their old scores would head-pin every page forever);
288
+ * - parent alive but not waiting-children (mid-staging): re-arm to now so
289
+ * it rotates behind fresher work;
290
+ * - returned rows: re-arm to now (defer-on-return, per the contract) so a
291
+ * full page rotates across sweeps;
292
+ * - a non-pending row's membership is stale (rows never return to pending):
293
+ * self-heal by removing the member.
294
+ *
295
+ * Cascade lists flowcascade members (cancels still owed to child stores).
296
+ * Spec JSON strings pass through untouched — the script never cjson-decodes
297
+ * stored payloads (precision, lone surrogates).
298
+ */
299
+ export declare const flowSweepWork: Redis.Script<{
300
+ params: [prefix: string, pendingAgeMs: number, limit: number, now: number];
301
+ result: string;
302
+ }>;
303
+ /**
304
+ * markChildrenCascaded(prefix, flowId, childKeysJson) — idempotent; unknown
305
+ * keys are ignored (their index members are still cleared).
306
+ */
307
+ export declare const markChildrenCascaded: Redis.Script<{
308
+ params: [prefix: string, flowId: string, childKeysJson: string];
309
+ result: string;
310
+ }>;
204
311
  //# sourceMappingURL=scripts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scripts.d.ts","sourceRoot":"","sources":["../../src/redis/scripts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAA;AA0JnD;;;;;GAKG;AACH,eAAO,MAAM,OAAO;;;EAgIM,CAAA;AAE1B;;;;;GAKG;AACH,eAAO,MAAM,KAAK;;;EAsEQ,CAAA;AAE1B;;;;GAIG;AACH,eAAO,MAAM,GAAG;;;EAyEU,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,OAAO;;;EA4BM,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,WAAW;;;EAuBE,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,cAAc;;;EAgDD,CAAA;AAE1B,sEAAsE;AACtE,eAAO,MAAM,MAAM;;;EAUO,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,IAAI;;;EAsES,CAAA;AAE1B,mDAAmD;AACnD,eAAO,MAAM,MAAM;;;EAUO,CAAA;AAE1B,uEAAuE;AACvE,eAAO,MAAM,MAAM;;;EAcO,CAAA;AAE1B,sEAAsE;AACtE,eAAO,MAAM,KAAK;;;EA2BQ,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,MAAM;;;EAiCO,CAAA;AAE1B,yDAAyD;AACzD,eAAO,MAAM,OAAO;;;EAqBM,CAAA;AAE1B;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;;;EA4DD,CAAA;AAE1B,sDAAsD;AACtD,eAAO,MAAM,cAAc;;;EAUD,CAAA;AAE1B,yEAAyE;AACzE,eAAO,MAAM,aAAa;;;EAqBA,CAAA;AAE1B,gEAAgE;AAChE,eAAO,MAAM,YAAY;;;EAcC,CAAA;AAE1B,gFAAgF;AAChF,eAAO,MAAM,eAAe;;;EAcF,CAAA;AAE1B;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY;;;EAwDC,CAAA;AAE1B;;;;;;GAMG;AACH,eAAO,MAAM,WAAW;;;EA+CE,CAAA;AAE1B;;;;;GAKG;AACH,eAAO,MAAM,UAAU;;;EA0DG,CAAA;AAE1B;;;;GAIG;AACH,eAAO,MAAM,YAAY;;;EAyCC,CAAA"}
1
+ {"version":3,"file":"scripts.d.ts","sourceRoot":"","sources":["../../src/redis/scripts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAA;AAkPnD;;;;;;GAMG;AACH,eAAO,MAAM,OAAO;;;EAkIM,CAAA;AAE1B;;;;;GAKG;AACH,eAAO,MAAM,KAAK;;;EAsEQ,CAAA;AAE1B;;;;GAIG;AACH,eAAO,MAAM,GAAG;;;EA0EU,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,OAAO;;;EA4BM,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,WAAW;;;EAuBE,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,cAAc;;;EAiDD,CAAA;AAE1B,sEAAsE;AACtE,eAAO,MAAM,MAAM;;;EAUO,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,IAAI;;;EAsES,CAAA;AAE1B,mDAAmD;AACnD,eAAO,MAAM,MAAM;;;EAUO,CAAA;AAE1B,+EAA+E;AAC/E,eAAO,MAAM,MAAM;;;EAeO,CAAA;AAE1B,sEAAsE;AACtE,eAAO,MAAM,KAAK;;;EA2BQ,CAAA;AAE1B;;;;;GAKG;AACH,eAAO,MAAM,MAAM;;;EAqCO,CAAA;AAE1B,yDAAyD;AACzD,eAAO,MAAM,OAAO;;;EAqBM,CAAA;AAE1B;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;;;EA8DD,CAAA;AAE1B,sDAAsD;AACtD,eAAO,MAAM,cAAc;;;EAUD,CAAA;AAE1B,yEAAyE;AACzE,eAAO,MAAM,aAAa;;;EAsBA,CAAA;AAE1B,gEAAgE;AAChE,eAAO,MAAM,YAAY;;;EAcC,CAAA;AAE1B,gFAAgF;AAChF,eAAO,MAAM,eAAe;;;EAcF,CAAA;AAE1B;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY;;;EA0DC,CAAA;AAE1B;;;;;;GAMG;AACH,eAAO,MAAM,WAAW;;;EA+CE,CAAA;AAE1B;;;;;GAKG;AACH,eAAO,MAAM,UAAU;;;EA4DG,CAAA;AAE1B;;;;GAIG;AACH,eAAO,MAAM,YAAY;;;EAyCC,CAAA;AAE1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,MAAM;;;EA+FO,CAAA;AAE1B;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB;;;EAmGL,CAAA;AAE1B;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB;;;EAkBH,CAAA;AAE1B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,aAAa;;;EA0EA,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;EAgBP,CAAA"}