@nestjs/workflows 0.0.1

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 (265) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +50 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +8 -0
  5. package/dist/core/codecs/aes-gcm-payload.codec.d.ts +42 -0
  6. package/dist/core/codecs/aes-gcm-payload.codec.js +119 -0
  7. package/dist/core/codecs/index.d.ts +2 -0
  8. package/dist/core/codecs/index.js +2 -0
  9. package/dist/core/codecs/payload-codecs.d.ts +59 -0
  10. package/dist/core/codecs/payload-codecs.js +130 -0
  11. package/dist/core/index.d.ts +16 -0
  12. package/dist/core/index.js +21 -0
  13. package/dist/core/interfaces/clock.interface.d.ts +18 -0
  14. package/dist/core/interfaces/clock.interface.js +1 -0
  15. package/dist/core/interfaces/index.d.ts +9 -0
  16. package/dist/core/interfaces/index.js +9 -0
  17. package/dist/core/interfaces/leased-worker-options.interface.d.ts +94 -0
  18. package/dist/core/interfaces/leased-worker-options.interface.js +1 -0
  19. package/dist/core/interfaces/limits.interface.d.ts +92 -0
  20. package/dist/core/interfaces/limits.interface.js +1 -0
  21. package/dist/core/interfaces/payload-codec.interface.d.ts +64 -0
  22. package/dist/core/interfaces/payload-codec.interface.js +1 -0
  23. package/dist/core/interfaces/result-waiter-options.interface.d.ts +52 -0
  24. package/dist/core/interfaces/result-waiter-options.interface.js +1 -0
  25. package/dist/core/interfaces/retry-settings.interface.d.ts +71 -0
  26. package/dist/core/interfaces/retry-settings.interface.js +1 -0
  27. package/dist/core/interfaces/schedule-store.interface.d.ts +143 -0
  28. package/dist/core/interfaces/schedule-store.interface.js +1 -0
  29. package/dist/core/interfaces/schedule.interface.d.ts +106 -0
  30. package/dist/core/interfaces/schedule.interface.js +1 -0
  31. package/dist/core/interfaces/scheduler-options.interface.d.ts +152 -0
  32. package/dist/core/interfaces/scheduler-options.interface.js +1 -0
  33. package/dist/core/limits/index.d.ts +1 -0
  34. package/dist/core/limits/index.js +1 -0
  35. package/dist/core/limits/limits.d.ts +61 -0
  36. package/dist/core/limits/limits.js +130 -0
  37. package/dist/core/results/index.d.ts +1 -0
  38. package/dist/core/results/index.js +1 -0
  39. package/dist/core/results/result-waiter.d.ts +54 -0
  40. package/dist/core/results/result-waiter.js +140 -0
  41. package/dist/core/retries/index.d.ts +1 -0
  42. package/dist/core/retries/index.js +1 -0
  43. package/dist/core/retries/retry.d.ts +60 -0
  44. package/dist/core/retries/retry.js +107 -0
  45. package/dist/core/scheduling/cron.util.d.ts +52 -0
  46. package/dist/core/scheduling/cron.util.js +262 -0
  47. package/dist/core/scheduling/encoded-schedule.store.d.ts +26 -0
  48. package/dist/core/scheduling/encoded-schedule.store.js +54 -0
  49. package/dist/core/scheduling/in-memory-schedule.store.d.ts +20 -0
  50. package/dist/core/scheduling/in-memory-schedule.store.js +90 -0
  51. package/dist/core/scheduling/index.d.ts +3 -0
  52. package/dist/core/scheduling/index.js +3 -0
  53. package/dist/core/scheduling/rrule.util.d.ts +55 -0
  54. package/dist/core/scheduling/rrule.util.js +371 -0
  55. package/dist/core/scheduling/schedule-spec.d.ts +57 -0
  56. package/dist/core/scheduling/schedule-spec.js +233 -0
  57. package/dist/core/scheduling/scheduler.d.ts +134 -0
  58. package/dist/core/scheduling/scheduler.js +450 -0
  59. package/dist/core/scheduling/time-zone.util.d.ts +18 -0
  60. package/dist/core/scheduling/time-zone.util.js +88 -0
  61. package/dist/core/time/clock.d.ts +28 -0
  62. package/dist/core/time/clock.js +35 -0
  63. package/dist/core/time/duration.d.ts +21 -0
  64. package/dist/core/time/duration.js +30 -0
  65. package/dist/core/time/index.d.ts +2 -0
  66. package/dist/core/time/index.js +2 -0
  67. package/dist/core/utils/canonical.util.d.ts +2 -0
  68. package/dist/core/utils/canonical.util.js +4 -0
  69. package/dist/core/utils/maybe.util.d.ts +8 -0
  70. package/dist/core/utils/maybe.util.js +14 -0
  71. package/dist/core/utils/payload-place.util.d.ts +10 -0
  72. package/dist/core/utils/payload-place.util.js +32 -0
  73. package/dist/core/workers/index.d.ts +1 -0
  74. package/dist/core/workers/index.js +1 -0
  75. package/dist/core/workers/leased-worker.d.ts +86 -0
  76. package/dist/core/workers/leased-worker.js +280 -0
  77. package/dist/cqrs/decorators/index.d.ts +2 -0
  78. package/dist/cqrs/decorators/index.js +2 -0
  79. package/dist/cqrs/decorators/signal-on.decorator.d.ts +18 -0
  80. package/dist/cqrs/decorators/signal-on.decorator.js +34 -0
  81. package/dist/cqrs/decorators/start-on.decorator.d.ts +18 -0
  82. package/dist/cqrs/decorators/start-on.decorator.js +49 -0
  83. package/dist/cqrs/index.d.ts +3 -0
  84. package/dist/cqrs/index.js +5 -0
  85. package/dist/cqrs/interfaces/index.d.ts +4 -0
  86. package/dist/cqrs/interfaces/index.js +4 -0
  87. package/dist/cqrs/interfaces/signal-on-options.interface.d.ts +22 -0
  88. package/dist/cqrs/interfaces/signal-on-options.interface.js +1 -0
  89. package/dist/cqrs/interfaces/start-on-options.interface.d.ts +23 -0
  90. package/dist/cqrs/interfaces/start-on-options.interface.js +1 -0
  91. package/dist/cqrs/interfaces/workflow-dispatcher-context.interface.d.ts +10 -0
  92. package/dist/cqrs/interfaces/workflow-dispatcher-context.interface.js +1 -0
  93. package/dist/cqrs/interfaces/workflow-event-route.interface.d.ts +20 -0
  94. package/dist/cqrs/interfaces/workflow-event-route.interface.js +1 -0
  95. package/dist/cqrs/utils/event-routes.util.d.ts +7 -0
  96. package/dist/cqrs/utils/event-routes.util.js +22 -0
  97. package/dist/cqrs/workflow-event.publisher.d.ts +52 -0
  98. package/dist/cqrs/workflow-event.publisher.js +257 -0
  99. package/dist/cqrs/workflow-events.explorer.d.ts +27 -0
  100. package/dist/cqrs/workflow-events.explorer.js +92 -0
  101. package/dist/cqrs/workflows-cqrs.module.d.ts +10 -0
  102. package/dist/cqrs/workflows-cqrs.module.js +27 -0
  103. package/dist/decorators/index.d.ts +1 -0
  104. package/dist/decorators/index.js +1 -0
  105. package/dist/decorators/workflow.decorator.d.ts +7 -0
  106. package/dist/decorators/workflow.decorator.js +52 -0
  107. package/dist/errors/child-workflow-failed.error.d.ts +13 -0
  108. package/dist/errors/child-workflow-failed.error.js +14 -0
  109. package/dist/errors/index.d.ts +13 -0
  110. package/dist/errors/index.js +13 -0
  111. package/dist/errors/non-retryable-step.error.d.ts +4 -0
  112. package/dist/errors/non-retryable-step.error.js +7 -0
  113. package/dist/errors/step-failed.error.d.ts +15 -0
  114. package/dist/errors/step-failed.error.js +16 -0
  115. package/dist/errors/step-timeout.error.d.ts +5 -0
  116. package/dist/errors/step-timeout.error.js +8 -0
  117. package/dist/errors/workflow-definition.error.d.ts +10 -0
  118. package/dist/errors/workflow-definition.error.js +13 -0
  119. package/dist/errors/workflow-failed.error.d.ts +20 -0
  120. package/dist/errors/workflow-failed.error.js +17 -0
  121. package/dist/errors/workflow-id-conflict.error.d.ts +6 -0
  122. package/dist/errors/workflow-id-conflict.error.js +9 -0
  123. package/dist/errors/workflow-interrupt.error.d.ts +32 -0
  124. package/dist/errors/workflow-interrupt.error.js +19 -0
  125. package/dist/errors/workflow-non-determinism.error.d.ts +4 -0
  126. package/dist/errors/workflow-non-determinism.error.js +7 -0
  127. package/dist/errors/workflow-not-found.error.d.ts +10 -0
  128. package/dist/errors/workflow-not-found.error.js +13 -0
  129. package/dist/errors/workflow-result-timeout.error.d.ts +11 -0
  130. package/dist/errors/workflow-result-timeout.error.js +13 -0
  131. package/dist/errors/workflow-state.error.d.ts +11 -0
  132. package/dist/errors/workflow-state.error.js +14 -0
  133. package/dist/errors/workflow.error.d.ts +7 -0
  134. package/dist/errors/workflow.error.js +7 -0
  135. package/dist/events/index.d.ts +3 -0
  136. package/dist/events/index.js +3 -0
  137. package/dist/events/workflow-events.interface.d.ts +145 -0
  138. package/dist/events/workflow-events.interface.js +1 -0
  139. package/dist/events/workflow-events.service.d.ts +17 -0
  140. package/dist/events/workflow-events.service.js +38 -0
  141. package/dist/events/workflows.channels.d.ts +3 -0
  142. package/dist/events/workflows.channels.js +9 -0
  143. package/dist/index.d.ts +20 -0
  144. package/dist/index.js +23 -0
  145. package/dist/interfaces/index.d.ts +13 -0
  146. package/dist/interfaces/index.js +13 -0
  147. package/dist/interfaces/serialized-workflow-error.interface.d.ts +6 -0
  148. package/dist/interfaces/serialized-workflow-error.interface.js +1 -0
  149. package/dist/interfaces/workflow-client.interface.d.ts +156 -0
  150. package/dist/interfaces/workflow-client.interface.js +1 -0
  151. package/dist/interfaces/workflow-clock.interface.d.ts +7 -0
  152. package/dist/interfaces/workflow-clock.interface.js +1 -0
  153. package/dist/interfaces/workflow-context.interface.d.ts +241 -0
  154. package/dist/interfaces/workflow-context.interface.js +1 -0
  155. package/dist/interfaces/workflow-decorator-options.interface.d.ts +101 -0
  156. package/dist/interfaces/workflow-decorator-options.interface.js +1 -0
  157. package/dist/interfaces/workflow-instance.interface.d.ts +83 -0
  158. package/dist/interfaces/workflow-instance.interface.js +1 -0
  159. package/dist/interfaces/workflow-payload-codec.interface.d.ts +32 -0
  160. package/dist/interfaces/workflow-payload-codec.interface.js +1 -0
  161. package/dist/interfaces/workflow-retry-options.interface.d.ts +9 -0
  162. package/dist/interfaces/workflow-retry-options.interface.js +1 -0
  163. package/dist/interfaces/workflow-runner.interface.d.ts +11 -0
  164. package/dist/interfaces/workflow-runner.interface.js +1 -0
  165. package/dist/interfaces/workflow-schedule.interface.d.ts +104 -0
  166. package/dist/interfaces/workflow-schedule.interface.js +1 -0
  167. package/dist/interfaces/workflow-storage.interface.d.ts +4 -0
  168. package/dist/interfaces/workflow-storage.interface.js +1 -0
  169. package/dist/interfaces/workflow-store.interface.d.ts +488 -0
  170. package/dist/interfaces/workflow-store.interface.js +1 -0
  171. package/dist/interfaces/workflows-module-options.interface.d.ts +80 -0
  172. package/dist/interfaces/workflows-module-options.interface.js +1 -0
  173. package/dist/mysql/index.d.ts +5 -0
  174. package/dist/mysql/index.js +8 -0
  175. package/dist/mysql/interfaces/index.d.ts +1 -0
  176. package/dist/mysql/interfaces/index.js +1 -0
  177. package/dist/mysql/interfaces/mysql-workflow-store-options.interface.d.ts +25 -0
  178. package/dist/mysql/interfaces/mysql-workflow-store-options.interface.js +1 -0
  179. package/dist/mysql/key-limits.d.ts +30 -0
  180. package/dist/mysql/key-limits.js +41 -0
  181. package/dist/mysql/migrations/index.d.ts +7 -0
  182. package/dist/mysql/migrations/index.js +16 -0
  183. package/dist/mysql/migrations/initial.migration.d.ts +9 -0
  184. package/dist/mysql/migrations/initial.migration.js +104 -0
  185. package/dist/mysql/mysql-workflow.store.d.ts +197 -0
  186. package/dist/mysql/mysql-workflow.store.js +828 -0
  187. package/dist/postgres/errors/index.d.ts +1 -0
  188. package/dist/postgres/errors/index.js +2 -0
  189. package/dist/postgres/index.d.ts +5 -0
  190. package/dist/postgres/index.js +8 -0
  191. package/dist/postgres/interfaces/index.d.ts +1 -0
  192. package/dist/postgres/interfaces/index.js +1 -0
  193. package/dist/postgres/interfaces/postgres-workflow-store-options.interface.d.ts +22 -0
  194. package/dist/postgres/interfaces/postgres-workflow-store-options.interface.js +1 -0
  195. package/dist/postgres/migrations/index.d.ts +6 -0
  196. package/dist/postgres/migrations/index.js +15 -0
  197. package/dist/postgres/migrations/initial.migration.d.ts +6 -0
  198. package/dist/postgres/migrations/initial.migration.js +106 -0
  199. package/dist/postgres/postgres-workflow.store.d.ts +132 -0
  200. package/dist/postgres/postgres-workflow.store.js +586 -0
  201. package/dist/services/index.d.ts +6 -0
  202. package/dist/services/index.js +6 -0
  203. package/dist/services/workflow-client.service.d.ts +146 -0
  204. package/dist/services/workflow-client.service.js +436 -0
  205. package/dist/services/workflow-execution.service.d.ts +267 -0
  206. package/dist/services/workflow-execution.service.js +1076 -0
  207. package/dist/services/workflow-registry.service.d.ts +56 -0
  208. package/dist/services/workflow-registry.service.js +172 -0
  209. package/dist/services/workflow-scheduler.service.d.ts +54 -0
  210. package/dist/services/workflow-scheduler.service.js +173 -0
  211. package/dist/services/workflow-schedules.service.d.ts +60 -0
  212. package/dist/services/workflow-schedules.service.js +193 -0
  213. package/dist/services/workflow-worker.service.d.ts +92 -0
  214. package/dist/services/workflow-worker.service.js +468 -0
  215. package/dist/signals/index.d.ts +1 -0
  216. package/dist/signals/index.js +1 -0
  217. package/dist/signals/workflow.signal.d.ts +17 -0
  218. package/dist/signals/workflow.signal.js +28 -0
  219. package/dist/sql/rate-windows.d.ts +41 -0
  220. package/dist/sql/rate-windows.js +53 -0
  221. package/dist/sql/workflow-rows.d.ts +16 -0
  222. package/dist/sql/workflow-rows.js +102 -0
  223. package/dist/sql/workflow-schema.error.d.ts +21 -0
  224. package/dist/sql/workflow-schema.error.js +15 -0
  225. package/dist/storage/encoded-workflow.store.d.ts +71 -0
  226. package/dist/storage/encoded-workflow.store.js +201 -0
  227. package/dist/storage/index.d.ts +1 -0
  228. package/dist/storage/index.js +1 -0
  229. package/dist/storage/workflow.storage.d.ts +55 -0
  230. package/dist/storage/workflow.storage.js +159 -0
  231. package/dist/stores/in-memory-workflow.store.d.ts +75 -0
  232. package/dist/stores/in-memory-workflow.store.js +380 -0
  233. package/dist/stores/index.d.ts +1 -0
  234. package/dist/stores/index.js +1 -0
  235. package/dist/testing/assertions.d.ts +10 -0
  236. package/dist/testing/assertions.js +72 -0
  237. package/dist/testing/index.d.ts +38 -0
  238. package/dist/testing/index.js +1089 -0
  239. package/dist/testing/schedule-store.contract.d.ts +33 -0
  240. package/dist/testing/schedule-store.contract.js +161 -0
  241. package/dist/utils/index.d.ts +7 -0
  242. package/dist/utils/index.js +7 -0
  243. package/dist/utils/journal-limits.util.d.ts +6 -0
  244. package/dist/utils/journal-limits.util.js +25 -0
  245. package/dist/utils/new-instance.util.d.ts +26 -0
  246. package/dist/utils/new-instance.util.js +87 -0
  247. package/dist/utils/normalize.util.d.ts +5 -0
  248. package/dist/utils/normalize.util.js +11 -0
  249. package/dist/utils/run-timeout.util.d.ts +3 -0
  250. package/dist/utils/run-timeout.util.js +15 -0
  251. package/dist/utils/schedule-spec.util.d.ts +16 -0
  252. package/dist/utils/schedule-spec.util.js +9 -0
  253. package/dist/utils/schedule-store.util.d.ts +15 -0
  254. package/dist/utils/schedule-store.util.js +44 -0
  255. package/dist/utils/serialize-error.util.d.ts +7 -0
  256. package/dist/utils/serialize-error.util.js +22 -0
  257. package/dist/utils/step-scope.util.d.ts +16 -0
  258. package/dist/utils/step-scope.util.js +38 -0
  259. package/dist/workflows.constants.d.ts +10 -0
  260. package/dist/workflows.constants.js +10 -0
  261. package/dist/workflows.module-definition.d.ts +4 -0
  262. package/dist/workflows.module-definition.js +6 -0
  263. package/dist/workflows.module.d.ts +24 -0
  264. package/dist/workflows.module.js +68 -0
  265. package/package.json +123 -0
