@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
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# @ankhorage/flow
|
|
2
|
+
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 63d8dd6: Add provider-neutral durable execution with fenced workers, retries, checkpoints, cron matching,
|
|
8
|
+
outbox idempotency, cancellation, and published-security approval waits.
|
|
9
|
+
|
|
10
|
+
## 0.4.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 8e96c13: Load the optional MCP SDK only when MCP discovery or approved execution opens a provider connection.
|
|
15
|
+
|
|
16
|
+
## 0.4.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- f7431ac: Add a provider-neutral Flow security port with scoped authorization, full-identity approval gates,
|
|
21
|
+
host-persisted atomic claim and consumption, audit events, rate limits, SSRF-safe targets, and
|
|
22
|
+
server-only secret resolution. Remote MCP execution now requires this explicit security boundary
|
|
23
|
+
and a host-owned transport that controls every resolved peer connection.
|
|
24
|
+
|
|
25
|
+
## 0.3.0
|
|
26
|
+
|
|
27
|
+
### Minor Changes
|
|
28
|
+
|
|
29
|
+
- 9bd21b1: Add an optional, fail-closed MCP tool discovery entrypoint with explicit provider and tool scopes.
|
|
30
|
+
|
|
31
|
+
## 0.2.0
|
|
32
|
+
|
|
33
|
+
### Minor Changes
|
|
34
|
+
|
|
35
|
+
- 24a9d4d: Bootstrap the standalone Flow package with public provider subpaths and packed-consumer validation.
|
|
36
|
+
- 1ba3dca: Add deterministic headless graph compilation, safe built-in operations, and single-process execution.
|
|
37
|
+
|
|
38
|
+
### Patch Changes
|
|
39
|
+
|
|
40
|
+
- 5b3e567: Declare the published workflow contracts dependency used by Flow's public consumer boundary.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ankhorage
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- markdownlint-disable MD013 MD033 -->
|
|
2
|
+
<!-- This file is generated by Paradox. Do not edit manually. -->
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
# @ankhorage/flow
|
|
5
|
+
|
|
6
|
+
        
