@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.
@@ -1,5 +1,5 @@
1
1
  import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from 'cloudflare:workers';
2
- import { W as WorkflowDefinition } from "../packem_shared/types.d-CQO_koGe.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-CQO_koGe.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
- import { convertNonRetryableError } from '../packem_shared/convertNonRetryableError-Dn2dTyBS.mjs';
4
- import { createWorkflowRunContext } from '../packem_shared/createWorkflowLogger-FktqxNLe.mjs';
3
+ import { convertNonRetryableError } from '../packem_shared/NonRetryableError-Dn2dTyBS.mjs';
4
+ import { extractBranchMarker, stripBranchMarker, signalBranchParentSafe, errorOutcome, okOutcome } from '../packem_shared/MAX_BRANCHES-EO5xYS_3.mjs';
5
+ import { createWorkflowRunContext } from '../packem_shared/createWorkflowRunContext-D62MbkFK.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 signalBranchParentSafe({ env: this.env, log: context.log, step: nativeStep }, marker, errorOutcome(error));
34
+ }
27
35
  return convertNonRetryableError(error, NonRetryableError);
28
36
  }
37
+ if (marker) {
38
+ await signalBranchParentSafe({ env: this.env, log: context.log, step: nativeStep }, marker, okOutcome(output));
39
+ }
40
+ return output;
29
41
  }
30
42
  }
31
43
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
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 WorkflowLogger, g as WorkflowEventLike, h as WorkflowStepLike, i as WorkflowRunContext, j as WorkflowRunFunction, k as WorkflowRunStepFunction } from "./packem_shared/types.d-CQO_koGe.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-CQO_koGe.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
+ import { LunoraError } from '@lunora/errors';
3
4
  import '@lunora/values';
4
5
  /** Wiring info for one declared workflow, emitted by codegen into the generated shard. */
