@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.
- package/README.md +144 -13
- package/dist/activation-BBh1p82z.d.cts +57 -0
- package/dist/activation-DnhG7SmF.d.ts +57 -0
- package/dist/assemble-BLqQ8lSu.d.cts +39 -0
- package/dist/assemble-DWgaXHd-.d.ts +39 -0
- package/dist/chunk-2J6TF7YI.cjs +86 -0
- package/dist/chunk-4LYSEANA.js +199 -0
- package/dist/chunk-623FVQSN.cjs +251 -0
- package/dist/{chunk-W32BSO3M.js → chunk-6FSHT5IL.js} +2 -2
- package/dist/{chunk-E3APHA6O.cjs → chunk-7AAZGVKD.js} +1 -64
- package/dist/chunk-ABFZWYHT.js +612 -0
- package/dist/{chunk-6U446SKY.js → chunk-BD7ZPG53.js} +2 -2
- package/dist/chunk-BO6OVILK.js +79 -0
- package/dist/chunk-C5OLWZ42.js +245 -0
- package/dist/{chunk-OSPTJCDI.cjs → chunk-CPDK3K5N.cjs} +6 -6
- package/dist/chunk-DHTLALBL.cjs +80 -0
- package/dist/chunk-DJYMDLV5.js +227 -0
- package/dist/{chunk-IHKXLG6I.js → chunk-E2VK7J2C.js} +1 -1
- package/dist/{chunk-3M34AUIY.cjs → chunk-FEW3JBYA.cjs} +3 -3
- package/dist/chunk-FMSW2HDF.cjs +267 -0
- package/dist/chunk-FR7KYWRO.cjs +41 -0
- package/dist/{chunk-7YR7DI7B.cjs → chunk-GBQ33SIZ.cjs} +44 -44
- package/dist/{chunk-6AIAC2FP.cjs → chunk-GVTZMA5C.cjs} +4 -4
- package/dist/{chunk-EEAIM35V.js → chunk-HWJZVOG7.js} +64 -23
- package/dist/chunk-I5LR3D2K.js +869 -0
- package/dist/chunk-IISBRUWJ.js +13 -0
- package/dist/chunk-JD2VQIDZ.cjs +15 -0
- package/dist/{chunk-UB2BHU2M.js → chunk-JZ4TOOLN.js} +1 -1
- package/dist/chunk-L24YIZY7.cjs +207 -0
- package/dist/chunk-LNUQE6D6.js +39 -0
- package/dist/chunk-M4BBAERY.cjs +1635 -0
- package/dist/chunk-N23SKIG4.js +73 -0
- package/dist/{chunk-TWOYB4Y7.cjs → chunk-NEIUQASE.cjs} +13 -13
- package/dist/chunk-P6TA5EZT.cjs +634 -0
- package/dist/chunk-PMCM2OH4.js +46 -0
- package/dist/chunk-PZCVF76Q.cjs +868 -0
- package/dist/{chunk-6BEW4QD6.js → chunk-QAN5MZEM.js} +1 -1
- package/dist/{chunk-CSYBNN3V.cjs → chunk-QKZL3ONM.cjs} +2 -2
- package/dist/chunk-QQ6ULXHI.cjs +885 -0
- package/dist/chunk-SAENRNBJ.cjs +52 -0
- package/dist/{chunk-TQF7MYEN.cjs → chunk-SAOBPYYJ.cjs} +134 -96
- package/dist/{chunk-XBIID5DY.js → chunk-SMCANK3F.js} +5 -4
- package/dist/{chunk-UWQWD7BP.cjs → chunk-SX2PNKKT.cjs} +18 -17
- package/dist/{chunk-UJNHTS6D.js → chunk-TBDXTV22.cjs} +8 -45
- package/dist/chunk-UK3WM7YZ.cjs +75 -0
- package/dist/chunk-VOKMAAJP.js +848 -0
- package/dist/chunk-VRHHBPT6.js +1622 -0
- package/dist/{chunk-5AW745OG.js → chunk-XHXIDVEC.js} +1 -1
- package/dist/chunk-XKJA7KX3.js +72 -0
- package/dist/chunk-Z2Y65YOY.cjs +7 -0
- package/dist/codegen/index.cjs +9 -7
- package/dist/codegen/index.d.cts +2 -2
- package/dist/codegen/index.d.ts +2 -2
- package/dist/codegen/index.js +6 -5
- package/dist/file-BOoHZgoQ.d.cts +1557 -0
- package/dist/file-yuPCFU5b.d.ts +1557 -0
- package/dist/history/index.cjs +287 -0
- package/dist/history/index.d.cts +157 -0
- package/dist/history/index.d.ts +157 -0
- package/dist/history/index.js +5 -0
- package/dist/index.cjs +134 -36
- package/dist/index.d.cts +137 -9
- package/dist/index.d.ts +137 -9
- package/dist/index.js +8 -3
- package/dist/ir/index.cjs +152 -158
- package/dist/ir/index.d.cts +14 -4
- package/dist/ir/index.d.ts +14 -4
- package/dist/ir/index.js +41 -50
- package/dist/lower-BzmUz0sq.d.cts +129 -0
- package/dist/lower-_-iT4HYs.d.ts +129 -0
- package/dist/lower-types-DQNBfWQn.d.cts +37 -0
- package/dist/lower-types-DQNBfWQn.d.ts +37 -0
- package/dist/{manifest-Duf-gHdO.d.cts → manifest-0OLw0i7k.d.cts} +1 -1
- package/dist/{manifest-Bp9ZJlav.d.ts → manifest-C8N6HDYl.d.ts} +1 -1
- package/dist/node-evaluator-4MSDKWOG.cjs +22 -0
- package/dist/node-evaluator-SX4LEYQC.js +12 -0
- package/dist/quickjs/index.cjs +236 -12
- package/dist/quickjs/index.d.cts +46 -5
- package/dist/quickjs/index.d.ts +46 -5
- package/dist/quickjs/index.js +230 -9
- package/dist/roundtrip/index.cjs +333 -131
- package/dist/roundtrip/index.d.cts +39 -7
- package/dist/roundtrip/index.d.ts +39 -7
- package/dist/roundtrip/index.js +185 -13
- package/dist/runtime/index.cjs +90 -0
- package/dist/runtime/index.d.cts +297 -0
- package/dist/runtime/index.d.ts +297 -0
- package/dist/runtime/index.js +8 -0
- package/dist/testing/index.cjs +1057 -0
- package/dist/testing/index.d.cts +305 -0
- package/dist/testing/index.d.ts +305 -0
- package/dist/testing/index.js +1037 -0
- package/dist/{types-Dr2Fn1Ax.d.ts → types-BGoPzgcl.d.cts} +1 -1
- package/dist/{types-Dr2Fn1Ax.d.cts → types-BGoPzgcl.d.ts} +1 -1
- package/dist/types-CpBBjaGX.d.cts +380 -0
- package/dist/types-DV-HX_Gt.d.ts +380 -0
- package/dist/{assemble-CrpUyd_3.d.ts → workflow-Bpr3vnA_.d.ts} +40 -160
- package/dist/workflow-C2a05WUJ.d.ts +444 -0
- package/dist/{assemble-DEf3znGD.d.cts → workflow-FXUkpIBc.d.cts} +40 -160
- package/dist/workflow-J-yYixny.d.cts +444 -0
- package/fixtures/README.md +128 -0
- package/fixtures/histories/activity-failure-fails-run.history.json +163 -0
- package/fixtures/histories/activity-retry-then-success.history.json +147 -0
- package/fixtures/histories/cancel-activity-abandon.history.json +126 -0
- package/fixtures/histories/cancel-activity-compensate.history.json +307 -0
- package/fixtures/histories/cancel-concurrent-non-cancellable.history.json +234 -0
- package/fixtures/histories/cancel-concurrent-scope-timeout.history.json +184 -0
- package/fixtures/histories/cancel-condition-after-cancel.history.json +200 -0
- package/fixtures/histories/cancel-during-timer.history.json +123 -0
- package/fixtures/histories/cancel-saga-compensations.history.json +325 -0
- package/fixtures/histories/cancel-scope-timeout.history.json +148 -0
- package/fixtures/histories/child-abandon-outlives-parent.history.json +191 -0
- package/fixtures/histories/child-cancel-default.history.json +234 -0
- package/fixtures/histories/child-cancel-request.history.json +206 -0
- package/fixtures/histories/child-durable-child-side.history.json +124 -0
- package/fixtures/histories/child-durable-result.history.json +163 -0
- package/fixtures/histories/child-failure-caught.history.json +166 -0
- package/fixtures/histories/child-graph-by-key.history.json +138 -0
- package/fixtures/histories/child-terminated-by-parent-close.history.json +85 -0
- package/fixtures/histories/continue-as-new-first-run.history.json +146 -0
- package/fixtures/histories/continue-as-new-history-limit.history.json +489 -0
- package/fixtures/histories/continue-as-new-last-run.history.json +147 -0
- package/fixtures/histories/continue-as-new-suggested.history.json +352 -0
- package/fixtures/histories/details-and-context.history.json +176 -0
- package/fixtures/histories/deterministic-values.history.json +128 -0
- package/fixtures/histories/engine-builtins.history.json +172 -0
- package/fixtures/histories/gather-activities.history.json +291 -0
- package/fixtures/histories/gather-all-settled.history.json +219 -0
- package/fixtures/histories/gather-mixed.history.json +281 -0
- package/fixtures/histories/input-during-running-task.history.json +111 -0
- package/fixtures/histories/nondeterminism-reordered.history.json +218 -0
- package/fixtures/histories/patched-deprecated.history.json +271 -0
- package/fixtures/histories/patched-new-run.history.json +270 -0
- package/fixtures/histories/patched-old-history.history.json +190 -0
- package/fixtures/histories/race-activity-wins.history.json +152 -0
- package/fixtures/histories/race-signal-beats-child.history.json +161 -0
- package/fixtures/histories/race-timeout-wins.history.json +118 -0
- package/fixtures/histories/side-effect-recorded.history.json +153 -0
- package/fixtures/histories/signal-buffered-at-start.history.json +157 -0
- package/fixtures/histories/signal-default-handler.history.json +187 -0
- package/fixtures/histories/signal-handler.history.json +191 -0
- package/fixtures/histories/signal-parent-child-side.history.json +132 -0
- package/fixtures/histories/signal-parent.history.json +202 -0
- package/fixtures/histories/signal-run-delivered.history.json +128 -0
- package/fixtures/histories/signal-run-undelivered.history.json +134 -0
- package/fixtures/histories/signal-wait.history.json +111 -0
- package/fixtures/histories/task-failure-code-error.history.json +137 -0
- package/fixtures/histories/task-failure-then-transient-attempt.history.json +140 -0
- package/fixtures/histories/timer-condition-met.history.json +201 -0
- package/fixtures/histories/timer-condition-timeout.history.json +118 -0
- package/fixtures/histories/timer-sleep-once.history.json +118 -0
- package/fixtures/histories/timer-sleep-sequence.history.json +210 -0
- package/fixtures/histories/update-accepted-completed.history.json +149 -0
- package/fixtures/histories/update-with-start-rejected-validation.history.json +91 -0
- package/package.json +44 -5
- package/schema/history-file.schema.json +2544 -0
- package/schema/ir-v2.schema.json +16 -0
- package/schema/run-event.schema.json +2374 -0
- package/schema/workflow-command.schema.json +1095 -0
- package/schema/workflow-task.schema.json +3560 -0
- package/dist/chunk-MBBKGD7K.js +0 -440
- package/dist/chunk-Y7NWR3WH.cjs +0 -450
- package/dist/lower-CXggJTIo.d.ts +0 -67
- package/dist/lower-DAzvsJCo.d.cts +0 -67
- package/dist/node-evaluator-CH3XCJND.cjs +0 -17
- 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
|
|
4
|
-
|
|
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
|
|
25
|
-
|
|
|
26
|
-
| `@fluidgrids/workflows`
|
|
27
|
-
| `@fluidgrids/workflows/
|
|
28
|
-
| `@fluidgrids/workflows/
|
|
29
|
-
| `@fluidgrids/workflows/
|
|
30
|
-
| `@fluidgrids/workflows/
|
|
31
|
-
| `@fluidgrids/workflows/
|
|
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;
|