@fluidgrids/workflows 2026.1027.1 → 2026.1028.2

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 (166) hide show
  1. package/README.md +144 -13
  2. package/dist/activation-BBh1p82z.d.cts +57 -0
  3. package/dist/activation-DnhG7SmF.d.ts +57 -0
  4. package/dist/assemble-BLqQ8lSu.d.cts +39 -0
  5. package/dist/assemble-DWgaXHd-.d.ts +39 -0
  6. package/dist/chunk-2J6TF7YI.cjs +86 -0
  7. package/dist/chunk-4LYSEANA.js +199 -0
  8. package/dist/chunk-623FVQSN.cjs +251 -0
  9. package/dist/{chunk-W32BSO3M.js → chunk-6FSHT5IL.js} +2 -2
  10. package/dist/{chunk-E3APHA6O.cjs → chunk-7AAZGVKD.js} +1 -64
  11. package/dist/chunk-ABFZWYHT.js +612 -0
  12. package/dist/{chunk-6U446SKY.js → chunk-BD7ZPG53.js} +2 -2
  13. package/dist/chunk-BO6OVILK.js +79 -0
  14. package/dist/chunk-C5OLWZ42.js +245 -0
  15. package/dist/{chunk-OSPTJCDI.cjs → chunk-CPDK3K5N.cjs} +6 -6
  16. package/dist/chunk-DHTLALBL.cjs +80 -0
  17. package/dist/chunk-DJYMDLV5.js +227 -0
  18. package/dist/{chunk-IHKXLG6I.js → chunk-E2VK7J2C.js} +1 -1
  19. package/dist/{chunk-3M34AUIY.cjs → chunk-FEW3JBYA.cjs} +3 -3
  20. package/dist/chunk-FMSW2HDF.cjs +267 -0
  21. package/dist/chunk-FR7KYWRO.cjs +41 -0
  22. package/dist/{chunk-7YR7DI7B.cjs → chunk-GBQ33SIZ.cjs} +44 -44
  23. package/dist/{chunk-6AIAC2FP.cjs → chunk-GVTZMA5C.cjs} +4 -4
  24. package/dist/{chunk-EEAIM35V.js → chunk-HWJZVOG7.js} +64 -23
  25. package/dist/chunk-I5LR3D2K.js +869 -0
  26. package/dist/chunk-IISBRUWJ.js +13 -0
  27. package/dist/chunk-JD2VQIDZ.cjs +15 -0
  28. package/dist/{chunk-UB2BHU2M.js → chunk-JZ4TOOLN.js} +1 -1
  29. package/dist/chunk-L24YIZY7.cjs +207 -0
  30. package/dist/chunk-LNUQE6D6.js +39 -0
  31. package/dist/chunk-M4BBAERY.cjs +1635 -0
  32. package/dist/chunk-N23SKIG4.js +73 -0
  33. package/dist/{chunk-TWOYB4Y7.cjs → chunk-NEIUQASE.cjs} +13 -13
  34. package/dist/chunk-P6TA5EZT.cjs +634 -0
  35. package/dist/chunk-PMCM2OH4.js +46 -0
  36. package/dist/chunk-PZCVF76Q.cjs +868 -0
  37. package/dist/{chunk-6BEW4QD6.js → chunk-QAN5MZEM.js} +1 -1
  38. package/dist/{chunk-CSYBNN3V.cjs → chunk-QKZL3ONM.cjs} +2 -2
  39. package/dist/chunk-QQ6ULXHI.cjs +885 -0
  40. package/dist/chunk-SAENRNBJ.cjs +52 -0
  41. package/dist/{chunk-TQF7MYEN.cjs → chunk-SAOBPYYJ.cjs} +134 -96
  42. package/dist/{chunk-XBIID5DY.js → chunk-SMCANK3F.js} +5 -4
  43. package/dist/{chunk-UWQWD7BP.cjs → chunk-SX2PNKKT.cjs} +18 -17
  44. package/dist/{chunk-UJNHTS6D.js → chunk-TBDXTV22.cjs} +8 -45
  45. package/dist/chunk-UK3WM7YZ.cjs +75 -0
  46. package/dist/chunk-VOKMAAJP.js +848 -0
  47. package/dist/chunk-VRHHBPT6.js +1622 -0
  48. package/dist/{chunk-5AW745OG.js → chunk-XHXIDVEC.js} +1 -1
  49. package/dist/chunk-XKJA7KX3.js +72 -0
  50. package/dist/chunk-Z2Y65YOY.cjs +7 -0
  51. package/dist/codegen/index.cjs +9 -7
  52. package/dist/codegen/index.d.cts +2 -2
  53. package/dist/codegen/index.d.ts +2 -2
  54. package/dist/codegen/index.js +6 -5
  55. package/dist/file-BOoHZgoQ.d.cts +1557 -0
  56. package/dist/file-yuPCFU5b.d.ts +1557 -0
  57. package/dist/history/index.cjs +287 -0
  58. package/dist/history/index.d.cts +157 -0
  59. package/dist/history/index.d.ts +157 -0
  60. package/dist/history/index.js +5 -0
  61. package/dist/index.cjs +134 -36
  62. package/dist/index.d.cts +137 -9
  63. package/dist/index.d.ts +137 -9
  64. package/dist/index.js +8 -3
  65. package/dist/ir/index.cjs +152 -158
  66. package/dist/ir/index.d.cts +14 -4
  67. package/dist/ir/index.d.ts +14 -4
  68. package/dist/ir/index.js +41 -50
  69. package/dist/lower-BzmUz0sq.d.cts +129 -0
  70. package/dist/lower-_-iT4HYs.d.ts +129 -0
  71. package/dist/lower-types-DQNBfWQn.d.cts +37 -0
  72. package/dist/lower-types-DQNBfWQn.d.ts +37 -0
  73. package/dist/{manifest-Duf-gHdO.d.cts → manifest-0OLw0i7k.d.cts} +1 -1
  74. package/dist/{manifest-Bp9ZJlav.d.ts → manifest-C8N6HDYl.d.ts} +1 -1
  75. package/dist/node-evaluator-4MSDKWOG.cjs +22 -0
  76. package/dist/node-evaluator-SX4LEYQC.js +12 -0
  77. package/dist/quickjs/index.cjs +236 -12
  78. package/dist/quickjs/index.d.cts +46 -5
  79. package/dist/quickjs/index.d.ts +46 -5
  80. package/dist/quickjs/index.js +230 -9
  81. package/dist/roundtrip/index.cjs +333 -131
  82. package/dist/roundtrip/index.d.cts +39 -7
  83. package/dist/roundtrip/index.d.ts +39 -7
  84. package/dist/roundtrip/index.js +185 -13
  85. package/dist/runtime/index.cjs +90 -0
  86. package/dist/runtime/index.d.cts +297 -0
  87. package/dist/runtime/index.d.ts +297 -0
  88. package/dist/runtime/index.js +8 -0
  89. package/dist/testing/index.cjs +1057 -0
  90. package/dist/testing/index.d.cts +305 -0
  91. package/dist/testing/index.d.ts +305 -0
  92. package/dist/testing/index.js +1037 -0
  93. package/dist/{types-Dr2Fn1Ax.d.ts → types-BGoPzgcl.d.cts} +1 -1
  94. package/dist/{types-Dr2Fn1Ax.d.cts → types-BGoPzgcl.d.ts} +1 -1
  95. package/dist/types-CpBBjaGX.d.cts +380 -0
  96. package/dist/types-DV-HX_Gt.d.ts +380 -0
  97. package/dist/{assemble-CrpUyd_3.d.ts → workflow-Bpr3vnA_.d.ts} +40 -160
  98. package/dist/workflow-C2a05WUJ.d.ts +444 -0
  99. package/dist/{assemble-DEf3znGD.d.cts → workflow-FXUkpIBc.d.cts} +40 -160
  100. package/dist/workflow-J-yYixny.d.cts +444 -0
  101. package/fixtures/README.md +128 -0
  102. package/fixtures/histories/activity-failure-fails-run.history.json +163 -0
  103. package/fixtures/histories/activity-retry-then-success.history.json +147 -0
  104. package/fixtures/histories/cancel-activity-abandon.history.json +126 -0
  105. package/fixtures/histories/cancel-activity-compensate.history.json +307 -0
  106. package/fixtures/histories/cancel-concurrent-non-cancellable.history.json +234 -0
  107. package/fixtures/histories/cancel-concurrent-scope-timeout.history.json +184 -0
  108. package/fixtures/histories/cancel-condition-after-cancel.history.json +200 -0
  109. package/fixtures/histories/cancel-during-timer.history.json +123 -0
  110. package/fixtures/histories/cancel-saga-compensations.history.json +325 -0
  111. package/fixtures/histories/cancel-scope-timeout.history.json +148 -0
  112. package/fixtures/histories/child-abandon-outlives-parent.history.json +191 -0
  113. package/fixtures/histories/child-cancel-default.history.json +234 -0
  114. package/fixtures/histories/child-cancel-request.history.json +206 -0
  115. package/fixtures/histories/child-durable-child-side.history.json +124 -0
  116. package/fixtures/histories/child-durable-result.history.json +163 -0
  117. package/fixtures/histories/child-failure-caught.history.json +166 -0
  118. package/fixtures/histories/child-graph-by-key.history.json +138 -0
  119. package/fixtures/histories/child-terminated-by-parent-close.history.json +85 -0
  120. package/fixtures/histories/continue-as-new-first-run.history.json +146 -0
  121. package/fixtures/histories/continue-as-new-history-limit.history.json +489 -0
  122. package/fixtures/histories/continue-as-new-last-run.history.json +147 -0
  123. package/fixtures/histories/continue-as-new-suggested.history.json +352 -0
  124. package/fixtures/histories/details-and-context.history.json +176 -0
  125. package/fixtures/histories/deterministic-values.history.json +128 -0
  126. package/fixtures/histories/engine-builtins.history.json +172 -0
  127. package/fixtures/histories/gather-activities.history.json +291 -0
  128. package/fixtures/histories/gather-all-settled.history.json +219 -0
  129. package/fixtures/histories/gather-mixed.history.json +281 -0
  130. package/fixtures/histories/input-during-running-task.history.json +111 -0
  131. package/fixtures/histories/nondeterminism-reordered.history.json +218 -0
  132. package/fixtures/histories/patched-deprecated.history.json +271 -0
  133. package/fixtures/histories/patched-new-run.history.json +270 -0
  134. package/fixtures/histories/patched-old-history.history.json +190 -0
  135. package/fixtures/histories/race-activity-wins.history.json +152 -0
  136. package/fixtures/histories/race-signal-beats-child.history.json +161 -0
  137. package/fixtures/histories/race-timeout-wins.history.json +118 -0
  138. package/fixtures/histories/side-effect-recorded.history.json +153 -0
  139. package/fixtures/histories/signal-buffered-at-start.history.json +157 -0
  140. package/fixtures/histories/signal-default-handler.history.json +187 -0
  141. package/fixtures/histories/signal-handler.history.json +191 -0
  142. package/fixtures/histories/signal-parent-child-side.history.json +132 -0
  143. package/fixtures/histories/signal-parent.history.json +202 -0
  144. package/fixtures/histories/signal-run-delivered.history.json +128 -0
  145. package/fixtures/histories/signal-run-undelivered.history.json +134 -0
  146. package/fixtures/histories/signal-wait.history.json +111 -0
  147. package/fixtures/histories/task-failure-code-error.history.json +137 -0
  148. package/fixtures/histories/task-failure-then-transient-attempt.history.json +140 -0
  149. package/fixtures/histories/timer-condition-met.history.json +201 -0
  150. package/fixtures/histories/timer-condition-timeout.history.json +118 -0
  151. package/fixtures/histories/timer-sleep-once.history.json +118 -0
  152. package/fixtures/histories/timer-sleep-sequence.history.json +210 -0
  153. package/fixtures/histories/update-accepted-completed.history.json +149 -0
  154. package/fixtures/histories/update-with-start-rejected-validation.history.json +91 -0
  155. package/package.json +44 -5
  156. package/schema/history-file.schema.json +2544 -0
  157. package/schema/ir-v2.schema.json +16 -0
  158. package/schema/run-event.schema.json +2374 -0
  159. package/schema/workflow-command.schema.json +1095 -0
  160. package/schema/workflow-task.schema.json +3560 -0
  161. package/dist/chunk-MBBKGD7K.js +0 -440
  162. package/dist/chunk-Y7NWR3WH.cjs +0 -450
  163. package/dist/lower-CXggJTIo.d.ts +0 -67
  164. package/dist/lower-DAzvsJCo.d.cts +0 -67
  165. package/dist/node-evaluator-CH3XCJND.cjs +0 -17
  166. package/dist/node-evaluator-TULXA3WE.js +0 -8
