flowcraft 2.10.0 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/adapter-DzeZVjSE.d.mts +133 -0
- package/dist/adapters/index.d.mts +2 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/adapters/persistent-event-bus.d.mts +2 -0
- package/dist/adapters/persistent-event-bus.mjs +59 -0
- package/dist/analysis-B5Twr7sD.d.mts +52 -0
- package/dist/analysis.d.mts +2 -0
- package/dist/analysis.mjs +164 -0
- package/dist/batch-gather-BhF-IzQR.d.mts +8 -0
- package/dist/batch-scatter-DD8TU0Wm.d.mts +8 -0
- package/dist/container-BKdd-9wf.d.mts +24 -0
- package/dist/container-factory-fDY2kkxt.d.mts +17 -0
- package/dist/container-factory.d.mts +2 -0
- package/dist/container-factory.mjs +23 -0
- package/dist/container.d.mts +2 -0
- package/dist/container.mjs +43 -0
- package/dist/context-ZVtzXuZu.d.mts +64 -0
- package/dist/context.d.mts +2 -0
- package/dist/context.mjs +145 -0
- package/dist/error-mapper-BAv_YQMQ.d.mts +14 -0
- package/dist/error-mapper.d.mts +2 -0
- package/dist/error-mapper.mjs +37 -0
- package/dist/errors-CyyIj3OO.d.mts +21 -0
- package/dist/errors.d.mts +2 -0
- package/dist/errors.mjs +24 -0
- package/dist/evaluator-Dnj5qJ92.d.mts +31 -0
- package/dist/evaluator.d.mts +2 -0
- package/dist/evaluator.mjs +80 -0
- package/dist/flow-CZGpYpl-.d.mts +94 -0
- package/dist/flow.d.mts +2 -0
- package/dist/flow.mjs +328 -0
- package/dist/index-9iG2qHLe.d.mts +1 -0
- package/dist/index-Bk0eNZmQ.d.mts +1 -0
- package/dist/index-CNgSR_kt.d.mts +1 -0
- package/dist/index-CW2WHUXP.d.mts +1 -0
- package/dist/index.d.mts +24 -1
- package/dist/index.mjs +31 -791
- package/dist/linter-B8KALEae.d.mts +25 -0
- package/dist/linter.d.mts +2 -0
- package/dist/linter.mjs +74 -0
- package/dist/logger-BvDgvNHQ.d.mts +19 -0
- package/dist/logger.d.mts +2 -0
- package/dist/logger.mjs +26 -0
- package/dist/node.d.mts +2 -0
- package/dist/node.mjs +55 -0
- package/dist/nodes/batch-gather.d.mts +2 -0
- package/dist/nodes/batch-gather.mjs +47 -0
- package/dist/nodes/batch-scatter.d.mts +2 -0
- package/dist/nodes/batch-scatter.mjs +52 -0
- package/dist/nodes/index.d.mts +7 -0
- package/dist/nodes/index.mjs +8 -0
- package/dist/nodes/sleep.d.mts +2 -0
- package/dist/nodes/sleep.mjs +41 -0
- package/dist/nodes/subflow.d.mts +2 -0
- package/dist/nodes/subflow.mjs +64 -0
- package/dist/nodes/wait.d.mts +2 -0
- package/dist/nodes/wait.mjs +12 -0
- package/dist/nodes/webhook.d.mts +2 -0
- package/dist/nodes/webhook.mjs +24 -0
- package/dist/orchestrator-DwMIJRFI.d.mts +8 -0
- package/dist/persistent-event-bus-COiQOpWh.d.mts +68 -0
- package/dist/replay-CVOy6d_L.d.mts +44 -0
- package/dist/runtime/adapter.d.mts +2 -0
- package/dist/runtime/adapter.mjs +349 -0
- package/dist/runtime/builtin-keys.d.mts +37 -0
- package/dist/runtime/builtin-keys.mjs +12 -0
- package/dist/runtime/execution-context.d.mts +2 -0
- package/dist/runtime/execution-context.mjs +26 -0
- package/dist/runtime/executors.d.mts +2 -0
- package/dist/runtime/executors.mjs +259 -0
- package/dist/runtime/index.d.mts +6 -0
- package/dist/runtime/index.mjs +10 -0
- package/dist/runtime/node-executor-factory.d.mts +11 -0
- package/dist/runtime/node-executor-factory.mjs +41 -0
- package/dist/runtime/orchestrator.d.mts +2 -0
- package/dist/runtime/orchestrator.mjs +41 -0
- package/dist/runtime/orchestrators/replay.d.mts +2 -0
- package/dist/{replay-BB11M6K1.mjs → runtime/orchestrators/replay.mjs} +1 -20
- package/dist/runtime/orchestrators/step-by-step.d.mts +15 -0
- package/dist/runtime/orchestrators/step-by-step.mjs +41 -0
- package/dist/runtime/orchestrators/utils.d.mts +2 -0
- package/dist/runtime/orchestrators/utils.mjs +79 -0
- package/dist/runtime/runtime.d.mts +2 -0
- package/dist/runtime/runtime.mjs +425 -0
- package/dist/runtime/scheduler.d.mts +2 -0
- package/dist/runtime/scheduler.mjs +64 -0
- package/dist/runtime/state.d.mts +2 -0
- package/dist/runtime/state.mjs +127 -0
- package/dist/runtime/traverser.d.mts +2 -0
- package/dist/runtime/traverser.mjs +213 -0
- package/dist/runtime/types.d.mts +2 -0
- package/dist/runtime/types.mjs +1 -0
- package/dist/runtime/workflow-logic-handler.d.mts +16 -0
- package/dist/runtime/workflow-logic-handler.mjs +159 -0
- package/dist/sanitizer-Bi00YjvO.d.mts +11 -0
- package/dist/sanitizer.d.mts +2 -0
- package/dist/sanitizer.mjs +37 -0
- package/dist/sdk.d.mts +1 -2
- package/dist/sdk.mjs +1 -2
- package/dist/serializer-BnmJr13R.d.mts +17 -0
- package/dist/serializer.d.mts +2 -0
- package/dist/serializer.mjs +34 -0
- package/dist/sleep-DpwYaY5b.d.mts +8 -0
- package/dist/subflow-n2IMsRe2.d.mts +8 -0
- package/dist/testing/event-logger.d.mts +62 -0
- package/dist/testing/event-logger.mjs +98 -0
- package/dist/testing/index.d.mts +5 -172
- package/dist/testing/index.mjs +6 -276
- package/dist/testing/run-with-trace.d.mts +37 -0
- package/dist/testing/run-with-trace.mjs +49 -0
- package/dist/testing/stepper.d.mts +78 -0
- package/dist/testing/stepper.mjs +100 -0
- package/dist/types-BcrXJEPI.d.mts +687 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +1 -0
- package/dist/utils-BUEgr9V2.d.mts +34 -0
- package/dist/wait-2Q-LA7V7.d.mts +8 -0
- package/dist/webhook-BiCm-HLx.d.mts +12 -0
- package/package.json +4 -4
- package/dist/index-BXRN44Qf.d.mts +0 -1347
- package/dist/index.mjs.map +0 -1
- package/dist/replay-BB11M6K1.mjs.map +0 -1
- package/dist/runtime-ChsWirQN.mjs +0 -2256
- package/dist/runtime-ChsWirQN.mjs.map +0 -1
- package/dist/sdk.mjs.map +0 -1
- package/dist/testing/index.mjs.map +0 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { d as NodeClass, h as NodeFunction, w as WorkflowBlueprint } from "./types-BcrXJEPI.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/linter.d.ts
|
|
4
|
+
type LinterIssueCode = 'INVALID_EDGE_SOURCE' | 'INVALID_EDGE_TARGET' | 'MISSING_NODE_IMPLEMENTATION' | 'ORPHAN_NODE' | 'INVALID_BATCH_WORKER_KEY' | 'INVALID_SUBFLOW_BLUEPRINT_ID';
|
|
5
|
+
interface LinterIssue {
|
|
6
|
+
code: LinterIssueCode;
|
|
7
|
+
message: string;
|
|
8
|
+
nodeId?: string;
|
|
9
|
+
relatedId?: string;
|
|
10
|
+
}
|
|
11
|
+
interface LinterResult {
|
|
12
|
+
isValid: boolean;
|
|
13
|
+
issues: LinterIssue[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Statically analyzes a workflow blueprint against a registry of implementations
|
|
17
|
+
* to find common errors before runtime.
|
|
18
|
+
*
|
|
19
|
+
* @param blueprint The WorkflowBlueprint to analyze.
|
|
20
|
+
* @param registry A map of node implementations (functions or classes) to check against.
|
|
21
|
+
* @returns A LinterResult object containing any issues found.
|
|
22
|
+
*/
|
|
23
|
+
declare function lintBlueprint(blueprint: WorkflowBlueprint, registry: Map<string, NodeFunction | NodeClass> | Record<string, NodeFunction | NodeClass>, blueprints?: Record<string, WorkflowBlueprint>): LinterResult;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { lintBlueprint as i, LinterIssueCode as n, LinterResult as r, LinterIssue as t };
|
package/dist/linter.mjs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { analyzeBlueprint } from "./analysis.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/linter.ts
|
|
4
|
+
/**
|
|
5
|
+
* Statically analyzes a workflow blueprint against a registry of implementations
|
|
6
|
+
* to find common errors before runtime.
|
|
7
|
+
*
|
|
8
|
+
* @param blueprint The WorkflowBlueprint to analyze.
|
|
9
|
+
* @param registry A map of node implementations (functions or classes) to check against.
|
|
10
|
+
* @returns A LinterResult object containing any issues found.
|
|
11
|
+
*/
|
|
12
|
+
function lintBlueprint(blueprint, registry, blueprints) {
|
|
13
|
+
const issues = [];
|
|
14
|
+
const nodeIds = new Set(blueprint.nodes.map((n) => n.id));
|
|
15
|
+
const registryKeys = registry instanceof Map ? new Set(registry.keys()) : new Set(Object.keys(registry));
|
|
16
|
+
for (const node of blueprint.nodes) if (!node.uses.startsWith("batch-") && !node.uses.startsWith("loop-") && !registryKeys.has(node.uses)) issues.push({
|
|
17
|
+
code: "MISSING_NODE_IMPLEMENTATION",
|
|
18
|
+
message: `Node implementation key '${node.uses}' is not found in the provided registry.`,
|
|
19
|
+
nodeId: node.id
|
|
20
|
+
});
|
|
21
|
+
for (const node of blueprint.nodes) {
|
|
22
|
+
if (node.uses.startsWith("batch-") && node.params?.workerUsesKey) {
|
|
23
|
+
if (!registryKeys.has(node.params.workerUsesKey)) issues.push({
|
|
24
|
+
code: "INVALID_BATCH_WORKER_KEY",
|
|
25
|
+
message: `Batch node '${node.id}' references workerUsesKey '${node.params.workerUsesKey}' which is not found in the registry.`,
|
|
26
|
+
nodeId: node.id
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (node.uses === "subflow" && node.params?.blueprintId) {
|
|
30
|
+
if (!blueprints?.[node.params.blueprintId]) issues.push({
|
|
31
|
+
code: "INVALID_SUBFLOW_BLUEPRINT_ID",
|
|
32
|
+
message: `Subflow node '${node.id}' references blueprintId '${node.params.blueprintId}' which is not found in the blueprints registry.`,
|
|
33
|
+
nodeId: node.id
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const edge of blueprint.edges || []) {
|
|
38
|
+
if (!nodeIds.has(edge.source)) issues.push({
|
|
39
|
+
code: "INVALID_EDGE_SOURCE",
|
|
40
|
+
message: `Edge source '${edge.source}' does not correspond to a valid node ID.`,
|
|
41
|
+
relatedId: edge.target
|
|
42
|
+
});
|
|
43
|
+
if (!nodeIds.has(edge.target)) issues.push({
|
|
44
|
+
code: "INVALID_EDGE_TARGET",
|
|
45
|
+
message: `Edge target '${edge.target}' does not correspond to a valid node ID.`,
|
|
46
|
+
relatedId: edge.source
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (blueprint.nodes.length > 1) {
|
|
50
|
+
const analysis = analyzeBlueprint(blueprint);
|
|
51
|
+
const connectedNodes = /* @__PURE__ */ new Set();
|
|
52
|
+
const nodesToVisit = [...analysis.startNodeIds];
|
|
53
|
+
const visited = /* @__PURE__ */ new Set();
|
|
54
|
+
while (nodesToVisit.length > 0) {
|
|
55
|
+
const currentId = nodesToVisit.pop();
|
|
56
|
+
if (!currentId || visited.has(currentId)) continue;
|
|
57
|
+
visited.add(currentId);
|
|
58
|
+
connectedNodes.add(currentId);
|
|
59
|
+
for (const targetEdge of blueprint.edges.filter((e) => e.source === currentId)) nodesToVisit.push(targetEdge.target);
|
|
60
|
+
}
|
|
61
|
+
for (const nodeId of nodeIds) if (!connectedNodes.has(nodeId)) issues.push({
|
|
62
|
+
code: "ORPHAN_NODE",
|
|
63
|
+
message: `Node '${nodeId}' is not reachable from any start node.`,
|
|
64
|
+
nodeId
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
isValid: issues.length === 0,
|
|
69
|
+
issues
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { lintBlueprint };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { s as ILogger } from "./types-BcrXJEPI.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/logger.d.ts
|
|
4
|
+
/** A logger implementation that outputs to the console. */
|
|
5
|
+
declare class ConsoleLogger implements ILogger {
|
|
6
|
+
debug(message: string, meta?: Record<string, any>): void;
|
|
7
|
+
info(message: string, meta?: Record<string, any>): void;
|
|
8
|
+
warn(message: string, meta?: Record<string, any>): void;
|
|
9
|
+
error(message: string, meta?: Record<string, any>): void;
|
|
10
|
+
}
|
|
11
|
+
/** A logger implementation that does nothing (no-op). */
|
|
12
|
+
declare class NullLogger implements ILogger {
|
|
13
|
+
debug(_message: string, _meta?: Record<string, any>): void;
|
|
14
|
+
info(_message: string, _meta?: Record<string, any>): void;
|
|
15
|
+
warn(_message: string, _meta?: Record<string, any>): void;
|
|
16
|
+
error(_message: string, _meta?: Record<string, any>): void;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { NullLogger as n, ConsoleLogger as t };
|
package/dist/logger.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/logger.ts
|
|
2
|
+
/** A logger implementation that outputs to the console. */
|
|
3
|
+
var ConsoleLogger = class {
|
|
4
|
+
debug(message, meta) {
|
|
5
|
+
console.debug(`[DEBUG] ${message}`, meta || "");
|
|
6
|
+
}
|
|
7
|
+
info(message, meta) {
|
|
8
|
+
console.info(`[INFO] ${message}`, meta || "");
|
|
9
|
+
}
|
|
10
|
+
warn(message, meta) {
|
|
11
|
+
console.warn(`[WARN] ${message}`, meta || "");
|
|
12
|
+
}
|
|
13
|
+
error(message, meta) {
|
|
14
|
+
console.error(`[ERROR] ${message}`, meta || "");
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/** A logger implementation that does nothing (no-op). */
|
|
18
|
+
var NullLogger = class {
|
|
19
|
+
debug(_message, _meta) {}
|
|
20
|
+
info(_message, _meta) {}
|
|
21
|
+
warn(_message, _meta) {}
|
|
22
|
+
error(_message, _meta) {}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { ConsoleLogger, NullLogger };
|
package/dist/node.d.mts
ADDED
package/dist/node.mjs
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//#region src/node.ts
|
|
2
|
+
/** A type guard to reliably distinguish a NodeClass from a NodeFunction. */
|
|
3
|
+
function isNodeClass(impl) {
|
|
4
|
+
return typeof impl === "function" && !!impl.prototype?.exec;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A structured, class-based node for complex logic with a safe, granular lifecycle.
|
|
8
|
+
* This class is generic, allowing implementations to specify the exact context
|
|
9
|
+
* and dependency types they expect.
|
|
10
|
+
*/
|
|
11
|
+
var BaseNode = class {
|
|
12
|
+
/**
|
|
13
|
+
* @param params Static parameters for this node instance, passed from the blueprint.
|
|
14
|
+
* @param nodeId The ID of the node in the blueprint.
|
|
15
|
+
*/
|
|
16
|
+
constructor(params, nodeId) {
|
|
17
|
+
this.params = params;
|
|
18
|
+
this.nodeId = nodeId;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Phase 1: Gathers and prepares data for execution. This phase is NOT retried on failure.
|
|
22
|
+
* @param context The node's execution context.
|
|
23
|
+
* @returns The data needed for the `exec` phase.
|
|
24
|
+
*/
|
|
25
|
+
async prep(context) {
|
|
26
|
+
return context.input;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Phase 3: Processes the result and saves state. This phase is NOT retried.
|
|
30
|
+
* @param execResult The successful result from the `exec` or `fallback` phase.
|
|
31
|
+
* @param _context The node's execution context.
|
|
32
|
+
*/
|
|
33
|
+
async post(execResult, _context) {
|
|
34
|
+
return execResult;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* An optional safety net that runs if all `exec` retries fail.
|
|
38
|
+
* @param error The final error from the last `exec` attempt.
|
|
39
|
+
* @param _context The node's execution context.
|
|
40
|
+
*/
|
|
41
|
+
async fallback(error, _context) {
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* An optional cleanup phase for non-retriable errors that occur outside the main `exec` method.
|
|
46
|
+
* This method is invoked in a `finally` block or equivalent construct if a fatal, unhandled exception occurs in the `prep`, `exec`, or `post` phases.
|
|
47
|
+
* Allows nodes to perform crucial cleanup, such as closing database connections or releasing locks.
|
|
48
|
+
* @param _error The error that caused the failure.
|
|
49
|
+
* @param _context The node's execution context.
|
|
50
|
+
*/
|
|
51
|
+
async recover(_error, _context) {}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
export { BaseNode, isNodeClass };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BaseNode } from "../node.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/nodes/batch-gather.ts
|
|
4
|
+
var BatchGatherNode = class extends BaseNode {
|
|
5
|
+
async exec(_prepResult, context) {
|
|
6
|
+
const { gatherNodeId, outputKey } = this.params || {};
|
|
7
|
+
const hasMore = await context.context.get(`${gatherNodeId}_hasMore`) || false;
|
|
8
|
+
const dynamicNodes = [];
|
|
9
|
+
let results = [];
|
|
10
|
+
if (hasMore) {
|
|
11
|
+
const newScatterId = `${gatherNodeId}_scatter_next`;
|
|
12
|
+
dynamicNodes.push({
|
|
13
|
+
id: newScatterId,
|
|
14
|
+
uses: "batch-scatter",
|
|
15
|
+
inputs: context.input,
|
|
16
|
+
params: {
|
|
17
|
+
...this.params,
|
|
18
|
+
gatherNodeId
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
} else {
|
|
22
|
+
const allWorkerIds = await context.context.get(`${gatherNodeId}_allWorkerIds`) || [];
|
|
23
|
+
results = [];
|
|
24
|
+
for (const workerId of allWorkerIds) {
|
|
25
|
+
const result = await context.context.get(`_outputs.${workerId}`);
|
|
26
|
+
if (result !== void 0) results.push(result);
|
|
27
|
+
}
|
|
28
|
+
await context.context.set(outputKey, results);
|
|
29
|
+
const parentBatchId = gatherNodeId.replace("_gather", "");
|
|
30
|
+
await context.dependencies.runtime.services.eventBus.emit({
|
|
31
|
+
type: "batch:finish",
|
|
32
|
+
payload: {
|
|
33
|
+
batchId: parentBatchId,
|
|
34
|
+
gatherNodeId,
|
|
35
|
+
results
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
dynamicNodes,
|
|
41
|
+
output: results
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { BatchGatherNode };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BaseNode } from "../node.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/nodes/batch-scatter.ts
|
|
4
|
+
var BatchScatterNode = class extends BaseNode {
|
|
5
|
+
async exec(_prepResult, context) {
|
|
6
|
+
const inputArray = context.input || [];
|
|
7
|
+
if (!Array.isArray(inputArray)) throw new Error(`Input for batch-scatter node '${this.nodeId}' must be an array.`);
|
|
8
|
+
const { chunkSize = inputArray.length, workerUsesKey, gatherNodeId } = this.params || {};
|
|
9
|
+
if (!workerUsesKey || !gatherNodeId) throw new Error(`BatchScatterNode requires 'workerUsesKey' and 'gatherNodeId' parameters.`);
|
|
10
|
+
const batchId = globalThis.crypto.randomUUID();
|
|
11
|
+
const currentIndex = await context.context.get(`${this.nodeId}_currentIndex`) || 0;
|
|
12
|
+
const endIndex = Math.min(currentIndex + chunkSize, inputArray.length);
|
|
13
|
+
const dynamicNodes = [];
|
|
14
|
+
const workerIds = [];
|
|
15
|
+
for (let i = currentIndex; i < endIndex; i++) {
|
|
16
|
+
const item = inputArray[i];
|
|
17
|
+
const itemInputKey = `_batch.${this.nodeId}_${batchId}_item_${i}`;
|
|
18
|
+
await context.context.set(itemInputKey, item);
|
|
19
|
+
const workerId = `${workerUsesKey}_${batchId}_${i}`;
|
|
20
|
+
workerIds.push(workerId);
|
|
21
|
+
dynamicNodes.push({
|
|
22
|
+
id: workerId,
|
|
23
|
+
uses: workerUsesKey,
|
|
24
|
+
inputs: itemInputKey
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const parentBatchId = this.nodeId?.replace("_scatter", "") || "";
|
|
28
|
+
await context.dependencies.runtime.services.eventBus.emit({
|
|
29
|
+
type: "batch:start",
|
|
30
|
+
payload: {
|
|
31
|
+
batchId: parentBatchId,
|
|
32
|
+
scatterNodeId: this.nodeId,
|
|
33
|
+
workerNodeIds: workerIds
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
await context.context.set(`${this.nodeId}_currentIndex`, endIndex);
|
|
37
|
+
const hasMore = endIndex < inputArray.length;
|
|
38
|
+
await context.context.set(`${gatherNodeId}_hasMore`, hasMore);
|
|
39
|
+
const allWorkerIds = [...await context.context.get(`${gatherNodeId}_allWorkerIds`) || [], ...workerIds];
|
|
40
|
+
await context.context.set(`${gatherNodeId}_allWorkerIds`, allWorkerIds);
|
|
41
|
+
return {
|
|
42
|
+
dynamicNodes,
|
|
43
|
+
output: {
|
|
44
|
+
gatherNodeId,
|
|
45
|
+
hasMore
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { BatchScatterNode };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { t as BatchGatherNode } from "../batch-gather-BhF-IzQR.mjs";
|
|
2
|
+
import { t as BatchScatterNode } from "../batch-scatter-DD8TU0Wm.mjs";
|
|
3
|
+
import { t as SleepNode } from "../sleep-DpwYaY5b.mjs";
|
|
4
|
+
import { t as SubflowNode } from "../subflow-n2IMsRe2.mjs";
|
|
5
|
+
import { t as WaitNode } from "../wait-2Q-LA7V7.mjs";
|
|
6
|
+
import { t as WebhookNode } from "../webhook-BiCm-HLx.mjs";
|
|
7
|
+
export { BatchGatherNode, BatchScatterNode, SleepNode, SubflowNode, WaitNode, WebhookNode };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BatchGatherNode } from "./batch-gather.mjs";
|
|
2
|
+
import { BatchScatterNode } from "./batch-scatter.mjs";
|
|
3
|
+
import { SleepNode } from "./sleep.mjs";
|
|
4
|
+
import { SubflowNode } from "./subflow.mjs";
|
|
5
|
+
import { WaitNode } from "./wait.mjs";
|
|
6
|
+
import { WebhookNode } from "./webhook.mjs";
|
|
7
|
+
|
|
8
|
+
export { BatchGatherNode, BatchScatterNode, SleepNode, SubflowNode, WaitNode, WebhookNode };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BaseNode } from "../node.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/nodes/sleep.ts
|
|
4
|
+
var SleepNode = class extends BaseNode {
|
|
5
|
+
async exec(prepResult, context) {
|
|
6
|
+
const durationParam = this.params?.duration;
|
|
7
|
+
let durationMs;
|
|
8
|
+
if (typeof durationParam === "string") {
|
|
9
|
+
const match = durationParam.match(/^(\d+)([smhd])$/);
|
|
10
|
+
if (!match) throw new Error(`SleepNode '${this.nodeId}' received an invalid duration string: '${durationParam}'. Expected format: '5m', '10s', '1h', '2d'`);
|
|
11
|
+
const [, numStr, unit] = match;
|
|
12
|
+
const num = parseInt(numStr, 10);
|
|
13
|
+
switch (unit) {
|
|
14
|
+
case "s":
|
|
15
|
+
durationMs = num * 1e3;
|
|
16
|
+
break;
|
|
17
|
+
case "m":
|
|
18
|
+
durationMs = num * 60 * 1e3;
|
|
19
|
+
break;
|
|
20
|
+
case "h":
|
|
21
|
+
durationMs = num * 60 * 60 * 1e3;
|
|
22
|
+
break;
|
|
23
|
+
case "d":
|
|
24
|
+
durationMs = num * 24 * 60 * 60 * 1e3;
|
|
25
|
+
break;
|
|
26
|
+
default: throw new Error(`Invalid duration unit: ${unit}`);
|
|
27
|
+
}
|
|
28
|
+
} else if (typeof durationParam === "number") durationMs = durationParam;
|
|
29
|
+
else throw new Error(`SleepNode '${this.nodeId}' received an invalid duration type: ${typeof durationParam}`);
|
|
30
|
+
if (durationMs < 0) throw new Error(`SleepNode '${this.nodeId}' received a negative duration.`);
|
|
31
|
+
const wakeUpAt = new Date(Date.now() + durationMs).toISOString();
|
|
32
|
+
await context.dependencies.workflowState.markAsAwaiting(this.nodeId ?? "", {
|
|
33
|
+
reason: "timer",
|
|
34
|
+
wakeUpAt
|
|
35
|
+
});
|
|
36
|
+
return { output: prepResult };
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { SleepNode };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { analyzeBlueprint } from "../analysis.mjs";
|
|
2
|
+
import { FlowcraftError } from "../errors.mjs";
|
|
3
|
+
import { WorkflowState } from "../runtime/state.mjs";
|
|
4
|
+
import { ExecutionContext } from "../runtime/execution-context.mjs";
|
|
5
|
+
import { BaseNode } from "../node.mjs";
|
|
6
|
+
import { GraphTraverser } from "../runtime/traverser.mjs";
|
|
7
|
+
|
|
8
|
+
//#region src/nodes/subflow.ts
|
|
9
|
+
var SubflowNode = class extends BaseNode {
|
|
10
|
+
async exec(_prepResult, context) {
|
|
11
|
+
const { blueprintId, inputs, outputs } = this.params ?? {};
|
|
12
|
+
const { runtime, workflowState } = context.dependencies;
|
|
13
|
+
if (!blueprintId) throw new FlowcraftError(`Subflow node '${this.nodeId}' is missing 'blueprintId' parameter.`, { isFatal: true });
|
|
14
|
+
const subBlueprint = runtime.blueprints?.[blueprintId] || runtime.runtime?.blueprints?.[blueprintId];
|
|
15
|
+
if (!subBlueprint) throw new FlowcraftError(`Sub-blueprint '${blueprintId}' not found in runtime registry.`, { isFatal: true });
|
|
16
|
+
const subflowInitialContext = {};
|
|
17
|
+
if (inputs) for (const [targetKey, sourceKey] of Object.entries(inputs)) {
|
|
18
|
+
let value = await context.context.get(sourceKey);
|
|
19
|
+
if (value === void 0) value = await context.context.get(`_outputs.${sourceKey}`);
|
|
20
|
+
subflowInitialContext[targetKey] = value;
|
|
21
|
+
}
|
|
22
|
+
else if (context.input !== void 0) {
|
|
23
|
+
const subAnalysis = analyzeBlueprint(subBlueprint);
|
|
24
|
+
for (const startNodeId of subAnalysis.startNodeIds) {
|
|
25
|
+
const inputKey = `_inputs.${startNodeId}`;
|
|
26
|
+
subflowInitialContext[inputKey] = context.input;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const subflowExecContext = new ExecutionContext(subBlueprint, new WorkflowState(subflowInitialContext), runtime.nodeRegistry, runtime.executionId, runtime.runtime, runtime.services, runtime.signal, runtime.concurrency);
|
|
30
|
+
const subflowTraverser = new GraphTraverser(subBlueprint);
|
|
31
|
+
const subflowResult = await runtime.runtime.orchestrator.run(subflowExecContext, subflowTraverser);
|
|
32
|
+
if (subflowResult.status === "awaiting") {
|
|
33
|
+
await workflowState.markAsAwaiting(this.nodeId ?? "");
|
|
34
|
+
const subflowStateKey = `_subflowState.${this.nodeId}`;
|
|
35
|
+
await context.context.set(subflowStateKey, subflowResult.serializedContext);
|
|
36
|
+
return { output: void 0 };
|
|
37
|
+
}
|
|
38
|
+
if (subflowResult.status !== "completed") {
|
|
39
|
+
const firstError = subflowResult.errors?.[0];
|
|
40
|
+
const errorMessage = firstError?.message || "Unknown error";
|
|
41
|
+
throw new FlowcraftError(`Sub-workflow '${blueprintId}' did not complete successfully. Status: ${subflowResult.status}. Error: ${errorMessage}`, {
|
|
42
|
+
cause: firstError,
|
|
43
|
+
nodeId: this.nodeId,
|
|
44
|
+
blueprintId
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const subflowFinalContext = subflowResult.context;
|
|
48
|
+
if (outputs) {
|
|
49
|
+
for (const [parentKey, subKey] of Object.entries(outputs)) {
|
|
50
|
+
const value = subflowFinalContext[`_outputs.${subKey}`] ?? subflowFinalContext[subKey];
|
|
51
|
+
await context.context.set(parentKey, value);
|
|
52
|
+
}
|
|
53
|
+
return { output: subflowFinalContext };
|
|
54
|
+
}
|
|
55
|
+
const subAnalysis = analyzeBlueprint(subBlueprint);
|
|
56
|
+
if (subAnalysis.terminalNodeIds.length === 1) return { output: subflowFinalContext[`_outputs.${subAnalysis.terminalNodeIds[0]}`] };
|
|
57
|
+
const terminalOutputs = {};
|
|
58
|
+
for (const terminalId of subAnalysis.terminalNodeIds) terminalOutputs[terminalId] = subflowFinalContext[`_outputs.${terminalId}`];
|
|
59
|
+
return { output: terminalOutputs };
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
export { SubflowNode };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseNode } from "../node.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/nodes/wait.ts
|
|
4
|
+
var WaitNode = class extends BaseNode {
|
|
5
|
+
async exec(_prepResult, context) {
|
|
6
|
+
await context.dependencies.workflowState.markAsAwaiting(this.nodeId ?? "", { reason: "external_event" });
|
|
7
|
+
return { output: void 0 };
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { WaitNode };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BaseNode } from "../node.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/nodes/webhook.ts
|
|
4
|
+
var WebhookNode = class extends BaseNode {
|
|
5
|
+
async prep(context) {
|
|
6
|
+
const runId = context.dependencies.runtime.executionId;
|
|
7
|
+
const nodeId = this.nodeId ?? "";
|
|
8
|
+
const { url, event } = await context.dependencies.adapter.registerWebhookEndpoint(runId, nodeId);
|
|
9
|
+
return {
|
|
10
|
+
url,
|
|
11
|
+
event
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
async exec(prepResult, _context) {
|
|
15
|
+
return { output: {
|
|
16
|
+
url: prepResult.url,
|
|
17
|
+
event: prepResult.event,
|
|
18
|
+
request: new Promise(() => {})
|
|
19
|
+
} };
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { WebhookNode };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { D as WorkflowResult, F as GraphTraverser, M as IOrchestrator, k as ExecutionContext } from "./types-BcrXJEPI.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/runtime/orchestrator.d.ts
|
|
4
|
+
declare class DefaultOrchestrator implements IOrchestrator {
|
|
5
|
+
run(context: ExecutionContext<any, any>, traverser: GraphTraverser): Promise<WorkflowResult<any>>;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { DefaultOrchestrator as t };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { o as IEventBus, r as FlowcraftEvent } from "./types-BcrXJEPI.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/persistent-event-bus.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Interface for a persistent storage mechanism for events.
|
|
6
|
+
* Implementations can store events in databases, log streams, files, etc.
|
|
7
|
+
*/
|
|
8
|
+
interface IEventStore {
|
|
9
|
+
/**
|
|
10
|
+
* Store an event persistently.
|
|
11
|
+
* @param event The event to store
|
|
12
|
+
* @param executionId The execution ID for grouping events
|
|
13
|
+
*/
|
|
14
|
+
store(event: FlowcraftEvent, executionId: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Retrieve all events for a specific execution.
|
|
17
|
+
* @param executionId The execution ID
|
|
18
|
+
* @returns Array of events in chronological order
|
|
19
|
+
*/
|
|
20
|
+
retrieve(executionId: string): Promise<FlowcraftEvent[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Retrieve events for multiple executions.
|
|
23
|
+
* @param executionIds Array of execution IDs
|
|
24
|
+
* @returns Map of execution ID to array of events
|
|
25
|
+
*/
|
|
26
|
+
retrieveMultiple(executionIds: string[]): Promise<Map<string, FlowcraftEvent[]>>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A pluggable event bus adapter that persists all workflow events
|
|
30
|
+
* to a configurable storage backend, enabling time-travel debugging and replay.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* // Using a database-backed store
|
|
35
|
+
* const eventStore = new DatabaseEventStore(dbConnection)
|
|
36
|
+
* const eventBus = new PersistentEventBusAdapter(eventStore)
|
|
37
|
+
* const runtime = new FlowRuntime({ eventBus })
|
|
38
|
+
*
|
|
39
|
+
* // Later, replay the execution
|
|
40
|
+
* const events = await eventStore.retrieve(executionId)
|
|
41
|
+
* const finalState = await runtime.replay(blueprint, events)
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
declare class PersistentEventBusAdapter implements IEventBus {
|
|
45
|
+
private store;
|
|
46
|
+
constructor(store: IEventStore);
|
|
47
|
+
/**
|
|
48
|
+
* Emit an event by storing it persistently.
|
|
49
|
+
* Also emits to console for debugging (can be made configurable).
|
|
50
|
+
*/
|
|
51
|
+
emit(event: FlowcraftEvent): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Simple in-memory event store for testing and development.
|
|
55
|
+
* Not suitable for production use.
|
|
56
|
+
*/
|
|
57
|
+
declare class InMemoryEventStore implements IEventStore {
|
|
58
|
+
private events;
|
|
59
|
+
store(event: FlowcraftEvent, executionId: string): Promise<void>;
|
|
60
|
+
retrieve(executionId: string): Promise<FlowcraftEvent[]>;
|
|
61
|
+
retrieveMultiple(executionIds: string[]): Promise<Map<string, FlowcraftEvent[]>>;
|
|
62
|
+
/**
|
|
63
|
+
* Clear all stored events (useful for testing).
|
|
64
|
+
*/
|
|
65
|
+
clear(): void;
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { InMemoryEventStore as n, PersistentEventBusAdapter as r, IEventStore as t };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { D as WorkflowResult, F as GraphTraverser, M as IOrchestrator, k as ExecutionContext, r as FlowcraftEvent } from "./types-BcrXJEPI.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/runtime/orchestrators/replay.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* An orchestrator that replays a pre-recorded sequence of workflow events
|
|
6
|
+
* to reconstruct the workflow state without executing any node logic.
|
|
7
|
+
*
|
|
8
|
+
* This enables time-travel debugging by allowing developers to inspect
|
|
9
|
+
* the exact state of a workflow at any point in its execution history.
|
|
10
|
+
*/
|
|
11
|
+
declare class ReplayOrchestrator implements IOrchestrator {
|
|
12
|
+
private events;
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new ReplayOrchestrator with a sequence of recorded workflow events.
|
|
15
|
+
*
|
|
16
|
+
* @param events - Array of FlowcraftEvent objects representing the recorded workflow execution
|
|
17
|
+
*/
|
|
18
|
+
constructor(events: FlowcraftEvent[]);
|
|
19
|
+
/**
|
|
20
|
+
* Replays the recorded workflow events to reconstruct the workflow state.
|
|
21
|
+
*
|
|
22
|
+
* This method filters events for the specific execution, applies each event in sequence
|
|
23
|
+
* to rebuild the context state, and returns the final reconstructed workflow result.
|
|
24
|
+
* Replayed executions always have a "completed" status since they reconstruct the final state.
|
|
25
|
+
*
|
|
26
|
+
* @param context - The execution context containing state and services
|
|
27
|
+
* @param _traverser - Graph traverser (unused in replay mode)
|
|
28
|
+
* @returns Promise resolving to the reconstructed workflow result
|
|
29
|
+
*/
|
|
30
|
+
run(context: ExecutionContext<any, any>, _traverser: GraphTraverser): Promise<WorkflowResult<any>>;
|
|
31
|
+
/**
|
|
32
|
+
* Applies a single workflow event to reconstruct the execution state.
|
|
33
|
+
*
|
|
34
|
+
* This method handles different event types by updating the workflow state accordingly,
|
|
35
|
+
* including node completions, context changes, errors, fallbacks, and workflow control events.
|
|
36
|
+
*
|
|
37
|
+
* @param event - The workflow event to apply
|
|
38
|
+
* @param context - The execution context to update
|
|
39
|
+
* @param fallbackMap - Map tracking fallback node relationships (fallbackNodeId -> originalNodeId)
|
|
40
|
+
*/
|
|
41
|
+
private applyEvent;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { ReplayOrchestrator as t };
|