@lunora/workflow 1.0.0-alpha.3 → 1.0.0-alpha.5

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.
@@ -1,5 +1,5 @@
1
1
  import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from 'cloudflare:workers';
2
- import { W as WorkflowDefinition } from "../packem_shared/types.d-gNYxuUwu.mjs";
2
+ import { W as WorkflowDefinition } from "../packem_shared/types.d-C9W6rEWv.mjs";
3
3
  import '@lunora/values';
4
4
  /**
5
5
  * Base class for the generated `WorkflowEntrypoint` classes. Applies a
@@ -1,5 +1,5 @@
1
1
  import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from 'cloudflare:workers';
2
- import { W as WorkflowDefinition } from "../packem_shared/types.d-gNYxuUwu.js";
2
+ import { W as WorkflowDefinition } from "../packem_shared/types.d-C9W6rEWv.js";
3
3
  import '@lunora/values';
4
4
  /**
5
5
  * Base class for the generated `WorkflowEntrypoint` classes. Applies a
package/dist/do/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import { WorkflowEntrypoint } from 'cloudflare:workers';
2
2
  import { NonRetryableError } from 'cloudflare:workflows';
3
3
  import { convertNonRetryableError } from '../packem_shared/NonRetryableError-Dn2dTyBS.mjs';
4
- import { createWorkflowRunContext } from '../packem_shared/createWorkflowRunContext-DJj_y0fa.mjs';
4
+ import { stripBranchMarker, signalBranchParent, extractBranchMarker, errorOutcome, okOutcome } from '../packem_shared/MAX_BRANCHES-D6cQabJ1.mjs';
5
+ import { createWorkflowRunContext } from '../packem_shared/createWorkflowRunContext-CYI9SeI9.mjs';
5
6
 
6
7
  class LunoraWorkflow extends WorkflowEntrypoint {
7
8
  /** The `lunora/workflows.ts` export name, for log correlation. */
@@ -14,18 +15,29 @@ class LunoraWorkflow extends WorkflowEntrypoint {
14
15
  this.#lunoraName = exportName ?? "workflow";
15
16
  }
16
17
  async run(event, step) {
18
+ const nativeStep = step;
19
+ const marker = extractBranchMarker(event.payload);
20
+ const handlerEvent = marker ? { ...event, payload: stripBranchMarker(event.payload) } : event;
17
21
  const context = createWorkflowRunContext({
18
22
  env: this.env,
19
- event,
23
+ event: handlerEvent,
20
24
  exportName: this.#lunoraName,
21
25
  nonRetryableErrorClass: NonRetryableError,
22
- step
26
+ step: nativeStep
23
27
  });
28
+ let output;
24
29
  try {
25
- return await this.#definition.handler(context);
30
+ output = await this.#definition.handler(context);
26
31
  } catch (error) {
32
+ if (marker) {
33
+ await signalBranchParent({ env: this.env, step: nativeStep }, marker, errorOutcome(error));
34
+ }
27
35
  return convertNonRetryableError(error, NonRetryableError);
28
36
  }
37
+ if (marker) {
38
+ await signalBranchParent({ env: this.env, step: nativeStep }, marker, okOutcome(output));
39
+ }
40
+ return output;
29
41
  }
30
42
  }
