@ankhorage/flow 0.0.0-stage → 0.5.0
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/CHANGELOG.md +40 -0
- package/LICENSE +21 -0
- package/README.md +19 -2
- package/dist/ai.d.ts +7 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +7 -0
- package/dist/ai.js.map +1 -0
- package/dist/execution.d.ts +5 -0
- package/dist/execution.d.ts.map +1 -0
- package/dist/execution.js +5 -0
- package/dist/execution.js.map +1 -0
- package/dist/features/execution/adapters/FileFlowPersistenceAdapter.d.ts +4 -0
- package/dist/features/execution/adapters/FileFlowPersistenceAdapter.d.ts.map +1 -0
- package/dist/features/execution/adapters/FileFlowPersistenceAdapter.js +219 -0
- package/dist/features/execution/adapters/FileFlowPersistenceAdapter.js.map +1 -0
- package/dist/features/execution/application/cancelDurableRunAsync.d.ts +8 -0
- package/dist/features/execution/application/cancelDurableRunAsync.d.ts.map +1 -0
- package/dist/features/execution/application/cancelDurableRunAsync.js +22 -0
- package/dist/features/execution/application/cancelDurableRunAsync.js.map +1 -0
- package/dist/features/execution/application/createDurableRunAsync.d.ts +12 -0
- package/dist/features/execution/application/createDurableRunAsync.d.ts.map +1 -0
- package/dist/features/execution/application/createDurableRunAsync.js +8 -0
- package/dist/features/execution/application/createDurableRunAsync.js.map +1 -0
- package/dist/features/execution/application/executeFlowAsync.d.ts +4 -0
- package/dist/features/execution/application/executeFlowAsync.d.ts.map +1 -0
- package/dist/features/execution/application/executeFlowAsync.js +211 -0
- package/dist/features/execution/application/executeFlowAsync.js.map +1 -0
- package/dist/features/execution/application/runDurableWorkerAsync.d.ts +52 -0
- package/dist/features/execution/application/runDurableWorkerAsync.d.ts.map +1 -0
- package/dist/features/execution/application/runDurableWorkerAsync.js +195 -0
- package/dist/features/execution/application/runDurableWorkerAsync.js.map +1 -0
- package/dist/features/execution/domain/assertFlowGraphSchemasSupported.d.ts +5 -0
- package/dist/features/execution/domain/assertFlowGraphSchemasSupported.d.ts.map +1 -0
- package/dist/features/execution/domain/assertFlowGraphSchemasSupported.js +11 -0
- package/dist/features/execution/domain/assertFlowGraphSchemasSupported.js.map +1 -0
- package/dist/features/execution/domain/calculateRetryDueAt.d.ts +4 -0
- package/dist/features/execution/domain/calculateRetryDueAt.d.ts.map +1 -0
- package/dist/features/execution/domain/calculateRetryDueAt.js +14 -0
- package/dist/features/execution/domain/calculateRetryDueAt.js.map +1 -0
- package/dist/features/execution/domain/compileFlowGraph.d.ts +5 -0
- package/dist/features/execution/domain/compileFlowGraph.d.ts.map +1 -0
- package/dist/features/execution/domain/compileFlowGraph.js +76 -0
- package/dist/features/execution/domain/compileFlowGraph.js.map +1 -0
- package/dist/features/execution/domain/createDefaultNodeRegistry.d.ts +4 -0
- package/dist/features/execution/domain/createDefaultNodeRegistry.d.ts.map +1 -0
- package/dist/features/execution/domain/createDefaultNodeRegistry.js +15 -0
- package/dist/features/execution/domain/createDefaultNodeRegistry.js.map +1 -0
- package/dist/features/execution/domain/createDurableRun.d.ts +7 -0
- package/dist/features/execution/domain/createDurableRun.d.ts.map +1 -0
- package/dist/features/execution/domain/createDurableRun.js +10 -0
- package/dist/features/execution/domain/createDurableRun.js.map +1 -0
- package/dist/features/execution/domain/createRetryWork.d.ts +4 -0
- package/dist/features/execution/domain/createRetryWork.d.ts.map +1 -0
- package/dist/features/execution/domain/createRetryWork.js +17 -0
- package/dist/features/execution/domain/createRetryWork.js.map +1 -0
- package/dist/features/execution/domain/isCronDueAt.d.ts +7 -0
- package/dist/features/execution/domain/isCronDueAt.d.ts.map +1 -0
- package/dist/features/execution/domain/isCronDueAt.js +114 -0
- package/dist/features/execution/domain/isCronDueAt.js.map +1 -0
- package/dist/features/execution/domain/validateFlowDataSchema.d.ts +6 -0
- package/dist/features/execution/domain/validateFlowDataSchema.d.ts.map +1 -0
- package/dist/features/execution/domain/validateFlowDataSchema.js +144 -0
- package/dist/features/execution/domain/validateFlowDataSchema.js.map +1 -0
- package/dist/features/execution/ports/FlowPersistencePort.d.ts +34 -0
- package/dist/features/execution/ports/FlowPersistencePort.d.ts.map +1 -0
- package/dist/features/execution/ports/FlowPersistencePort.js +2 -0
- package/dist/features/execution/ports/FlowPersistencePort.js.map +1 -0
- package/dist/features/mcp/McpToolError.d.ts +6 -0
- package/dist/features/mcp/McpToolError.d.ts.map +1 -0
- package/dist/features/mcp/McpToolError.js +10 -0
- package/dist/features/mcp/McpToolError.js.map +1 -0
- package/dist/features/mcp/createMcpRemoteTransport.d.ts +8 -0
- package/dist/features/mcp/createMcpRemoteTransport.d.ts.map +1 -0
- package/dist/features/mcp/createMcpRemoteTransport.js +23 -0
- package/dist/features/mcp/createMcpRemoteTransport.js.map +1 -0
- package/dist/features/mcp/createMcpToolRegistry.d.ts +10 -0
- package/dist/features/mcp/createMcpToolRegistry.d.ts.map +1 -0
- package/dist/features/mcp/createMcpToolRegistry.js +185 -0
- package/dist/features/mcp/createMcpToolRegistry.js.map +1 -0
- package/dist/features/mcp/loadMcpSdkAsync.d.ts +12 -0
- package/dist/features/mcp/loadMcpSdkAsync.d.ts.map +1 -0
- package/dist/features/mcp/loadMcpSdkAsync.js +20 -0
- package/dist/features/mcp/loadMcpSdkAsync.js.map +1 -0
- package/dist/features/mcp/prepareMcpProviders.d.ts +4 -0
- package/dist/features/mcp/prepareMcpProviders.d.ts.map +1 -0
- package/dist/features/mcp/prepareMcpProviders.js +97 -0
- package/dist/features/mcp/prepareMcpProviders.js.map +1 -0
- package/dist/features/mcp/validateMcpSchema.d.ts +6 -0
- package/dist/features/mcp/validateMcpSchema.d.ts.map +1 -0
- package/dist/features/mcp/validateMcpSchema.js +220 -0
- package/dist/features/mcp/validateMcpSchema.js.map +1 -0
- package/dist/features/security/FlowSecurityError.d.ts +6 -0
- package/dist/features/security/FlowSecurityError.d.ts.map +1 -0
- package/dist/features/security/FlowSecurityError.js +10 -0
- package/dist/features/security/FlowSecurityError.js.map +1 -0
- package/dist/features/security/application/createFlowSecurityPort.d.ts +12 -0
- package/dist/features/security/application/createFlowSecurityPort.d.ts.map +1 -0
- package/dist/features/security/application/createFlowSecurityPort.js +208 -0
- package/dist/features/security/application/createFlowSecurityPort.js.map +1 -0
- package/dist/features/security/domain/validateFlowSecurityTarget.d.ts +4 -0
- package/dist/features/security/domain/validateFlowSecurityTarget.d.ts.map +1 -0
- package/dist/features/security/domain/validateFlowSecurityTarget.js +57 -0
- package/dist/features/security/domain/validateFlowSecurityTarget.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +4 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +3 -0
- package/dist/mcp.js.map +1 -0
- package/dist/social.d.ts +7 -0
- package/dist/social.d.ts.map +1 -0
- package/dist/social.js +7 -0
- package/dist/social.js.map +1 -0
- package/dist/types/durableExecution.d.ts +55 -0
- package/dist/types/durableExecution.d.ts.map +1 -0
- package/dist/types/durableExecution.js +2 -0
- package/dist/types/durableExecution.js.map +1 -0
- package/dist/types/execution.d.ts +33 -0
- package/dist/types/execution.d.ts.map +1 -0
- package/dist/types/execution.js +2 -0
- package/dist/types/execution.js.map +1 -0
- package/dist/types/mcp.d.ts +61 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +2 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/types/security.d.ts +123 -0
- package/dist/types/security.d.ts.map +1 -0
- package/dist/types/security.js +2 -0
- package/dist/types/security.js.map +1 -0
- package/package.json +90 -4
- package/src/ai.ts +6 -0
- package/src/execution.ts +9 -0
- package/src/features/execution/adapters/FileFlowPersistenceAdapter.ts +333 -0
- package/src/features/execution/application/cancelDurableRunAsync.ts +28 -0
- package/src/features/execution/application/createDurableRunAsync.ts +14 -0
- package/src/features/execution/application/executeFlowAsync.ts +338 -0
- package/src/features/execution/application/runDurableWorkerAsync.ts +337 -0
- package/src/features/execution/domain/assertFlowGraphSchemasSupported.ts +17 -0
- package/src/features/execution/domain/calculateRetryDueAt.ts +21 -0
- package/src/features/execution/domain/compileFlowGraph.ts +97 -0
- package/src/features/execution/domain/createDefaultNodeRegistry.ts +26 -0
- package/src/features/execution/domain/createDurableRun.ts +14 -0
- package/src/features/execution/domain/createRetryWork.ts +22 -0
- package/src/features/execution/domain/isCronDueAt.ts +131 -0
- package/src/features/execution/domain/validateFlowDataSchema.ts +218 -0
- package/src/features/execution/ports/FlowPersistencePort.ts +38 -0
- package/src/features/mcp/McpToolError.ts +10 -0
- package/src/features/mcp/createMcpRemoteTransport.ts +34 -0
- package/src/features/mcp/createMcpToolRegistry.ts +324 -0
- package/src/features/mcp/loadMcpSdkAsync.ts +32 -0
- package/src/features/mcp/prepareMcpProviders.ts +135 -0
- package/src/features/mcp/validateMcpSchema.ts +269 -0
- package/src/features/security/FlowSecurityError.ts +17 -0
- package/src/features/security/application/createFlowSecurityPort.ts +339 -0
- package/src/features/security/domain/validateFlowSecurityTarget.ts +73 -0
- package/src/index.ts +49 -0
- package/src/mcp.ts +11 -0
- package/src/social.ts +6 -0
- package/src/types/durableExecution.ts +61 -0
- package/src/types/execution.ts +41 -0
- package/src/types/mcp.ts +70 -0
- package/src/types/security.ts +148 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FlowDefinition } from '@ankhorage/contracts/flow';
|
|
2
|
+
import type { FlowCompiledGraph, FlowNodeRegistry } from '../../../types/execution.js';
|
|
3
|
+
/*** Compile a valid acyclic graph into deterministic execution order. */
|
|
4
|
+
export declare const compileFlowGraph: (definition: FlowDefinition, registry: FlowNodeRegistry) => FlowCompiledGraph;
|
|
5
|
+
//# sourceMappingURL=compileFlowGraph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compileFlowGraph.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/domain/compileFlowGraph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAsB,MAAM,2BAA2B,CAAC;AAEpF,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEvF,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,GAC3B,YAAY,cAAc,EAC1B,UAAU,gBAAgB,KACzB,iBAYF,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*** Compile a valid acyclic graph into deterministic execution order. */
|
|
2
|
+
export const compileFlowGraph = (definition, registry) => {
|
|
3
|
+
const nodesById = indexNodes(definition.nodes);
|
|
4
|
+
const edgesBySourceNodeId = indexEdges(definition.edges, nodesById);
|
|
5
|
+
for (const node of definition.nodes)
|
|
6
|
+
assertNodeIsExecutable(node, registry);
|
|
7
|
+
return {
|
|
8
|
+
definition,
|
|
9
|
+
edgesBySourceNodeId,
|
|
10
|
+
topologicalNodeIds: orderNodes(definition.nodes.map(({ id }) => id), definition.edges),
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/*** Index nodes while rejecting duplicate graph identifiers. */
|
|
14
|
+
const indexNodes = (nodes) => {
|
|
15
|
+
const entries = nodes.map((node) => [node.id, node]);
|
|
16
|
+
if (new Set(entries.map(([id]) => id)).size !== entries.length)
|
|
17
|
+
throw new Error('FLOW_GRAPH_DUPLICATE_NODE_ID');
|
|
18
|
+
return Object.fromEntries(entries);
|
|
19
|
+
};
|
|
20
|
+
/*** Validate and group graph edges by their producing node. */
|
|
21
|
+
const indexEdges = (edges, nodesById) => {
|
|
22
|
+
if (new Set(edges.map(({ id }) => id)).size !== edges.length)
|
|
23
|
+
throw new Error('FLOW_GRAPH_DUPLICATE_EDGE_ID');
|
|
24
|
+
for (const edge of edges) {
|
|
25
|
+
const source = nodesById[edge.source.nodeId];
|
|
26
|
+
const target = nodesById[edge.target.nodeId];
|
|
27
|
+
if (!source || !target)
|
|
28
|
+
throw new Error(`FLOW_GRAPH_UNKNOWN_EDGE_NODE:${edge.id}`);
|
|
29
|
+
assertPort(source, edge.source.portId, 'output', edge.id);
|
|
30
|
+
assertPort(target, edge.target.portId, 'input', edge.id);
|
|
31
|
+
}
|
|
32
|
+
return edges.reduce((grouped, edge) => ({
|
|
33
|
+
...grouped,
|
|
34
|
+
[edge.source.nodeId]: [...(grouped[edge.source.nodeId] ?? []), edge],
|
|
35
|
+
}), {});
|
|
36
|
+
};
|
|
37
|
+
/*** Ensure a graph port exists exactly once with the expected direction. */
|
|
38
|
+
const assertPort = (node, portId, direction, edgeId) => {
|
|
39
|
+
if (node.ports.filter((port) => port.id === portId && port.direction === direction).length !== 1)
|
|
40
|
+
throw new Error(`FLOW_GRAPH_INVALID_EDGE_PORT:${edgeId}`);
|
|
41
|
+
};
|
|
42
|
+
/*** Reject unknown operations and code capabilities before execution can begin. */
|
|
43
|
+
const assertNodeIsExecutable = (node, registry) => {
|
|
44
|
+
const { capabilityId, providerId } = node.capability;
|
|
45
|
+
const operationId = `${providerId}:${capabilityId}`;
|
|
46
|
+
if (capabilityId === 'code')
|
|
47
|
+
throw new Error('FLOW_CODE_EXECUTION_DISABLED');
|
|
48
|
+
if (!Object.hasOwn(registry, operationId))
|
|
49
|
+
throw new Error(`FLOW_OPERATION_UNREGISTERED:${operationId}`);
|
|
50
|
+
if (new Set(node.ports.map(({ direction, id }) => `${direction}:${id}`)).size !== node.ports.length)
|
|
51
|
+
throw new Error(`FLOW_GRAPH_DUPLICATE_PORT_ID:${node.id}`);
|
|
52
|
+
};
|
|
53
|
+
/*** Produce stable topological order and reject cycles without an explicit bounded-loop contract. */
|
|
54
|
+
const orderNodes = (nodeIds, edges) => {
|
|
55
|
+
const incomingCounts = new Map(nodeIds.map((nodeId) => [nodeId, 0]));
|
|
56
|
+
for (const edge of edges)
|
|
57
|
+
incomingCounts.set(edge.target.nodeId, (incomingCounts.get(edge.target.nodeId) ?? 0) + 1);
|
|
58
|
+
const pending = nodeIds.filter((nodeId) => incomingCounts.get(nodeId) === 0);
|
|
59
|
+
const ordered = [];
|
|
60
|
+
while (pending.length > 0) {
|
|
61
|
+
const nodeId = pending.shift();
|
|
62
|
+
if (!nodeId)
|
|
63
|
+
continue;
|
|
64
|
+
ordered.push(nodeId);
|
|
65
|
+
for (const edge of edges.filter((candidate) => candidate.source.nodeId === nodeId)) {
|
|
66
|
+
const nextCount = (incomingCounts.get(edge.target.nodeId) ?? 0) - 1;
|
|
67
|
+
incomingCounts.set(edge.target.nodeId, nextCount);
|
|
68
|
+
if (nextCount === 0)
|
|
69
|
+
pending.push(edge.target.nodeId);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (ordered.length !== nodeIds.length)
|
|
73
|
+
throw new Error('FLOW_GRAPH_CYCLE');
|
|
74
|
+
return ordered;
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=compileFlowGraph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compileFlowGraph.js","sourceRoot":"","sources":["../../../../src/features/execution/domain/compileFlowGraph.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FlowDefinition, FlowEdge, FlowNode } from '@ankhorage/contracts/flow';\n\nimport type { FlowCompiledGraph, FlowNodeRegistry } from '../../../types/execution.js';\n\n/*** Compile a valid acyclic graph into deterministic execution order. */\nexport const compileFlowGraph = (\n definition: FlowDefinition,\n registry: FlowNodeRegistry,\n): FlowCompiledGraph => {\n const nodesById = indexNodes(definition.nodes);\n const edgesBySourceNodeId = indexEdges(definition.edges, nodesById);\n for (const node of definition.nodes) assertNodeIsExecutable(node, registry);\n return {\n definition,\n edgesBySourceNodeId,\n topologicalNodeIds: orderNodes(\n definition.nodes.map(({ id }) => id),\n definition.edges,\n ),\n };\n};\n\n/*** Index nodes while rejecting duplicate graph identifiers. */\nconst indexNodes = (nodes: readonly FlowNode[]): Readonly<Record<string, FlowNode>> => {\n const entries = nodes.map((node) => [node.id, node] as const);\n if (new Set(entries.map(([id]) => id)).size !== entries.length)\n throw new Error('FLOW_GRAPH_DUPLICATE_NODE_ID');\n return Object.fromEntries(entries);\n};\n\n/*** Validate and group graph edges by their producing node. */\nconst indexEdges = (\n edges: readonly FlowEdge[],\n nodesById: Readonly<Record<string, FlowNode>>,\n): Readonly<Record<string, readonly FlowEdge[]>> => {\n if (new Set(edges.map(({ id }) => id)).size !== edges.length)\n throw new Error('FLOW_GRAPH_DUPLICATE_EDGE_ID');\n for (const edge of edges) {\n const source = nodesById[edge.source.nodeId];\n const target = nodesById[edge.target.nodeId];\n if (!source || !target) throw new Error(`FLOW_GRAPH_UNKNOWN_EDGE_NODE:${edge.id}`);\n assertPort(source, edge.source.portId, 'output', edge.id);\n assertPort(target, edge.target.portId, 'input', edge.id);\n }\n return edges.reduce<Readonly<Record<string, readonly FlowEdge[]>>>(\n (grouped, edge) => ({\n ...grouped,\n [edge.source.nodeId]: [...(grouped[edge.source.nodeId] ?? []), edge],\n }),\n {},\n );\n};\n\n/*** Ensure a graph port exists exactly once with the expected direction. */\nconst assertPort = (\n node: FlowNode,\n portId: string,\n direction: 'input' | 'output',\n edgeId: string,\n): void => {\n if (node.ports.filter((port) => port.id === portId && port.direction === direction).length !== 1)\n throw new Error(`FLOW_GRAPH_INVALID_EDGE_PORT:${edgeId}`);\n};\n\n/*** Reject unknown operations and code capabilities before execution can begin. */\nconst assertNodeIsExecutable = (node: FlowNode, registry: FlowNodeRegistry): void => {\n const { capabilityId, providerId } = node.capability;\n const operationId = `${providerId}:${capabilityId}`;\n if (capabilityId === 'code') throw new Error('FLOW_CODE_EXECUTION_DISABLED');\n if (!Object.hasOwn(registry, operationId))\n throw new Error(`FLOW_OPERATION_UNREGISTERED:${operationId}`);\n if (\n new Set(node.ports.map(({ direction, id }) => `${direction}:${id}`)).size !== node.ports.length\n )\n throw new Error(`FLOW_GRAPH_DUPLICATE_PORT_ID:${node.id}`);\n};\n\n/*** Produce stable topological order and reject cycles without an explicit bounded-loop contract. */\nconst orderNodes = (nodeIds: readonly string[], edges: readonly FlowEdge[]): readonly string[] => {\n const incomingCounts = new Map(nodeIds.map((nodeId) => [nodeId, 0]));\n for (const edge of edges)\n incomingCounts.set(edge.target.nodeId, (incomingCounts.get(edge.target.nodeId) ?? 0) + 1);\n const pending = nodeIds.filter((nodeId) => incomingCounts.get(nodeId) === 0);\n const ordered: string[] = [];\n while (pending.length > 0) {\n const nodeId = pending.shift();\n if (!nodeId) continue;\n ordered.push(nodeId);\n for (const edge of edges.filter((candidate) => candidate.source.nodeId === nodeId)) {\n const nextCount = (incomingCounts.get(edge.target.nodeId) ?? 0) - 1;\n incomingCounts.set(edge.target.nodeId, nextCount);\n if (nextCount === 0) pending.push(edge.target.nodeId);\n }\n }\n if (ordered.length !== nodeIds.length) throw new Error('FLOW_GRAPH_CYCLE');\n return ordered;\n};\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FlowNodeRegistry } from '../../../types/execution.js';
|
|
2
|
+
/*** Create the built-in operations that have no provider or host dependency. */
|
|
3
|
+
export declare const createDefaultNodeRegistry: () => FlowNodeRegistry;
|
|
4
|
+
//# sourceMappingURL=createDefaultNodeRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createDefaultNodeRegistry.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/domain/createDefaultNodeRegistry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAqB,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAEvF,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,QAAO,gBAI3C,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*** Create the built-in operations that have no provider or host dependency. */
|
|
2
|
+
export const createDefaultNodeRegistry = () => ({
|
|
3
|
+
'flow:condition': { executeAsync: () => Promise.resolve({}) },
|
|
4
|
+
'flow:noop': { executeAsync: () => Promise.resolve({}) },
|
|
5
|
+
'flow:transform': { executeAsync: transformAsync },
|
|
6
|
+
});
|
|
7
|
+
/*** Copy configured inline or artifact values to matching output ports without evaluating code. */
|
|
8
|
+
const transformAsync = ({ config, node }) => {
|
|
9
|
+
const outputIds = new Set(node.ports.filter((port) => port.direction === 'output').map(({ id }) => id));
|
|
10
|
+
const output = Object.fromEntries(Object.entries(config ?? {}).filter((entry) => outputIds.has(entry[0]) && isDataRef(entry[1])));
|
|
11
|
+
return Promise.resolve({ output });
|
|
12
|
+
};
|
|
13
|
+
/*** Narrow a configuration value to data that may safely leave a node. */
|
|
14
|
+
const isDataRef = (value) => value.kind !== 'secret';
|
|
15
|
+
//# sourceMappingURL=createDefaultNodeRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createDefaultNodeRegistry.js","sourceRoot":"","sources":["../../../../src/features/execution/domain/createDefaultNodeRegistry.ts"],"names":[],"mappings":"AAIA,gFAAgF;AAChF,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAqB,EAAE,CAAC,CAAC;IAChE,gBAAgB,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;IAC7D,WAAW,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;IACxD,gBAAgB,EAAE,EAAE,YAAY,EAAE,cAAc,EAAE;CACnD,CAAC,CAAC;AAEH,mGAAmG;AACnG,MAAM,cAAc,GAAsC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;IAC7E,MAAM,SAAS,GAAG,IAAI,GAAG,CACvB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAC7E,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAC/B,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,CACjC,CAAC,KAAK,EAAkC,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAC1F,CACF,CAAC;IACF,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;AACrC,CAAC,CAAC;AAEF,0EAA0E;AAC1E,MAAM,SAAS,GAAG,CAAC,KAA0B,EAAwB,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC","sourcesContent":["import type { FlowDataRef, FlowNodeConfigValue } from '@ankhorage/contracts/flow';\n\nimport type { FlowNodeOperation, FlowNodeRegistry } from '../../../types/execution.js';\n\n/*** Create the built-in operations that have no provider or host dependency. */\nexport const createDefaultNodeRegistry = (): FlowNodeRegistry => ({\n 'flow:condition': { executeAsync: () => Promise.resolve({}) },\n 'flow:noop': { executeAsync: () => Promise.resolve({}) },\n 'flow:transform': { executeAsync: transformAsync },\n});\n\n/*** Copy configured inline or artifact values to matching output ports without evaluating code. */\nconst transformAsync: FlowNodeOperation['executeAsync'] = ({ config, node }) => {\n const outputIds = new Set(\n node.ports.filter((port) => port.direction === 'output').map(({ id }) => id),\n );\n const output = Object.fromEntries(\n Object.entries(config ?? {}).filter(\n (entry): entry is [string, FlowDataRef] => outputIds.has(entry[0]) && isDataRef(entry[1]),\n ),\n );\n return Promise.resolve({ output });\n};\n\n/*** Narrow a configuration value to data that may safely leave a node. */\nconst isDataRef = (value: FlowNodeConfigValue): value is FlowDataRef => value.kind !== 'secret';\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FlowDefinitionRef, FlowDurableRun } from '../../../types/durableExecution.js';
|
|
2
|
+
/*** Create a revision-zero run pinned permanently to one published flow definition. */
|
|
3
|
+
export declare const createDurableRun: (input: {
|
|
4
|
+
readonly flow: FlowDefinitionRef;
|
|
5
|
+
readonly id: string;
|
|
6
|
+
}, now: Date) => FlowDurableRun;
|
|
7
|
+
//# sourceMappingURL=createDurableRun.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createDurableRun.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/domain/createDurableRun.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAE5F,uFAAuF;AACvF,eAAO,MAAM,gBAAgB,GAC3B,OAAO;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAChE,KAAK,IAAI,KACR,cAOD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*** Create a revision-zero run pinned permanently to one published flow definition. */
|
|
2
|
+
export const createDurableRun = (input, now) => ({
|
|
3
|
+
createdAt: now.toISOString(),
|
|
4
|
+
flow: { ...input.flow },
|
|
5
|
+
id: input.id,
|
|
6
|
+
revision: 0,
|
|
7
|
+
status: 'queued',
|
|
8
|
+
updatedAt: now.toISOString(),
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=createDurableRun.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createDurableRun.js","sourceRoot":"","sources":["../../../../src/features/execution/domain/createDurableRun.ts"],"names":[],"mappings":"AAEA,uFAAuF;AACvF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,KAAgE,EAChE,GAAS,EACO,EAAE,CAAC,CAAC;IACpB,SAAS,EAAE,GAAG,CAAC,WAAW,EAAE;IAC5B,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE;IACvB,EAAE,EAAE,KAAK,CAAC,EAAE;IACZ,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,QAAQ;IAChB,SAAS,EAAE,GAAG,CAAC,WAAW,EAAE;CAC7B,CAAC,CAAC","sourcesContent":["import type { FlowDefinitionRef, FlowDurableRun } from '../../../types/durableExecution.js';\n\n/*** Create a revision-zero run pinned permanently to one published flow definition. */\nexport const createDurableRun = (\n input: { readonly flow: FlowDefinitionRef; readonly id: string },\n now: Date,\n): FlowDurableRun => ({\n createdAt: now.toISOString(),\n flow: { ...input.flow },\n id: input.id,\n revision: 0,\n status: 'queued',\n updatedAt: now.toISOString(),\n});\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FlowDurableWork, FlowRetryPolicy } from '../../../types/durableExecution.js';
|
|
2
|
+
/*** Create the next immutable work attempt from a completed attempt and controlled clock. */
|
|
3
|
+
export declare const createRetryWork: (completed: FlowDurableWork, now: Date, policy: FlowRetryPolicy) => FlowDurableWork;
|
|
4
|
+
//# sourceMappingURL=createRetryWork.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createRetryWork.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/domain/createRetryWork.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAG3F,6FAA6F;AAC7F,eAAO,MAAM,eAAe,GAC1B,WAAW,eAAe,EAC1B,KAAK,IAAI,EACT,QAAQ,eAAe,KACtB,eAaD,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { calculateRetryDueAt } from './calculateRetryDueAt.js';
|
|
2
|
+
/*** Create the next immutable work attempt from a completed attempt and controlled clock. */
|
|
3
|
+
export const createRetryWork = (completed, now, policy) => ({
|
|
4
|
+
...(completed.idempotencyKey === undefined ? {} : { idempotencyKey: completed.idempotencyKey }),
|
|
5
|
+
attempt: completed.attempt + 1,
|
|
6
|
+
dueAt: calculateRetryDueAt(now, completed.attempt, policy),
|
|
7
|
+
effectKind: completed.effectKind,
|
|
8
|
+
flow: { ...completed.flow },
|
|
9
|
+
id: `${completed.runId}:attempt:${completed.attempt + 1}`,
|
|
10
|
+
runId: completed.runId,
|
|
11
|
+
...(completed.securityOperation === undefined
|
|
12
|
+
? {}
|
|
13
|
+
: { securityOperation: completed.securityOperation }),
|
|
14
|
+
...(completed.stepId === undefined ? {} : { stepId: completed.stepId }),
|
|
15
|
+
status: 'ready',
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=createRetryWork.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createRetryWork.js","sourceRoot":"","sources":["../../../../src/features/execution/domain/createRetryWork.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,6FAA6F;AAC7F,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,SAA0B,EAC1B,GAAS,EACT,MAAuB,EACN,EAAE,CAAC,CAAC;IACrB,GAAG,CAAC,SAAS,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,SAAS,CAAC,cAAc,EAAE,CAAC;IAC/F,OAAO,EAAE,SAAS,CAAC,OAAO,GAAG,CAAC;IAC9B,KAAK,EAAE,mBAAmB,CAAC,GAAG,EAAE,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1D,UAAU,EAAE,SAAS,CAAC,UAAU;IAChC,IAAI,EAAE,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE;IAC3B,EAAE,EAAE,GAAG,SAAS,CAAC,KAAK,YAAY,SAAS,CAAC,OAAO,GAAG,CAAC,EAAE;IACzD,KAAK,EAAE,SAAS,CAAC,KAAK;IACtB,GAAG,CAAC,SAAS,CAAC,iBAAiB,KAAK,SAAS;QAC3C,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,EAAE,iBAAiB,EAAE,SAAS,CAAC,iBAAiB,EAAE,CAAC;IACvD,GAAG,CAAC,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC;IACvE,MAAM,EAAE,OAAO;CAChB,CAAC,CAAC","sourcesContent":["import type { FlowDurableWork, FlowRetryPolicy } from '../../../types/durableExecution.js';\nimport { calculateRetryDueAt } from './calculateRetryDueAt.js';\n\n/*** Create the next immutable work attempt from a completed attempt and controlled clock. */\nexport const createRetryWork = (\n completed: FlowDurableWork,\n now: Date,\n policy: FlowRetryPolicy,\n): FlowDurableWork => ({\n ...(completed.idempotencyKey === undefined ? {} : { idempotencyKey: completed.idempotencyKey }),\n attempt: completed.attempt + 1,\n dueAt: calculateRetryDueAt(now, completed.attempt, policy),\n effectKind: completed.effectKind,\n flow: { ...completed.flow },\n id: `${completed.runId}:attempt:${completed.attempt + 1}`,\n runId: completed.runId,\n ...(completed.securityOperation === undefined\n ? {}\n : { securityOperation: completed.securityOperation }),\n ...(completed.stepId === undefined ? {} : { stepId: completed.stepId }),\n status: 'ready',\n});\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Determine whether one UTC instant matches a five-field POSIX cron expression in an IANA zone. */
|
|
2
|
+
export declare const isCronDueAt: (input: {
|
|
3
|
+
readonly cron: string;
|
|
4
|
+
readonly now: Date;
|
|
5
|
+
readonly timezone?: string;
|
|
6
|
+
}) => boolean;
|
|
7
|
+
//# sourceMappingURL=isCronDueAt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isCronDueAt.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/domain/isCronDueAt.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,eAAO,MAAM,WAAW,GAAI,OAAO;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;IACnB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B,KAAG,OAuBH,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/** Determine whether one UTC instant matches a five-field POSIX cron expression in an IANA zone. */
|
|
2
|
+
export const isCronDueAt = (input) => {
|
|
3
|
+
const fields = input.cron.trim().split(/\s+/);
|
|
4
|
+
if (fields.length !== 5)
|
|
5
|
+
throw new Error('FLOW_CRON_INVALID');
|
|
6
|
+
const local = localParts(input.now, input.timezone ?? 'Etc/UTC');
|
|
7
|
+
const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
|
|
8
|
+
if (minute === undefined ||
|
|
9
|
+
hour === undefined ||
|
|
10
|
+
dayOfMonth === undefined ||
|
|
11
|
+
month === undefined ||
|
|
12
|
+
dayOfWeek === undefined)
|
|
13
|
+
throw new Error('FLOW_CRON_INVALID');
|
|
14
|
+
const minuteMatches = matchesField(minute, local.minute, 0, 59);
|
|
15
|
+
const hourMatches = matchesField(hour, local.hour, 0, 23);
|
|
16
|
+
const monthMatches = matchesField(month, local.month, 1, 12);
|
|
17
|
+
const dayOfMonthMatches = matchesField(dayOfMonth, local.day, 1, 31);
|
|
18
|
+
const dayOfWeekMatches = matchesField(dayOfWeek, local.dayOfWeek, 0, 7);
|
|
19
|
+
const dayMatches = dayOfMonth === '*' || dayOfWeek === '*'
|
|
20
|
+
? dayOfMonthMatches && dayOfWeekMatches
|
|
21
|
+
: dayOfMonthMatches || dayOfWeekMatches;
|
|
22
|
+
return minuteMatches && hourMatches && monthMatches && dayMatches;
|
|
23
|
+
};
|
|
24
|
+
/** Format a UTC instant into its real local wall-clock fields; duplicate fall-back instants remain distinct instants. */
|
|
25
|
+
const localParts = (now, timezone) => {
|
|
26
|
+
try {
|
|
27
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
28
|
+
day: 'numeric',
|
|
29
|
+
hour: 'numeric',
|
|
30
|
+
hourCycle: 'h23',
|
|
31
|
+
minute: 'numeric',
|
|
32
|
+
month: 'numeric',
|
|
33
|
+
timeZone: timezone,
|
|
34
|
+
weekday: 'short',
|
|
35
|
+
}).formatToParts(now);
|
|
36
|
+
const read = (type) => parts.find((part) => part.type === type)?.value ?? invalidTimezone();
|
|
37
|
+
return {
|
|
38
|
+
day: Number(read('day')),
|
|
39
|
+
dayOfWeek: weekday(read('weekday')),
|
|
40
|
+
hour: Number(read('hour')),
|
|
41
|
+
minute: Number(read('minute')),
|
|
42
|
+
month: Number(read('month')),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw new Error('FLOW_CRON_TIMEZONE_INVALID');
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
/** Match one POSIX cron field including lists, ranges, and positive step values. */
|
|
50
|
+
const matchesField = (field, value, minimum, maximum) => field.split(',').some((part) => matchesPart(part, value, minimum, maximum));
|
|
51
|
+
/** Match one validated cron list member. */
|
|
52
|
+
const matchesPart = (part, value, minimum, maximum) => {
|
|
53
|
+
const [rangePart, stepPart] = part.split('/');
|
|
54
|
+
if (part.split('/').length > 2 || rangePart === undefined)
|
|
55
|
+
throw new Error('FLOW_CRON_INVALID');
|
|
56
|
+
const step = stepPart === undefined ? 1 : parseValue(stepPart, 1, maximum - minimum + 1);
|
|
57
|
+
const [start, end] = parseRange(rangePart, minimum, maximum);
|
|
58
|
+
return value >= start && value <= end && (value - start) % step === 0;
|
|
59
|
+
};
|
|
60
|
+
/** Parse a wildcard, number, or inclusive numeric range. */
|
|
61
|
+
const parseRange = (range, minimum, maximum) => {
|
|
62
|
+
if (range === '*')
|
|
63
|
+
return [minimum, maximum];
|
|
64
|
+
const values = range.split('-');
|
|
65
|
+
if (values.length === 1) {
|
|
66
|
+
const [single] = values;
|
|
67
|
+
if (single === undefined)
|
|
68
|
+
throw new Error('FLOW_CRON_INVALID');
|
|
69
|
+
const value = parseValue(single, minimum, maximum);
|
|
70
|
+
return [value, value];
|
|
71
|
+
}
|
|
72
|
+
if (values.length !== 2 || values[0] === undefined || values[1] === undefined)
|
|
73
|
+
throw new Error('FLOW_CRON_INVALID');
|
|
74
|
+
const start = parseValue(values[0], minimum, maximum);
|
|
75
|
+
const end = parseValue(values[1], minimum, maximum);
|
|
76
|
+
if (end < start)
|
|
77
|
+
throw new Error('FLOW_CRON_INVALID');
|
|
78
|
+
return [start, end];
|
|
79
|
+
};
|
|
80
|
+
/** Parse one strict POSIX cron integer. */
|
|
81
|
+
const parseValue = (value, minimum, maximum) => {
|
|
82
|
+
if (!/^\d+$/.test(value))
|
|
83
|
+
throw new Error('FLOW_CRON_INVALID');
|
|
84
|
+
const parsed = Number(value);
|
|
85
|
+
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum)
|
|
86
|
+
throw new Error('FLOW_CRON_INVALID');
|
|
87
|
+
return parsed;
|
|
88
|
+
};
|
|
89
|
+
/** Map English short weekday names produced by the fixed formatter to POSIX weekday values. */
|
|
90
|
+
const weekday = (value) => {
|
|
91
|
+
switch (value) {
|
|
92
|
+
case 'Sun':
|
|
93
|
+
return 0;
|
|
94
|
+
case 'Mon':
|
|
95
|
+
return 1;
|
|
96
|
+
case 'Tue':
|
|
97
|
+
return 2;
|
|
98
|
+
case 'Wed':
|
|
99
|
+
return 3;
|
|
100
|
+
case 'Thu':
|
|
101
|
+
return 4;
|
|
102
|
+
case 'Fri':
|
|
103
|
+
return 5;
|
|
104
|
+
case 'Sat':
|
|
105
|
+
return 6;
|
|
106
|
+
default:
|
|
107
|
+
return invalidTimezone();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
/** Keep an impossible formatter shape distinct from a cron syntax error. */
|
|
111
|
+
const invalidTimezone = () => {
|
|
112
|
+
throw new Error('FLOW_CRON_TIMEZONE_INVALID');
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=isCronDueAt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isCronDueAt.js","sourceRoot":"","sources":["../../../../src/features/execution/domain/isCronDueAt.ts"],"names":[],"mappings":"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","sourcesContent":["/** Determine whether one UTC instant matches a five-field POSIX cron expression in an IANA zone. */\nexport const isCronDueAt = (input: {\n readonly cron: string;\n readonly now: Date;\n readonly timezone?: string;\n}): boolean => {\n const fields = input.cron.trim().split(/\\s+/);\n if (fields.length !== 5) throw new Error('FLOW_CRON_INVALID');\n const local = localParts(input.now, input.timezone ?? 'Etc/UTC');\n const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;\n if (\n minute === undefined ||\n hour === undefined ||\n dayOfMonth === undefined ||\n month === undefined ||\n dayOfWeek === undefined\n )\n throw new Error('FLOW_CRON_INVALID');\n const minuteMatches = matchesField(minute, local.minute, 0, 59);\n const hourMatches = matchesField(hour, local.hour, 0, 23);\n const monthMatches = matchesField(month, local.month, 1, 12);\n const dayOfMonthMatches = matchesField(dayOfMonth, local.day, 1, 31);\n const dayOfWeekMatches = matchesField(dayOfWeek, local.dayOfWeek, 0, 7);\n const dayMatches =\n dayOfMonth === '*' || dayOfWeek === '*'\n ? dayOfMonthMatches && dayOfWeekMatches\n : dayOfMonthMatches || dayOfWeekMatches;\n return minuteMatches && hourMatches && monthMatches && dayMatches;\n};\n\n/** Format a UTC instant into its real local wall-clock fields; duplicate fall-back instants remain distinct instants. */\nconst localParts = (\n now: Date,\n timezone: string,\n): {\n readonly day: number;\n readonly dayOfWeek: number;\n readonly hour: number;\n readonly minute: number;\n readonly month: number;\n} => {\n try {\n const parts = new Intl.DateTimeFormat('en-US', {\n day: 'numeric',\n hour: 'numeric',\n hourCycle: 'h23',\n minute: 'numeric',\n month: 'numeric',\n timeZone: timezone,\n weekday: 'short',\n }).formatToParts(now);\n const read = (type: Intl.DateTimeFormatPartTypes): string =>\n parts.find((part) => part.type === type)?.value ?? invalidTimezone();\n return {\n day: Number(read('day')),\n dayOfWeek: weekday(read('weekday')),\n hour: Number(read('hour')),\n minute: Number(read('minute')),\n month: Number(read('month')),\n };\n } catch {\n throw new Error('FLOW_CRON_TIMEZONE_INVALID');\n }\n};\n\n/** Match one POSIX cron field including lists, ranges, and positive step values. */\nconst matchesField = (field: string, value: number, minimum: number, maximum: number): boolean =>\n field.split(',').some((part) => matchesPart(part, value, minimum, maximum));\n\n/** Match one validated cron list member. */\nconst matchesPart = (part: string, value: number, minimum: number, maximum: number): boolean => {\n const [rangePart, stepPart] = part.split('/');\n if (part.split('/').length > 2 || rangePart === undefined) throw new Error('FLOW_CRON_INVALID');\n const step = stepPart === undefined ? 1 : parseValue(stepPart, 1, maximum - minimum + 1);\n const [start, end] = parseRange(rangePart, minimum, maximum);\n return value >= start && value <= end && (value - start) % step === 0;\n};\n\n/** Parse a wildcard, number, or inclusive numeric range. */\nconst parseRange = (range: string, minimum: number, maximum: number): readonly [number, number] => {\n if (range === '*') return [minimum, maximum];\n const values = range.split('-');\n if (values.length === 1) {\n const [single] = values;\n if (single === undefined) throw new Error('FLOW_CRON_INVALID');\n const value = parseValue(single, minimum, maximum);\n return [value, value];\n }\n if (values.length !== 2 || values[0] === undefined || values[1] === undefined)\n throw new Error('FLOW_CRON_INVALID');\n const start = parseValue(values[0], minimum, maximum);\n const end = parseValue(values[1], minimum, maximum);\n if (end < start) throw new Error('FLOW_CRON_INVALID');\n return [start, end];\n};\n\n/** Parse one strict POSIX cron integer. */\nconst parseValue = (value: string, minimum: number, maximum: number): number => {\n if (!/^\\d+$/.test(value)) throw new Error('FLOW_CRON_INVALID');\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum)\n throw new Error('FLOW_CRON_INVALID');\n return parsed;\n};\n\n/** Map English short weekday names produced by the fixed formatter to POSIX weekday values. */\nconst weekday = (value: string): number => {\n switch (value) {\n case 'Sun':\n return 0;\n case 'Mon':\n return 1;\n case 'Tue':\n return 2;\n case 'Wed':\n return 3;\n case 'Thu':\n return 4;\n case 'Fri':\n return 5;\n case 'Sat':\n return 6;\n default:\n return invalidTimezone();\n }\n};\n\n/** Keep an impossible formatter shape distinct from a cron syntax error. */\nconst invalidTimezone = (): never => {\n throw new Error('FLOW_CRON_TIMEZONE_INVALID');\n};\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DataContractValue, DataSchema, DataSchemaRegistry } from '@ankhorage/contracts/data';
|
|
2
|
+
/*** Validate serializable data against the supported portable schema vocabulary. */
|
|
3
|
+
export declare const validateFlowDataSchema: (value: DataContractValue, schema: DataSchema, schemas: DataSchemaRegistry | undefined) => boolean;
|
|
4
|
+
/*** Reject unsupported, unresolved, cyclic, or excessively nested portable schemas before use. */
|
|
5
|
+
export declare const assertFlowDataSchemaSupported: (schema: DataSchema, schemas: DataSchemaRegistry | undefined) => void;
|
|
6
|
+
//# sourceMappingURL=validateFlowDataSchema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateFlowDataSchema.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/domain/validateFlowDataSchema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAInG,oFAAoF;AACpF,eAAO,MAAM,sBAAsB,GACjC,OAAO,iBAAiB,EACxB,QAAQ,UAAU,EAClB,SAAS,kBAAkB,GAAG,SAAS,KACtC,OAGF,CAAC;AAEF,kGAAkG;AAClG,eAAO,MAAM,6BAA6B,GACxC,QAAQ,UAAU,EAClB,SAAS,kBAAkB,GAAG,SAAS,KACtC,IAAsE,CAAC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const maximumSchemaDepth = 64;
|
|
2
|
+
/*** Validate serializable data against the supported portable schema vocabulary. */
|
|
3
|
+
export const validateFlowDataSchema = (value, schema, schemas) => {
|
|
4
|
+
assertFlowDataSchemaSupported(schema, schemas);
|
|
5
|
+
return matchesSchema(value, schema, schemas, createTraversalState());
|
|
6
|
+
};
|
|
7
|
+
/*** Reject unsupported, unresolved, cyclic, or excessively nested portable schemas before use. */
|
|
8
|
+
export const assertFlowDataSchemaSupported = (schema, schemas) => assertSupportedSchema(schema, schemas, createTraversalState());
|
|
9
|
+
/*** Resolve a schema reference only from the explicit execution registry. */
|
|
10
|
+
const resolveSchema = (schema, schemas, state) => {
|
|
11
|
+
if (!schema.ref)
|
|
12
|
+
return schema;
|
|
13
|
+
if (state.referenceIds.has(schema.ref.id))
|
|
14
|
+
throw new Error(`FLOW_SCHEMA_REFERENCE_CYCLE:${schema.ref.id}`);
|
|
15
|
+
const resolved = schemas?.[schema.ref.id];
|
|
16
|
+
if (!resolved)
|
|
17
|
+
throw new Error(`FLOW_SCHEMA_REFERENCE_UNRESOLVED:${schema.ref.id}`);
|
|
18
|
+
return resolved;
|
|
19
|
+
};
|
|
20
|
+
/*** Apply portable schema constraints without coercing a JSON-compatible value. */
|
|
21
|
+
const matchesSchema = (value, schema, schemas, state) => {
|
|
22
|
+
const resolved = resolveSchema(schema, schemas, state);
|
|
23
|
+
const nextState = advanceTraversalState(resolved, schema.ref?.id, state);
|
|
24
|
+
return (matchesTypes(value, resolved) &&
|
|
25
|
+
matchesValues(value, resolved) &&
|
|
26
|
+
matchesComposition(value, resolved, schemas, nextState) &&
|
|
27
|
+
matchesStructure(value, resolved, schemas, nextState));
|
|
28
|
+
};
|
|
29
|
+
/*** Reject portable annotations that require a validator capability this engine does not own. */
|
|
30
|
+
const assertSupportedSchema = (schema, schemas, state) => {
|
|
31
|
+
if (schema.format !== undefined)
|
|
32
|
+
throw new Error(`FLOW_SCHEMA_UNSUPPORTED_CONSTRAINT:format`);
|
|
33
|
+
const nextState = advanceTraversalState(schema, undefined, state);
|
|
34
|
+
visitChildSchemas(schema, schemas, nextState);
|
|
35
|
+
if (!schema.ref)
|
|
36
|
+
return;
|
|
37
|
+
const resolved = resolveSchema(schema, schemas, state);
|
|
38
|
+
assertSupportedSchema(resolved, schemas, {
|
|
39
|
+
...nextState,
|
|
40
|
+
referenceIds: new Set([...state.referenceIds, schema.ref.id]),
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
/*** Visit every schema child so unsupported declarations fail before value matching begins. */
|
|
44
|
+
const visitChildSchemas = (schema, schemas, state) => {
|
|
45
|
+
const children = [
|
|
46
|
+
...(schema.items ? [schema.items] : []),
|
|
47
|
+
...(typeof schema.additionalProperties === 'object' ? [schema.additionalProperties] : []),
|
|
48
|
+
...Object.values(schema.properties ?? {}),
|
|
49
|
+
...(schema.allOf ?? []),
|
|
50
|
+
...(schema.anyOf ?? []),
|
|
51
|
+
...(schema.oneOf ?? []),
|
|
52
|
+
];
|
|
53
|
+
for (const child of children)
|
|
54
|
+
assertSupportedSchema(child, schemas, state);
|
|
55
|
+
};
|
|
56
|
+
/*** Match the declared primitive type union and explicit nullability. */
|
|
57
|
+
const matchesTypes = (value, schema) => {
|
|
58
|
+
if (value === null && schema.nullable)
|
|
59
|
+
return true;
|
|
60
|
+
if (schema.type === undefined)
|
|
61
|
+
return true;
|
|
62
|
+
if (typeof schema.type === 'string')
|
|
63
|
+
return matchesType(value, schema.type);
|
|
64
|
+
return schema.type.some((type) => matchesType(value, type));
|
|
65
|
+
};
|
|
66
|
+
/*** Match one JSON primitive category without coercion. */
|
|
67
|
+
const matchesType = (value, type) => {
|
|
68
|
+
if (type === 'array')
|
|
69
|
+
return isDataArray(value);
|
|
70
|
+
if (type === 'integer')
|
|
71
|
+
return typeof value === 'number' && Number.isInteger(value);
|
|
72
|
+
if (type === 'null')
|
|
73
|
+
return value === null;
|
|
74
|
+
if (type === 'object')
|
|
75
|
+
return isDataRecord(value);
|
|
76
|
+
return typeof value === type;
|
|
77
|
+
};
|
|
78
|
+
/*** Apply constant and enumeration constraints using structural JSON equality. */
|
|
79
|
+
const matchesValues = (value, schema) => (schema.const === undefined || structurallyEqual(value, schema.const)) &&
|
|
80
|
+
(!schema.enum || schema.enum.some((candidate) => structurallyEqual(value, candidate)));
|
|
81
|
+
/*** Apply logical schema composition with its standard cardinality semantics. */
|
|
82
|
+
const matchesComposition = (value, schema, schemas, state) => (!schema.allOf ||
|
|
83
|
+
schema.allOf.every((candidate) => matchesSchema(value, candidate, schemas, state))) &&
|
|
84
|
+
(!schema.anyOf ||
|
|
85
|
+
schema.anyOf.some((candidate) => matchesSchema(value, candidate, schemas, state))) &&
|
|
86
|
+
(!schema.oneOf ||
|
|
87
|
+
schema.oneOf.filter((candidate) => matchesSchema(value, candidate, schemas, state)).length ===
|
|
88
|
+
1);
|
|
89
|
+
/*** Recursively apply object and array member constraints. */
|
|
90
|
+
const matchesStructure = (value, schema, schemas, state) => matchesItems(value, schema.items, schemas, state) &&
|
|
91
|
+
(!isDataRecord(value) || matchesProperties(value, schema, schemas, state));
|
|
92
|
+
/*** Validate array elements only when an item schema is declared. */
|
|
93
|
+
const matchesItems = (value, items, schemas, state) => !items ||
|
|
94
|
+
!isDataArray(value) ||
|
|
95
|
+
value.every((item) => matchesSchema(item, items, schemas, state));
|
|
96
|
+
/*** Validate required object members and declared or additional property schemas. */
|
|
97
|
+
const matchesProperties = (value, schema, schemas, state) => {
|
|
98
|
+
const properties = schema.properties ?? {};
|
|
99
|
+
return ((schema.required ?? []).every((key) => Object.entries(value).some(([name]) => name === key)) &&
|
|
100
|
+
Object.entries(value).every(([key, item]) => {
|
|
101
|
+
const property = Object.entries(properties).find(([name]) => name === key)?.[1];
|
|
102
|
+
if (property)
|
|
103
|
+
return matchesSchema(item, property, schemas, state);
|
|
104
|
+
if (schema.additionalProperties === false)
|
|
105
|
+
return false;
|
|
106
|
+
return (typeof schema.additionalProperties !== 'object' ||
|
|
107
|
+
matchesSchema(item, schema.additionalProperties, schemas, state));
|
|
108
|
+
}));
|
|
109
|
+
};
|
|
110
|
+
/*** Start bounded schema traversal without retaining mutable validation state. */
|
|
111
|
+
const createTraversalState = () => ({
|
|
112
|
+
depth: 0,
|
|
113
|
+
referenceIds: new Set(),
|
|
114
|
+
schemas: new Set(),
|
|
115
|
+
});
|
|
116
|
+
/*** Advance a schema traversal while rejecting direct cycles and excessive nesting deterministically. */
|
|
117
|
+
const advanceTraversalState = (schema, referenceId, state) => {
|
|
118
|
+
if (state.depth >= maximumSchemaDepth)
|
|
119
|
+
throw new Error('FLOW_SCHEMA_MAX_DEPTH');
|
|
120
|
+
if (state.schemas.has(schema))
|
|
121
|
+
throw new Error('FLOW_SCHEMA_CYCLE');
|
|
122
|
+
return {
|
|
123
|
+
depth: state.depth + 1,
|
|
124
|
+
referenceIds: referenceId ? new Set([...state.referenceIds, referenceId]) : state.referenceIds,
|
|
125
|
+
schemas: new Set([...state.schemas, schema]),
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
/*** Compare JSON-compatible values independently of object key order. */
|
|
129
|
+
const structurallyEqual = (left, right) => JSON.stringify(normalizeValue(left)) === JSON.stringify(normalizeValue(right));
|
|
130
|
+
/*** Canonicalize object member ordering for structural comparison. */
|
|
131
|
+
const normalizeValue = (value) => {
|
|
132
|
+
if (isDataArray(value))
|
|
133
|
+
return value.map(normalizeValue);
|
|
134
|
+
if (isDataRecord(value))
|
|
135
|
+
return Object.fromEntries(Object.entries(value)
|
|
136
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
137
|
+
.map(([key, item]) => [key, normalizeValue(item)]));
|
|
138
|
+
return value;
|
|
139
|
+
};
|
|
140
|
+
/*** Narrow a JSON-compatible object to a non-array member record. */
|
|
141
|
+
const isDataRecord = (value) => value !== null && typeof value === 'object' && !isDataArray(value);
|
|
142
|
+
/*** Narrow a JSON-compatible array without losing its serializable member type. */
|
|
143
|
+
const isDataArray = (value) => Array.isArray(value);
|
|
144
|
+
//# sourceMappingURL=validateFlowDataSchema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateFlowDataSchema.js","sourceRoot":"","sources":["../../../../src/features/execution/domain/validateFlowDataSchema.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DataContractValue, DataSchema, DataSchemaRegistry } from '@ankhorage/contracts/data';\n\nconst maximumSchemaDepth = 64;\n\n/*** Validate serializable data against the supported portable schema vocabulary. */\nexport const validateFlowDataSchema = (\n value: DataContractValue,\n schema: DataSchema,\n schemas: DataSchemaRegistry | undefined,\n): boolean => {\n assertFlowDataSchemaSupported(schema, schemas);\n return matchesSchema(value, schema, schemas, createTraversalState());\n};\n\n/*** Reject unsupported, unresolved, cyclic, or excessively nested portable schemas before use. */\nexport const assertFlowDataSchemaSupported = (\n schema: DataSchema,\n schemas: DataSchemaRegistry | undefined,\n): void => assertSupportedSchema(schema, schemas, createTraversalState());\n\n/*** Resolve a schema reference only from the explicit execution registry. */\nconst resolveSchema = (\n schema: DataSchema,\n schemas: DataSchemaRegistry | undefined,\n state: SchemaTraversalState,\n): DataSchema => {\n if (!schema.ref) return schema;\n if (state.referenceIds.has(schema.ref.id))\n throw new Error(`FLOW_SCHEMA_REFERENCE_CYCLE:${schema.ref.id}`);\n const resolved = schemas?.[schema.ref.id];\n if (!resolved) throw new Error(`FLOW_SCHEMA_REFERENCE_UNRESOLVED:${schema.ref.id}`);\n return resolved;\n};\n\n/*** Apply portable schema constraints without coercing a JSON-compatible value. */\nconst matchesSchema = (\n value: DataContractValue,\n schema: DataSchema,\n schemas: DataSchemaRegistry | undefined,\n state: SchemaTraversalState,\n): boolean => {\n const resolved = resolveSchema(schema, schemas, state);\n const nextState = advanceTraversalState(resolved, schema.ref?.id, state);\n return (\n matchesTypes(value, resolved) &&\n matchesValues(value, resolved) &&\n matchesComposition(value, resolved, schemas, nextState) &&\n matchesStructure(value, resolved, schemas, nextState)\n );\n};\n\n/*** Reject portable annotations that require a validator capability this engine does not own. */\nconst assertSupportedSchema = (\n schema: DataSchema,\n schemas: DataSchemaRegistry | undefined,\n state: SchemaTraversalState,\n): void => {\n if (schema.format !== undefined) throw new Error(`FLOW_SCHEMA_UNSUPPORTED_CONSTRAINT:format`);\n const nextState = advanceTraversalState(schema, undefined, state);\n visitChildSchemas(schema, schemas, nextState);\n if (!schema.ref) return;\n const resolved = resolveSchema(schema, schemas, state);\n assertSupportedSchema(resolved, schemas, {\n ...nextState,\n referenceIds: new Set([...state.referenceIds, schema.ref.id]),\n });\n};\n\n/*** Visit every schema child so unsupported declarations fail before value matching begins. */\nconst visitChildSchemas = (\n schema: DataSchema,\n schemas: DataSchemaRegistry | undefined,\n state: SchemaTraversalState,\n): void => {\n const children = [\n ...(schema.items ? [schema.items] : []),\n ...(typeof schema.additionalProperties === 'object' ? [schema.additionalProperties] : []),\n ...Object.values(schema.properties ?? {}),\n ...(schema.allOf ?? []),\n ...(schema.anyOf ?? []),\n ...(schema.oneOf ?? []),\n ];\n for (const child of children) assertSupportedSchema(child, schemas, state);\n};\n\n/*** Match the declared primitive type union and explicit nullability. */\nconst matchesTypes = (value: DataContractValue, schema: DataSchema): boolean => {\n if (value === null && schema.nullable) return true;\n if (schema.type === undefined) return true;\n if (typeof schema.type === 'string') return matchesType(value, schema.type);\n return schema.type.some((type) => matchesType(value, type));\n};\n\n/*** Match one JSON primitive category without coercion. */\nconst matchesType = (value: DataContractValue, type: string): boolean => {\n if (type === 'array') return isDataArray(value);\n if (type === 'integer') return typeof value === 'number' && Number.isInteger(value);\n if (type === 'null') return value === null;\n if (type === 'object') return isDataRecord(value);\n return typeof value === type;\n};\n\n/*** Apply constant and enumeration constraints using structural JSON equality. */\nconst matchesValues = (value: DataContractValue, schema: DataSchema): boolean =>\n (schema.const === undefined || structurallyEqual(value, schema.const)) &&\n (!schema.enum || schema.enum.some((candidate) => structurallyEqual(value, candidate)));\n\n/*** Apply logical schema composition with its standard cardinality semantics. */\nconst matchesComposition = (\n value: DataContractValue,\n schema: DataSchema,\n schemas: DataSchemaRegistry | undefined,\n state: SchemaTraversalState,\n): boolean =>\n (!schema.allOf ||\n schema.allOf.every((candidate) => matchesSchema(value, candidate, schemas, state))) &&\n (!schema.anyOf ||\n schema.anyOf.some((candidate) => matchesSchema(value, candidate, schemas, state))) &&\n (!schema.oneOf ||\n schema.oneOf.filter((candidate) => matchesSchema(value, candidate, schemas, state)).length ===\n 1);\n\n/*** Recursively apply object and array member constraints. */\nconst matchesStructure = (\n value: DataContractValue,\n schema: DataSchema,\n schemas: DataSchemaRegistry | undefined,\n state: SchemaTraversalState,\n): boolean =>\n matchesItems(value, schema.items, schemas, state) &&\n (!isDataRecord(value) || matchesProperties(value, schema, schemas, state));\n\n/*** Validate array elements only when an item schema is declared. */\nconst matchesItems = (\n value: DataContractValue,\n items: DataSchema | undefined,\n schemas: DataSchemaRegistry | undefined,\n state: SchemaTraversalState,\n): boolean =>\n !items ||\n !isDataArray(value) ||\n value.every((item) => matchesSchema(item, items, schemas, state));\n\n/*** Validate required object members and declared or additional property schemas. */\nconst matchesProperties = (\n value: Readonly<Record<string, DataContractValue>>,\n schema: DataSchema,\n schemas: DataSchemaRegistry | undefined,\n state: SchemaTraversalState,\n): boolean => {\n const properties = schema.properties ?? {};\n return (\n (schema.required ?? []).every((key) => Object.entries(value).some(([name]) => name === key)) &&\n Object.entries(value).every(([key, item]) => {\n const property = Object.entries(properties).find(([name]) => name === key)?.[1];\n if (property) return matchesSchema(item, property, schemas, state);\n if (schema.additionalProperties === false) return false;\n return (\n typeof schema.additionalProperties !== 'object' ||\n matchesSchema(item, schema.additionalProperties, schemas, state)\n );\n })\n );\n};\n\ninterface SchemaTraversalState {\n readonly depth: number;\n readonly referenceIds: ReadonlySet<string>;\n readonly schemas: ReadonlySet<DataSchema>;\n}\n\n/*** Start bounded schema traversal without retaining mutable validation state. */\nconst createTraversalState = (): SchemaTraversalState => ({\n depth: 0,\n referenceIds: new Set(),\n schemas: new Set(),\n});\n\n/*** Advance a schema traversal while rejecting direct cycles and excessive nesting deterministically. */\nconst advanceTraversalState = (\n schema: DataSchema,\n referenceId: string | undefined,\n state: SchemaTraversalState,\n): SchemaTraversalState => {\n if (state.depth >= maximumSchemaDepth) throw new Error('FLOW_SCHEMA_MAX_DEPTH');\n if (state.schemas.has(schema)) throw new Error('FLOW_SCHEMA_CYCLE');\n return {\n depth: state.depth + 1,\n referenceIds: referenceId ? new Set([...state.referenceIds, referenceId]) : state.referenceIds,\n schemas: new Set([...state.schemas, schema]),\n };\n};\n\n/*** Compare JSON-compatible values independently of object key order. */\nconst structurallyEqual = (left: DataContractValue, right: DataContractValue): boolean =>\n JSON.stringify(normalizeValue(left)) === JSON.stringify(normalizeValue(right));\n\n/*** Canonicalize object member ordering for structural comparison. */\nconst normalizeValue = (value: DataContractValue): DataContractValue => {\n if (isDataArray(value)) return value.map(normalizeValue);\n if (isDataRecord(value))\n return Object.fromEntries(\n Object.entries(value)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, item]) => [key, normalizeValue(item)]),\n );\n return value;\n};\n\n/*** Narrow a JSON-compatible object to a non-array member record. */\nconst isDataRecord = (\n value: DataContractValue,\n): value is Readonly<Record<string, DataContractValue>> =>\n value !== null && typeof value === 'object' && !isDataArray(value);\n\n/*** Narrow a JSON-compatible array without losing its serializable member type. */\nconst isDataArray = (value: DataContractValue): value is readonly DataContractValue[] =>\n Array.isArray(value);\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { FlowDurableRun, FlowDurableWork, FlowOutboxRecord } from '../../../types/durableExecution.js';
|
|
2
|
+
export interface FlowPersistencePort {
|
|
3
|
+
readonly cancelRunWorkAsync: (runId: string) => Promise<readonly string[]>;
|
|
4
|
+
readonly claimDueWorkAsync: (input: {
|
|
5
|
+
readonly leaseDurationMs: number;
|
|
6
|
+
readonly limit: number;
|
|
7
|
+
readonly now: Date;
|
|
8
|
+
readonly workerId: string;
|
|
9
|
+
}) => Promise<readonly FlowDurableWork[]>;
|
|
10
|
+
readonly completeWorkAsync: (input: {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly leaseFencingToken: number;
|
|
13
|
+
readonly now: Date;
|
|
14
|
+
readonly workerId: string;
|
|
15
|
+
}) => Promise<boolean>;
|
|
16
|
+
readonly createOutboxAsync: (record: FlowOutboxRecord) => Promise<void>;
|
|
17
|
+
readonly createRunAsync: (run: FlowDurableRun) => Promise<void>;
|
|
18
|
+
readonly enqueueWorkAsync: (work: FlowDurableWork) => Promise<void>;
|
|
19
|
+
readonly markOutboxDeliveredAsync: (id: string) => Promise<boolean>;
|
|
20
|
+
readonly readOutboxAsync: (id: string) => Promise<FlowOutboxRecord | undefined>;
|
|
21
|
+
readonly readRunAsync: (id: string) => Promise<FlowDurableRun | undefined>;
|
|
22
|
+
readonly recoverExpiredClaimsAsync: (now: Date) => Promise<readonly string[]>;
|
|
23
|
+
readonly releaseWorkAsync: (input: {
|
|
24
|
+
readonly dueAt: string;
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly leaseFencingToken: number;
|
|
27
|
+
readonly workerId: string;
|
|
28
|
+
}) => Promise<boolean>;
|
|
29
|
+
readonly transitionRunAsync: (input: {
|
|
30
|
+
readonly expectedRevision: number;
|
|
31
|
+
readonly run: FlowDurableRun;
|
|
32
|
+
}) => Promise<boolean>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=FlowPersistencePort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowPersistencePort.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/ports/FlowPersistencePort.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,EACjB,MAAM,oCAAoC,CAAC;AAE5C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAC3E,QAAQ,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE;QAClC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;QACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;QACnB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,KAAK,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE;QAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;QACnC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;QACnB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvB,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,QAAQ,CAAC,cAAc,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,QAAQ,CAAC,wBAAwB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,QAAQ,CAAC,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAAC;IAChF,QAAQ,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IAC3E,QAAQ,CAAC,yBAAyB,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAC9E,QAAQ,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE;QACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;QACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvB,QAAQ,CAAC,kBAAkB,EAAE,CAAC,KAAK,EAAE;QACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;QAClC,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;KAC9B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowPersistencePort.js","sourceRoot":"","sources":["../../../../src/features/execution/ports/FlowPersistencePort.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n FlowDurableRun,\n FlowDurableWork,\n FlowOutboxRecord,\n} from '../../../types/durableExecution.js';\n\nexport interface FlowPersistencePort {\n readonly cancelRunWorkAsync: (runId: string) => Promise<readonly string[]>;\n readonly claimDueWorkAsync: (input: {\n readonly leaseDurationMs: number;\n readonly limit: number;\n readonly now: Date;\n readonly workerId: string;\n }) => Promise<readonly FlowDurableWork[]>;\n readonly completeWorkAsync: (input: {\n readonly id: string;\n readonly leaseFencingToken: number;\n readonly now: Date;\n readonly workerId: string;\n }) => Promise<boolean>;\n readonly createOutboxAsync: (record: FlowOutboxRecord) => Promise<void>;\n readonly createRunAsync: (run: FlowDurableRun) => Promise<void>;\n readonly enqueueWorkAsync: (work: FlowDurableWork) => Promise<void>;\n readonly markOutboxDeliveredAsync: (id: string) => Promise<boolean>;\n readonly readOutboxAsync: (id: string) => Promise<FlowOutboxRecord | undefined>;\n readonly readRunAsync: (id: string) => Promise<FlowDurableRun | undefined>;\n readonly recoverExpiredClaimsAsync: (now: Date) => Promise<readonly string[]>;\n readonly releaseWorkAsync: (input: {\n readonly dueAt: string;\n readonly id: string;\n readonly leaseFencingToken: number;\n readonly workerId: string;\n }) => Promise<boolean>;\n readonly transitionRunAsync: (input: {\n readonly expectedRevision: number;\n readonly run: FlowDurableRun;\n }) => Promise<boolean>;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpToolError.d.ts","sourceRoot":"","sources":["../../../src/features/mcp/McpToolError.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,qBAAa,YAAa,SAAQ,KAAK;IAEnC,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM;CAKlB"}
|