@lunora/workflow 1.0.0-alpha.1 → 1.0.0-alpha.10

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.
@@ -264,9 +264,86 @@ interface RunStepOptions {
264
264
  */
265
265
  type WorkflowRunStepFunction = <A extends StepArgsValidator, Result>(step: StepDefinition<A, Result>, args: InferStepArgs<A>, options?: RunStepOptions) => Promise<Result>;
266
266
  /**
267
+ * One branch of a {@link WorkflowParallelFunction} fan-out — a declared child
268
+ * workflow (referenced by its `lunora/workflows.ts` export name) plus the params
269
+ * it is created with. The phantom `Output` carries the child's result type into
270
+ * the `ctx.parallel(...)` result tuple. Build one with the `branch(...)` helper.
271
+ */
272
+ interface WorkflowBranch<Output = unknown> {
273
+ /** Phantom marker for the branch output type — never present at runtime. */
274
+ readonly __output?: Output;
275
+ /**
276
+ * Optional group-saga compensation (plan 075 Phase 3): the `lunora/workflows.ts`
277
+ * export name of a workflow to run if a **sibling** branch in the same
278
+ * `ctx.parallel(...)` group fails **after** this branch has already completed.
279
+ * It is spawned fire-and-forget (a durable, replay-safe idempotent create) with
280
+ * {@link BranchCompensationParams} as its `ctx.params`. Omit for no
281
+ * compensation — a group where no branch sets this behaves exactly as a plain
282
+ * fan-out (fail-fast, no rollback).
283
+ */
284
+ readonly compensateWith?: string;
285
+ /** Optional explicit child instance id (defaults to a deterministic parent-derived id). */
286
+ readonly id?: string;
287
+ /** The params the child instance is created with — surfaced as the child's `ctx.params`. */
288
+ readonly params?: Record<string, unknown>;
289
+ /** Optional wait timeout for this branch (the parent's `waitForEvent` timeout). */
290
+ readonly timeout?: number | string;
291
+ /** The `lunora/workflows.ts` export name of the child workflow to run. */
292
+ readonly workflow: string;
293
+ }
294
+ /**
295
+ * The `ctx.params` a group-saga compensation workflow (a branch's
296
+ * {@link WorkflowBranch.compensateWith}) receives when a sibling's failure rolls
297
+ * back the group. Everything is plain-serialisable — the compensation is an
298
+ * ordinary declared workflow, so it can `ctx.runStep(...)` its own undo logic.
299
+ */
300
+ interface BranchCompensationParams {
301
+ /** Index signature: this is a workflow `params` bag, so it is a valid `Record&lt;string, unknown>` payload. */
302
+ [key: string]: unknown;
303
+ /** The export name of the completed branch being compensated. */
304
+ branch: string;
305
+ /** The serialised error of the sibling branch whose failure triggered the group rollback. */
306
+ error: {
307
+ message: string;
308
+ name: string;
309
+ };
310
+ /** Declaration-order index of the completed branch being compensated. */
311
+ index: number;
312
+ /** The completed branch's output value — what it returned before the group failed. */
313
+ output?: unknown;
314
+ }
315
+ /** Map a tuple of {@link WorkflowBranch}es to the tuple of their output types, preserving order. */
316
+ type WorkflowBranchOutputs<B extends ReadonlyArray<WorkflowBranch>> = { -readonly [K in keyof B]: B[K] extends WorkflowBranch<infer Output> ? Output : never };
317
+ /**
318
+ * Run branches as isolated child workflow instances and resolve with their
319
+ * outputs in declaration order. Each branch gets its own Durable Object (own
320
+ * memory / CPU / retry budget); the parent hibernates while they execute. Rejects
321
+ * (non-retryable) on the first branch that fails.
322
+ *
323
+ * ```ts
324
+ * const [tags, thumb] = await ctx.parallel([
325
+ * branch("imageTag", { key }),
326
+ * branch("thumbnail", { key }),
327
+ * ]);
328
+ * ```
329
+ */
330
+ type WorkflowParallelFunction = <const B extends ReadonlyArray<WorkflowBranch>>(branches: B) => Promise<WorkflowBranchOutputs<B>>;
331
+ /** Per-call options for {@link WorkflowSpawnFunction}. */
332
+ interface WorkflowSpawnOptions {
333
+ /** Explicit child instance id (defaults to a deterministic parent-derived id). */
334
+ id?: string;
335
+ }
336
+ /**
337
+ * Fire-and-forget start of a declared child workflow from inside a workflow body
338
+ * — replay-safe (idempotent create), returns a live handle to the child. Use
339
+ * {@link WorkflowParallelFunction} instead when you need to await results.
340
+ */
341
+ type WorkflowSpawnFunction = (workflow: string, params?: Record<string, unknown>, options?: WorkflowSpawnOptions) => Promise<WorkflowInstanceLike>;
342
+ /**
267
343
  * The context object passed to a `defineWorkflow` handler. Bundles the native
268
344
  * Cloudflare durability primitives (`step`, `event`) with the Lunora runner
269
- * (`run`), the reusable-step runner (`runStep`), the Worker `env`, and a logger.
345
+ * (`run`), the reusable-step runner (`runStep`), the fan-out primitives
346
+ * (`parallel` / `spawn`), the Worker `env`, and a logger.
270
347
  */
271
348
  interface WorkflowRunContext<Params = Record<string, unknown>> {
272
349
  /** The Worker environment bindings. */
@@ -275,12 +352,16 @@ interface WorkflowRunContext<Params = Record<string, unknown>> {
275
352
  readonly event: WorkflowEventLike<Params>;
276
353
  /** Structured logger surfaced in `wrangler tail` / Studio logs. */
277
354
  readonly log: WorkflowLogger;
355
+ /** Run branches as isolated child workflow instances and await their outputs (declaration-ordered tuple). */
356
+ readonly parallel: WorkflowParallelFunction;
278
357
  /** Convenience alias for `event.payload`. */
279
358
  readonly params: Readonly<Params>;
280
359
  /** Invoke a Lunora function; wrap in `step.do(...)` for durability. */
281
360
  readonly run: WorkflowRunFunction;
282
361
  /** Run a reusable, schema-validated {@link StepDefinition} as a durable step. */
283
362
  readonly runStep: WorkflowRunStepFunction;
363
+ /** Fire-and-forget start of a declared child workflow (replay-safe; returns a live handle). */
364
+ readonly spawn: WorkflowSpawnFunction;
284
365
  /** The native Cloudflare Workflows durable-step API. */
285
366
  readonly step: WorkflowStepLike;
286
367
  }
@@ -341,4 +422,4 @@ interface LunoraWorkflowsOptions {
341
422
  */
342
423
  bindings: Record<string, WorkflowBindingLike>;
343
424
  }
344
- export { ArgsOf as A, WorkflowStepRollbackOptionsLike as B, FunctionReference as F, InferStepArgs as I, LunoraWorkflowsOptions as L, RunFunctionOptions as R, StepArgsValidator as S, WorkflowDefinition as W, Workflows as a, StepConfig as b, StepDefinition as c, WorkflowConfig as d, WorkflowInstanceStatus as e, WorkflowLogger as f, WorkflowEventLike as g, WorkflowStepLike as h, WorkflowRunContext as i, WorkflowRunFunction as j, WorkflowRunStepFunction as k, RunStepOptions as l, StepHandler as m, StepRollbackContext as n, StepRollbackHandler as o, StepRunContext as p, WorkflowBindingLike as q, WorkflowCreateOptions as r, WorkflowHandle as s, WorkflowHandler as t, WorkflowInstanceLike as u, WorkflowRollbackContextLike as v, WorkflowRollbackHandlerLike as w, WorkflowStatusResult as x, WorkflowStepConfigLike as y, WorkflowStepContextLike as z };
425
+ export { ArgsOf as A, BranchCompensationParams as B, WorkflowSpawnFunction as C, WorkflowSpawnOptions as D, WorkflowStatusResult as E, FunctionReference as F, WorkflowStepConfigLike as G, WorkflowStepContextLike as H, InferStepArgs as I, WorkflowStepRollbackOptionsLike as J, LunoraWorkflowsOptions as L, RunFunctionOptions as R, StepArgsValidator as S, WorkflowDefinition as W, Workflows as a, StepConfig as b, StepDefinition as c, WorkflowConfig as d, WorkflowBranch as e, WorkflowInstanceStatus as f, WorkflowEventLike as g, WorkflowStepLike as h, WorkflowRunContext as i, WorkflowLogger as j, WorkflowRunFunction as k, WorkflowRunStepFunction as l, RunStepOptions as m, StepHandler as n, StepRollbackContext as o, StepRollbackHandler as p, StepRunContext as q, WorkflowBindingLike as r, WorkflowBranchOutputs as s, WorkflowCreateOptions as t, WorkflowHandle as u, WorkflowHandler as v, WorkflowInstanceLike as w, WorkflowParallelFunction as x, WorkflowRollbackContextLike as y, WorkflowRollbackHandlerLike as z };
@@ -264,9 +264,86 @@ interface RunStepOptions {
264
264
  */
265
265
  type WorkflowRunStepFunction = <A extends StepArgsValidator, Result>(step: StepDefinition<A, Result>, args: InferStepArgs<A>, options?: RunStepOptions) => Promise<Result>;
266
266
  /**
267
+ * One branch of a {@link WorkflowParallelFunction} fan-out — a declared child
268
+ * workflow (referenced by its `lunora/workflows.ts` export name) plus the params
269
+ * it is created with. The phantom `Output` carries the child's result type into
270
+ * the `ctx.parallel(...)` result tuple. Build one with the `branch(...)` helper.
271
+ */
272
+ interface WorkflowBranch<Output = unknown> {
273
+ /** Phantom marker for the branch output type — never present at runtime. */
274
+ readonly __output?: Output;
275
+ /**
276
+ * Optional group-saga compensation (plan 075 Phase 3): the `lunora/workflows.ts`
277
+ * export name of a workflow to run if a **sibling** branch in the same
278
+ * `ctx.parallel(...)` group fails **after** this branch has already completed.
279
+ * It is spawned fire-and-forget (a durable, replay-safe idempotent create) with
280
+ * {@link BranchCompensationParams} as its `ctx.params`. Omit for no
281
+ * compensation — a group where no branch sets this behaves exactly as a plain
282
+ * fan-out (fail-fast, no rollback).
283
+ */
284
+ readonly compensateWith?: string;
285
+ /** Optional explicit child instance id (defaults to a deterministic parent-derived id). */
286
+ readonly id?: string;
287
+ /** The params the child instance is created with — surfaced as the child's `ctx.params`. */
288
+ readonly params?: Record<string, unknown>;
289
+ /** Optional wait timeout for this branch (the parent's `waitForEvent` timeout). */
290
+ readonly timeout?: number | string;
291
+ /** The `lunora/workflows.ts` export name of the child workflow to run. */
292
+ readonly workflow: string;
293
+ }
294
+ /**
295
+ * The `ctx.params` a group-saga compensation workflow (a branch's
296
+ * {@link WorkflowBranch.compensateWith}) receives when a sibling's failure rolls
297
+ * back the group. Everything is plain-serialisable — the compensation is an
298
+ * ordinary declared workflow, so it can `ctx.runStep(...)` its own undo logic.
299
+ */
300
+ interface BranchCompensationParams {
301
+ /** Index signature: this is a workflow `params` bag, so it is a valid `Record&lt;string, unknown>` payload. */
302
+ [key: string]: unknown;
303
+ /** The export name of the completed branch being compensated. */
304
+ branch: string;
305
+ /** The serialised error of the sibling branch whose failure triggered the group rollback. */
306
+ error: {
307
+ message: string;
308
+ name: string;
309
+ };
310
+ /** Declaration-order index of the completed branch being compensated. */
311
+ index: number;
312
+ /** The completed branch's output value — what it returned before the group failed. */
313
+ output?: unknown;
314
+ }
315
+ /** Map a tuple of {@link WorkflowBranch}es to the tuple of their output types, preserving order. */
316
+ type WorkflowBranchOutputs<B extends ReadonlyArray<WorkflowBranch>> = { -readonly [K in keyof B]: B[K] extends WorkflowBranch<infer Output> ? Output : never };
317
+ /**
318
+ * Run branches as isolated child workflow instances and resolve with their
319
+ * outputs in declaration order. Each branch gets its own Durable Object (own
320
+ * memory / CPU / retry budget); the parent hibernates while they execute. Rejects
321
+ * (non-retryable) on the first branch that fails.
322
+ *
323
+ * ```ts
324
+ * const [tags, thumb] = await ctx.parallel([
325
+ * branch("imageTag", { key }),
326
+ * branch("thumbnail", { key }),
327
+ * ]);
328
+ * ```
329
+ */
330
+ type WorkflowParallelFunction = <const B extends ReadonlyArray<WorkflowBranch>>(branches: B) => Promise<WorkflowBranchOutputs<B>>;
331
+ /** Per-call options for {@link WorkflowSpawnFunction}. */
332
+ interface WorkflowSpawnOptions {
333
+ /** Explicit child instance id (defaults to a deterministic parent-derived id). */
334
+ id?: string;
335
+ }
336
+ /**
337
+ * Fire-and-forget start of a declared child workflow from inside a workflow body
338
+ * — replay-safe (idempotent create), returns a live handle to the child. Use
339
+ * {@link WorkflowParallelFunction} instead when you need to await results.
340
+ */
341
+ type WorkflowSpawnFunction = (workflow: string, params?: Record<string, unknown>, options?: WorkflowSpawnOptions) => Promise<WorkflowInstanceLike>;
342
+ /**
267
343
  * The context object passed to a `defineWorkflow` handler. Bundles the native
268
344
  * Cloudflare durability primitives (`step`, `event`) with the Lunora runner
269
- * (`run`), the reusable-step runner (`runStep`), the Worker `env`, and a logger.
345
+ * (`run`), the reusable-step runner (`runStep`), the fan-out primitives
346
+ * (`parallel` / `spawn`), the Worker `env`, and a logger.
270
347
  */
271
348
  interface WorkflowRunContext<Params = Record<string, unknown>> {
272
349
  /** The Worker environment bindings. */
@@ -275,12 +352,16 @@ interface WorkflowRunContext<Params = Record<string, unknown>> {
275
352
  readonly event: WorkflowEventLike<Params>;
276
353
  /** Structured logger surfaced in `wrangler tail` / Studio logs. */
277
354
  readonly log: WorkflowLogger;
355
+ /** Run branches as isolated child workflow instances and await their outputs (declaration-ordered tuple). */
356
+ readonly parallel: WorkflowParallelFunction;
278
357
  /** Convenience alias for `event.payload`. */
279
358
  readonly params: Readonly<Params>;
280
359
  /** Invoke a Lunora function; wrap in `step.do(...)` for durability. */
281
360
  readonly run: WorkflowRunFunction;
282
361
  /** Run a reusable, schema-validated {@link StepDefinition} as a durable step. */
283
362
  readonly runStep: WorkflowRunStepFunction;
363
+ /** Fire-and-forget start of a declared child workflow (replay-safe; returns a live handle). */
364
+ readonly spawn: WorkflowSpawnFunction;
284
365
  /** The native Cloudflare Workflows durable-step API. */
285
366
  readonly step: WorkflowStepLike;
286
367
  }
@@ -341,4 +422,4 @@ interface LunoraWorkflowsOptions {
341
422
  */
342
423
  bindings: Record<string, WorkflowBindingLike>;
343
424
  }
344
- export { ArgsOf as A, WorkflowStepRollbackOptionsLike as B, FunctionReference as F, InferStepArgs as I, LunoraWorkflowsOptions as L, RunFunctionOptions as R, StepArgsValidator as S, WorkflowDefinition as W, Workflows as a, StepConfig as b, StepDefinition as c, WorkflowConfig as d, WorkflowInstanceStatus as e, WorkflowLogger as f, WorkflowEventLike as g, WorkflowStepLike as h, WorkflowRunContext as i, WorkflowRunFunction as j, WorkflowRunStepFunction as k, RunStepOptions as l, StepHandler as m, StepRollbackContext as n, StepRollbackHandler as o, StepRunContext as p, WorkflowBindingLike as q, WorkflowCreateOptions as r, WorkflowHandle as s, WorkflowHandler as t, WorkflowInstanceLike as u, WorkflowRollbackContextLike as v, WorkflowRollbackHandlerLike as w, WorkflowStatusResult as x, WorkflowStepConfigLike as y, WorkflowStepContextLike as z };
425
+ export { ArgsOf as A, BranchCompensationParams as B, WorkflowSpawnFunction as C, WorkflowSpawnOptions as D, WorkflowStatusResult as E, FunctionReference as F, WorkflowStepConfigLike as G, WorkflowStepContextLike as H, InferStepArgs as I, WorkflowStepRollbackOptionsLike as J, LunoraWorkflowsOptions as L, RunFunctionOptions as R, StepArgsValidator as S, WorkflowDefinition as W, Workflows as a, StepConfig as b, StepDefinition as c, WorkflowConfig as d, WorkflowBranch as e, WorkflowInstanceStatus as f, WorkflowEventLike as g, WorkflowStepLike as h, WorkflowRunContext as i, WorkflowLogger as j, WorkflowRunFunction as k, WorkflowRunStepFunction as l, RunStepOptions as m, StepHandler as n, StepRollbackContext as o, StepRollbackHandler as p, StepRunContext as q, WorkflowBindingLike as r, WorkflowBranchOutputs as s, WorkflowCreateOptions as t, WorkflowHandle as u, WorkflowHandler as v, WorkflowInstanceLike as w, WorkflowParallelFunction as x, WorkflowRollbackContextLike as y, WorkflowRollbackHandlerLike as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/workflow",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.10",
4
4
  "description": "Durable workflows for Lunora: defineWorkflow over Cloudflare Workflows, generated WorkflowEntrypoint classes, and the ctx.workflows surface",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -23,7 +23,7 @@
23
23
  "directory": "packages/workflow"
24
24
  },
25
25
  "files": [
26
- "dist",
26
+ "./dist",
27
27
  "README.md",
28
28
  "LICENSE.md",
29
29
  "__assets__"
@@ -48,7 +48,8 @@
48
48
  "access": "public"
49
49
  },
50
50
  "dependencies": {
51
- "@lunora/values": "1.0.0-alpha.1"
51
+ "@lunora/errors": "1.0.0-alpha.5",
52
+ "@lunora/values": "1.0.0-alpha.8"
52
53
  },
53
54
  "engines": {
54
55
  "node": "^22.15.0 || >=24.11.0"
@@ -1,76 +0,0 @@
1
- import { createRunStep } from './createRunStep-BsK4LsUX.mjs';
2
-
3
- const trimTrailingSlashes = (value) => {
4
- let end = value.length;
5
- while (end > 0 && value[end - 1] === "/") {
6
- end -= 1;
7
- }
8
- return value.slice(0, end);
9
- };
10
- const createWorkflowRunner = (options) => {
11
- const globalFetch = globalThis.fetch;
12
- const fetchImpl = options.fetchImpl ?? (typeof globalFetch === "function" ? globalFetch.bind(globalThis) : void 0);
13
- return async (function_, args, runOptions = {}) => {
14
- if (typeof fetchImpl !== "function") {
15
- throw new TypeError("@lunora/workflow: no fetch implementation available — pass fetchImpl or run on a platform with global fetch");
16
- }
17
- const origin = options.env.LUNORA_ORIGIN_URL;
18
- if (typeof origin !== "string" || origin.length === 0) {
19
- throw new Error("@lunora/workflow: `LUNORA_ORIGIN_URL` must be set on the Worker env so a workflow can call back into Lunora functions");
20
- }
21
- const token = options.env.LUNORA_ADMIN_TOKEN;
22
- if (typeof token !== "string" || token.length === 0) {
23
- throw new Error("@lunora/workflow: `LUNORA_ADMIN_TOKEN` must be set on the Worker env to authenticate workflow function dispatch");
24
- }
25
- const url = `${trimTrailingSlashes(origin)}/_lunora/scheduler/dispatch`;
26
- const response = await fetchImpl(url, {
27
- body: JSON.stringify({ args: args ?? {}, functionPath: function_.__lunoraRef, shardKey: runOptions.shardKey }),
28
- headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
29
- method: "POST"
30
- });
31
- if (!response.ok) {
32
- throw new Error(`@lunora/workflow: function dispatch failed (${String(response.status)}): ${await response.text()}`);
33
- }
34
- const text = await response.text();
35
- if (text.length === 0) {
36
- return void 0;
37
- }
38
- try {
39
- return JSON.parse(text);
40
- } catch {
41
- return text;
42
- }
43
- };
44
- };
45
- const createWorkflowLogger = (exportName) => {
46
- const prefix = `[workflow:${exportName}]`;
47
- return {
48
- debug: (message, ...rest) => {
49
- console.debug(prefix, message, ...rest);
50
- },
51
- error: (message, ...rest) => {
52
- console.error(prefix, message, ...rest);
53
- },
54
- info: (message, ...rest) => {
55
- console.info(prefix, message, ...rest);
56
- },
57
- warn: (message, ...rest) => {
58
- console.warn(prefix, message, ...rest);
59
- }
60
- };
61
- };
62
- const createWorkflowRunContext = (options) => {
63
- const log = createWorkflowLogger(options.exportName);
64
- const run = createWorkflowRunner({ env: options.env, fetchImpl: options.fetchImpl });
65
- return {
66
- env: options.env,
67
- event: options.event,
68
- log,
69
- params: options.event.payload,
70
- run,
71
- runStep: createRunStep({ env: options.env, log, nonRetryableErrorClass: options.nonRetryableErrorClass, run, step: options.step }),
72
- step: options.step
73
- };
74
- };
75
-
76
- export { createWorkflowLogger, createWorkflowRunContext, createWorkflowRunner };
@@ -1,23 +0,0 @@
1
- const handleFor = (binding) => {
2
- return {
3
- create: async (options) => binding.create(options),
4
- createBatch: async (batch) => binding.createBatch(batch),
5
- get: async (id) => binding.get(id)
6
- };
7
- };
8
- const createWorkflows = (options) => {
9
- const bindings = options.bindings ?? {};
10
- return {
11
- get: (name) => {
12
- const binding = bindings[name];
13
- if (binding === void 0) {
14
- const known = Object.keys(bindings);
15
- const suffix = known.length === 0 ? "no workflows are declared" : `known workflows: ${known.join(", ")}`;
16
- throw new Error(`@lunora/workflow: no workflow named "${name}" (${suffix})`);
17
- }
18
- return handleFor(binding);
19
- }
20
- };
21
- };
22
-
23
- export { createWorkflows as default };