5
6
  interface WorkflowBindingSpec {
@@ -131,6 +132,22 @@ declare const toNativeNonRetryableError: (error: NonRetryableError, NativeNonRet
131
132
  * Always throws — the `never` return lets callers `return convertNonRetryableError(...)`.
132
133
  */
133
134
  declare const convertNonRetryableError: (error: unknown, NativeNonRetryableError: NativeNonRetryableErrorConstructor | undefined) => never;
135
+ /** Hard cap on branches per `ctx.parallel` call — auto-scale, never silently spawn unbounded DOs. */
136
+ declare const MAX_BRANCHES = 100;
137
+ /** The params key the parent injects so a child knows to signal completion back. Stripped before the user handler sees `ctx.params`. */
138
+
139
+ /**
140
+ * Build a single fan-out branch: a declared child workflow referenced by its
141
+ * `lunora/workflows.ts` export name, plus the params it is created with. Pass the
142
+ * output type as the generic argument so `ctx.parallel(...)` infers the result
143
+ * tuple — e.g. `branch("imageTag", { key })` typed as `branch` of `{ tags }`.
144
+ */
145
+ declare const branch: <Output = unknown>(workflow: string, params?: Record<string, unknown>, options?: {
146
+ compensateWith?: string;
147
+ id?: string;
148
+ timeout?: number | string;
149
+ }) => WorkflowBranch<Output>;
150
+ /** Build the success outcome a completed branch reports to its parent. */
134
151
  /** The lifecycle mutations the REST API exposes via `PATCH .../instances/{id}`. */
135
152
  type WorkflowInstanceAction = "pause" | "resume" | "terminate";
136
153
  /** Configuration for a {@link WorkflowsRestClient}. */
@@ -178,8 +195,7 @@ interface WorkflowInstancePage {
178
195
  totalCount?: number;
179
196
  }
180
197
  /** Thrown when the REST API responds non-2xx or `success: false`; carries the status plus body for the caller to surface. */
181
- declare class WorkflowsRestError extends Error {
182
- readonly status: number;
198
+ declare class WorkflowsRestError extends LunoraError {
183
199
  constructor(status: number, body: string);
184
200
  }
185
201
  /** The observe client: list instances, read one instance's steps, and (with Edit scope) mutate its status. */
@@ -209,24 +225,6 @@ interface WorkflowsRestClient {
209
225
  * snake_case payload into the camelCase shapes the studio renders.
210
226
  */
211
227
  declare const createWorkflowsRestClient: (config: WorkflowsRestConfig) => WorkflowsRestClient;
212
- interface RunnerOptions {
213
- /** Worker `env` — read `LUNORA_ORIGIN_URL` + `LUNORA_ADMIN_TOKEN` at call time. */
214
- env: Record<string, unknown>;
215
- /** Injectable fetch (tests); defaults to the global. */
216
- fetchImpl?: typeof fetch;
217
- }
218
- /**
219
- * Build a {@link WorkflowRunFunction} that invokes a Lunora function by POSTing
220
- * to the Worker's `/_lunora/scheduler/dispatch` endpoint — the same path the
221
- * SchedulerDO and the Queues workpool dispatch through — authenticated with the
222
- * admin bearer. The parsed JSON body (the function's return value) is resolved;
223
- * an empty/non-JSON body resolves to `undefined`.
224
- *
225
- * Wrap calls in `ctx.step.do(...)` to make them durable + memoized + retried.
226
- */
227
- declare const createWorkflowRunner: (options: RunnerOptions) => WorkflowRunFunction;
228
- /** Console-backed logger, prefixed with the workflow name for log correlation. */
229
- declare const createWorkflowLogger: (exportName: string) => WorkflowLogger;
230
228
  interface RunContextOptions<Params> {
231
229
  env: Record<string, unknown>;
232
230
  event: WorkflowEventLike<Params>;
@@ -266,4 +264,4 @@ interface RunStepDeps {
266
264
  * converted to the native one and any declared rollback forwarded to Cloudflare.
267
265
  */
268
266
  declare const createRunStep: (deps: RunStepDeps) => WorkflowRunStepFunction;
269
- 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, createWorkflowLogger, createWorkflowRunContext, createWorkflowRunner, 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,6 @@
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 WorkflowLogger, g as WorkflowEventLike, h as WorkflowStepLike, i as WorkflowRunContext, j as WorkflowRunFunction, k as WorkflowRunStepFunction } from "./packem_shared/types.d-CQO_koGe.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-CQO_koGe.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
+ import { LunoraError } from '@lunora/errors';
3
4
  import '@lunora/values';
4
5
  /** Wiring info for one declared workflow, emitted by codegen into the generated shard. */
5
6
  interface WorkflowBindingSpec {
@@ -131,6 +132,22 @@ declare const toNativeNonRetryableError: (error: NonRetryableError, NativeNonRet
131
132
  * Always throws — the `never` return lets callers `return convertNonRetryableError(...)`.
132
133
  */
133
134
  declare const convertNonRetryableError: (error: unknown, NativeNonRetryableError: NativeNonRetryableErrorConstructor | undefined) => never;
135
+ /** Hard cap on branches per `ctx.parallel` call — auto-scale, never silently spawn unbounded DOs. */
136
+ declare const MAX_BRANCHES = 100;
137
+ /** The params key the parent injects so a child knows to signal completion back. Stripped before the user handler sees `ctx.params`. */
138
+
139
+ /**
140
+ * Build a single fan-out branch: a declared child workflow referenced by its
141
+ * `lunora/workflows.ts` export name, plus the params it is created with. Pass the
142
+ * output type as the generic argument so `ctx.parallel(...)` infers the result
143
+ * tuple — e.g. `branch("imageTag", { key })` typed as `branch` of `{ tags }`.
144
+ */
145
+ declare const branch: <Output = unknown>(workflow: string, params?: Record<string, unknown>, options?: {
146
+ compensateWith?: string;
147
+ id?: string;
148
+ timeout?: number | string;
149
+ }) => WorkflowBranch<Output>;
150
+ /** Build the success outcome a completed branch reports to its parent. */
134
151
  /** The lifecycle mutations the REST API exposes via `PATCH .../instances/{id}`. */
135
152
  type WorkflowInstanceAction = "pause" | "resume" | "terminate";
136
153
  /** Configuration for a {@link WorkflowsRestClient}. */
@@ -178,8 +195,7 @@ interface WorkflowInstancePage {
178
195
  totalCount?: number;
179
196
  }
180
197
  /** Thrown when the REST API responds non-2xx or `success: false`; carries the status plus body for the caller to surface. */
181
- declare class WorkflowsRestError extends Error {
182
- readonly status: number;
198
+ declare class WorkflowsRestError extends LunoraError {
183
199
  constructor(status: number, body: string);
184
200
  }
185
201
  /** The observe client: list instances, read one instance's steps, and (with Edit scope) mutate its status. */
@@ -209,24 +225,6 @@ interface WorkflowsRestClient {
209
225
  * snake_case payload into the camelCase shapes the studio renders.
210
226
  */
211
227
  declare const createWorkflowsRestClient: (config: WorkflowsRestConfig) => WorkflowsRestClient;
212
- interface RunnerOptions {
213
- /** Worker `env` — read `LUNORA_ORIGIN_URL` + `LUNORA_ADMIN_TOKEN` at call time. */
214
- env: Record<string, unknown>;
215
- /** Injectable fetch (tests); defaults to the global. */
216
- fetchImpl?: typeof fetch;
217
- }
218
- /**
219
- * Build a {@link WorkflowRunFunction} that invokes a Lunora function by POSTing
220
- * to the Worker's `/_lunora/scheduler/dispatch` endpoint — the same path the
221
- * SchedulerDO and the Queues workpool dispatch through — authenticated with the
222
- * admin bearer. The parsed JSON body (the function's return value) is resolved;
223
- * an empty/non-JSON body resolves to `undefined`.
224
- *
225
- * Wrap calls in `ctx.step.do(...)` to make them durable + memoized + retried.
226
- */
227
- declare const createWorkflowRunner: (options: RunnerOptions) => WorkflowRunFunction;
228
- /** Console-backed logger, prefixed with the workflow name for log correlation. */
229
- declare const createWorkflowLogger: (exportName: string) => WorkflowLogger;
230
228
  interface RunContextOptions<Params> {
231
229
  env: Record<string, unknown>;
232
230
  event: WorkflowEventLike<Params>;
@@ -266,4 +264,4 @@ interface RunStepDeps {
266
264
  * converted to the native one and any declared rollback forwarded to Cloudflare.
267
265
  */
268
266
  declare const createRunStep: (deps: RunStepDeps) => WorkflowRunStepFunction;
269
- 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, createWorkflowLogger, createWorkflowRunContext, createWorkflowRunner, 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
@@ -1,8 +1,9 @@
1
- export { createWorkflowContext } from './packem_shared/createWorkflowContext-D6thzmlF.mjs';
2
- export { default as createWorkflows } from './packem_shared/createWorkflows-BoSYVIXg.mjs';
1
+ export { createWorkflowContext } from './packem_shared/createWorkflowContext-oxZ5zj3x.mjs';
2
+ export { default as createWorkflows } from './packem_shared/createWorkflows-wq4fs72q.mjs';
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
- export { NonRetryableError, convertNonRetryableError, isNonRetryableError, toNativeNonRetryableError } from './packem_shared/convertNonRetryableError-Dn2dTyBS.mjs';
6
- export { WorkflowsRestError, createWorkflowsRestClient } from './packem_shared/WorkflowsRestError-b06i7K5j.mjs';
7
- export { createWorkflowLogger, createWorkflowRunContext, createWorkflowRunner } from './packem_shared/createWorkflowLogger-FktqxNLe.mjs';
8
- export { createRunStep, validateStepArgs } from './packem_shared/createRunStep-BsK4LsUX.mjs';
5
+ export { NonRetryableError, convertNonRetryableError, isNonRetryableError, toNativeNonRetryableError } from './packem_shared/NonRetryableError-Dn2dTyBS.mjs';
6
+ export { MAX_BRANCHES, branch } from './packem_shared/MAX_BRANCHES-EO5xYS_3.mjs';
7
+ export { WorkflowsRestError, createWorkflowsRestClient } from './packem_shared/WorkflowsRestError-zmjOxTR1.mjs';
8
+ export { createWorkflowRunContext } from './packem_shared/createWorkflowRunContext-D62MbkFK.mjs';
9
+ export { createRunStep, validateStepArgs } from './packem_shared/createRunStep-8jOXxP2o.mjs';
@@ -0,0 +1,170 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+ import { NonRetryableError } from './NonRetryableError-Dn2dTyBS.mjs';
3
+
4
+ const MAX_BRANCHES = 100;
5
+ const BRANCH_MARKER_KEY = "__lunoraBranch";
6
+ const SPAWN_STEP_PREFIX = "lunora:spawn:";
7
+ const AWAIT_STEP_PREFIX = "lunora:await:";
8
+ const SIGNAL_STEP_PREFIX = "lunora:signal:";
9
+ const COMPENSATE_STEP_PREFIX = "lunora:compensate:";
10
+ const BRANCH_EVENT_PREFIX = "lunora:branch:";
11
+ const branch = (workflow, params, options) => {
12
+ return { compensateWith: options?.compensateWith, id: options?.id, params, timeout: options?.timeout, workflow };
13
+ };
14
+ const serializeError = (error) => {
15
+ if (error instanceof Error) {
16
+ return { message: error.message, name: error.name };
17
+ }
18
+ return { message: String(error), name: "Error" };
19
+ };
20
+ const okOutcome = (value) => {
21
+ return { status: "ok", value };
22
+ };
23
+ const errorOutcome = (error) => {
24
+ return { error: serializeError(error), status: "error" };
25
+ };
26
+ const compensateCompleted = async (deps, completed, error) => {
27
+ for (let cursor = completed.length - 1; cursor >= 0; cursor -= 1) {
28
+ const done = completed[cursor];
29
+ const compensateWith = done?.plan.item.compensateWith;
30
+ if (done === void 0 || compensateWith === void 0) {
31
+ continue;
32
+ }
33
+ try {
34
+ const compensation = deps.resolveBinding(compensateWith);
35
+ await deps.step.do(`${COMPENSATE_STEP_PREFIX}${done.plan.childId}`, async () => {
36
+ const compensateId = `${done.plan.childId}:compensate`;
37
+ const compensationParams = {
38
+ branch: done.plan.item.workflow,
39
+ error,
40
+ index: done.plan.index,
41
+ output: done.output
42
+ };
43
+ await compensation.create({ id: compensateId, params: compensationParams });
44
+ return compensateId;
45
+ });
46
+ } catch (compensationError) {
47
+ deps.log?.error(
48
+ `ctx.parallel: group-saga compensation "${compensateWith}" for branch "${done.plan.item.workflow}" (#${String(done.plan.index)}) failed`,
49
+ compensationError
50
+ );
51
+ }
52
+ }
53
+ };
54
+ const createParallel = (deps) => {
55
+ const run = async (branches) => {
56
+ if (branches.length === 0) {
57
+ return [];
58
+ }
59
+ if (branches.length > MAX_BRANCHES) {
60
+ throw new NonRetryableError(
61
+ `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`
62
+ );
63
+ }
64
+ const planned = branches.map((item, index) => {
65
+ const childId = deps.nextChildId(item.id);
66
+ return { childId, eventType: `${BRANCH_EVENT_PREFIX}${childId}`, index, item };
67
+ });
68
+ const seenIds = /* @__PURE__ */ new Set();
69
+ for (const plan of planned) {
70
+ if (seenIds.has(plan.childId)) {
71
+ throw new NonRetryableError(
72
+ `ctx.parallel: duplicate branch id "${plan.childId}" — each branch in a group must resolve to a unique child instance id (check explicit \`id\` options)`
73
+ );
74
+ }
75
+ seenIds.add(plan.childId);
76
+ }
77
+ await Promise.all(
78
+ planned.map(
79
+ (plan) => deps.step.do(`${SPAWN_STEP_PREFIX}${plan.childId}`, async () => {
80
+ const binding = deps.resolveBinding(plan.item.workflow);
81
+ const marker = { eventType: plan.eventType, index: plan.index, parentBinding: deps.parentBinding, parentId: deps.instanceId };
82
+ await binding.create({ id: plan.childId, params: { ...plan.item.params, [BRANCH_MARKER_KEY]: marker } });
83
+ return plan.childId;
84
+ })
85
+ )
86
+ );
87
+ const results = [];
88
+ const completed = [];
89
+ for (const plan of planned) {
90
+ let outcome;
91
+ try {
92
+ const event = await deps.step.waitForEvent(`${AWAIT_STEP_PREFIX}${plan.childId}`, {
93
+ timeout: plan.item.timeout,
94
+ type: plan.eventType
95
+ });
96
+ outcome = event.payload;
97
+ } catch (joinError) {
98
+ const joinFailure = serializeError(joinError);
99
+ await compensateCompleted(deps, completed, joinFailure);
100
+ throw new NonRetryableError(`ctx.parallel: branch "${plan.item.workflow}" (#${String(plan.index)}) join failed: ${joinFailure.message}`);
101
+ }
102
+ if (outcome.status === "error") {
103
+ await compensateCompleted(deps, completed, outcome.error);
104
+ throw new NonRetryableError(`ctx.parallel: branch "${plan.item.workflow}" (#${String(plan.index)}) failed: ${outcome.error.message}`);
105
+ }
106
+ completed.push({ output: outcome.value, plan });
107
+ results.push(outcome.value);
108
+ }
109
+ return results;
110
+ };
111
+ return run;
112
+ };
113
+ const createSpawn = (deps) => async (workflow, params, options) => {
114
+ if (params !== void 0 && Object.hasOwn(params, BRANCH_MARKER_KEY)) {
115
+ throw new LunoraError("BAD_REQUEST", `@lunora/workflow: params may not contain the reserved key "${BRANCH_MARKER_KEY}"`);
116
+ }
117
+ const childId = deps.nextChildId(options?.id);
118
+ await deps.step.do(`${SPAWN_STEP_PREFIX}${childId}`, async () => {
119
+ const binding = deps.resolveBinding(workflow);
120
+ await binding.create({ id: childId, params });
121
+ return childId;
122
+ });
123
+ return deps.resolveBinding(workflow).get(childId);
124
+ };
125
+ const extractBranchMarker = (payload) => {
126
+ if (typeof payload !== "object" || payload === null) {
127
+ return void 0;
128
+ }
129
+ const marker = payload[BRANCH_MARKER_KEY];
130
+ if (typeof marker !== "object" || marker === null) {
131
+ return void 0;
132
+ }
133
+ const candidate = marker;
134
+ if (typeof candidate.eventType !== "string" || typeof candidate.parentBinding !== "string" || typeof candidate.parentId !== "string" || typeof candidate.index !== "number") {
135
+ return void 0;
136
+ }
137
+ if (!candidate.parentBinding.startsWith("WORKFLOW_") || !candidate.eventType.startsWith(BRANCH_EVENT_PREFIX)) {
138
+ return void 0;
139
+ }
140
+ return { eventType: candidate.eventType, index: candidate.index, parentBinding: candidate.parentBinding, parentId: candidate.parentId };
141
+ };
142
+ const stripBranchMarker = (payload) => {
143
+ if (typeof payload !== "object" || payload === null) {
144
+ return payload;
145
+ }
146
+ const rest = { ...payload };
147
+ Reflect.deleteProperty(rest, BRANCH_MARKER_KEY);
148
+ return rest;
149
+ };
150
+ const signalBranchParent = async (deps, marker, outcome) => {
151
+ const binding = deps.env[marker.parentBinding];
152
+ if (!binding || typeof binding.get !== "function") {
153
+ return;
154
+ }
155
+ const getParent = binding.get.bind(binding);
156
+ await deps.step.do(`${SIGNAL_STEP_PREFIX}${String(marker.index)}`, async () => {
157
+ const parent = await getParent(marker.parentId);
158
+ await parent.sendEvent({ payload: outcome, type: marker.eventType });
159
+ return marker.eventType;
160
+ });
161
+ };
162
+ const signalBranchParentSafe = async (deps, marker, outcome) => {
163
+ try {
164
+ await signalBranchParent(deps, marker, outcome);
165
+ } catch (signalError) {
166
+ deps.log?.error(`@lunora/workflow: failed to signal branch parent "${marker.parentId}" (event "${marker.eventType}")`, signalError);
167
+ }
168
+ };
169
+
170
+ export { BRANCH_MARKER_KEY, MAX_BRANCHES, branch, createParallel, createSpawn, errorOutcome, extractBranchMarker, okOutcome, signalBranchParent, signalBranchParentSafe, stripBranchMarker };
@@ -1,3 +1,5 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
1
3
  const API_BASE = "https://api.cloudflare.com/client/v4/accounts";
2
4
  const KNOWN_STATUSES = {
3
5
  complete: true,
@@ -41,12 +43,9 @@ const toStep = (raw) => {
41
43
  type: asString(raw["type"])
42
44
  };
43
45
  };
44
- class WorkflowsRestError extends Error {
45
- status;
46
+ class WorkflowsRestError extends LunoraError {
46
47
  constructor(status, body) {
47
- super(`Cloudflare Workflows REST API returned ${String(status)}: ${body}`);
48
- this.name = "WorkflowsRestError";
49
- this.status = status;
48
+ super("WORKFLOWS_REST_ERROR", `Cloudflare Workflows REST API returned ${String(status)}: ${body}`, { name: "WorkflowsRestError", status });
50
49
  }
51
50
  }
52
51
  const createWorkflowsRestClient = (config) => {
@@ -1,5 +1,5 @@
1
1
  import { parseValidatorMap } from '@lunora/values';
2
- import { convertNonRetryableError, NonRetryableError } from './convertNonRetryableError-Dn2dTyBS.mjs';
2
+ import { convertNonRetryableError, NonRetryableError } from './NonRetryableError-Dn2dTyBS.mjs';
3
3
 
4
4
  const validateStepArgs = (validators, source) => parseValidatorMap(validators, source, "step args");
5
5
  const createRunStep = (deps) => async (step, args, options) => {
@@ -1,4 +1,4 @@
1
- import createWorkflows from './createWorkflows-BoSYVIXg.mjs';
1
+ import createWorkflows from './createWorkflows-wq4fs72q.mjs';
2
2
 
3
3
  const createWorkflowContext = (env, specs) => {
4
4
  const bindings = {};
@@ -0,0 +1,134 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+ import { workflowBindingName } from './defineWorkflow-DbUC-oCN.mjs';
3
+ import { createSpawn, createParallel } from './MAX_BRANCHES-EO5xYS_3.mjs';
4
+ import { createRunStep } from './createRunStep-8jOXxP2o.mjs';
5
+
6
+ const createDispatchLogger = (prefix) => {
7
+ return {
8
+ debug: (message, ...rest) => {
9
+ console.debug(prefix, message, ...rest);
10
+ },
11
+ error: (message, ...rest) => {
12
+ console.error(prefix, message, ...rest);
13
+ },
14
+ info: (message, ...rest) => {
15
+ console.info(prefix, message, ...rest);
16
+ },
17
+ warn: (message, ...rest) => {
18
+ console.warn(prefix, message, ...rest);
19
+ }
20
+ };
21
+ };
22
+
23
+ const SCHEDULER_DISPATCH_PATH = "/_lunora/scheduler/dispatch";
24
+ const trimTrailingSlashes = (value) => {
25
+ let end = value.length;
26
+ while (end > 0 && value[end - 1] === "/") {
27
+ end -= 1;
28
+ }
29
+ return value.slice(0, end);
30
+ };
31
+ const toDispatchError = (label, status, rawBody) => {
32
+ try {
33
+ const parsed = JSON.parse(rawBody);
34
+ const errorBody = parsed?.error;
35
+ if (typeof errorBody === "object" && errorBody !== null && typeof errorBody.code === "string") {
36
+ const { code, data, message } = errorBody;
37
+ return new LunoraError(code, typeof message === "string" ? message : void 0, { data, status });
38
+ }
39
+ } catch {
40
+ }
41
+ return new LunoraError("INTERNAL", `${label}: function dispatch failed (${String(status)}): ${rawBody}`, { status });
42
+ };
43
+ const createDispatchRunner = (options) => {
44
+ const { label } = options;
45
+ const globalFetch = globalThis.fetch;
46
+ const fetchImpl = options.fetchImpl ?? (typeof globalFetch === "function" ? globalFetch.bind(globalThis) : void 0);
47
+ return async (function_, args, runOptions = {}) => {
48
+ if (typeof fetchImpl !== "function") {
49
+ throw new TypeError(`${label}: no fetch implementation available — pass fetchImpl or run on a platform with global fetch`);
50
+ }
51
+ const origin = options.env.LUNORA_ORIGIN_URL;
52
+ if (typeof origin !== "string" || origin.length === 0) {
53
+ throw new LunoraError("INTERNAL", `${label}: \`LUNORA_ORIGIN_URL\` must be set on the Worker env so a handler can call back into Lunora functions`);
54
+ }
55
+ const token = options.env.LUNORA_ADMIN_TOKEN;
56
+ if (typeof token !== "string" || token.length === 0) {
57
+ throw new LunoraError("INTERNAL", `${label}: \`LUNORA_ADMIN_TOKEN\` must be set on the Worker env to authenticate function dispatch`);
58
+ }
59
+ const url = `${trimTrailingSlashes(origin)}${SCHEDULER_DISPATCH_PATH}`;
60
+ const headers = { authorization: `Bearer ${token}`, "content-type": "application/json" };
61
+ if (options.identity?.userId !== void 0) {
62
+ headers["x-lunora-userid"] = options.identity.userId;
63
+ }
64
+ if (options.identity?.claims !== void 0) {
65
+ headers["x-lunora-identity"] = JSON.stringify(options.identity.claims);
66
+ }
67
+ const response = await fetchImpl(url, {
68
+ body: JSON.stringify({ args: args ?? {}, functionPath: function_.__lunoraRef, shardKey: runOptions.shardKey }),
69
+ headers,
70
+ method: "POST"
71
+ });
72
+ if (!response.ok) {
73
+ throw toDispatchError(label, response.status, await response.text());
74
+ }
75
+ const text = await response.text();
76
+ if (text.length === 0) {
77
+ return void 0;
78
+ }
79
+ try {
80
+ return JSON.parse(text);
81
+ } catch {
82
+ throw new LunoraError("INTERNAL", `${label}: function dispatch returned a non-JSON body (${String(response.status)}): ${text}`, {
83
+ status: response.status
84
+ });
85
+ }
86
+ };
87
+ };
88
+
89
+ const createWorkflowRunContext = (options) => {
90
+ const log = createDispatchLogger(`[workflow:${options.exportName}]`);
91
+ const run = createDispatchRunner({ env: options.env, fetchImpl: options.fetchImpl, label: "@lunora/workflow" });
92
+ const resolveBinding = (workflow) => {
93
+ const bindingName = workflowBindingName(workflow);
94
+ const binding = options.env[bindingName];
95
+ if (!binding || typeof binding.create !== "function" || typeof binding.get !== "function") {
96
+ throw new LunoraError(
97
+ "INTERNAL",
98
+ `@lunora/workflow: cannot spawn child workflow "${workflow}" — no Workflow binding "${bindingName}" on env (is it declared in lunora/workflows.ts?)`
99
+ );
100
+ }
101
+ return binding;
102
+ };
103
+ let childCounter = 0;
104
+ const nextChildId = (explicit) => {
105
+ if (explicit !== void 0) {
106
+ return explicit;
107
+ }
108
+ const id = `${options.event.instanceId}-c${String(childCounter)}`;
109
+ childCounter += 1;
110
+ return id;
111
+ };
112
+ const fanOutDeps = {
113
+ env: options.env,
114
+ instanceId: options.event.instanceId,
115
+ log,
116
+ nextChildId,
117
+ parentBinding: workflowBindingName(options.exportName),
118
+ resolveBinding,
119
+ step: options.step
120
+ };
121
+ return {
122
+ env: options.env,
123
+ event: options.event,
124
+ log,
125
+ parallel: createParallel(fanOutDeps),
126
+ params: options.event.payload,
127
+ run,
128
+ runStep: createRunStep({ env: options.env, log, nonRetryableErrorClass: options.nonRetryableErrorClass, run, step: options.step }),
129
+ spawn: createSpawn(fanOutDeps),
130
+ step: options.step
131
+ };
132
+ };
133
+
134
+ export { createWorkflowRunContext };
@@ -0,0 +1,40 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+ import { BRANCH_MARKER_KEY } from './MAX_BRANCHES-EO5xYS_3.mjs';
3
+
4
+ const rejectReservedParams = (options) => {
5
+ const { params } = options ?? {};
6
+ if (params !== void 0 && Object.hasOwn(params, BRANCH_MARKER_KEY)) {
7
+ throw new LunoraError("BAD_REQUEST", `@lunora/workflow: params may not contain the reserved key "${BRANCH_MARKER_KEY}"`);
8
+ }
9
+ };
10
+ const handleFor = (binding) => {
11
+ return {
12
+ create: async (options) => {
13
+ rejectReservedParams(options);
14
+ return binding.create(options);
15
+ },
16
+ createBatch: async (batch) => {
17
+ for (const options of batch) {
18
+ rejectReservedParams(options);
19
+ }
20
+ return binding.createBatch(batch);
21
+ },
22
+ get: async (id) => binding.get(id)
23
+ };
24
+ };
25
+ const createWorkflows = (options) => {
26
+ const bindings = options.bindings ?? {};
27
+ return {
28
+ get: (name) => {
29
+ const binding = bindings[name];
30
+ if (binding === void 0) {
31
+ const known = Object.keys(bindings);
32
+ const suffix = known.length === 0 ? "no workflows are declared" : `known workflows: ${known.join(", ")}`;
33
+ throw new LunoraError("INTERNAL", `@lunora/workflow: no workflow named "${name}" (${suffix})`);
34
+ }
35
+ return handleFor(binding);
36
+ }
37
+ };
38
+ };
39
+
40
+ export { createWorkflows as default };