|
|
7
|
+
|
|
8
|
+
Standalone workflow engine foundation for Ankhorage.
|
|
9
|
+
|
|
10
|
+
## Generated documentation
|
|
11
|
+
|
|
12
|
+
- [Interactive documentation app](././paradox/index.html)
|
|
13
|
+
- [Public API reference](././paradox/exports.md)
|
|
14
|
+
- [Component registry](././paradox/components.md)
|
|
15
|
+
- [Architecture overview](././paradox/diagrams/architecture-overview.mmd)
|
|
16
|
+
- [Module relationships](././paradox/diagrams/module-relationships.mmd)
|
|
17
|
+
- [Export graph](././paradox/diagrams/export-graph.mmd)
|
|
18
|
+
- [compileFlowGraph sequence](././paradox/diagrams/sequences/compile-flow-graph.mmd)
|
|
19
|
+
- [createRetryWork sequence](././paradox/diagrams/sequences/create-retry-work.mmd)
|
|
20
|
+
- [isCronDueAt sequence](././paradox/diagrams/sequences/is-cron-due-at.mmd)
|
package/dist/ai.d.ts
ADDED
package/dist/ai.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,CAAC"}
|
package/dist/ai.js
ADDED
package/dist/ai.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,CAAC","sourcesContent":["/***\n * Reserved public subpath for future AI-provider adapters.\n *\n * This bootstrap intentionally provides no AI execution, configuration, or dependencies.\n */\nexport {};\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/*** Node-specific durable-execution adapters. */
|
|
2
|
+
export { FileFlowPersistenceAdapter } from './features/execution/adapters/FileFlowPersistenceAdapter.js';
|
|
3
|
+
export { cancelDurableRunAsync } from './features/execution/application/cancelDurableRunAsync.js';
|
|
4
|
+
export { type FlowDurableEffectPort, type FlowWorkerOutcome, type FlowWorkerResult, runDurableWorkerAsync, } from './features/execution/application/runDurableWorkerAsync.js';
|
|
5
|
+
//# sourceMappingURL=execution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution.d.ts","sourceRoot":"","sources":["../src/execution.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,6DAA6D,CAAC;AACzG,OAAO,EAAE,qBAAqB,EAAE,MAAM,2DAA2D,CAAC;AAClG,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,qBAAqB,GACtB,MAAM,2DAA2D,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/*** Node-specific durable-execution adapters. */
|
|
2
|
+
export { FileFlowPersistenceAdapter } from './features/execution/adapters/FileFlowPersistenceAdapter.js';
|
|
3
|
+
export { cancelDurableRunAsync } from './features/execution/application/cancelDurableRunAsync.js';
|
|
4
|
+
export { runDurableWorkerAsync, } from './features/execution/application/runDurableWorkerAsync.js';
|
|
5
|
+
//# sourceMappingURL=execution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution.js","sourceRoot":"","sources":["../src/execution.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,6DAA6D,CAAC;AACzG,OAAO,EAAE,qBAAqB,EAAE,MAAM,2DAA2D,CAAC;AAClG,OAAO,EAIL,qBAAqB,GACtB,MAAM,2DAA2D,CAAC","sourcesContent":["/*** Node-specific durable-execution adapters. */\nexport { FileFlowPersistenceAdapter } from './features/execution/adapters/FileFlowPersistenceAdapter.js';\nexport { cancelDurableRunAsync } from './features/execution/application/cancelDurableRunAsync.js';\nexport {\n type FlowDurableEffectPort,\n type FlowWorkerOutcome,\n type FlowWorkerResult,\n runDurableWorkerAsync,\n} from './features/execution/application/runDurableWorkerAsync.js';\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FlowPersistencePort } from '../ports/FlowPersistencePort.js';
|
|
2
|
+
/*** Create a self-hostable, single-process JSON persistence adapter for durable Flow state. */
|
|
3
|
+
export declare const FileFlowPersistenceAdapter: (path: string) => FlowPersistencePort;
|
|
4
|
+
//# sourceMappingURL=FileFlowPersistenceAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileFlowPersistenceAdapter.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/adapters/FileFlowPersistenceAdapter.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAE3E,+FAA+F;AAC/F,eAAO,MAAM,0BAA0B,GAAI,MAAM,MAAM,KAAG,mBA0BzD,CAAC"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
/*** Create a self-hostable, single-process JSON persistence adapter for durable Flow state. */
|
|
5
|
+
export const FileFlowPersistenceAdapter = (path) => {
|
|
6
|
+
const persistencePath = resolve(path);
|
|
7
|
+
const serializedCall = getSerializedCall(persistencePath);
|
|
8
|
+
return {
|
|
9
|
+
cancelRunWorkAsync: (runId) => serializedCall.runAsync(() => cancelRunWorkAsync(persistencePath, runId)),
|
|
10
|
+
claimDueWorkAsync: (input) => serializedCall.runAsync(() => claimDueWorkAsync(persistencePath, input)),
|
|
11
|
+
completeWorkAsync: (input) => serializedCall.runAsync(() => completeWorkAsync(persistencePath, input)),
|
|
12
|
+
createOutboxAsync: (record) => serializedCall.runAsync(() => createOutboxAsync(persistencePath, record)),
|
|
13
|
+
createRunAsync: (run) => serializedCall.runAsync(() => createRunAsync(persistencePath, run)),
|
|
14
|
+
enqueueWorkAsync: (work) => serializedCall.runAsync(() => enqueueWorkAsync(persistencePath, work)),
|
|
15
|
+
markOutboxDeliveredAsync: (id) => serializedCall.runAsync(() => markOutboxDeliveredAsync(persistencePath, id)),
|
|
16
|
+
readOutboxAsync: (id) => serializedCall.runAsync(() => readOutboxAsync(persistencePath, id)),
|
|
17
|
+
readRunAsync: (id) => serializedCall.runAsync(() => readRunAsync(persistencePath, id)),
|
|
18
|
+
recoverExpiredClaimsAsync: (now) => serializedCall.runAsync(() => recoverExpiredClaimsAsync(persistencePath, now)),
|
|
19
|
+
releaseWorkAsync: (input) => serializedCall.runAsync(() => releaseWorkAsync(persistencePath, input)),
|
|
20
|
+
transitionRunAsync: (input) => serializedCall.runAsync(() => transitionRunAsync(persistencePath, input)),
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
/** Cancel every nonterminal delivery for a run after its durable cancellation transition. */
|
|
24
|
+
const cancelRunWorkAsync = async (path, runId) => updateStoreAsync(path, (store) => {
|
|
25
|
+
const cancelled = store.work
|
|
26
|
+
.filter((work) => work.runId === runId && (work.status === 'ready' || work.status === 'claimed'))
|
|
27
|
+
.map((work) => ({ ...work, lease: undefined, status: 'cancelled' }));
|
|
28
|
+
return {
|
|
29
|
+
result: cancelled.map((work) => work.id),
|
|
30
|
+
store: { ...store, work: replaceWork(store.work, cancelled) },
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
/** Persist a stable outbox identity before any external effect is attempted. */
|
|
34
|
+
const createOutboxAsync = async (path, record) => updateStoreAsync(path, (store) => {
|
|
35
|
+
const existing = store.outbox.find((candidate) => candidate.id === record.id);
|
|
36
|
+
if (existing !== undefined && existing.idempotencyKey !== record.idempotencyKey)
|
|
37
|
+
throw new Error(`FLOW_OUTBOX_CONFLICT:${record.id}`);
|
|
38
|
+
return {
|
|
39
|
+
result: undefined,
|
|
40
|
+
store: existing === undefined ? { ...store, outbox: [...store.outbox, copy(record)] } : store,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
const serializedCallsByPath = new Map();
|
|
44
|
+
/*** Share serialization for every adapter instance targeting one normalized persistence path. */
|
|
45
|
+
const getSerializedCall = (path) => {
|
|
46
|
+
const existing = serializedCallsByPath.get(path);
|
|
47
|
+
if (existing !== undefined)
|
|
48
|
+
return existing;
|
|
49
|
+
const serializedCall = createSerializedCall();
|
|
50
|
+
serializedCallsByPath.set(path, serializedCall);
|
|
51
|
+
return serializedCall;
|
|
52
|
+
};
|
|
53
|
+
/*** Serialize one file path so concurrent callbacks cannot claim or overwrite the same state. */
|
|
54
|
+
const createSerializedCall = () => {
|
|
55
|
+
let previous = Promise.resolve();
|
|
56
|
+
return {
|
|
57
|
+
runAsync: async (operation) => {
|
|
58
|
+
const current = previous.then(operation, operation);
|
|
59
|
+
previous = current.then(() => undefined, () => undefined);
|
|
60
|
+
return current;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
/*** Claim due work in stable order and record a lease before it is returned to a worker. */
|
|
65
|
+
const claimDueWorkAsync = async (path, input) => updateStoreAsync(path, (store) => {
|
|
66
|
+
const claimed = store.work
|
|
67
|
+
.filter((work) => work.status === 'ready' && work.dueAt <= input.now.toISOString())
|
|
68
|
+
.sort((left, right) => left.dueAt.localeCompare(right.dueAt) || left.id.localeCompare(right.id))
|
|
69
|
+
.slice(0, input.limit)
|
|
70
|
+
.map((work, index) => claimWork(work, input, store.nextLeaseFencingToken + index));
|
|
71
|
+
return {
|
|
72
|
+
result: claimed,
|
|
73
|
+
store: {
|
|
74
|
+
...store,
|
|
75
|
+
nextLeaseFencingToken: store.nextLeaseFencingToken + claimed.length,
|
|
76
|
+
work: replaceWork(store.work, claimed),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
/*** Complete only the unexpired fenced lease returned by the successful claim. */
|
|
81
|
+
const completeWorkAsync = async (path, { id, leaseFencingToken, now, workerId }) => updateStoreAsync(path, (store) => {
|
|
82
|
+
const work = store.work.find((candidate) => candidate.id === id);
|
|
83
|
+
if (work?.status !== 'claimed' ||
|
|
84
|
+
work.lease?.workerId !== workerId ||
|
|
85
|
+
work.lease.fencingToken !== leaseFencingToken ||
|
|
86
|
+
work.lease.expiresAt <= now.toISOString())
|
|
87
|
+
return { result: false, store };
|
|
88
|
+
return {
|
|
89
|
+
result: true,
|
|
90
|
+
store: { ...store, work: replaceWork(store.work, [{ ...work, status: 'completed' }]) },
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
/*** Persist a new run once, rejecting a duplicate durable identity. */
|
|
94
|
+
const createRunAsync = async (path, run) => updateStoreAsync(path, (store) => {
|
|
95
|
+
if (store.runs.some((candidate) => candidate.id === run.id))
|
|
96
|
+
throw new Error(`FLOW_RUN_EXISTS:${run.id}`);
|
|
97
|
+
return { result: undefined, store: { ...store, runs: [...store.runs, copy(run)] } };
|
|
98
|
+
});
|
|
99
|
+
/*** Persist one immutable work identity once, rejecting duplicate delivery records. */
|
|
100
|
+
const enqueueWorkAsync = async (path, work) => updateStoreAsync(path, (store) => {
|
|
101
|
+
if (store.work.some((candidate) => candidate.id === work.id))
|
|
102
|
+
throw new Error(`FLOW_WORK_EXISTS:${work.id}`);
|
|
103
|
+
return { result: undefined, store: { ...store, work: [...store.work, copy(work)] } };
|
|
104
|
+
});
|
|
105
|
+
/** Mark an effect delivery complete; duplicate marks are harmless and do not rerun the effect. */
|
|
106
|
+
const markOutboxDeliveredAsync = async (path, id) => updateStoreAsync(path, (store) => {
|
|
107
|
+
const record = store.outbox.find((candidate) => candidate.id === id);
|
|
108
|
+
if (record === undefined)
|
|
109
|
+
return { result: false, store };
|
|
110
|
+
return {
|
|
111
|
+
result: true,
|
|
112
|
+
store: {
|
|
113
|
+
...store,
|
|
114
|
+
outbox: replaceOutbox(store.outbox, [{ ...record, status: 'delivered' }]),
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
/** Read a copied outbox snapshot without exposing adapter-owned mutable state. */
|
|
119
|
+
const readOutboxAsync = async (path, id) => copy((await readStoreAsync(path)).outbox.find((record) => record.id === id));
|
|
120
|
+
/*** Read a copied run snapshot so callers cannot mutate persisted state in-place. */
|
|
121
|
+
const readRunAsync = async (path, id) => copy((await readStoreAsync(path)).runs.find((run) => run.id === id));
|
|
122
|
+
/*** Make expired claims available for recovery after a worker crash or stop. */
|
|
123
|
+
const recoverExpiredClaimsAsync = async (path, now) => updateStoreAsync(path, (store) => {
|
|
124
|
+
const recovered = store.work
|
|
125
|
+
.filter((work) => work.status === 'claimed' &&
|
|
126
|
+
work.lease?.expiresAt !== undefined &&
|
|
127
|
+
work.lease.expiresAt <= now.toISOString())
|
|
128
|
+
.map((work) => ({ ...work, lease: undefined, status: 'ready' }));
|
|
129
|
+
return {
|
|
130
|
+
result: recovered.map((work) => work.id),
|
|
131
|
+
store: { ...store, work: replaceWork(store.work, recovered) },
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
/** Return a fenced claim to the queue without permitting a stale worker to reschedule it. */
|
|
135
|
+
const releaseWorkAsync = async (path, input) => updateStoreAsync(path, (store) => {
|
|
136
|
+
const work = store.work.find((candidate) => candidate.id === input.id);
|
|
137
|
+
if (work?.status !== 'claimed' ||
|
|
138
|
+
work.lease?.workerId !== input.workerId ||
|
|
139
|
+
work.lease.fencingToken !== input.leaseFencingToken)
|
|
140
|
+
return { result: false, store };
|
|
141
|
+
return {
|
|
142
|
+
result: true,
|
|
143
|
+
store: {
|
|
144
|
+
...store,
|
|
145
|
+
work: replaceWork(store.work, [
|
|
146
|
+
{ ...work, dueAt: input.dueAt, lease: undefined, status: 'ready' },
|
|
147
|
+
]),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
/*** Store a revision-checked run transition while preserving its immutable flow reference. */
|
|
152
|
+
const transitionRunAsync = async (path, { expectedRevision, run }) => updateStoreAsync(path, (store) => {
|
|
153
|
+
const current = store.runs.find((candidate) => candidate.id === run.id);
|
|
154
|
+
if (!sameFlow(current ?? run, run) || current?.revision !== expectedRevision)
|
|
155
|
+
return { result: false, store };
|
|
156
|
+
if (run.revision !== expectedRevision + 1)
|
|
157
|
+
throw new Error('FLOW_RUN_REVISION_INVALID');
|
|
158
|
+
return { result: true, store: { ...store, runs: replaceRuns(store.runs, [copy(run)]) } };
|
|
159
|
+
});
|
|
160
|
+
/*** Build a claimed work record with an explicit bounded lease. */
|
|
161
|
+
const claimWork = (work, input, fencingToken) => ({
|
|
162
|
+
...work,
|
|
163
|
+
lease: {
|
|
164
|
+
expiresAt: new Date(input.now.getTime() + input.leaseDurationMs).toISOString(),
|
|
165
|
+
fencingToken,
|
|
166
|
+
workerId: input.workerId,
|
|
167
|
+
},
|
|
168
|
+
status: 'claimed',
|
|
169
|
+
});
|
|
170
|
+
/*** Replace changed work entries by identity without exposing a mutable adapter collection. */
|
|
171
|
+
const replaceWork = (work, replacements) => work.map((candidate) => replacements.find((replacement) => replacement.id === candidate.id) ?? candidate);
|
|
172
|
+
/*** Replace changed run entries by identity without exposing a mutable adapter collection. */
|
|
173
|
+
const replaceRuns = (runs, replacements) => runs.map((candidate) => replacements.find((replacement) => replacement.id === candidate.id) ?? candidate);
|
|
174
|
+
/** Replace durable outbox records by identity. */
|
|
175
|
+
const replaceOutbox = (records, replacements) => records.map((candidate) => replacements.find((replacement) => replacement.id === candidate.id) ?? candidate);
|
|
176
|
+
/*** Read persisted state or initialize an empty store when it has not yet been created. */
|
|
177
|
+
const readStoreAsync = async (path) => {
|
|
178
|
+
try {
|
|
179
|
+
return readFileStore(JSON.parse(await readFile(path, 'utf8')));
|
|
180
|
+
}
|
|
181
|
+
catch (reason) {
|
|
182
|
+
if (isMissingFile(reason))
|
|
183
|
+
return { nextLeaseFencingToken: 1, outbox: [], runs: [], work: [] };
|
|
184
|
+
throw reason;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
/*** Validate the adapter-owned persistence envelope before using its fencing counter. */
|
|
188
|
+
const readFileStore = (value) => {
|
|
189
|
+
if (typeof value !== 'object' ||
|
|
190
|
+
value === null ||
|
|
191
|
+
!('nextLeaseFencingToken' in value) ||
|
|
192
|
+
typeof value.nextLeaseFencingToken !== 'number' ||
|
|
193
|
+
!Number.isSafeInteger(value.nextLeaseFencingToken) ||
|
|
194
|
+
value.nextLeaseFencingToken < 1 ||
|
|
195
|
+
!('runs' in value) ||
|
|
196
|
+
!Array.isArray(value.runs) ||
|
|
197
|
+
!('work' in value) ||
|
|
198
|
+
!Array.isArray(value.work) ||
|
|
199
|
+
!('outbox' in value) ||
|
|
200
|
+
!Array.isArray(value.outbox))
|
|
201
|
+
throw new Error('FLOW_PERSISTENCE_INVALID');
|
|
202
|
+
return value;
|
|
203
|
+
};
|
|
204
|
+
/*** Apply one single-process transaction and atomically replace the persisted JSON snapshot. */
|
|
205
|
+
const updateStoreAsync = async (path, update) => {
|
|
206
|
+
const updated = update(await readStoreAsync(path));
|
|
207
|
+
await mkdir(dirname(path), { recursive: true });
|
|
208
|
+
const temporaryPath = `${path}.${randomUUID()}.tmp`;
|
|
209
|
+
await writeFile(temporaryPath, JSON.stringify(updated.store));
|
|
210
|
+
await rename(temporaryPath, path);
|
|
211
|
+
return copy(updated.result);
|
|
212
|
+
};
|
|
213
|
+
/*** Preserve adapter encapsulation by returning data that callers cannot mutate in-place. */
|
|
214
|
+
const copy = (value) => structuredClone(value);
|
|
215
|
+
/*** Distinguish an absent first-run store from real filesystem or JSON errors. */
|
|
216
|
+
const isMissingFile = (reason) => typeof reason === 'object' && reason !== null && 'code' in reason && reason.code === 'ENOENT';
|
|
217
|
+
/*** Reject a transition that attempts to repoint a run at another immutable flow revision. */
|
|
218
|
+
const sameFlow = (left, right) => left.flow.id === right.flow.id && left.flow.version === right.flow.version;
|
|
219
|
+
//# sourceMappingURL=FileFlowPersistenceAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileFlowPersistenceAdapter.js","sourceRoot":"","sources":["../../../../src/features/execution/adapters/FileFlowPersistenceAdapter.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\n\nimport type {\n FlowDurableRun,\n FlowDurableWork,\n FlowOutboxRecord,\n} from '../../../types/durableExecution.js';\nimport type { FlowPersistencePort } from '../ports/FlowPersistencePort.js';\n\n/*** Create a self-hostable, single-process JSON persistence adapter for durable Flow state. */\nexport const FileFlowPersistenceAdapter = (path: string): FlowPersistencePort => {\n const persistencePath = resolve(path);\n const serializedCall = getSerializedCall(persistencePath);\n return {\n cancelRunWorkAsync: (runId) =>\n serializedCall.runAsync(() => cancelRunWorkAsync(persistencePath, runId)),\n claimDueWorkAsync: (input) =>\n serializedCall.runAsync(() => claimDueWorkAsync(persistencePath, input)),\n completeWorkAsync: (input) =>\n serializedCall.runAsync(() => completeWorkAsync(persistencePath, input)),\n createOutboxAsync: (record) =>\n serializedCall.runAsync(() => createOutboxAsync(persistencePath, record)),\n createRunAsync: (run) => serializedCall.runAsync(() => createRunAsync(persistencePath, run)),\n enqueueWorkAsync: (work) =>\n serializedCall.runAsync(() => enqueueWorkAsync(persistencePath, work)),\n markOutboxDeliveredAsync: (id) =>\n serializedCall.runAsync(() => markOutboxDeliveredAsync(persistencePath, id)),\n readOutboxAsync: (id) => serializedCall.runAsync(() => readOutboxAsync(persistencePath, id)),\n readRunAsync: (id) => serializedCall.runAsync(() => readRunAsync(persistencePath, id)),\n recoverExpiredClaimsAsync: (now) =>\n serializedCall.runAsync(() => recoverExpiredClaimsAsync(persistencePath, now)),\n releaseWorkAsync: (input) =>\n serializedCall.runAsync(() => releaseWorkAsync(persistencePath, input)),\n transitionRunAsync: (input) =>\n serializedCall.runAsync(() => transitionRunAsync(persistencePath, input)),\n };\n};\n\n/** Cancel every nonterminal delivery for a run after its durable cancellation transition. */\nconst cancelRunWorkAsync = async (path: string, runId: string): Promise<readonly string[]> =>\n updateStoreAsync(path, (store) => {\n const cancelled = store.work\n .filter(\n (work) => work.runId === runId && (work.status === 'ready' || work.status === 'claimed'),\n )\n .map((work) => ({ ...work, lease: undefined, status: 'cancelled' as const }));\n return {\n result: cancelled.map((work) => work.id),\n store: { ...store, work: replaceWork(store.work, cancelled) },\n };\n });\n\ninterface FileFlowStore {\n readonly nextLeaseFencingToken: number;\n readonly outbox: readonly FlowOutboxRecord[];\n readonly runs: readonly FlowDurableRun[];\n readonly work: readonly FlowDurableWork[];\n}\n\n/** Persist a stable outbox identity before any external effect is attempted. */\nconst createOutboxAsync = async (path: string, record: FlowOutboxRecord): Promise<void> =>\n updateStoreAsync(path, (store) => {\n const existing = store.outbox.find((candidate) => candidate.id === record.id);\n if (existing !== undefined && existing.idempotencyKey !== record.idempotencyKey)\n throw new Error(`FLOW_OUTBOX_CONFLICT:${record.id}`);\n return {\n result: undefined,\n store: existing === undefined ? { ...store, outbox: [...store.outbox, copy(record)] } : store,\n };\n });\n\ninterface SerializedCall {\n readonly runAsync: <Result>(operation: () => Promise<Result>) => Promise<Result>;\n}\n\nconst serializedCallsByPath = new Map<string, SerializedCall>();\n\n/*** Share serialization for every adapter instance targeting one normalized persistence path. */\nconst getSerializedCall = (path: string): SerializedCall => {\n const existing = serializedCallsByPath.get(path);\n if (existing !== undefined) return existing;\n const serializedCall = createSerializedCall();\n serializedCallsByPath.set(path, serializedCall);\n return serializedCall;\n};\n\n/*** Serialize one file path so concurrent callbacks cannot claim or overwrite the same state. */\nconst createSerializedCall = (): SerializedCall => {\n let previous = Promise.resolve();\n return {\n runAsync: async (operation) => {\n const current = previous.then(operation, operation);\n previous = current.then(\n () => undefined,\n () => undefined,\n );\n return current;\n },\n };\n};\n\n/*** Claim due work in stable order and record a lease before it is returned to a worker. */\nconst claimDueWorkAsync = async (\n path: string,\n input: Parameters<FlowPersistencePort['claimDueWorkAsync']>[0],\n): Promise<readonly FlowDurableWork[]> =>\n updateStoreAsync(path, (store) => {\n const claimed = store.work\n .filter((work) => work.status === 'ready' && work.dueAt <= input.now.toISOString())\n .sort(\n (left, right) => left.dueAt.localeCompare(right.dueAt) || left.id.localeCompare(right.id),\n )\n .slice(0, input.limit)\n .map((work, index) => claimWork(work, input, store.nextLeaseFencingToken + index));\n return {\n result: claimed,\n store: {\n ...store,\n nextLeaseFencingToken: store.nextLeaseFencingToken + claimed.length,\n work: replaceWork(store.work, claimed),\n },\n };\n });\n\n/*** Complete only the unexpired fenced lease returned by the successful claim. */\nconst completeWorkAsync = async (\n path: string,\n { id, leaseFencingToken, now, workerId }: Parameters<FlowPersistencePort['completeWorkAsync']>[0],\n): Promise<boolean> =>\n updateStoreAsync(path, (store) => {\n const work = store.work.find((candidate) => candidate.id === id);\n if (\n work?.status !== 'claimed' ||\n work.lease?.workerId !== workerId ||\n work.lease.fencingToken !== leaseFencingToken ||\n work.lease.expiresAt <= now.toISOString()\n )\n return { result: false, store };\n return {\n result: true,\n store: { ...store, work: replaceWork(store.work, [{ ...work, status: 'completed' }]) },\n };\n });\n\n/*** Persist a new run once, rejecting a duplicate durable identity. */\nconst createRunAsync = async (path: string, run: FlowDurableRun): Promise<void> =>\n updateStoreAsync(path, (store) => {\n if (store.runs.some((candidate) => candidate.id === run.id))\n throw new Error(`FLOW_RUN_EXISTS:${run.id}`);\n return { result: undefined, store: { ...store, runs: [...store.runs, copy(run)] } };\n });\n\n/*** Persist one immutable work identity once, rejecting duplicate delivery records. */\nconst enqueueWorkAsync = async (path: string, work: FlowDurableWork): Promise<void> =>\n updateStoreAsync(path, (store) => {\n if (store.work.some((candidate) => candidate.id === work.id))\n throw new Error(`FLOW_WORK_EXISTS:${work.id}`);\n return { result: undefined, store: { ...store, work: [...store.work, copy(work)] } };\n });\n\n/** Mark an effect delivery complete; duplicate marks are harmless and do not rerun the effect. */\nconst markOutboxDeliveredAsync = async (path: string, id: string): Promise<boolean> =>\n updateStoreAsync(path, (store) => {\n const record = store.outbox.find((candidate) => candidate.id === id);\n if (record === undefined) return { result: false, store };\n return {\n result: true,\n store: {\n ...store,\n outbox: replaceOutbox(store.outbox, [{ ...record, status: 'delivered' }]),\n },\n };\n });\n\n/** Read a copied outbox snapshot without exposing adapter-owned mutable state. */\nconst readOutboxAsync = async (path: string, id: string): Promise<FlowOutboxRecord | undefined> =>\n copy((await readStoreAsync(path)).outbox.find((record) => record.id === id));\n\n/*** Read a copied run snapshot so callers cannot mutate persisted state in-place. */\nconst readRunAsync = async (path: string, id: string): Promise<FlowDurableRun | undefined> =>\n copy((await readStoreAsync(path)).runs.find((run) => run.id === id));\n\n/*** Make expired claims available for recovery after a worker crash or stop. */\nconst recoverExpiredClaimsAsync = async (path: string, now: Date): Promise<readonly string[]> =>\n updateStoreAsync(path, (store) => {\n const recovered = store.work\n .filter(\n (work) =>\n work.status === 'claimed' &&\n work.lease?.expiresAt !== undefined &&\n work.lease.expiresAt <= now.toISOString(),\n )\n .map((work) => ({ ...work, lease: undefined, status: 'ready' as const }));\n return {\n result: recovered.map((work) => work.id),\n store: { ...store, work: replaceWork(store.work, recovered) },\n };\n });\n\n/** Return a fenced claim to the queue without permitting a stale worker to reschedule it. */\nconst releaseWorkAsync = async (\n path: string,\n input: Parameters<FlowPersistencePort['releaseWorkAsync']>[0],\n): Promise<boolean> =>\n updateStoreAsync(path, (store) => {\n const work = store.work.find((candidate) => candidate.id === input.id);\n if (\n work?.status !== 'claimed' ||\n work.lease?.workerId !== input.workerId ||\n work.lease.fencingToken !== input.leaseFencingToken\n )\n return { result: false, store };\n return {\n result: true,\n store: {\n ...store,\n work: replaceWork(store.work, [\n { ...work, dueAt: input.dueAt, lease: undefined, status: 'ready' },\n ]),\n },\n };\n });\n\n/*** Store a revision-checked run transition while preserving its immutable flow reference. */\nconst transitionRunAsync = async (\n path: string,\n { expectedRevision, run }: Parameters<FlowPersistencePort['transitionRunAsync']>[0],\n): Promise<boolean> =>\n updateStoreAsync(path, (store) => {\n const current = store.runs.find((candidate) => candidate.id === run.id);\n if (!sameFlow(current ?? run, run) || current?.revision !== expectedRevision)\n return { result: false, store };\n if (run.revision !== expectedRevision + 1) throw new Error('FLOW_RUN_REVISION_INVALID');\n return { result: true, store: { ...store, runs: replaceRuns(store.runs, [copy(run)]) } };\n });\n\n/*** Build a claimed work record with an explicit bounded lease. */\nconst claimWork = (\n work: FlowDurableWork,\n input: Parameters<FlowPersistencePort['claimDueWorkAsync']>[0],\n fencingToken: number,\n): FlowDurableWork => ({\n ...work,\n lease: {\n expiresAt: new Date(input.now.getTime() + input.leaseDurationMs).toISOString(),\n fencingToken,\n workerId: input.workerId,\n },\n status: 'claimed',\n});\n\n/*** Replace changed work entries by identity without exposing a mutable adapter collection. */\nconst replaceWork = (\n work: readonly FlowDurableWork[],\n replacements: readonly FlowDurableWork[],\n): readonly FlowDurableWork[] =>\n work.map(\n (candidate) => replacements.find((replacement) => replacement.id === candidate.id) ?? candidate,\n );\n\n/*** Replace changed run entries by identity without exposing a mutable adapter collection. */\nconst replaceRuns = (\n runs: readonly FlowDurableRun[],\n replacements: readonly FlowDurableRun[],\n): readonly FlowDurableRun[] =>\n runs.map(\n (candidate) => replacements.find((replacement) => replacement.id === candidate.id) ?? candidate,\n );\n\n/** Replace durable outbox records by identity. */\nconst replaceOutbox = (\n records: readonly FlowOutboxRecord[],\n replacements: readonly FlowOutboxRecord[],\n): readonly FlowOutboxRecord[] =>\n records.map(\n (candidate) => replacements.find((replacement) => replacement.id === candidate.id) ?? candidate,\n );\n\n/*** Read persisted state or initialize an empty store when it has not yet been created. */\nconst readStoreAsync = async (path: string): Promise<FileFlowStore> => {\n try {\n return readFileStore(JSON.parse(await readFile(path, 'utf8')));\n } catch (reason) {\n if (isMissingFile(reason)) return { nextLeaseFencingToken: 1, outbox: [], runs: [], work: [] };\n throw reason;\n }\n};\n\n/*** Validate the adapter-owned persistence envelope before using its fencing counter. */\nconst readFileStore = (value: unknown): FileFlowStore => {\n if (\n typeof value !== 'object' ||\n value === null ||\n !('nextLeaseFencingToken' in value) ||\n typeof value.nextLeaseFencingToken !== 'number' ||\n !Number.isSafeInteger(value.nextLeaseFencingToken) ||\n value.nextLeaseFencingToken < 1 ||\n !('runs' in value) ||\n !Array.isArray(value.runs) ||\n !('work' in value) ||\n !Array.isArray(value.work) ||\n !('outbox' in value) ||\n !Array.isArray(value.outbox)\n )\n throw new Error('FLOW_PERSISTENCE_INVALID');\n return value as FileFlowStore;\n};\n\n/*** Apply one single-process transaction and atomically replace the persisted JSON snapshot. */\nconst updateStoreAsync = async <Result>(\n path: string,\n update: (store: FileFlowStore) => { readonly result: Result; readonly store: FileFlowStore },\n): Promise<Result> => {\n const updated = update(await readStoreAsync(path));\n await mkdir(dirname(path), { recursive: true });\n const temporaryPath = `${path}.${randomUUID()}.tmp`;\n await writeFile(temporaryPath, JSON.stringify(updated.store));\n await rename(temporaryPath, path);\n return copy(updated.result);\n};\n\n/*** Preserve adapter encapsulation by returning data that callers cannot mutate in-place. */\nconst copy = <Value>(value: Value): Value => structuredClone(value);\n\n/*** Distinguish an absent first-run store from real filesystem or JSON errors. */\nconst isMissingFile = (reason: unknown): reason is NodeJS.ErrnoException =>\n typeof reason === 'object' && reason !== null && 'code' in reason && reason.code === 'ENOENT';\n\n/*** Reject a transition that attempts to repoint a run at another immutable flow revision. */\nconst sameFlow = (left: FlowDurableRun, right: FlowDurableRun): boolean =>\n left.flow.id === right.flow.id && left.flow.version === right.flow.version;\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FlowClock } from '../../../types/execution.js';
|
|
2
|
+
import type { FlowPersistencePort } from '../ports/FlowPersistencePort.js';
|
|
3
|
+
/** Cancel a durable run and atomically prevent future ready or claimed deliveries from starting. */
|
|
4
|
+
export declare const cancelDurableRunAsync: (runId: string, dependencies: {
|
|
5
|
+
readonly clock: FlowClock;
|
|
6
|
+
readonly persistence: FlowPersistencePort;
|
|
7
|
+
}) => Promise<boolean>;
|
|
8
|
+
//# sourceMappingURL=cancelDurableRunAsync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancelDurableRunAsync.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/application/cancelDurableRunAsync.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAE3E,oGAAoG;AACpG,eAAO,MAAM,qBAAqB,GAChC,OAAO,MAAM,EACb,cAAc;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAA;CAAE,KACrF,OAAO,CAAC,OAAO,CAejB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Cancel a durable run and atomically prevent future ready or claimed deliveries from starting. */
|
|
2
|
+
export const cancelDurableRunAsync = async (runId, dependencies) => {
|
|
3
|
+
const run = await dependencies.persistence.readRunAsync(runId);
|
|
4
|
+
if (run === undefined || run.status === 'cancelled' || isTerminal(run))
|
|
5
|
+
return false;
|
|
6
|
+
const transitioned = await dependencies.persistence.transitionRunAsync({
|
|
7
|
+
expectedRevision: run.revision,
|
|
8
|
+
run: {
|
|
9
|
+
...run,
|
|
10
|
+
revision: run.revision + 1,
|
|
11
|
+
status: 'cancelled',
|
|
12
|
+
updatedAt: dependencies.clock.now().toISOString(),
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
if (!transitioned)
|
|
16
|
+
return false;
|
|
17
|
+
await dependencies.persistence.cancelRunWorkAsync(runId);
|
|
18
|
+
return true;
|
|
19
|
+
};
|
|
20
|
+
/** Leave completed and terminally failed runs immutable. */
|
|
21
|
+
const isTerminal = (run) => run.status === 'failed' || run.status === 'succeeded';
|
|
22
|
+
//# sourceMappingURL=cancelDurableRunAsync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancelDurableRunAsync.js","sourceRoot":"","sources":["../../../../src/features/execution/application/cancelDurableRunAsync.ts"],"names":[],"mappings":"AAIA,oGAAoG;AACpG,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,EACxC,KAAa,EACb,YAAsF,EACpE,EAAE;IACpB,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,WAAW,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAC/D,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,WAAW,IAAI,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACrF,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,WAAW,CAAC,kBAAkB,CAAC;QACrE,gBAAgB,EAAE,GAAG,CAAC,QAAQ;QAC9B,GAAG,EAAE;YACH,GAAG,GAAG;YACN,QAAQ,EAAE,GAAG,CAAC,QAAQ,GAAG,CAAC;YAC1B,MAAM,EAAE,WAAW;YACnB,SAAS,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;SAClD;KACF,CAAC,CAAC;IACH,IAAI,CAAC,YAAY;QAAE,OAAO,KAAK,CAAC;IAChC,MAAM,YAAY,CAAC,WAAW,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACzD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,4DAA4D;AAC5D,MAAM,UAAU,GAAG,CAAC,GAAmB,EAAW,EAAE,CAClD,GAAG,CAAC,MAAM,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC","sourcesContent":["import type { FlowDurableRun } from '../../../types/durableExecution.js';\nimport type { FlowClock } from '../../../types/execution.js';\nimport type { FlowPersistencePort } from '../ports/FlowPersistencePort.js';\n\n/** Cancel a durable run and atomically prevent future ready or claimed deliveries from starting. */\nexport const cancelDurableRunAsync = async (\n runId: string,\n dependencies: { readonly clock: FlowClock; readonly persistence: FlowPersistencePort },\n): Promise<boolean> => {\n const run = await dependencies.persistence.readRunAsync(runId);\n if (run === undefined || run.status === 'cancelled' || isTerminal(run)) return false;\n const transitioned = await dependencies.persistence.transitionRunAsync({\n expectedRevision: run.revision,\n run: {\n ...run,\n revision: run.revision + 1,\n status: 'cancelled',\n updatedAt: dependencies.clock.now().toISOString(),\n },\n });\n if (!transitioned) return false;\n await dependencies.persistence.cancelRunWorkAsync(runId);\n return true;\n};\n\n/** Leave completed and terminally failed runs immutable. */\nconst isTerminal = (run: FlowDurableRun): boolean =>\n run.status === 'failed' || run.status === 'succeeded';\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FlowDefinitionRef, FlowDurableRun } from '../../../types/durableExecution.js';
|
|
2
|
+
import type { FlowClock } from '../../../types/execution.js';
|
|
3
|
+
import type { FlowPersistencePort } from '../ports/FlowPersistencePort.js';
|
|
4
|
+
/*** Persist a new durable run without executing an operation or provider effect. */
|
|
5
|
+
export declare const createDurableRunAsync: (input: {
|
|
6
|
+
readonly flow: FlowDefinitionRef;
|
|
7
|
+
readonly id: string;
|
|
8
|
+
}, dependencies: {
|
|
9
|
+
readonly clock: FlowClock;
|
|
10
|
+
readonly persistence: FlowPersistencePort;
|
|
11
|
+
}) => Promise<FlowDurableRun>;
|
|
12
|
+
//# sourceMappingURL=createDurableRunAsync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createDurableRunAsync.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/application/createDurableRunAsync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC5F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAE3E,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,GAChC,OAAO;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAChE,cAAc;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAA;CAAE,KACrF,OAAO,CAAC,cAAc,CAIxB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createDurableRun } from '../domain/createDurableRun.js';
|
|
2
|
+
/*** Persist a new durable run without executing an operation or provider effect. */
|
|
3
|
+
export const createDurableRunAsync = async (input, dependencies) => {
|
|
4
|
+
const run = createDurableRun(input, dependencies.clock.now());
|
|
5
|
+
await dependencies.persistence.createRunAsync(run);
|
|
6
|
+
return run;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=createDurableRunAsync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createDurableRunAsync.js","sourceRoot":"","sources":["../../../../src/features/execution/application/createDurableRunAsync.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGjE,oFAAoF;AACpF,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,EACxC,KAAgE,EAChE,YAAsF,EAC7D,EAAE;IAC3B,MAAM,GAAG,GAAG,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;IAC9D,MAAM,YAAY,CAAC,WAAW,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IACnD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC","sourcesContent":["import type { FlowDefinitionRef, FlowDurableRun } from '../../../types/durableExecution.js';\nimport type { FlowClock } from '../../../types/execution.js';\nimport { createDurableRun } from '../domain/createDurableRun.js';\nimport type { FlowPersistencePort } from '../ports/FlowPersistencePort.js';\n\n/*** Persist a new durable run without executing an operation or provider effect. */\nexport const createDurableRunAsync = async (\n input: { readonly flow: FlowDefinitionRef; readonly id: string },\n dependencies: { readonly clock: FlowClock; readonly persistence: FlowPersistencePort },\n): Promise<FlowDurableRun> => {\n const run = createDurableRun(input, dependencies.clock.now());\n await dependencies.persistence.createRunAsync(run);\n return run;\n};\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FlowClock, FlowCompiledGraph, FlowExecutionResult, FlowNodeRegistry, FlowRunRequest } from '../../../types/execution.js';
|
|
2
|
+
/*** Execute a compiled graph in deterministic order within one process. */
|
|
3
|
+
export declare const executeFlowAsync: (graph: FlowCompiledGraph, registry: FlowNodeRegistry, request: FlowRunRequest, clock?: FlowClock) => Promise<FlowExecutionResult>;
|
|
4
|
+
//# sourceMappingURL=executeFlowAsync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executeFlowAsync.d.ts","sourceRoot":"","sources":["../../../../src/features/execution/application/executeFlowAsync.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,SAAS,EACT,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACf,MAAM,6BAA6B,CAAC;AAIrC,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,GAC3B,OAAO,iBAAiB,EACxB,UAAU,gBAAgB,EAC1B,SAAS,cAAc,EACvB,QAAO,SAAqC,KAC3C,OAAO,CAAC,mBAAmB,CAiB7B,CAAC"}
|