31
43
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as Workflows, L as LunoraWorkflowsOptions, S as StepArgsValidator, b as StepConfig, c as StepDefinition, d as WorkflowConfig, W as WorkflowDefinition, e as WorkflowInstanceStatus, f as WorkflowEventLike, g as WorkflowStepLike, h as WorkflowRunContext, i as WorkflowLogger, j as WorkflowRunFunction, k as WorkflowRunStepFunction } from "./packem_shared/types.d-gNYxuUwu.mjs";
2
- export type { A as ArgsOf, F as FunctionReference, I as InferStepArgs, R as RunFunctionOptions, l as RunStepOptions, m as StepHandler, n as StepRollbackContext, o as StepRollbackHandler, p as StepRunContext, q as WorkflowBindingLike, r as WorkflowCreateOptions, s as WorkflowHandle, t as WorkflowHandler, u as WorkflowInstanceLike, v as WorkflowRollbackContextLike, w as WorkflowRollbackHandlerLike, x as WorkflowStatusResult, y as WorkflowStepConfigLike, z as WorkflowStepContextLike, B as WorkflowStepRollbackOptionsLike } from "./packem_shared/types.d-gNYxuUwu.mjs";
1
+ import { a as Workflows, L as LunoraWorkflowsOptions, S as StepArgsValidator, b as StepConfig, c as StepDefinition, d as WorkflowConfig, W as WorkflowDefinition, e as WorkflowBranch, f as WorkflowInstanceStatus, g as WorkflowEventLike, h as WorkflowStepLike, i as WorkflowRunContext, j as WorkflowLogger, k as WorkflowRunFunction, l as WorkflowRunStepFunction } from "./packem_shared/types.d-C9W6rEWv.mjs";
2
+ export type { A as ArgsOf, B as BranchCompensationParams, F as FunctionReference, I as InferStepArgs, R as RunFunctionOptions, m as RunStepOptions, n as StepHandler, o as StepRollbackContext, p as StepRollbackHandler, q as StepRunContext, r as WorkflowBindingLike, s as WorkflowBranchOutputs, t as WorkflowCreateOptions, u as WorkflowHandle, v as WorkflowHandler, w as WorkflowInstanceLike, x as WorkflowParallelFunction, y as WorkflowRollbackContextLike, z as WorkflowRollbackHandlerLike, C as WorkflowSpawnFunction, D as WorkflowSpawnOptions, E as WorkflowStatusResult, G as WorkflowStepConfigLike, H as WorkflowStepContextLike, J as WorkflowStepRollbackOptionsLike } from "./packem_shared/types.d-C9W6rEWv.mjs";
3
3
  import '@lunora/values';
4
4
  /** Wiring info for one declared workflow, emitted by codegen into the generated shard. */