package/README.md CHANGED
@@ -1,18 +1,23 @@
1
1
  # @fluidgrids/workflows
2
2
 
3
- Author [FluidGrids](https://fluidgrids.ai) workflows as code. This package is
4
- Phase A of the code-first workflows spec set (BOFF-9902 / BOFF-9904,
5
- `fluidgrids-specs/product/specs/code-first/`):
3
+ Author [FluidGrids](https://fluidgrids.ai) workflows as code. This package
4
+ implements Phases A and B of the code-first workflows spec set (BOFF-9902,
5
+ BOFF-9904, BOFF-9905; `fluidgrids-specs/product/specs/code-first/`):
6
6
 
7
7
  - the workflow IR, schema version 2: types, JSON Schema 2020-12, normalisation,
8
8
  validation, hashing, diff, interface derivation and the export envelope;
9
9
  - the tier-1 graph builder: TypeScript that constructs a canvas workflow;
10
+ - tier-2 durable workflow functions (`workflow()`), executed by replay;
11
+ - the durable-function contract shared with the service, the workers and the
12
+ Python SDK: the runtime ABI, the history, command and task JSON Schemas, the
13
+ command-to-event state machine, the retry policy and the activity allow-list;
14
+ - the QuickJS workflow task runner, bundles with vendored pure-ESM
15
+ dependencies and the source archive;
16
+ - a test environment, replay and the replay conformance fixtures;
10
17
  - lift (IR to TypeScript or Python) and lower (TypeScript to IR), with a
11
18
  fidelity report computed by actually lowering the code;
12
19
  - typed node clients generated from a node registry snapshot.
13
20
 
14
- Durable (tier-2) workflow functions are Phase B and are not in this package yet.
15
-
16
21
  ## Install
17
22
 
18
23
  ```bash
@@ -21,14 +26,18 @@ bun add @fluidgrids/workflows
21
26
 
22
27
  ## Entry points
23
28
 
24
- | Import | Contents | Runs in |
25
- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
26
- | `@fluidgrids/workflows` | `defineWorkflow`, `triggers`, `nodes`, `edge`, `expr`, `and`, `or`, `op`, `trigger`, `toIR` | anywhere; also bundled into workflow code |
27
- | `@fluidgrids/workflows/ir` | IR v2 types, `IR_V2_SCHEMA`, `normalizeDefinition`, `validate`, `irHash`, `canonicalJson`, `diffDefinitions`, `deriveInterface`, envelope helpers | anywhere, browser included |
28
- | `@fluidgrids/workflows/roundtrip` | `liftToTypeScript`, `liftToPython`, `lower`, `exportWorkflowCode`, `checkFidelity` | Node or Bun |
29
- | `@fluidgrids/workflows/codegen` | `generateNodeClients(manifests, 'ts' \| 'py')` | anywhere |
30
- | `@fluidgrids/workflows/quickjs` | `createQuickJSEvaluator()` for lowering untrusted code | Node or Bun |
31
- | `@fluidgrids/workflows/schema/ir-v2.schema.json` | the JSON Schema | data |
29
+ | Import | Contents | Runs in |
30
+ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------- |
31
+ | `@fluidgrids/workflows` | `defineWorkflow`, `triggers`, `nodes`, `edge`, `expr`, `and`, `or`, `op`, `trigger`, `toIR`; `workflow`, `defineSignal`, `defineQuery`, `defineUpdate`, the failure classes | anywhere; also bundled into workflow code |
32
+ | `@fluidgrids/workflows/history` | `EVENT_TYPES`, `COMMAND_TYPES`, `TASK_FAILURE_CAUSES`, the JSON Schemas, `validateCommands`, `eventsForCommands`, `projectRunState`, `splitActivations`, `computeRetryDelay`, `ACTIVITY_NODE_ALLOWLIST`, `assertActivityNodeType`, history files | Node or Bun |
33
+ | `@fluidgrids/workflows/runtime` | the runtime ABI (`createWorkflowRuntime`, `installWorkflowRuntime`), the scheduler, the determinism shims | anywhere; bundled into workflow code |
34
+ | `@fluidgrids/workflows/testing` | `TestWorkflowEnvironment`, `replay`, `assertHistory`, `expectIR`, `simulate`, the `vm` task runner | Node or Bun |
35
+ | `@fluidgrids/workflows/ir` | IR v2 types, `IR_V2_SCHEMA`, `normalizeDefinition`, `validate`, `irHash`, `canonicalJson`, `diffDefinitions`, `deriveInterface`, envelope helpers | anywhere, browser included |
36
+ | `@fluidgrids/workflows/roundtrip` | `liftToTypeScript`, `liftToPython`, `lower`, `exportWorkflowCode`, `checkFidelity`, `buildBundle`, `vendorDependencies`, `packSources`, `unpackSources` | Node or Bun |
37
+ | `@fluidgrids/workflows/codegen` | `generateNodeClients(manifests, 'ts' \| 'py')` | anywhere |
38
+ | `@fluidgrids/workflows/quickjs` | `createQuickJSEvaluator()` for lowering untrusted code, `createWorkflowTaskRunner()` for workflow tasks | Node or Bun |
39
+ | `@fluidgrids/workflows/schema/*.schema.json` | `ir-v2`, `run-event`, `workflow-command`, `workflow-task`, `history-file` | data |
40
+ | `@fluidgrids/workflows/fixtures/histories/*` | the replay conformance histories | data |
32
41
 
33
42
  ## Tier 1: the graph builder
34
43
 
@@ -180,6 +189,127 @@ manifest's `input_fields` / `output_fields`; trigger operations also appear
180
189
  under `triggers`. Internal nodes ship in the package as `nodes.internal.*` and
181
190
  `triggers.*`.
182
191
 
192
+ ## Tier 2: durable workflow functions
193
+
194
+ A tier-2 workflow is an `async` function that the engine runs by **replay**:
195
+ from the top on every workflow task, with recorded results fed back, until it
196
+ returns or awaits something not yet in its history. Everything that touches
197
+ the world is a command (`ctx.run` for node activities, timers, child runs,
198
+ signals to other runs); the function itself has no I/O.
199
+
200
+ ```ts
201
+ import { workflow, defineSignal, ApplicationError } from '@fluidgrids/workflows';
202
+ import { nodes as n } from './.fluidgrids/nodes';
203
+
204
+ export const approve = defineSignal<{ by: string }>('approve');
205
+
206
+ export const refundOrder = workflow(
207
+ 'refund-order',
208
+ { signals: [approve] },
209
+ async (ctx, order: { id: string }) => {
210
+ const decision = await Promise.race([
211
+ ctx.signalReceived(approve),
212
+ ctx.sleep('2d').then(() => undefined),
213
+ ]);
214
+ if (!decision) throw new ApplicationError('Not approved in time', { type: 'PERMANENT' });
215
+ const refund = await ctx.run(n.stripe.refund, {
216
+ args: { chargeId: order.id },
217
+ retry: { maxRetries: 5, retryDelay: 1000 },
218
+ });
219
+ return { refunded: refund.id, approvedBy: decision.by };
220
+ }
221
+ );
222
+ ```
223
+
224
+ - `ctx.run`, `ctx.sleep`, `ctx.condition`, `ctx.signalReceived` / `ctx.onSignal`
225
+ / `ctx.setDefaultSignalHandler`, `ctx.child` / `ctx.startChild`,
226
+ `ctx.signalRun` / `ctx.signalParent` / `ctx.getRunHandle`, `ctx.sideEffect`,
227
+ `ctx.patched` / `ctx.deprecatePatch`, `ctx.now` / `ctx.random` / `ctx.uuid`,
228
+ `ctx.setCurrentDetails`, cancellation scopes, `ctx.saga()`, and the parity
229
+ commands `ctx.upsertContext` and `ctx.continueAsNew`. User activities, local
230
+ activities, custom queries, updates and search attributes are Phase C and fail
231
+ the workflow task with `UNSUPPORTED_COMMAND`.
232
+ - Only store and registry nodes and `internal:http`, `internal:datatable`,
233
+ `internal:set` and `internal:code` may run as activities
234
+ (`ACTIVITY_NODE_ALLOWLIST`).
235
+ - An `ApplicationError` (or any failure class, or a class listed in
236
+ `failureErrorTypes`) fails the run; any other throw, a determinism violation
237
+ or a Phase C call fails only the workflow task, which the engine retries.
238
+ - A cancellation scope function receives a `ctx` bound to its scope; use it
239
+ inside the function. The outer `ctx` joins the scope only while the function
240
+ runs synchronously (`ctx.nonCancellable(() => ctx.run(...))` works), never
241
+ after an await, so concurrent branches never share a scope by accident.
242
+ `saga.compensate()` runs each compensation in its own non-cancellable scope
243
+ and passes it that scope's `ctx` (`saga.add((ctx) => ...)`).
244
+ - `ctx.condition` in a scope that is already cancelled rejects with
245
+ `CancelledFailure`; `ctx.allHandlersFinished()` ignores cancellation.
246
+ - A child whose options omit `cancellationType` uses
247
+ `WAIT_CANCELLATION_COMPLETED`: a cancelled scope sends
248
+ `REQUEST_CANCEL_EXTERNAL_RUN` with a `{ childId }` target (enabled with the
249
+ core commands) and the awaitable rejects once the child has ended.
250
+ - `ctx.random()` and `ctx.uuid()` are seeded from `randomnessSeed` in the
251
+ `WORKFLOW_STARTED` payload (the service writes the run id), falling back to
252
+ `info.runId`, so a history replays on its own.
253
+ - Inside the isolates, local time is UTC on every host, the `toLocale*` and
254
+ `localeCompare` methods are locale-independent, `Intl` is absent and
255
+ `WeakRef` / `FinalizationRegistry` throw, so QuickJS and `vm` run code the
256
+ same way. The in-process test environment runs on the host's built-ins.
257
+ - In tier-1 code, `wf.codeWorkflow(fn, options)` embeds a function as an
258
+ `internal:code-workflow` node. A function that declares a trigger, or that no
259
+ tier-1 workflow embeds, lowers to a workflow of its own.
260
+
261
+ ### The runtime ABI and the task runner
262
+
263
+ Every bundle installs `globalThis.__fluidgridsRuntime = { protocol: 1,
264
+ sdkVersion, start(requestJson), step(), complete() }`. A host calls `start`,
265
+ drains promise jobs while `step()` returns `"more"`, drains once more and reads
266
+ the `WorkflowTaskResult` JSON from `complete()`. `createWorkflowTaskRunner()`
267
+ (`@fluidgrids/workflows/quickjs`) does this in QuickJS on a pool of worker
268
+ threads with a heap cap, a stack cap (default 256 KiB, about 1,300 frames,
269
+ because QuickJS's C frames also use the host thread's stack) and a wall-clock
270
+ budget, verifies bundles against their `buildId`, and replaces threads that
271
+ time out or end a task with a fatal engine error before they get another task.
272
+
273
+ ### Testing
274
+
275
+ ```ts
276
+ import { TestWorkflowEnvironment, replay } from '@fluidgrids/workflows/testing';
277
+
278
+ const env = await TestWorkflowEnvironment.create();
279
+ env.mock(n.stripe.refund, async () => ({ id: 're_1' }));
280
+ const handle = await env.start(refundOrder, { input: { id: 'ch_1' } });
281
+ await handle.signal(approve, { by: 'ana' });
282
+ expect(await handle.result()).toEqual({ refunded: 're_1', approvedBy: 'ana' });
283
+ await replay(refundOrder, handle.history()); // throws DeterminismViolation on a mismatch
284
+ ```
285
+
286
+ The test environment appends exactly the events the service appends
287
+ (`eventsForCommands`) and retries activities with `computeRetryDelay`. Its
288
+ documented differences from the service: workflow tasks run in this process
289
+ without the determinism shims (pass `taskExecutor` to run them in an isolate), a
290
+ failed workflow task stops the run instead of being retried, activity
291
+ start-to-close and heartbeat timeouts are not timed (a mock may throw a
292
+ `TimeoutFailure`), and graph children are mocks (`mockChild`).
293
+
294
+ `simulate(definition, { trigger, mocks, workflows })` walks a tier-1 graph in
295
+ process. It is a reduced form: no expressions are evaluated, a node without a
296
+ mock passes its input through, routing follows the handle a mock returns (or
297
+ every edge), loops run their body once, and error policies, retries, waits and
298
+ merges are not modelled.
299
+
300
+ ### Replay conformance fixtures
301
+
302
+ `fixtures/histories/` holds the recorded histories of spec 10 §3 (timers,
303
+ signals, cancellation, child runs, race, gather, continue-as-new, patched,
304
+ side effects, activities, task failures, activation boundaries, and updates as
305
+ parse-only fixtures), with their workflow sources in `fixtures/histories/src/`
306
+ and the scenarios that recorded them in `fixtures/histories/scenarios.ts`.
307
+ `bun run check:histories` re-records every history, replays it in QuickJS and in
308
+ `vm`, requires both results to equal the recorded `expected`, and replays every
309
+ completed workflow task to exactly the commands it recorded, then does all of
310
+ it again under `TZ=Asia/Kolkata`; `tests/histories.test.ts` adds the in-process
311
+ replay. `--write` re-records.
312
+
183
313
  ## Shared fixtures
184
314
 
185
315
  `fixtures/` holds the conformance fixtures shared with the Python SDK: every
@@ -196,6 +326,7 @@ bun run test:coverage # with the 85% gate
196
326
  bun run lint
197
327
  bun run typecheck # src, tests and the fixture projects
198
328
  bun run check:fixtures # fixtures are current; add --ruff to run ruff format --check
329
+ bun run check:histories # replay conformance in QuickJS and vm; --write re-records
199
330
  bun run build # tsup: ESM + CJS + .d.ts
200
331
  bun run sanity # everything above
201
332
  ```
@@ -0,0 +1,57 @@
1
+ import { U as RunEventRecord } from './types-CpBBjaGX.cjs';
2
+
3
+ /**
4
+ * Split a history into activations (C-HIST, spec 04 §2).
5
+ *
6
+ * An activation is what one workflow task saw and did: the input events it
7
+ * delivered to the code, the clock it ran with, and (for completed tasks) the
8
+ * command events it appended. Replaying a history means running the code
9
+ * through the same activations in order and checking that each completed one
10
+ * issues the commands it recorded; the last, open activation then produces the
11
+ * new commands.
12
+ *
13
+ * The rules:
14
+ * 1. A boundary is a `WORKFLOW_TASK_STARTED` followed (input events may
15
+ * interleave) by `WORKFLOW_TASK_COMPLETED` of the same `taskId`. A Started
16
+ * followed by Failed or TimedOut is not a boundary; its inputs carry over.
17
+ * 2. Activation k delivers the inputs appended before its Started, up to the
18
+ * history length the task was leased with (`payload.historyLength`, written
19
+ * on transient attempts, else the Started event's own seq).
20
+ * 3. Inputs appended after that (while the task ran) belong to activation k+1.
21
+ * 4. Command events after Completed(k) belong to activation k.
22
+ * 5. The clock of activation k is `payload.startedAt` of its Started event
23
+ * when present, else the event time. The open activation uses `nowMs`.
24
+ * 6. The history length activation k reports is its cutoff (rule 2); the open
25
+ * activation reports the leased length.
26
+ */
27
+
28
+ interface Activation {
29
+ /** Input events to deliver, in history order. */
30
+ inputs: RunEventRecord[];
31
+ /** Command events the task appended; undefined for the open (new) activation. */
32
+ recorded: RunEventRecord[] | undefined;
33
+ /** The workflow clock during the activation. */
34
+ timeMs: number;
35
+ /** The completed task's id. */
36
+ taskId?: string;
37
+ /**
38
+ * The history length the task saw when it started (what `ctx.info.historyLength`
39
+ * and `continueAsNewSuggested` report during the activation, so replay sees
40
+ * the same values the original task did).
41
+ */
42
+ historyLength: number;
43
+ }
44
+ /**
45
+ * The activations of `history`. With `includeOpen`, the last entry is the
46
+ * open activation (the task being executed), even when it has no inputs.
47
+ */
48
+ declare function splitActivations(history: readonly RunEventRecord[], nowMs: number, includeOpen: boolean): Activation[];
49
+ /**
50
+ * Input events that no completed workflow task has delivered yet, in history
51
+ * order, including those that arrived while a task was running (rule 3). The
52
+ * service schedules a new workflow task when this is not empty after a task
53
+ * completes or an input is appended.
54
+ */
55
+ declare function unseenInputs(history: readonly RunEventRecord[]): RunEventRecord[];
56
+
57
+ export { type Activation as A, splitActivations as s, unseenInputs as u };
@@ -0,0 +1,57 @@
1
+ import { U as RunEventRecord } from './types-DV-HX_Gt.js';
2
+
3
+ /**
4
+ * Split a history into activations (C-HIST, spec 04 §2).
5
+ *
6
+ * An activation is what one workflow task saw and did: the input events it
7
+ * delivered to the code, the clock it ran with, and (for completed tasks) the
8
+ * command events it appended. Replaying a history means running the code
9
+ * through the same activations in order and checking that each completed one
10
+ * issues the commands it recorded; the last, open activation then produces the
11
+ * new commands.
12
+ *
13
+ * The rules:
14
+ * 1. A boundary is a `WORKFLOW_TASK_STARTED` followed (input events may
15
+ * interleave) by `WORKFLOW_TASK_COMPLETED` of the same `taskId`. A Started
16
+ * followed by Failed or TimedOut is not a boundary; its inputs carry over.
17
+ * 2. Activation k delivers the inputs appended before its Started, up to the
18
+ * history length the task was leased with (`payload.historyLength`, written
19
+ * on transient attempts, else the Started event's own seq).
20
+ * 3. Inputs appended after that (while the task ran) belong to activation k+1.
21
+ * 4. Command events after Completed(k) belong to activation k.
22
+ * 5. The clock of activation k is `payload.startedAt` of its Started event
23
+ * when present, else the event time. The open activation uses `nowMs`.
24
+ * 6. The history length activation k reports is its cutoff (rule 2); the open
25
+ * activation reports the leased length.
26
+ */
27
+
28
+ interface Activation {
29
+ /** Input events to deliver, in history order. */
30
+ inputs: RunEventRecord[];
31
+ /** Command events the task appended; undefined for the open (new) activation. */
32
+ recorded: RunEventRecord[] | undefined;
33
+ /** The workflow clock during the activation. */
34
+ timeMs: number;
35
+ /** The completed task's id. */
36
+ taskId?: string;
37
+ /**
38
+ * The history length the task saw when it started (what `ctx.info.historyLength`
39
+ * and `continueAsNewSuggested` report during the activation, so replay sees
40
+ * the same values the original task did).
41
+ */
42
+ historyLength: number;
43
+ }
44
+ /**
45
+ * The activations of `history`. With `includeOpen`, the last entry is the
46
+ * open activation (the task being executed), even when it has no inputs.
47
+ */
48
+ declare function splitActivations(history: readonly RunEventRecord[], nowMs: number, includeOpen: boolean): Activation[];
49
+ /**
50
+ * Input events that no completed workflow task has delivered yet, in history
51
+ * order, including those that arrived while a task was running (rule 3). The
52
+ * service schedules a new workflow task when this is not empty after a task
53
+ * completes or an input is appended.
54
+ */
55
+ declare function unseenInputs(history: readonly RunEventRecord[]): RunEventRecord[];
56
+
57
+ export { type Activation as A, splitActivations as s, unseenInputs as u };
@@ -0,0 +1,39 @@
1
+ import { P as Position, J as JsonObject, d as ConnectionRef, i as WorkflowAuthoring, b as WorkflowDefinition } from './types-BGoPzgcl.cjs';
2
+ import { T as Tier1Recording, x as Tier1Workflow } from './workflow-FXUkpIBc.cjs';
3
+
4
+ declare const LAYOUT_FORMAT = "fluidgrids.layout";
5
+ declare const LAYOUT_FORMAT_VERSION = 1;
6
+ /**
7
+ * The layout sidecar (`workflows/<key>.layout.json`): canvas positions, sticky
8
+ * notes and `meta.layout`, kept out of the code so it stays readable.
9
+ */
10
+ interface LayoutSidecar {
11
+ format: typeof LAYOUT_FORMAT;
12
+ formatVersion: typeof LAYOUT_FORMAT_VERSION;
13
+ positions: Record<string, Position>;
14
+ stickyNotes?: JsonObject[];
15
+ metaLayout?: JsonObject;
16
+ }
17
+ /** How connection names in code resolve to connection references. */
18
+ type ConnectionResolver = Readonly<Record<string, ConnectionRef>> | ((name: string) => ConnectionRef | undefined);
19
+ interface AssembleOptions {
20
+ layout?: LayoutSidecar;
21
+ connections?: ConnectionResolver;
22
+ /** TEST-only pinned data (`workflows/<key>.pinned.json`). */
23
+ pinnedData?: JsonObject;
24
+ /** Provenance; defaults to `{ mode: 'canvas', generator: 'sdk-ts', generatorVersion }`. */
25
+ authoring?: WorkflowAuthoring;
26
+ }
27
+ /** Connection names used in code that the target workspace does not have. */
28
+ declare class UnresolvedConnectionsError extends Error {
29
+ readonly names: string[];
30
+ constructor(names: string[]);
31
+ }
32
+ /** Validate a parsed layout sidecar. */
33
+ declare function parseLayoutSidecar(value: unknown): LayoutSidecar;
34
+ /** Assemble IR v2 from a recording. */
35
+ declare function assembleRecording(recording: Tier1Recording, options?: AssembleOptions): WorkflowDefinition;
36
+ /** Lower a tier-1 workflow to IR v2 (spec 10 §1 `toIR`). */
37
+ declare function toIR(workflow: Tier1Workflow, options?: AssembleOptions): WorkflowDefinition;
38
+
39
+ export { type AssembleOptions as A, type ConnectionResolver as C, LAYOUT_FORMAT as L, UnresolvedConnectionsError as U, LAYOUT_FORMAT_VERSION as a, type LayoutSidecar as b, assembleRecording as c, parseLayoutSidecar as p, toIR as t };
@@ -0,0 +1,39 @@
1
+ import { P as Position, J as JsonObject, d as ConnectionRef, i as WorkflowAuthoring, b as WorkflowDefinition } from './types-BGoPzgcl.js';
2
+ import { T as Tier1Recording, x as Tier1Workflow } from './workflow-Bpr3vnA_.js';
3
+
4
+ declare const LAYOUT_FORMAT = "fluidgrids.layout";
5
+ declare const LAYOUT_FORMAT_VERSION = 1;
6
+ /**
7
+ * The layout sidecar (`workflows/<key>.layout.json`): canvas positions, sticky
8
+ * notes and `meta.layout`, kept out of the code so it stays readable.
9
+ */
10
+ interface LayoutSidecar {
11
+ format: typeof LAYOUT_FORMAT;
12
+ formatVersion: typeof LAYOUT_FORMAT_VERSION;
13
+ positions: Record<string, Position>;
14
+ stickyNotes?: JsonObject[];
15
+ metaLayout?: JsonObject;
16
+ }
17
+ /** How connection names in code resolve to connection references. */
18
+ type ConnectionResolver = Readonly<Record<string, ConnectionRef>> | ((name: string) => ConnectionRef | undefined);
19
+ interface AssembleOptions {
20
+ layout?: LayoutSidecar;
21
+ connections?: ConnectionResolver;
22
+ /** TEST-only pinned data (`workflows/<key>.pinned.json`). */
23
+ pinnedData?: JsonObject;
24
+ /** Provenance; defaults to `{ mode: 'canvas', generator: 'sdk-ts', generatorVersion }`. */
25
+ authoring?: WorkflowAuthoring;
26
+ }
27
+ /** Connection names used in code that the target workspace does not have. */
28
+ declare class UnresolvedConnectionsError extends Error {
29
+ readonly names: string[];
30
+ constructor(names: string[]);
31
+ }
32
+ /** Validate a parsed layout sidecar. */
33
+ declare function parseLayoutSidecar(value: unknown): LayoutSidecar;
34
+ /** Assemble IR v2 from a recording. */
35
+ declare function assembleRecording(recording: Tier1Recording, options?: AssembleOptions): WorkflowDefinition;
36
+ /** Lower a tier-1 workflow to IR v2 (spec 10 §1 `toIR`). */
37
+ declare function toIR(workflow: Tier1Workflow, options?: AssembleOptions): WorkflowDefinition;
38
+
39
+ export { type AssembleOptions as A, type ConnectionResolver as C, LAYOUT_FORMAT as L, UnresolvedConnectionsError as U, LAYOUT_FORMAT_VERSION as a, type LayoutSidecar as b, assembleRecording as c, parseLayoutSidecar as p, toIR as t };
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+
3
+ // src/history/allowlist.ts
4
+ var ACTIVITY_INTERNAL_ALLOWLIST = /* @__PURE__ */ new Set([
5
+ "internal:http",
6
+ "internal:datatable",
7
+ "internal:set",
8
+ "internal:code"
9
+ ]);
10
+ var ACTIVITY_NODE_DENYLIST = /* @__PURE__ */ new Set([
11
+ "internal:end",
12
+ "internal:if",
13
+ "internal:switch",
14
+ "internal:loop",
15
+ "internal:merge",
16
+ "internal:wait",
17
+ "internal:subworkflow",
18
+ "internal:code-workflow",
19
+ "internal:wait-for-signal",
20
+ "internal:send-and-wait",
21
+ "internal:respond-to-webhook",
22
+ "internal:failure-triage"
23
+ ]);
24
+ var ACTIVITY_NODE_ALLOWLIST = {
25
+ internal: ACTIVITY_INTERNAL_ALLOWLIST,
26
+ refused: ACTIVITY_NODE_DENYLIST,
27
+ /** Non-built-in node types (store and registry nodes) are allowed. */
28
+ external: "allowed"
29
+ };
30
+ var LEGACY_SHORT_NAMES = /* @__PURE__ */ new Set([
31
+ "if",
32
+ "switch",
33
+ "loop",
34
+ "merge",
35
+ "set",
36
+ "filter",
37
+ "aggregate",
38
+ "wait",
39
+ "noop",
40
+ "code",
41
+ "http",
42
+ "transform",
43
+ "validate",
44
+ "log",
45
+ "error",
46
+ "subworkflow",
47
+ "end",
48
+ "trigger",
49
+ "webhook",
50
+ "schedule",
51
+ "manual",
52
+ "api"
53
+ ]);
54
+ function canonicalType(nodeType) {
55
+ const lower = nodeType.trim().toLowerCase();
56
+ return LEGACY_SHORT_NAMES.has(lower) ? `internal:${lower}` : lower;
57
+ }
58
+ function isActivityNodeTypeAllowed(nodeType) {
59
+ if (typeof nodeType !== "string" || nodeType.trim() === "") return false;
60
+ const type = canonicalType(nodeType);
61
+ if (!type.startsWith("internal:")) return true;
62
+ return ACTIVITY_INTERNAL_ALLOWLIST.has(type);
63
+ }
64
+ var ActivityNodeTypeNotAllowedError = class extends Error {
65
+ constructor(nodeType) {
66
+ super(
67
+ `${nodeType} cannot run as a durable activity: only store and registry nodes and internal:http, internal:datatable, internal:set and internal:code can`
68
+ );
69
+ this.nodeType = nodeType;
70
+ this.name = "ActivityNodeTypeNotAllowedError";
71
+ }
72
+ nodeType;
73
+ code = "durable.activityTypeNotAllowed";
74
+ errorType = "VALIDATION";
75
+ nonRetryable = true;
76
+ };
77
+ function assertActivityNodeType(nodeType) {
78
+ if (!isActivityNodeTypeAllowed(nodeType)) throw new ActivityNodeTypeNotAllowedError(nodeType);
79
+ }
80
+
81
+ exports.ACTIVITY_INTERNAL_ALLOWLIST = ACTIVITY_INTERNAL_ALLOWLIST;
82
+ exports.ACTIVITY_NODE_ALLOWLIST = ACTIVITY_NODE_ALLOWLIST;
83
+ exports.ACTIVITY_NODE_DENYLIST = ACTIVITY_NODE_DENYLIST;
84
+ exports.ActivityNodeTypeNotAllowedError = ActivityNodeTypeNotAllowedError;
85
+ exports.assertActivityNodeType = assertActivityNodeType;
86
+ exports.isActivityNodeTypeAllowed = isActivityNodeTypeAllowed;