@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.
- package/dist/do/index.d.mts +1 -1
- package/dist/do/index.d.ts +1 -1
- package/dist/do/index.mjs +16 -4
- package/dist/index.d.mts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.mjs +2 -1
- package/dist/packem_shared/MAX_BRANCHES-D6cQabJ1.mjs +132 -0
- package/dist/packem_shared/{createWorkflowRunContext-DJj_y0fa.mjs → createWorkflowRunContext-CYI9SeI9.mjs} +31 -0
- package/dist/packem_shared/{types.d-gNYxuUwu.d.mts → types.d-C9W6rEWv.d.mts} +83 -2
- package/dist/packem_shared/{types.d-gNYxuUwu.d.ts → types.d-C9W6rEWv.d.ts} +83 -2
- package/package.json +1 -1
package/dist/do/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from 'cloudflare:workers';
|
|
2
|
-
import { W as WorkflowDefinition } from "../packem_shared/types.d-
|
|
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
|
package/dist/do/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from 'cloudflare:workers';
|
|
2
|
-
import { W as WorkflowDefinition } from "../packem_shared/types.d-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
2
|
-
export type { A as ArgsOf, F as FunctionReference, I as InferStepArgs, R as RunFunctionOptions,
|
|
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
|
|
2
|
-
export type { A as ArgsOf, F as FunctionReference, I as InferStepArgs, R as RunFunctionOptions,
|
|
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-
|
|
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<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
|
|
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,
|
|
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<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
|
|
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,
|
|
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
|
+
"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",
|