5
5
  interface WorkflowBindingSpec {
@@ -131,6 +131,22 @@ declare const toNativeNonRetryableError: (error: NonRetryableError, NativeNonRet
131
131
  * Always throws — the `never` return lets callers `return convertNonRetryableError(...)`.
132
132
  */
133
133
  declare const convertNonRetryableError: (error: unknown, NativeNonRetryableError: NativeNonRetryableErrorConstructor | undefined) => never;
134
+ /** Hard cap on branches per `ctx.parallel` call — auto-scale, never silently spawn unbounded DOs. */
135
+ declare const MAX_BRANCHES = 100;
136
+ /** The params key the parent injects so a child knows to signal completion back. Stripped before the user handler sees `ctx.params`. */
137
+
138
+ /**
139
+ * Build a single fan-out branch: a declared child workflow referenced by its
140
+ * `lunora/workflows.ts` export name, plus the params it is created with. Pass the
141
+ * output type as the generic argument so `ctx.parallel(...)` infers the result
142
+ * tuple — e.g. `branch("imageTag", { key })` typed as `branch` of `{ tags }`.
143
+ */
144
+ declare const branch: <Output = unknown>(workflow: string, params?: Record<string, unknown>, options?: {
145
+ compensateWith?: string;
146
+ id?: string;
147
+ timeout?: number | string;
148
+ }) => WorkflowBranch<Output>;
149
+ /** Build the success outcome a completed branch reports to its parent. */
134
150
  /** The lifecycle mutations the REST API exposes via `PATCH .../instances/{id}`. */
135
151
  type WorkflowInstanceAction = "pause" | "resume" | "terminate";
136
152
  /** Configuration for a {@link WorkflowsRestClient}. */
@@ -248,4 +264,4 @@ interface RunStepDeps {
248
264
  * converted to the native one and any declared rollback forwarded to Cloudflare.
249
265
  */
250
266
  declare const createRunStep: (deps: RunStepDeps) => WorkflowRunStepFunction;
251
- export { type LunoraWorkflowsOptions, type NativeNonRetryableErrorConstructor, NonRetryableError, type StepArgsValidator, type StepConfig, type StepDefinition, type WorkflowBindingSpec, type WorkflowConfig, type WorkflowDefinition, type WorkflowEventLike, type WorkflowInstanceAction, type WorkflowInstanceDetail, type WorkflowInstancePage, type WorkflowInstanceStatus, type WorkflowInstanceSummary, type WorkflowLogger, type WorkflowRunContext, type WorkflowRunFunction, type WorkflowRunStepFunction, type WorkflowStepDetail, type WorkflowStepLike, type Workflows, type WorkflowsRestClient, type WorkflowsRestConfig, WorkflowsRestError, convertNonRetryableError, createRunStep, createWorkflowContext, createWorkflowRunContext, createWorkflows, createWorkflowsRestClient, defineStep, defineWorkflow, isNonRetryableError, isStepDefinition, isWorkflowDefinition, toNativeNonRetryableError, validateStepArgs, workflowBindingName, workflowClassName, workflowDefaultName };
267
+ export { type LunoraWorkflowsOptions, MAX_BRANCHES, type NativeNonRetryableErrorConstructor, NonRetryableError, type StepArgsValidator, type StepConfig, type StepDefinition, type WorkflowBindingSpec, type WorkflowBranch, type WorkflowConfig, type WorkflowDefinition, type WorkflowEventLike, type WorkflowInstanceAction, type WorkflowInstanceDetail, type WorkflowInstancePage, type WorkflowInstanceStatus, type WorkflowInstanceSummary, type WorkflowLogger, type WorkflowRunContext, type WorkflowRunFunction, type WorkflowRunStepFunction, type WorkflowStepDetail, type WorkflowStepLike, type Workflows, type WorkflowsRestClient, type WorkflowsRestConfig, WorkflowsRestError, branch, convertNonRetryableError, createRunStep, createWorkflowContext, createWorkflowRunContext, createWorkflows, createWorkflowsRestClient, defineStep, defineWorkflow, isNonRetryableError, isStepDefinition, isWorkflowDefinition, toNativeNonRetryableError, validateStepArgs, workflowBindingName, workflowClassName, workflowDefaultName };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as Workflows, L as LunoraWorkflowsOptions, S as StepArgsValidator, b as StepConfig, c as StepDefinition, d as WorkflowConfig, W as WorkflowDefinition, e as WorkflowInstanceStatus, f as WorkflowEventLike, g as WorkflowStepLike, h as WorkflowRunContext, i as WorkflowLogger, j as WorkflowRunFunction, k as WorkflowRunStepFunction } from "./packem_shared/types.d-gNYxuUwu.js";
2
- export type { A as ArgsOf, F as FunctionReference, I as InferStepArgs, R as RunFunctionOptions, l as RunStepOptions, m as StepHandler, n as StepRollbackContext, o as StepRollbackHandler, p as StepRunContext, q as WorkflowBindingLike, r as WorkflowCreateOptions, s as WorkflowHandle, t as WorkflowHandler, u as WorkflowInstanceLike, v as WorkflowRollbackContextLike, w as WorkflowRollbackHandlerLike, x as WorkflowStatusResult, y as WorkflowStepConfigLike, z as WorkflowStepContextLike, B as WorkflowStepRollbackOptionsLike } from "./packem_shared/types.d-gNYxuUwu.js";
1
+ import { a as Workflows, L as LunoraWorkflowsOptions, S as StepArgsValidator, b as StepConfig, c as StepDefinition, d as WorkflowConfig, W as WorkflowDefinition, e as WorkflowBranch, f as WorkflowInstanceStatus, g as WorkflowEventLike, h as WorkflowStepLike, i as WorkflowRunContext, j as WorkflowLogger, k as WorkflowRunFunction, l as WorkflowRunStepFunction } from "./packem_shared/types.d-C9W6rEWv.js";
2
+ export type { A as ArgsOf, B as BranchCompensationParams, F as FunctionReference, I as InferStepArgs, R as RunFunctionOptions, m as RunStepOptions, n as StepHandler, o as StepRollbackContext, p as StepRollbackHandler, q as StepRunContext, r as WorkflowBindingLike, s as WorkflowBranchOutputs, t as WorkflowCreateOptions, u as WorkflowHandle, v as WorkflowHandler, w as WorkflowInstanceLike, x as WorkflowParallelFunction, y as WorkflowRollbackContextLike, z as WorkflowRollbackHandlerLike, C as WorkflowSpawnFunction, D as WorkflowSpawnOptions, E as WorkflowStatusResult, G as WorkflowStepConfigLike, H as WorkflowStepContextLike, J as WorkflowStepRollbackOptionsLike } from "./packem_shared/types.d-C9W6rEWv.js";
3
3
  import '@lunora/values';
4
4
  /** Wiring info for one declared workflow, emitted by codegen into the generated shard. */
5
5
  interface WorkflowBindingSpec {
@@ -131,6 +131,22 @@ declare const toNativeNonRetryableError: (error: NonRetryableError, NativeNonRet
131
131
  * Always throws — the `never` return lets callers `return convertNonRetryableError(...)`.
132
132
  */
133
133
  declare const convertNonRetryableError: (error: unknown, NativeNonRetryableError: NativeNonRetryableErrorConstructor | undefined) => never;
134
+ /** Hard cap on branches per `ctx.parallel` call — auto-scale, never silently spawn unbounded DOs. */
135
+ declare const MAX_BRANCHES = 100;
136
+ /** The params key the parent injects so a child knows to signal completion back. Stripped before the user handler sees `ctx.params`. */
137
+
138
+ /**
139
+ * Build a single fan-out branch: a declared child workflow referenced by its
140
+ * `lunora/workflows.ts` export name, plus the params it is created with. Pass the
141
+ * output type as the generic argument so `ctx.parallel(...)` infers the result
142
+ * tuple — e.g. `branch("imageTag", { key })` typed as `branch` of `{ tags }`.
143
+ */
144
+ declare const branch: <Output = unknown>(workflow: string, params?: Record<string, unknown>, options?: {
145
+ compensateWith?: string;
146
+ id?: string;
147
+ timeout?: number | string;
148
+ }) => WorkflowBranch<Output>;
149
+ /** Build the success outcome a completed branch reports to its parent. */
134
150
  /** The lifecycle mutations the REST API exposes via `PATCH .../instances/{id}`. */
135
151
  type WorkflowInstanceAction = "pause" | "resume" | "terminate";
136
152
  /** Configuration for a {@link WorkflowsRestClient}. */
@@ -248,4 +264,4 @@ interface RunStepDeps {
248
264
  * converted to the native one and any declared rollback forwarded to Cloudflare.
249
265
  */
250
266
  declare const createRunStep: (deps: RunStepDeps) => WorkflowRunStepFunction;
251
- export { type LunoraWorkflowsOptions, type NativeNonRetryableErrorConstructor, NonRetryableError, type StepArgsValidator, type StepConfig, type StepDefinition, type WorkflowBindingSpec, type WorkflowConfig, type WorkflowDefinition, type WorkflowEventLike, type WorkflowInstanceAction, type WorkflowInstanceDetail, type WorkflowInstancePage, type WorkflowInstanceStatus, type WorkflowInstanceSummary, type WorkflowLogger, type WorkflowRunContext, type WorkflowRunFunction, type WorkflowRunStepFunction, type WorkflowStepDetail, type WorkflowStepLike, type Workflows, type WorkflowsRestClient, type WorkflowsRestConfig, WorkflowsRestError, convertNonRetryableError, createRunStep, createWorkflowContext, createWorkflowRunContext, createWorkflows, createWorkflowsRestClient, defineStep, defineWorkflow, isNonRetryableError, isStepDefinition, isWorkflowDefinition, toNativeNonRetryableError, validateStepArgs, workflowBindingName, workflowClassName, workflowDefaultName };
267
+ export { type LunoraWorkflowsOptions, MAX_BRANCHES, type NativeNonRetryableErrorConstructor, NonRetryableError, type StepArgsValidator, type StepConfig, type StepDefinition, type WorkflowBindingSpec, type WorkflowBranch, type WorkflowConfig, type WorkflowDefinition, type WorkflowEventLike, type WorkflowInstanceAction, type WorkflowInstanceDetail, type WorkflowInstancePage, type WorkflowInstanceStatus, type WorkflowInstanceSummary, type WorkflowLogger, type WorkflowRunContext, type WorkflowRunFunction, type WorkflowRunStepFunction, type WorkflowStepDetail, type WorkflowStepLike, type Workflows, type WorkflowsRestClient, type WorkflowsRestConfig, WorkflowsRestError, branch, convertNonRetryableError, createRunStep, createWorkflowContext, createWorkflowRunContext, createWorkflows, createWorkflowsRestClient, defineStep, defineWorkflow, isNonRetryableError, isStepDefinition, isWorkflowDefinition, toNativeNonRetryableError, validateStepArgs, workflowBindingName, workflowClassName, workflowDefaultName };
package/dist/index.mjs CHANGED
@@ -3,6 +3,7 @@ export { default as createWorkflows } from './packem_shared/createWorkflows-BoSY
3
3
  export { defineStep, isStepDefinition } from './packem_shared/defineStep-DJQtLw7g.mjs';
4
4
  export { defineWorkflow, isWorkflowDefinition, workflowBindingName, workflowClassName, workflowDefaultName } from './packem_shared/defineWorkflow-DbUC-oCN.mjs';
5
5
  export { NonRetryableError, convertNonRetryableError, isNonRetryableError, toNativeNonRetryableError } from './packem_shared/NonRetryableError-Dn2dTyBS.mjs';
6
+ export { MAX_BRANCHES, branch } from './packem_shared/MAX_BRANCHES-D6cQabJ1.mjs';
6
7
  export { WorkflowsRestError, createWorkflowsRestClient } from './packem_shared/WorkflowsRestError-b06i7K5j.mjs';
7
- export { createWorkflowRunContext } from './packem_shared/createWorkflowRunContext-DJj_y0fa.mjs';
8
+ export { createWorkflowRunContext } from './packem_shared/createWorkflowRunContext-CYI9SeI9.mjs';
8
9
  export { createRunStep, validateStepArgs } from './packem_shared/createRunStep-8jOXxP2o.mjs';
@@ -0,0 +1,132 @@
1
+ import { NonRetryableError } from './NonRetryableError-Dn2dTyBS.mjs';
2
+
3
+ const MAX_BRANCHES = 100;
4
+ const BRANCH_MARKER_KEY = "__lunoraBranch";
5
+ const SPAWN_STEP_PREFIX = "lunora:spawn:";
6
+ const AWAIT_STEP_PREFIX = "lunora:await:";
7
+ const SIGNAL_STEP_PREFIX = "lunora:signal:";
8
+ const COMPENSATE_STEP_PREFIX = "lunora:compensate:";
9
+ const BRANCH_EVENT_PREFIX = "lunora:branch:";
10
+ const branch = (workflow, params, options) => {
11
+ return { compensateWith: options?.compensateWith, id: options?.id, params, timeout: options?.timeout, workflow };
12
+ };
13
+ const serializeError = (error) => {
14
+ if (error instanceof Error) {
15
+ return { message: error.message, name: error.name };
16
+ }
17
+ return { message: String(error), name: "Error" };
18
+ };
19
+ const okOutcome = (value) => {
20
+ return { status: "ok", value };
21
+ };
22
+ const errorOutcome = (error) => {
23
+ return { error: serializeError(error), status: "error" };
24
+ };
25
+ const compensateCompleted = async (deps, completed, error) => {
26
+ for (let cursor = completed.length - 1; cursor >= 0; cursor -= 1) {
27
+ const done = completed[cursor];
28
+ const compensateWith = done?.plan.item.compensateWith;
29
+ if (done === void 0 || compensateWith === void 0) {
30
+ continue;
31
+ }
32
+ await deps.step.do(`${COMPENSATE_STEP_PREFIX}${done.plan.childId}`, async () => {
33
+ const compensateId = `${done.plan.childId}:compensate`;
34
+ const compensationParams = {
35
+ branch: done.plan.item.workflow,
36
+ error,
37
+ index: done.plan.index,
38
+ output: done.output
39
+ };
40
+ await deps.resolveBinding(compensateWith).create({ id: compensateId, params: compensationParams });
41
+ return compensateId;
42
+ });
43
+ }
44
+ };
45
+ const createParallel = (deps) => {
46
+ const run = async (branches) => {
47
+ if (branches.length === 0) {
48
+ return [];
49
+ }
50
+ if (branches.length > MAX_BRANCHES) {
51
+ throw new NonRetryableError(
52
+ `ctx.parallel: ${String(branches.length)} branches exceeds the cap of ${String(MAX_BRANCHES)} — split the fan-out or raise the work into fewer child workflows`
53
+ );
54
+ }
55
+ const planned = branches.map((item, index) => {
56
+ const childId = deps.nextChildId(item.id);
57
+ return { childId, eventType: `${BRANCH_EVENT_PREFIX}${childId}`, index, item };
58
+ });
59
+ await Promise.all(
60
+ planned.map(
61
+ (plan) => deps.step.do(`${SPAWN_STEP_PREFIX}${plan.childId}`, async () => {
62
+ const binding = deps.resolveBinding(plan.item.workflow);
63
+ const marker = { eventType: plan.eventType, index: plan.index, parentBinding: deps.parentBinding, parentId: deps.instanceId };
64
+ await binding.create({ id: plan.childId, params: { ...plan.item.params, [BRANCH_MARKER_KEY]: marker } });
65
+ return plan.childId;
66
+ })
67
+ )
68
+ );
69
+ const results = [];
70
+ const completed = [];
71
+ for (const plan of planned) {
72
+ const event = await deps.step.waitForEvent(`${AWAIT_STEP_PREFIX}${plan.childId}`, {
73
+ timeout: plan.item.timeout,
74
+ type: plan.eventType
75
+ });
76
+ const outcome = event.payload;
77
+ if (outcome.status === "error") {
78
+ await compensateCompleted(deps, completed, outcome.error);
79
+ throw new NonRetryableError(`ctx.parallel: branch "${plan.item.workflow}" (#${String(plan.index)}) failed: ${outcome.error.message}`);
80
+ }
81
+ completed.push({ output: outcome.value, plan });
82
+ results.push(outcome.value);
83
+ }
84
+ return results;
85
+ };
86
+ return run;
87
+ };
88
+ const createSpawn = (deps) => async (workflow, params, options) => {
89
+ const childId = deps.nextChildId(options?.id);
90
+ await deps.step.do(`${SPAWN_STEP_PREFIX}${childId}`, async () => {
91
+ const binding = deps.resolveBinding(workflow);
92
+ await binding.create({ id: childId, params });
93
+ return childId;
94
+ });
95
+ return deps.resolveBinding(workflow).get(childId);
96
+ };
97
+ const extractBranchMarker = (payload) => {
98
+ if (typeof payload !== "object" || payload === null) {
99
+ return void 0;
100
+ }
101
+ const marker = payload[BRANCH_MARKER_KEY];
102
+ if (typeof marker !== "object" || marker === null) {
103
+ return void 0;
104
+ }
105
+ const candidate = marker;
106
+ if (typeof candidate.eventType !== "string" || typeof candidate.parentBinding !== "string" || typeof candidate.parentId !== "string" || typeof candidate.index !== "number") {
107
+ return void 0;
108
+ }
109
+ return { eventType: candidate.eventType, index: candidate.index, parentBinding: candidate.parentBinding, parentId: candidate.parentId };
110
+ };
111
+ const stripBranchMarker = (payload) => {
112
+ if (typeof payload !== "object" || payload === null) {
113
+ return payload;
114
+ }
115
+ const rest = { ...payload };
116
+ Reflect.deleteProperty(rest, BRANCH_MARKER_KEY);
117
+ return rest;
118
+ };
119
+ const signalBranchParent = async (deps, marker, outcome) => {
120
+ const binding = deps.env[marker.parentBinding];
121
+ if (!binding || typeof binding.get !== "function") {
122
+ return;
123
+ }
124
+ const getParent = binding.get.bind(binding);
125
+ await deps.step.do(`${SIGNAL_STEP_PREFIX}${String(marker.index)}`, async () => {
126
+ const parent = await getParent(marker.parentId);
127
+ await parent.sendEvent({ payload: outcome, type: marker.eventType });
128
+ return marker.eventType;
129
+ });
130
+ };
131
+
132
+ export { BRANCH_MARKER_KEY, MAX_BRANCHES, branch, createParallel, createSpawn, errorOutcome, extractBranchMarker, okOutcome, signalBranchParent, stripBranchMarker };
@@ -1,3 +1,5 @@
1
+ import { workflowBindingName } from './defineWorkflow-DbUC-oCN.mjs';
2
+ import { createSpawn, createParallel } from './MAX_BRANCHES-D6cQabJ1.mjs';
1
3
  import { createRunStep } from './createRunStep-8jOXxP2o.mjs';
2
4
 
3
5
  const createDispatchLogger = (prefix) => {
@@ -65,13 +67,42 @@ const createDispatchRunner = (options) => {
65
67
  const createWorkflowRunContext = (options) => {
66
68
  const log = createDispatchLogger(`[workflow:${options.exportName}]`);
67
69
  const run = createDispatchRunner({ env: options.env, fetchImpl: options.fetchImpl, label: "@lunora/workflow" });
70
+ const resolveBinding = (workflow) => {
71
+ const bindingName = workflowBindingName(workflow);
72
+ const binding = options.env[bindingName];
73
+ if (!binding || typeof binding.create !== "function" || typeof binding.get !== "function") {
74
+ throw new Error(
75
+ `@lunora/workflow: cannot spawn child workflow "${workflow}" — no Workflow binding "${bindingName}" on env (is it declared in lunora/workflows.ts?)`
76
+ );
77
+ }
78
+ return binding;
79
+ };
80
+ let childCounter = 0;
81
+ const nextChildId = (explicit) => {
82
+ if (explicit !== void 0) {
83
+ return explicit;
84
+ }
85
+ const id = `${options.event.instanceId}-c${String(childCounter)}`;
86
+ childCounter += 1;
87
+ return id;
88
+ };
89
+ const fanOutDeps = {
90
+ env: options.env,
91
+ instanceId: options.event.instanceId,
92
+ nextChildId,
93
+ parentBinding: workflowBindingName(options.exportName),
94
+ resolveBinding,
95
+ step: options.step
96
+ };
68
97
  return {
69
98
  env: options.env,
70
99
  event: options.event,
71
100
  log,
101
+ parallel: createParallel(fanOutDeps),
72
102
  params: options.event.payload,
73
103
  run,
74
104
  runStep: createRunStep({ env: options.env, log, nonRetryableErrorClass: options.nonRetryableErrorClass, run, step: options.step }),
105
+ spawn: createSpawn(fanOutDeps),
75
106
  step: options.step
76
107
  };
77
108
  };
@@ -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, WorkflowEventLike as f, WorkflowStepLike as g, WorkflowRunContext as h, WorkflowLogger 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, WorkflowEventLike as f, WorkflowStepLike as g, WorkflowRunContext as h, WorkflowLogger 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.3",
3
+ "version": "1.0.0-alpha.5",
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",