@@ -0,0 +1,33 @@
1
+ import type { ScheduleStore } from '../core/interfaces/schedule-store.interface.js';
2
+ export interface ScheduleStoreContractOptions {
3
+ /**
4
+ * Also run the concurrency cases: claims and saves in flight at once. Set it for a store on a connection pool (they
5
+ * are the cases a store without the right locks fails); a single connection serializes them, which they must
6
+ * survive too.
7
+ */
8
+ concurrent?: boolean;
9
+ /** Called after each case with the store `createStore` returned (close it, drop its tables). */
10
+ dispose?: (store: ScheduleStore) => unknown;
11
+ }
12
+ export interface ScheduleStoreContractCase {
13
+ name: string;
14
+ /** Throws on failure. */
15
+ run(): Promise<void>;
16
+ }
17
+ /**
18
+ * The core's `ScheduleStore` contract as test cases, for any test runner: what a store of schedules must do for the
19
+ * `Scheduler`, whatever it runs on (SQL, Redis scripts, memory), races included.
20
+ *
21
+ * ```ts
22
+ * const cases = scheduleStoreContract(async () => new RedisScheduleStore(await freshRedis()), { concurrent: true });
23
+ * for (const c of cases) it(c.name, c.run);
24
+ * ```
25
+ *
26
+ * `createStore` is called once per case and must return a store without schedules.
27
+ */
28
+ export declare function scheduleStoreContract(createStore: () => ScheduleStore | Promise<ScheduleStore>, options?: ScheduleStoreContractOptions): ScheduleStoreContractCase[];
29
+ /**
30
+ * @internal The contract's cases, by `group`, added with `add`: `workflowStoreContract()` runs them on a workflow
31
+ * store's schedules.
32
+ */
33
+ export declare function scheduleCases(add: (name: string, run: (store: ScheduleStore) => Promise<void>) => void, group: 'base' | 'concurrent'): void;
@@ -0,0 +1,161 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { equal, expect, jitter } from './assertions.js';
3
+ /**
4
+ * The core's `ScheduleStore` contract as test cases, for any test runner: what a store of schedules must do for the
5
+ * `Scheduler`, whatever it runs on (SQL, Redis scripts, memory), races included.
6
+ *
7
+ * ```ts
8
+ * const cases = scheduleStoreContract(async () => new RedisScheduleStore(await freshRedis()), { concurrent: true });
9
+ * for (const c of cases) it(c.name, c.run);
10
+ * ```
11
+ *
12
+ * `createStore` is called once per case and must return a store without schedules.
13
+ */
14
+ export function scheduleStoreContract(createStore, options = {}) {
15
+ const cases = [];
16
+ const add = (name, run) => cases.push({
17
+ name,
18
+ run: async () => {
19
+ const store = await createStore();
20
+ try {
21
+ await run(store);
22
+ }
23
+ finally {
24
+ await options.dispose?.(store);
25
+ }
26
+ },
27
+ });
28
+ scheduleCases(add, 'base');
29
+ if (options.concurrent) {
30
+ scheduleCases(add, 'concurrent');
31
+ }
32
+ return cases;
33
+ }
34
+ /**
35
+ * @internal The contract's cases, by `group`, added with `add`: `workflowStoreContract()` runs them on a workflow
36
+ * store's schedules.
37
+ */
38
+ export function scheduleCases(add, group) {
39
+ if (group === 'concurrent') {
40
+ add('concurrent claimSchedules() never return the same schedule twice, and saves of one revision land once', async (store) => {
41
+ for (let i = 0; i < 40; i++) {
42
+ await store.saveSchedule(schedule(`s${String(i).padStart(2, '0')}`, { wakeAt: i }));
43
+ }
44
+ const owners = new Map();
45
+ const claimer = async (owner) => {
46
+ for (;;) {
47
+ const claimed = await store.claimSchedules({ owner, token: randomUUID(), now: 1_000, leaseUntil: FAR, limit: 3, targets: [TARGET] });
48
+ if (claimed.length === 0) {
49
+ return;
50
+ }
51
+ for (const record of claimed) {
52
+ if (owners.has(record.id)) {
53
+ throw new Error(`${record.id} was claimed by ${owners.get(record.id)} and ${owner}`);
54
+ }
55
+ owners.set(record.id, owner);
56
+ }
57
+ await jitter();
58
+ }
59
+ };
60
+ await Promise.all(['c1', 'c2', 'c3', 'c4', 'c5'].map(claimer));
61
+ equal(owners.size, 40, 'every schedule claimed');
62
+ const saves = await Promise.all(Array.from({ length: 8 }, (_, i) => jitter().then(() => store.saveSchedule(schedule('s00', { paused: true, now: 2_000 + i, expectRevision: 1 })))));
63
+ equal(saves.filter((saved) => saved !== null).length, 1, 'one save at revision 1');
64
+ const inserts = await Promise.all(Array.from({ length: 8 }, () => jitter().then(() => store.saveSchedule(schedule('new')))));
65
+ equal(inserts.filter((saved) => saved !== null).length, 1, 'one insert');
66
+ });
67
+ return;
68
+ }
69
+ add('saveSchedule() creates a schedule once, and replaces one only at the revision it read', async (store) => {
70
+ const payload = { tenant: "O'Reilly — ü 🚀", ids: [1, null], empty: '' };
71
+ const created = await store.saveSchedule(schedule('s1', { payload, now: 10 }));
72
+ expect(created, { id: 's1', target: TARGET, declared: false, spec: SPEC, payload, paused: false, wakeAt: 100, state: { next: 100, runs: 0 }, revision: 1, leaseOwner: null, leaseUntil: null, createdAt: 10, updatedAt: 10 }, 'the new schedule');
73
+ equal(await store.saveSchedule(schedule('s1', { now: 11 })), null, 'a second insert');
74
+ equal(await store.getSchedule('s1'), created, 'getSchedule()');
75
+ equal(await store.getSchedule('missing'), null, 'an unknown id');
76
+ const replaced = await store.saveSchedule(schedule('s1', { paused: true, payload: null, wakeAt: null, declared: true, now: 20, expectRevision: 1 }));
77
+ expect(replaced, { revision: 2, paused: true, payload: null, wakeAt: null, declared: true, createdAt: 10, updatedAt: 20 }, 'replaced at revision 1');
78
+ equal(await store.saveSchedule(schedule('s1', { now: 21, expectRevision: 1 })), null, 'a save at a stale revision');
79
+ equal(await store.saveSchedule(schedule('other', { now: 21, expectRevision: 1 })), null, 'a save of an unknown id');
80
+ expect(await store.getSchedule('s1'), { revision: 2, paused: true, updatedAt: 20 }, 'unchanged by them');
81
+ const encoded = await store.saveSchedule(schedule('s2', { payload: '$wf1:aes-256-gcm:k1.Zm9v' }));
82
+ equal(encoded?.payload, '$wf1:aes-256-gcm:k1.Zm9v', 'a string payload, as it is');
83
+ });
84
+ add('listSchedules() filters by target and declared, and pages by id; deleteSchedule() deletes at a revision, or any', async (store) => {
85
+ for (const [id, target, declared] of [['c', TARGET, true], ['a', TARGET, false], ['b', OTHER, true], ['d', TARGET, false]]) {
86
+ await store.saveSchedule(schedule(id, { target, declared }));
87
+ }
88
+ const ids = async (query = {}) => (await store.listSchedules({ limit: 100, offset: 0, ...query })).map((s) => s.id);
89
+ equal(await ids(), ['a', 'b', 'c', 'd'], 'every schedule, by id');
90
+ equal(await ids({ target: TARGET }), ['a', 'c', 'd'], 'by target');
91
+ equal(await ids({ declared: true }), ['b', 'c'], 'the declared ones');
92
+ equal(await ids({ target: TARGET, declared: false }), ['a', 'd'], 'by both');
93
+ equal(await ids({ target: TARGET, limit: 1, offset: 1 }), ['c'], 'a page');
94
+ equal(await store.deleteSchedule('a', 2), false, 'at another revision');
95
+ equal(await store.deleteSchedule('a', 1), true, 'at its revision');
96
+ equal(await store.deleteSchedule('c'), true, 'at any revision');
97
+ equal(await store.deleteSchedule('c'), false, 'already gone');
98
+ equal(await ids(), ['b', 'd'], 'the rest');
99
+ expect(await store.saveSchedule(schedule('a')), { revision: 1 }, 'the id, free again');
100
+ });
101
+ add('claimSchedules() leases due, unpaused, unleased schedules of the given targets, most overdue first', async (store) => {
102
+ await store.saveSchedule(schedule('late', { wakeAt: 50 }));
103
+ await store.saveSchedule(schedule('early', { wakeAt: 10 }));
104
+ await store.saveSchedule(schedule('paused', { wakeAt: 10, paused: true }));
105
+ await store.saveSchedule(schedule('ended', { wakeAt: null }));
106
+ await store.saveSchedule(schedule('future', { wakeAt: 5_000 }));
107
+ await store.saveSchedule(schedule('others', { wakeAt: 20, target: OTHER }));
108
+ const claim = (now, o = {}) => store.claimSchedules({ owner: o.owner ?? 'w1', token: o.token ?? randomUUID(), now, leaseUntil: now + 1_000, limit: o.limit ?? 100, targets: o.targets ?? [TARGET] });
109
+ const first = await claim(100, { token: 't1' });
110
+ equal(first.map((s) => s.id), ['early', 'late'], 'due and unpaused, by wakeAt');
111
+ expect(first[0], { leaseOwner: 'w1', leaseUntil: 1_100, revision: 1, updatedAt: 0 }, 'leased, nothing else changed');
112
+ equal(await claim(1_100), [], 'leased until 1100 inclusive');
113
+ equal((await claim(1_101, { owner: 'w2' })).map((s) => [s.id, s.leaseOwner]), [['early', 'w2'], ['late', 'w2']], 'claimed again once the lease expired');
114
+ equal((await claim(1_101, { targets: [OTHER, 'unknown'] })).map((s) => s.id), ['others'], 'only the given targets');
115
+ equal((await claim(9_000, { limit: 1 })).map((s) => s.id), ['early'], 'at most limit');
116
+ });
117
+ add('writeSchedule() writes only under the lease token, and a save that releases the lease makes the token stale', async (store) => {
118
+ await store.saveSchedule(schedule('s', { wakeAt: 10 }));
119
+ const claim = (now, token) => store.claimSchedules({ owner: 'w1', token, now, leaseUntil: now + 1_000, limit: 10, targets: [TARGET] });
120
+ await claim(10, 't1');
121
+ const state = { next: 500, runs: 1, pending: [{ at: 10, cancel: [] }] };
122
+ equal(await store.writeSchedule('s', 't1', { now: 11, state, wakeAt: 11, release: false }), true, 'the lease holder');
123
+ expect(await store.getSchedule('s'), { state, wakeAt: 11, revision: 2, updatedAt: 11, leaseUntil: 1_010 }, 'written, still leased');
124
+ equal(await store.writeSchedule('s', 'other', { now: 12, state: {}, wakeAt: 0, release: true }), false, 'another token');
125
+ equal(await store.writeSchedule('missing', 't1', { now: 12, state: {}, wakeAt: 0, release: true }), false, 'an unknown id');
126
+ // A save that keeps the lease (a pause) leaves the holder its write, which keeps the save's fields.
127
+ const current = (await store.getSchedule('s'));
128
+ await store.saveSchedule(schedule('s', { paused: true, wakeAt: current.wakeAt, state: current.state, now: 13, expectRevision: current.revision }));
129
+ equal(await store.writeSchedule('s', 't1', { now: 14, state: { next: 500, runs: 1, pending: [] }, wakeAt: 500, release: true }), true, 'after a save that kept the lease');
130
+ expect(await store.getSchedule('s'), { paused: true, wakeAt: 500, revision: 4, leaseUntil: null, state: { pending: [] } }, 'released, still paused');
131
+ equal(await claim(600, 't2'), [], 'a paused schedule is not claimed');
132
+ await store.saveSchedule(schedule('s', { paused: false, wakeAt: 600, now: 15, expectRevision: 4 }));
133
+ equal((await claim(600, 't3')).map((s) => s.id), ['s'], 'resumed');
134
+ await store.saveSchedule(schedule('s', { wakeAt: 700, now: 16, expectRevision: 5, releaseLease: true }));
135
+ equal(await store.writeSchedule('s', 't3', { now: 17, state: {}, wakeAt: 0, release: true }), false, 'the token of a lease a save released');
136
+ expect(await store.getSchedule('s'), { wakeAt: 700, leaseUntil: null, revision: 6 }, "the save's");
137
+ equal((await claim(700, 't4')).map((s) => s.id), ['s'], 'claimable at once');
138
+ });
139
+ }
140
+ const FAR = 9_000_000_000_000; // a deadline nobody reaches in a test
141
+ const TARGET = 'order-fulfilment';
142
+ const OTHER = 'invoice-batch';
143
+ /** A schedule's spec, as the scheduler stores it: JSON the store keeps as it is. */
144
+ const SPEC = { cron: '0 0 8 * * MON', tz: 'Europe/Warsaw', startAt: null, limit: 52, missed: 'once', overlap: 'buffer-one' };
145
+ /** `saveSchedule()`'s argument for a schedule of `TARGET`, due at 100, to insert unless `expectRevision` says otherwise. */
146
+ function schedule(id, o = {}) {
147
+ return {
148
+ id,
149
+ target: TARGET,
150
+ declared: false,
151
+ spec: SPEC,
152
+ payload: null,
153
+ paused: false,
154
+ wakeAt: 100,
155
+ state: { next: 100, runs: 0, pending: [] },
156
+ expectRevision: null,
157
+ releaseLease: false,
158
+ now: 0,
159
+ ...o,
160
+ };
161
+ }
@@ -0,0 +1,7 @@
1
+ export * from './run-timeout.util.js';
2
+ export * from './schedule-spec.util.js';
3
+ export * from './serialize-error.util.js';
4
+ export * from './step-scope.util.js';
5
+ export * from './journal-limits.util.js';
6
+ export * from './new-instance.util.js';
7
+ export * from './normalize.util.js';
@@ -0,0 +1,7 @@
1
+ export * from './run-timeout.util.js';
2
+ export * from './schedule-spec.util.js';
3
+ export * from './serialize-error.util.js';
4
+ export * from './step-scope.util.js';
5
+ export * from './journal-limits.util.js';
6
+ export * from './new-instance.util.js';
7
+ export * from './normalize.util.js';
@@ -0,0 +1,6 @@
1
+ import type { WorkflowJournalLimits } from '../interfaces/workflows-module-options.interface.js';
2
+ export declare const DEFAULT_JOURNAL_LIMITS: Required<WorkflowJournalLimits>;
3
+ /** The module's `journal` option over the defaults; throws for a limit that isn't a positive number. */
4
+ export declare function resolveJournalLimits(limits?: WorkflowJournalLimits): Required<WorkflowJournalLimits>;
5
+ /** An entry's size as the journal stores it: UTF-8 JSON. */
6
+ export declare function entryBytes(entry: unknown): number;
@@ -0,0 +1,25 @@
1
+ export const DEFAULT_JOURNAL_LIMITS = {
2
+ warnEntries: 1_000,
3
+ warnBytes: 1_000_000,
4
+ maxEntries: 10_000,
5
+ maxBytes: 10_000_000,
6
+ };
7
+ /** The module's `journal` option over the defaults; throws for a limit that isn't a positive number. */
8
+ export function resolveJournalLimits(limits = {}) {
9
+ const resolved = { ...DEFAULT_JOURNAL_LIMITS };
10
+ for (const key of Object.keys(DEFAULT_JOURNAL_LIMITS)) {
11
+ const value = limits[key];
12
+ if (value === undefined) {
13
+ continue;
14
+ }
15
+ if (typeof value !== 'number' || Number.isNaN(value) || value <= 0) {
16
+ throw new TypeError(`journal.${key} (${String(value)}) must be a positive number, or Infinity to turn the check off.`);
17
+ }
18
+ resolved[key] = value;
19
+ }
20
+ return resolved;
21
+ }
22
+ /** An entry's size as the journal stores it: UTF-8 JSON. */
23
+ export function entryBytes(entry) {
24
+ return Buffer.byteLength(JSON.stringify(entry) ?? '', 'utf8');
25
+ }
@@ -0,0 +1,26 @@
1
+ import type { WorkflowMetadata } from '../interfaces/workflow-decorator-options.interface.js';
2
+ import type { WorkflowInstance, WorkflowParentClose } from '../interfaces/workflow-instance.interface.js';
3
+ import type { NewWorkflowInstance } from '../interfaces/workflow-store.interface.js';
4
+ import type { Duration } from '../core/time/duration.js';
5
+ export interface NewInstanceOptions {
6
+ caller: string;
7
+ now: number;
8
+ timeout?: Duration;
9
+ concurrencyKey?: string;
10
+ rateLimitKey?: string;
11
+ priority?: number;
12
+ parentId?: string;
13
+ parentClose?: WorkflowParentClose;
14
+ scheduleId?: string;
15
+ scheduledAt?: number;
16
+ }
17
+ /** What `WorkflowClient.start()` and `ctx.startChild()` ask the store to create. */
18
+ export declare function newInstance(workflow: WorkflowMetadata, id: unknown, input: unknown, options: NewInstanceOptions): NewWorkflowInstance;
19
+ /**
20
+ * Throws `WorkflowIdConflictError` when the instance `create()` found under the id isn't the one
21
+ * asked for: another workflow, another input (when `input`), or another parent (when `parent`).
22
+ */
23
+ export declare function assertSameInstance(existing: WorkflowInstance, wanted: NewWorkflowInstance, compare: {
24
+ input: boolean;
25
+ parent: boolean;
26
+ }): void;
@@ -0,0 +1,87 @@
1
+ import { WorkflowIdConflictError } from '../errors/workflow-id-conflict.error.js';
2
+ import { assertPriority } from '../core/limits/limits.js';
3
+ import { canonical } from '../core/utils/canonical.util.js';
4
+ import { runTimeoutMs } from './run-timeout.util.js';
5
+ import { normalize } from './normalize.util.js';
6
+ /** What `WorkflowClient.start()` and `ctx.startChild()` ask the store to create. */
7
+ export function newInstance(workflow, id, input, options) {
8
+ if (typeof id !== 'string' || id.length === 0) {
9
+ throw new TypeError(`Invalid workflow instance id ${JSON.stringify(id)}. Use a non-empty string, such as \`order-\${orderId}\`.`);
10
+ }
11
+ if (options.priority !== undefined) {
12
+ assertPriority(options.priority, options.caller);
13
+ }
14
+ const json = normalize(input);
15
+ const timeoutMs = options.timeout === undefined ? workflow.timeout : runTimeoutMs(options.timeout, options.caller);
16
+ return {
17
+ id,
18
+ workflow: workflow.name,
19
+ version: workflow.version,
20
+ input: json,
21
+ deadline: timeoutMs === undefined ? null : options.now + timeoutMs,
22
+ parentId: options.parentId ?? null,
23
+ parentClose: options.parentClose ?? null,
24
+ concurrencyKey: keyOf(workflow, json, options.caller, 'concurrency', options.concurrencyKey),
25
+ rateLimitKey: keyOf(workflow, json, options.caller, 'rateLimit', options.rateLimitKey),
26
+ priority: options.priority ?? 0,
27
+ scheduleId: options.scheduleId ?? null,
28
+ scheduledAt: options.scheduledAt ?? null,
29
+ now: options.now,
30
+ };
31
+ }
32
+ const LIMITS = {
33
+ concurrency: { option: 'concurrencyKey', limit: 'concurrency limit', key: 'concurrency key', declare: '{ concurrency: { limit, key } }' },
34
+ rateLimit: { option: 'rateLimitKey', limit: 'rate limit', key: 'rate limit key', declare: '{ rateLimit: { max, duration, key } }' },
35
+ };
36
+ /**
37
+ * The instance's key for its workflow's per-key concurrency or rate limit: the start's, else the one the limit's
38
+ * `key` computes from the input.
39
+ */
40
+ function keyOf(workflow, input, caller, kind, given) {
41
+ const limit = workflow[kind];
42
+ const { option, limit: what, key: named, declare } = LIMITS[kind];
43
+ if (given !== undefined) {
44
+ if (typeof given !== 'string' || given.length === 0) {
45
+ throw new TypeError(`${caller}: invalid ${option} ${JSON.stringify(given)}. Use a non-empty string.`);
46
+ }
47
+ // Unknown here (started by name from a process that doesn't register it): the key stands.
48
+ if (limit !== undefined && limit?.perKey == null) {
49
+ throw new TypeError(`${caller}: workflow "${workflow.name}" has no ${what} per key, so ${option} would count for nothing. Declare one with @Workflow(name, ${declare}).`);
50
+ }
51
+ return given;
52
+ }
53
+ if (!limit?.key) {
54
+ return null;
55
+ }
56
+ let key;
57
+ try {
58
+ key = limit.key(input);
59
+ }
60
+ catch (error) {
61
+ throw new TypeError(`The ${named} of workflow "${workflow.name}" threw: ${error?.message ?? String(error)}`);
62
+ }
63
+ if (key === null || key === undefined) {
64
+ return null;
65
+ }
66
+ if (typeof key !== 'string' || key.length === 0) {
67
+ throw new TypeError(`The ${named} of workflow "${workflow.name}" returned ${JSON.stringify(key)}. Return a non-empty string, or null for none.`);
68
+ }
69
+ return key;
70
+ }
71
+ /**
72
+ * Throws `WorkflowIdConflictError` when the instance `create()` found under the id isn't the one
73
+ * asked for: another workflow, another input (when `input`), or another parent (when `parent`).
74
+ */
75
+ export function assertSameInstance(existing, wanted, compare) {
76
+ if (existing.workflow !== wanted.workflow) {
77
+ throw new WorkflowIdConflictError(`Instance "${wanted.id}" already exists for workflow "${existing.workflow}", not "${wanted.workflow}".`);
78
+ }
79
+ if (compare.parent && existing.parentId !== (wanted.parentId ?? null)) {
80
+ const parent = existing.parentId === null ? 'no parent' : `parent "${existing.parentId}"`;
81
+ throw new WorkflowIdConflictError(`Instance "${wanted.id}" of "${wanted.workflow}" already exists with ${parent}.`);
82
+ }
83
+ // A store may read an `undefined` input back as `null`.
84
+ if (compare.input && canonical(existing.input ?? null) !== canonical(wanted.input ?? null)) {
85
+ throw new WorkflowIdConflictError(`Instance "${wanted.id}" of "${wanted.workflow}" already exists with a different input.`);
86
+ }
87
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Round-trips through JSON so the first run sees exactly what every replay
3
+ * will see (a `Date` becomes a string in both, `undefined` stays `undefined`).
4
+ */
5
+ export declare function normalize<T>(value: T): T;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Round-trips through JSON so the first run sees exactly what every replay
3
+ * will see (a `Date` becomes a string in both, `undefined` stays `undefined`).
4
+ */
5
+ export function normalize(value) {
6
+ if (value === undefined) {
7
+ return value;
8
+ }
9
+ const json = JSON.stringify(value);
10
+ return json === undefined ? undefined : JSON.parse(json);
11
+ }
@@ -0,0 +1,3 @@
1
+ import { type Duration } from '../core/time/duration.js';
2
+ /** A workflow's run timeout in milliseconds: a zero one would time out before the first step. */
3
+ export declare function runTimeoutMs(timeout: Duration, owner: string): number;
@@ -0,0 +1,15 @@
1
+ import { parseDuration } from '../core/time/duration.js';
2
+ /** A workflow's run timeout in milliseconds: a zero one would time out before the first step. */
3
+ export function runTimeoutMs(timeout, owner) {
4
+ let ms;
5
+ try {
6
+ ms = parseDuration(timeout);
7
+ }
8
+ catch {
9
+ ms = 0;
10
+ }
11
+ if (ms <= 0) {
12
+ throw new TypeError(`Invalid timeout ${JSON.stringify(timeout)} for ${owner}. Use a positive duration, such as "30d".`);
13
+ }
14
+ return ms;
15
+ }
@@ -0,0 +1,16 @@
1
+ import type { ScheduleSpec } from '../core/interfaces/schedule.interface.js';
2
+ import type { WorkflowScheduleOptions } from '../interfaces/workflow-schedule.interface.js';
3
+ /** A workflow schedule's spec as the engine stores it (`WorkflowScheduleRecord.spec`): the core's, and what it starts. */
4
+ export interface WorkflowScheduleSpec extends ScheduleSpec {
5
+ /** The priority of the instances it starts: `0` for none. */
6
+ priority: number;
7
+ /** The pinned version, or `null`: the highest the starting worker registers. */
8
+ version: number | null;
9
+ /** The input is a function of the occurrence (a declared schedule): only a worker that declares it can compute it. */
10
+ inputFn: boolean;
11
+ }
12
+ /** Checks a schedule's options and returns its spec; throws a `TypeError` that names `owner` for anything invalid. */
13
+ export declare function scheduleSpec(owner: string, options: WorkflowScheduleOptions, extra: {
14
+ version: number | null;
15
+ inputFn: boolean;
16
+ }): WorkflowScheduleSpec;
@@ -0,0 +1,9 @@
1
+ import { assertPriority } from '../core/limits/limits.js';
2
+ import { parseSchedule } from '../core/scheduling/schedule-spec.js';
3
+ /** Checks a schedule's options and returns its spec; throws a `TypeError` that names `owner` for anything invalid. */
4
+ export function scheduleSpec(owner, options, extra) {
5
+ const spec = parseSchedule(options, owner);
6
+ const priority = options.priority ?? 0;
7
+ assertPriority(priority, owner);
8
+ return { ...spec, priority, ...extra };
9
+ }
@@ -0,0 +1,15 @@
1
+ import type { ScheduleStore } from '../core/interfaces/schedule-store.interface.js';
2
+ import type { WorkflowStore } from '../interfaces/workflow-store.interface.js';
3
+ /** The `WorkflowStore` methods that hold schedules: the core's `ScheduleStore`, in workflows' words. */
4
+ export type WorkflowScheduleMethods = 'saveSchedule' | 'getSchedule' | 'listSchedules' | 'deleteSchedule' | 'claimSchedules' | 'writeSchedule';
5
+ /**
6
+ * A workflow store's schedules as the core's `ScheduleStore`, for its `Scheduler`: a schedule's workflow is its
7
+ * target, and its input its payload. `store` is read at each call (the registered store), so it sees what the store's
8
+ * methods are then.
9
+ */
10
+ export declare function workflowScheduleStore(store: () => Pick<WorkflowStore, WorkflowScheduleMethods>): ScheduleStore;
11
+ /**
12
+ * A core `ScheduleStore` as a workflow store's schedule methods: how the in-memory workflow store keeps its schedules
13
+ * in the core's `InMemoryScheduleStore`.
14
+ */
15
+ export declare function workflowScheduleMethods(store: ScheduleStore): Pick<WorkflowStore, WorkflowScheduleMethods>;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A workflow store's schedules as the core's `ScheduleStore`, for its `Scheduler`: a schedule's workflow is its
3
+ * target, and its input its payload. `store` is read at each call (the registered store), so it sees what the store's
4
+ * methods are then.
5
+ */
6
+ export function workflowScheduleStore(store) {
7
+ return {
8
+ saveSchedule: async (save) => orNull(await store().saveSchedule(toWorkflowsSave(save)), fromWorkflows),
9
+ getSchedule: async (id) => orNull(await store().getSchedule(id), fromWorkflows),
10
+ listSchedules: async ({ target, ...query }) => (await store().listSchedules({ ...query, ...(target === undefined ? {} : { workflow: target }) })).map(fromWorkflows),
11
+ deleteSchedule: (id, revision) => store().deleteSchedule(id, revision),
12
+ claimSchedules: async ({ targets, ...request }) => (await store().claimSchedules({ ...request, workflows: targets })).map(fromWorkflows),
13
+ writeSchedule: (id, token, write) => store().writeSchedule(id, token, write),
14
+ };
15
+ }
16
+ /**
17
+ * A core `ScheduleStore` as a workflow store's schedule methods: how the in-memory workflow store keeps its schedules
18
+ * in the core's `InMemoryScheduleStore`.
19
+ */
20
+ export function workflowScheduleMethods(store) {
21
+ return {
22
+ saveSchedule: async (save) => orNull(await store.saveSchedule(fromWorkflowsSave(save)), toWorkflows),
23
+ getSchedule: async (id) => orNull(await store.getSchedule(id), toWorkflows),
24
+ listSchedules: async ({ workflow, ...query }) => (await store.listSchedules({ ...query, ...(workflow === undefined ? {} : { target: workflow }) })).map(toWorkflows),
25
+ deleteSchedule: (id, revision) => store.deleteSchedule(id, revision),
26
+ claimSchedules: async ({ workflows, ...request }) => (await store.claimSchedules({ ...request, targets: workflows })).map(toWorkflows),
27
+ writeSchedule: (id, token, write) => store.writeSchedule(id, token, write),
28
+ };
29
+ }
30
+ function toWorkflowsSave({ target, payload, ...save }) {
31
+ return { ...save, workflow: target, input: payload };
32
+ }
33
+ function fromWorkflowsSave({ workflow, input, ...save }) {
34
+ return { ...save, target: workflow, payload: input };
35
+ }
36
+ function fromWorkflows({ workflow, input, ...record }) {
37
+ return { ...record, target: workflow, payload: input };
38
+ }
39
+ function toWorkflows({ target, payload, ...record }) {
40
+ return { ...record, workflow: target, input: payload };
41
+ }
42
+ function orNull(value, map) {
43
+ return value === null ? null : map(value);
44
+ }
@@ -0,0 +1,7 @@
1
+ import type { SerializedWorkflowError } from '../interfaces/serialized-workflow-error.interface.js';
2
+ /**
3
+ * Never throws: a thrown value the engine can't serialize (a circular object, a
4
+ * BigInt, `undefined`) is described by `util.inspect()` instead, so the instance
5
+ * still records an outcome.
6
+ */
7
+ export declare function serializeError(error: unknown): SerializedWorkflowError;
@@ -0,0 +1,22 @@
1
+ import { inspect } from 'node:util';
2
+ /**
3
+ * Never throws: a thrown value the engine can't serialize (a circular object, a
4
+ * BigInt, `undefined`) is described by `util.inspect()` instead, so the instance
5
+ * still records an outcome.
6
+ */
7
+ export function serializeError(error) {
8
+ if (error instanceof Error) {
9
+ return { name: error.name || 'Error', message: String(error.message), stack: error.stack };
10
+ }
11
+ if (typeof error === 'string') {
12
+ return { name: 'Error', message: error };
13
+ }
14
+ let message;
15
+ try {
16
+ message = JSON.stringify(error);
17
+ }
18
+ catch {
19
+ // circular, or a BigInt: described below
20
+ }
21
+ return { name: 'Error', message: message ?? inspect(error, { depth: 3, breakLength: Infinity }) };
22
+ }
@@ -0,0 +1,16 @@
1
+ /** Runs a step attempt's function, so the signals it sends and the workflows it starts can derive their ids from the step. */
2
+ export declare function runInStepScope<T>(idempotencyKey: string, fn: () => T): T;
3
+ /**
4
+ * The dedupe id of a signal sent without one from inside a step, directly or through a mapped
5
+ * event: the step's `idempotencyKey`, the key, and how many signals with this name and key the
6
+ * attempt sent before it. A retried attempt sends the same ids in the same order, so the store
7
+ * keeps each signal once. `undefined` outside a step.
8
+ */
9
+ export declare function stepSignalId(name: string, key: string | null): string | undefined;
10
+ /**
11
+ * The id of a workflow started without one from inside a step: the step's `idempotencyKey`, the
12
+ * workflow's name, and how many instances of that workflow the attempt started before it. A
13
+ * retried attempt starts the same ids in the same order, so it gets its first instances back
14
+ * instead of new ones. `undefined` outside a step.
15
+ */
16
+ export declare function stepStartId(workflow: string): string | undefined;
@@ -0,0 +1,38 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ const scope = new AsyncLocalStorage();
3
+ /** Runs a step attempt's function, so the signals it sends and the workflows it starts can derive their ids from the step. */
4
+ export function runInStepScope(idempotencyKey, fn) {
5
+ return scope.run({ idempotencyKey, signals: new Map(), starts: new Map() }, fn);
6
+ }
7
+ /**
8
+ * The dedupe id of a signal sent without one from inside a step, directly or through a mapped
9
+ * event: the step's `idempotencyKey`, the key, and how many signals with this name and key the
10
+ * attempt sent before it. A retried attempt sends the same ids in the same order, so the store
11
+ * keeps each signal once. `undefined` outside a step.
12
+ */
13
+ export function stepSignalId(name, key) {
14
+ const step = scope.getStore();
15
+ if (!step) {
16
+ return undefined;
17
+ }
18
+ const counter = JSON.stringify([name, key]);
19
+ const n = (step.signals.get(counter) ?? 0) + 1;
20
+ step.signals.set(counter, n);
21
+ // JSON keeps the parts apart whatever they contain.
22
+ return JSON.stringify([step.idempotencyKey, key, n]);
23
+ }
24
+ /**
25
+ * The id of a workflow started without one from inside a step: the step's `idempotencyKey`, the
26
+ * workflow's name, and how many instances of that workflow the attempt started before it. A
27
+ * retried attempt starts the same ids in the same order, so it gets its first instances back
28
+ * instead of new ones. `undefined` outside a step.
29
+ */
30
+ export function stepStartId(workflow) {
31
+ const step = scope.getStore();
32
+ if (!step) {
33
+ return undefined;
34
+ }
35
+ const n = (step.starts.get(workflow) ?? 0) + 1;
36
+ step.starts.set(workflow, n);
37
+ return JSON.stringify([step.idempotencyKey, workflow, n]);
38
+ }
@@ -0,0 +1,10 @@
1
+ export declare const WORKFLOW_METADATA = "workflows:workflow";
2
+ /**
3
+ * The signal a child instance sends its parent when it ends, keyed by the child's id. Signal names
4
+ * starting with "$" are the engine's: `WorkflowSignal` and `WorkflowClient.signal()` refuse them.
5
+ */
6
+ export declare const CHILD_ENDED_SIGNAL = "$child-ended";
7
+ /** `@StartOn()` and `@SignalOn()` (`@nestjs/workflows/cqrs`) on a workflow class. */
8
+ export declare const WORKFLOW_EVENT_ROUTES_METADATA = "workflows:event-routes";
9
+ /** The module's payload codecs, resolved (`WorkflowsModuleOptions.codec`). */
10
+ export declare const WORKFLOW_PAYLOAD_CODECS: unique symbol;
@@ -0,0 +1,10 @@
1
+ export const WORKFLOW_METADATA = 'workflows:workflow';
2
+ /**
3
+ * The signal a child instance sends its parent when it ends, keyed by the child's id. Signal names
4
+ * starting with "$" are the engine's: `WorkflowSignal` and `WorkflowClient.signal()` refuse them.
5
+ */
6
+ export const CHILD_ENDED_SIGNAL = '$child-ended';
7
+ /** `@StartOn()` and `@SignalOn()` (`@nestjs/workflows/cqrs`) on a workflow class. */
8
+ export const WORKFLOW_EVENT_ROUTES_METADATA = 'workflows:event-routes';
9
+ /** The module's payload codecs, resolved (`WorkflowsModuleOptions.codec`). */
10
+ export const WORKFLOW_PAYLOAD_CODECS = Symbol('WORKFLOW_PAYLOAD_CODECS');
@@ -0,0 +1,4 @@
1
+ import type { WorkflowsModuleOptions } from './interfaces/workflows-module-options.interface.js';
2
+ export declare const ConfigurableModuleClass: import("@nestjs/common").ConfigurableModuleCls<WorkflowsModuleOptions, "forRoot", "createWorkflowsOptions", {
3
+ isGlobal?: boolean;
4
+ }>, WORKFLOWS_MODULE_OPTIONS: string | symbol;
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from '@nestjs/common';
2
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN: WORKFLOWS_MODULE_OPTIONS } = new ConfigurableModuleBuilder({ moduleName: 'Workflows' })
3
+ .setClassMethodName('forRoot')
4
+ .setFactoryMethodName('createWorkflowsOptions')
5
+ .setExtras({ isGlobal: true }, (definition, { isGlobal }) => ({ ...definition, global: isGlobal }))
6
+ .build();