@lostgradient/weft 0.12.0 → 0.14.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/README.md +1 -1
- package/dist/cli/api.d.ts +2 -0
- package/dist/cli/api.js +3 -4
- package/dist/cli/generated/operation-catalog.snapshot.json +508 -12
- package/dist/cli/generated/operation-client.generated.d.ts +118 -10
- package/dist/cli/generated/operation-client.generated.js +74 -1
- package/dist/cli/help-text.d.ts +3 -3
- package/dist/cli/help-text.js +4 -4
- package/dist/cli/operation-catalog-snapshot.d.ts +12 -3
- package/dist/cli/operation-catalog-snapshot.js +14 -5
- package/dist/cli/operation-client-runtime.d.ts +29 -0
- package/dist/cli/schedule.js +1 -3
- package/dist/cli/serve-registrations.d.ts +3 -3
- package/dist/cli/serve-registrations.js +2 -2
- package/dist/cli/shutdown.d.ts +11 -0
- package/dist/cli/shutdown.js +25 -0
- package/dist/cli/validate.d.ts +1 -1
- package/dist/cli/version-check.d.ts +1 -1
- package/dist/cli/version-check.js +1 -1
- package/dist/cli-main.js +10 -29
- package/dist/client/client-storage.d.ts +24 -0
- package/dist/client/client-storage.js +14 -0
- package/dist/client/handle-delegation.d.ts +3 -3
- package/dist/client/handle-delegation.js +2 -2
- package/dist/client/http-client-requests.d.ts +5 -3
- package/dist/client/http-client-requests.js +20 -2
- package/dist/client/http-client-storage.d.ts +3 -0
- package/dist/client/http-client-storage.js +82 -0
- package/dist/client/http-client.d.ts +9 -6
- package/dist/client/http-client.js +13 -18
- package/dist/client/http-operations.d.ts +3 -1
- package/dist/client/http-operations.js +77 -1
- package/dist/client/http-request.d.ts +20 -14
- package/dist/client/http-request.js +21 -31
- package/dist/client/in-process-operations.d.ts +2 -2
- package/dist/client/in-process-operations.js +2 -2
- package/dist/client/index.d.ts +5 -1
- package/dist/client/index.js +13 -2
- package/dist/client/interface.d.ts +26 -26
- package/dist/client/local.d.ts +9 -6
- package/dist/client/local.js +9 -4
- package/dist/client/search-params.js +10 -6
- package/dist/client/start-body.d.ts +3 -1
- package/dist/client/start-body.js +9 -0
- package/dist/core/bulk-workflow-filter.d.ts +2 -1
- package/dist/core/bulk-workflow-filter.js +5 -1
- package/dist/core/context/parallel-operations.d.ts +0 -2
- package/dist/core/context/parallel-operations.js +0 -7
- package/dist/core/engine/async-activity-records.d.ts +12 -1
- package/dist/core/engine/async-activity-records.js +81 -2
- package/dist/core/engine/bulk-operations-purge.js +16 -1
- package/dist/core/engine/bulk-operations-shared.js +2 -0
- package/dist/core/engine/callback-checkpoint-persistence.d.ts +2 -0
- package/dist/core/engine/callback-checkpoint-persistence.js +6 -3
- package/dist/core/engine/callback-creators-schedule.d.ts +2 -1
- package/dist/core/engine/callback-creators-schedule.js +3 -3
- package/dist/core/engine/callback-creators.js +4 -22
- package/dist/core/engine/checkpoint-io.d.ts +1 -1
- package/dist/core/engine/checkpoint-reads.js +11 -1
- package/dist/core/engine/child-workflow.js +15 -6
- package/dist/core/engine/construction.js +3 -0
- package/dist/core/engine/disposal.js +2 -0
- package/dist/core/engine/engine-internal-types.d.ts +4 -1
- package/dist/core/engine/fenced-write.d.ts +5 -3
- package/dist/core/engine/finalizer-status.d.ts +10 -0
- package/dist/core/engine/finalizer-status.js +82 -0
- package/dist/core/engine/index.d.ts +26 -2
- package/dist/core/engine/index.js +71 -8
- package/dist/core/engine/internals.d.ts +3 -0
- package/dist/core/engine/lease-health.d.ts +64 -0
- package/dist/core/engine/lease-health.js +0 -0
- package/dist/core/engine/lease-manager.d.ts +4 -2
- package/dist/core/engine/lease-manager.js +32 -4
- package/dist/core/engine/lifecycle/recovered-services.js +0 -2
- package/dist/core/engine/lifecycle/shared.d.ts +11 -8
- package/dist/core/engine/lifecycle/start-batch.js +12 -0
- package/dist/core/engine/lifecycle/start.d.ts +1 -1
- package/dist/core/engine/lifecycle/start.js +34 -5
- package/dist/core/engine/operations-coordination.d.ts +1 -12
- package/dist/core/engine/operations-coordination.js +58 -11
- package/dist/core/engine/operations-speculate.d.ts +1 -1
- package/dist/core/engine/operations-speculate.js +19 -5
- package/dist/core/engine/parallel-dispatch.d.ts +1 -1
- package/dist/core/engine/parallel-dispatch.js +1 -1
- package/dist/core/engine/registration.js +14 -0
- package/dist/core/engine/review-list-entries.d.ts +1 -1
- package/dist/core/engine/review-list-entries.js +6 -30
- package/dist/core/engine/schedule-handle.d.ts +4 -4
- package/dist/core/engine/schedule-handle.js +2 -2
- package/dist/core/engine/schedule-overlap.d.ts +11 -0
- package/dist/core/engine/schedule-overlap.js +50 -0
- package/dist/core/engine/schedule-run.d.ts +11 -3
- package/dist/core/engine/schedule-run.js +27 -4
- package/dist/core/engine/schedule-timer.js +10 -1
- package/dist/core/engine/schedules.d.ts +5 -4
- package/dist/core/engine/schedules.js +62 -56
- package/dist/core/engine/state-utilities.js +1 -1
- package/dist/core/engine/storage-io.d.ts +12 -0
- package/dist/core/engine/storage-io.js +49 -10
- package/dist/core/engine/termination/finalizer-claim.d.ts +8 -6
- package/dist/core/engine/termination/finalizer-claim.js +2 -1
- package/dist/core/engine/termination/finalizer.js +10 -14
- package/dist/core/engine/timeline-coordinator-constants.d.ts +3 -0
- package/dist/core/engine/timeline-coordinator-constants.js +1 -0
- package/dist/core/engine/timeline-coordinator-detail.d.ts +13 -0
- package/dist/core/engine/timeline-coordinator-detail.js +76 -0
- package/dist/core/engine/validation/schedule-options.d.ts +8 -1
- package/dist/core/engine/validation/schedule-options.js +18 -0
- package/dist/core/engine/validation/schedule.d.ts +1 -1
- package/dist/core/engine/validation/schedule.js +27 -3
- package/dist/core/engine/validation.js +66 -2
- package/dist/core/engine/workflow-state-stream.d.ts +5 -1
- package/dist/core/engine/workflow-state-stream.js +37 -14
- package/dist/core/events/schedule-events.d.ts +3 -4
- package/dist/core/events/workflow-events.d.ts +2 -1
- package/dist/core/list-filter-validation.d.ts +3 -0
- package/dist/core/list-filter-validation.js +11 -0
- package/dist/core/registry-snapshot.d.ts +32 -0
- package/dist/core/registry-snapshot.js +28 -0
- package/dist/core/review/index.d.ts +91 -4
- package/dist/core/review/index.js +29 -0
- package/dist/core/types/activity.d.ts +52 -5
- package/dist/core/types/bulk.d.ts +1 -0
- package/dist/core/types/list-options.d.ts +6 -0
- package/dist/core/types/options.d.ts +6 -5
- package/dist/core/types/reviews.d.ts +2 -2
- package/dist/core/types/schedules.d.ts +45 -2
- package/dist/core/types/services-resolution.d.ts +5 -5
- package/dist/core/types/state.d.ts +64 -0
- package/dist/core/types/workflow-log.d.ts +3 -2
- package/dist/core/types/workflow-observability.d.ts +50 -0
- package/dist/core/types/workflow-observability.js +0 -0
- package/dist/core/types/workflow-registry.d.ts +7 -0
- package/dist/core/types.d.ts +1 -0
- package/dist/core/types.js +1 -0
- package/dist/diagnostics/validate.d.ts +9 -30
- package/dist/diagnostics/validate.js +83 -21
- package/dist/diagnostics/version-check.d.ts +2 -2
- package/dist/http.js +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/indexeddb.js +1 -1
- package/dist/server/access-policy-metadata.d.ts +92 -0
- package/dist/server/access-policy-metadata.js +78 -0
- package/dist/server/fault-to-http.d.ts +3 -7
- package/dist/server/fault-to-http.js +2 -34
- package/dist/server/handler/index.js +1 -1
- package/dist/server/handler/route-dispatch.d.ts +10 -3
- package/dist/server/handler/route-dispatch.js +19 -4
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/interactive-operations.js +2 -0
- package/dist/server/openapi-error-responses.js +41 -4
- package/dist/server/openapi.d.ts +3 -1
- package/dist/server/openapi.js +4 -0
- package/dist/server/openrpc-document-schema.d.ts +64 -0
- package/dist/server/openrpc-document-schema.js +6 -26
- package/dist/server/openrpc.d.ts +4 -0
- package/dist/server/openrpc.js +9 -33
- package/dist/server/operation-catalog/index.d.ts +1 -1
- package/dist/server/operation-catalog/raise-fault.d.ts +2 -1
- package/dist/server/operation-catalog/types.d.ts +2 -1
- package/dist/server/operation-fault.d.ts +22 -8
- package/dist/server/operation-fault.js +65 -13
- package/dist/server/operation-registry.d.ts +1 -1
- package/dist/server/operations/aggregate-workflows.d.ts +6 -0
- package/dist/server/operations/aggregate-workflows.js +3 -2
- package/dist/server/operations/async-activity.d.ts +14 -0
- package/dist/server/operations/async-activity.js +88 -4
- package/dist/server/operations/bulk-filter-helpers.d.ts +3 -0
- package/dist/server/operations/bulk-filter-helpers.js +27 -3
- package/dist/server/operations/bulk-mutate-workflow-tags.d.ts +6 -0
- package/dist/server/operations/bulk-signal-workflows.d.ts +6 -0
- package/dist/server/operations/get-stream-chunks.js +2 -7
- package/dist/server/operations/get-system-lease.d.ts +19 -0
- package/dist/server/operations/get-system-lease.js +48 -0
- package/dist/server/operations/get-workflow-observability.d.ts +24 -0
- package/dist/server/operations/get-workflow-observability.js +80 -0
- package/dist/server/operations/get-workflow-result.js +2 -2
- package/dist/server/operations/get-workflow.d.ts +2 -2
- package/dist/server/operations/list-filter-query-extractor.js +15 -0
- package/dist/server/operations/list-reviews.js +2 -19
- package/dist/server/operations/list-workflows.d.ts +6 -0
- package/dist/server/operations/list-workflows.js +12 -3
- package/dist/server/operations/operation-helpers.d.ts +9 -18
- package/dist/server/operations/operation-helpers.js +5 -23
- package/dist/server/operations/purge-workflows.d.ts +3 -0
- package/dist/server/operations/recover-all.js +1 -9
- package/dist/server/operations/single-workflow-control-operation.d.ts +2 -1
- package/dist/server/operations/single-workflow-tag-mutation.js +2 -2
- package/dist/server/operations/sse-stream.js +2 -7
- package/dist/server/operations/storage-capabilities.d.ts +34 -0
- package/dist/server/operations/storage-capabilities.js +38 -0
- package/dist/server/operations/storage.js +8 -17
- package/dist/server/operations/stream-workflow-sse.js +3 -9
- package/dist/server/operations/update-schedule.d.ts +8 -0
- package/dist/server/operations/update-schedule.js +70 -7
- package/dist/server/operations/workflow-events-sse.js +2 -2
- package/dist/server/rest-binding.d.ts +7 -6
- package/dist/server/rest-bindings.d.ts +2 -13
- package/dist/server/rest-bindings.js +19 -10
- package/dist/server/task-queue.d.ts +7 -5
- package/dist/storage/bounded-ndjson-response.d.ts +5 -0
- package/dist/storage/bounded-ndjson-response.js +33 -0
- package/dist/storage/capabilities.d.ts +1 -1
- package/dist/storage/cloudflare-value-codec.d.ts +5 -3
- package/dist/storage/cloudflare-value-codec.js +1 -1
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/http.js +5 -37
- package/dist/storage/index.d.ts +7 -1
- package/dist/storage/interface.d.ts +16 -4
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +4 -0
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.d.ts +3 -16
- package/dist/storage/neon.js +4 -4
- package/dist/storage/postgres.js +4 -4
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web-extension.js +1 -1
- package/package.json +1 -1
|
@@ -175,9 +175,12 @@ export interface EngineInternals {
|
|
|
175
175
|
pendingNestingDepth: number | undefined;
|
|
176
176
|
pendingParentHeaders: Map<string, string> | undefined;
|
|
177
177
|
pendingExecutionStateOwnerId: string | null | undefined;
|
|
178
|
+
pendingParentWorkflowId: string | undefined;
|
|
179
|
+
pendingParentWorkflowExecutionToken: string | undefined;
|
|
178
180
|
workflowNestingDepths: Map<string, number>;
|
|
179
181
|
workflowHeaders: Map<string, Map<string, string>>;
|
|
180
182
|
workflowStateWriteChains: Map<string, Promise<void>>;
|
|
183
|
+
scheduleStateOperationChains: Map<string, Promise<void>>;
|
|
181
184
|
heartbeatDetails: Map<string, unknown>;
|
|
182
185
|
/**
|
|
183
186
|
* Last heartbeat payload PER ACTIVITY STEP, so a retry of a step can read the
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a holder lost its lease while running. `deposed` is confirmed ownership
|
|
3
|
+
* loss; `renewal-unconfirmable` means storage failures outlasted the last
|
|
4
|
+
* confirmed lease expiry.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import type { LeaseLostReason } from '@lostgradient/weft';
|
|
9
|
+
*
|
|
10
|
+
* function requiresImmediateHandoff(reason: LeaseLostReason): boolean {
|
|
11
|
+
* return reason === 'deposed';
|
|
12
|
+
* }
|
|
13
|
+
* void requiresImmediateHandoff;
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export type LeaseLostReason = 'deposed' | 'renewal-unconfirmable';
|
|
17
|
+
/** Process-local, last-known ownership state exposed through the operator API. */
|
|
18
|
+
export type LeaseManagerHealth = {
|
|
19
|
+
status: 'no-lease';
|
|
20
|
+
holdsLease: false;
|
|
21
|
+
} | {
|
|
22
|
+
status: 'healthy';
|
|
23
|
+
holdsLease: true;
|
|
24
|
+
holderId: string;
|
|
25
|
+
heldSince: number;
|
|
26
|
+
expiresAt: number;
|
|
27
|
+
lastRenewedAt: number;
|
|
28
|
+
fencingEpoch: number;
|
|
29
|
+
} | {
|
|
30
|
+
status: 'contested';
|
|
31
|
+
holdsLease: false;
|
|
32
|
+
lossReason?: LeaseLostReason;
|
|
33
|
+
holderId: string;
|
|
34
|
+
heldSince: number;
|
|
35
|
+
expiresAt: number;
|
|
36
|
+
lastRenewedAt: number;
|
|
37
|
+
fencingEpoch: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Public engine ownership-health snapshot returned by
|
|
41
|
+
* {@link Engine.getLeaseHealth} and `weft.system.lease`.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import type { EngineLeaseHealth } from '@lostgradient/weft';
|
|
46
|
+
*
|
|
47
|
+
* function needsOperatorAttention(health: EngineLeaseHealth): boolean {
|
|
48
|
+
* return health.status === 'contested';
|
|
49
|
+
* }
|
|
50
|
+
* void needsOperatorAttention;
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export type EngineLeaseHealth = {
|
|
54
|
+
mode: 'none';
|
|
55
|
+
status: 'disabled';
|
|
56
|
+
holdsLease: false;
|
|
57
|
+
} | ({
|
|
58
|
+
mode: 'lease';
|
|
59
|
+
} & LeaseManagerHealth) | {
|
|
60
|
+
mode: 'lease';
|
|
61
|
+
status: 'contested';
|
|
62
|
+
holdsLease: false;
|
|
63
|
+
lossReason: 'deposed';
|
|
64
|
+
};
|
|
File without changes
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
* @module core/engine/lease-manager
|
|
45
45
|
*/
|
|
46
46
|
import { type Storage } from '../../storage/interface.ts';
|
|
47
|
-
|
|
48
|
-
export type LeaseLostReason
|
|
47
|
+
import type { LeaseLostReason, LeaseManagerHealth } from './lease-health.ts';
|
|
48
|
+
export type { LeaseLostReason } from './lease-health.ts';
|
|
49
49
|
/** Options for {@link createLeaseManager}. */
|
|
50
50
|
export type LeaseManagerOptions = {
|
|
51
51
|
storage: Storage;
|
|
@@ -102,6 +102,8 @@ export type LeaseManager = {
|
|
|
102
102
|
startRenewal(): void;
|
|
103
103
|
renewOnce(): Promise<void>;
|
|
104
104
|
currentEpochBytes(): Uint8Array | null;
|
|
105
|
+
/** Return a defensive, synchronous view of this process's last-known lease state. */
|
|
106
|
+
health(): LeaseManagerHealth;
|
|
105
107
|
release(): Promise<void>;
|
|
106
108
|
stop(): void;
|
|
107
109
|
};
|
|
@@ -9,12 +9,13 @@ import { EngineLeaseAcquisitionTimeoutError, EngineLeaseCorruptedError } from ".
|
|
|
9
9
|
const DEFAULT_ACQUIRE_POLL_INTERVAL_MS = 1000;
|
|
10
10
|
export function createLeaseManager(options) {
|
|
11
11
|
const { storage, holderId, getNow, ttlMs, renewIntervalMs, waitTimeoutMs } = options, acquirePollIntervalMs = options.acquirePollIntervalMs ?? DEFAULT_ACQUIRE_POLL_INTERVAL_MS;
|
|
12
|
-
let stopped = !1, heldEpoch = null, heldEpochBytes = null, lastHolderBytes = null, renewalInterval = null, leaseLost = !1, inFlightRenewal = null;
|
|
12
|
+
let stopped = !1, heldEpoch = null, heldEpochBytes = null, lastHolderBytes = null, renewalInterval = null, leaseLost = !1, leaseLostReason = null, heldSince = null, lastRenewedAt = null, inFlightRenewal = null;
|
|
13
13
|
const delay = options.delay ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
14
14
|
function reportLeaseLost(reason) {
|
|
15
15
|
if (leaseLost)
|
|
16
16
|
return;
|
|
17
17
|
leaseLost = !0;
|
|
18
|
+
leaseLostReason = reason;
|
|
18
19
|
options.onLeaseLost?.(reason);
|
|
19
20
|
}
|
|
20
21
|
async function readState() {
|
|
@@ -27,7 +28,7 @@ export function createLeaseManager(options) {
|
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
async function takeOwnership(expectedEpochBytes, expectedHolderBytes, nextEpoch) {
|
|
30
|
-
const
|
|
31
|
+
const acquiredAt = getNow(), expiresAt = acquiredAt + ttlMs, holderBytes = encodeHolder({ holderId, expiresAt, epoch: nextEpoch }), committed = await storageConditionalBatch(storage, [
|
|
31
32
|
{ key: KEYS.leaseEpoch(), expectedValue: expectedEpochBytes },
|
|
32
33
|
{ key: KEYS.leaseHolder(), expectedValue: expectedHolderBytes }
|
|
33
34
|
], [
|
|
@@ -38,6 +39,8 @@ export function createLeaseManager(options) {
|
|
|
38
39
|
heldEpoch = nextEpoch;
|
|
39
40
|
heldEpochBytes = encodeEpoch(nextEpoch);
|
|
40
41
|
lastHolderBytes = holderBytes;
|
|
42
|
+
heldSince = acquiredAt;
|
|
43
|
+
lastRenewedAt = acquiredAt;
|
|
41
44
|
}
|
|
42
45
|
return committed;
|
|
43
46
|
}
|
|
@@ -75,7 +78,7 @@ export function createLeaseManager(options) {
|
|
|
75
78
|
async function renewOnce() {
|
|
76
79
|
if (stopped || leaseLost || heldEpoch === null || lastHolderBytes === null)
|
|
77
80
|
return;
|
|
78
|
-
const
|
|
81
|
+
const renewedAt = getNow(), expiresAt = renewedAt + ttlMs, holderBytes = encodeHolder({ holderId, expiresAt, epoch: heldEpoch });
|
|
79
82
|
let committed;
|
|
80
83
|
try {
|
|
81
84
|
committed = await storageConditionalBatch(storage, [{ key: KEYS.leaseHolder(), expectedValue: lastHolderBytes }], [{ type: "put", key: KEYS.leaseHolder(), value: holderBytes }]);
|
|
@@ -87,6 +90,7 @@ export function createLeaseManager(options) {
|
|
|
87
90
|
}
|
|
88
91
|
if (committed) {
|
|
89
92
|
lastHolderBytes = holderBytes;
|
|
93
|
+
lastRenewedAt = renewedAt;
|
|
90
94
|
return;
|
|
91
95
|
}
|
|
92
96
|
reportLeaseLost("deposed");
|
|
@@ -109,6 +113,30 @@ export function createLeaseManager(options) {
|
|
|
109
113
|
function currentEpochBytes() {
|
|
110
114
|
return heldEpochBytes === null ? null : heldEpochBytes.slice();
|
|
111
115
|
}
|
|
116
|
+
function health() {
|
|
117
|
+
if (stopped)
|
|
118
|
+
return { status: "no-lease", holdsLease: !1 };
|
|
119
|
+
const holder = lastHolderBytes === null ? null : decodeHolder(lastHolderBytes);
|
|
120
|
+
if (holder === null || heldEpoch === null || heldSince === null || lastRenewedAt === null)
|
|
121
|
+
return { status: "no-lease", holdsLease: !1 };
|
|
122
|
+
const lastKnownLease = {
|
|
123
|
+
holderId: holder.holderId,
|
|
124
|
+
heldSince,
|
|
125
|
+
expiresAt: holder.expiresAt,
|
|
126
|
+
lastRenewedAt,
|
|
127
|
+
fencingEpoch: heldEpoch
|
|
128
|
+
};
|
|
129
|
+
if (leaseLostReason !== null)
|
|
130
|
+
return {
|
|
131
|
+
status: "contested",
|
|
132
|
+
holdsLease: !1,
|
|
133
|
+
lossReason: leaseLostReason,
|
|
134
|
+
...lastKnownLease
|
|
135
|
+
};
|
|
136
|
+
if (getNow() >= holder.expiresAt)
|
|
137
|
+
return { status: "contested", holdsLease: !1, ...lastKnownLease };
|
|
138
|
+
return { status: "healthy", holdsLease: !0, ...lastKnownLease };
|
|
139
|
+
}
|
|
112
140
|
function clearRenewal() {
|
|
113
141
|
if (renewalInterval !== null) {
|
|
114
142
|
clearInterval(renewalInterval);
|
|
@@ -131,5 +159,5 @@ export function createLeaseManager(options) {
|
|
|
131
159
|
stopped = !0;
|
|
132
160
|
clearRenewal();
|
|
133
161
|
}
|
|
134
|
-
return { acquire, startRenewal, renewOnce, currentEpochBytes, release, stop };
|
|
162
|
+
return { acquire, startRenewal, renewOnce, currentEpochBytes, health, release, stop };
|
|
135
163
|
}
|
|
@@ -80,7 +80,5 @@ async function scheduleFromWorkflowState(internals, state) {
|
|
|
80
80
|
return metadata;
|
|
81
81
|
if (metadata.occurrence !== void 0 && scheduleState.nextFireAt === metadata.occurrence)
|
|
82
82
|
return metadata;
|
|
83
|
-
if (metadata.occurrence === void 0 && scheduleState.overlap === "queue" && scheduleState.queuedRuns > 0)
|
|
84
|
-
return metadata;
|
|
85
83
|
return null;
|
|
86
84
|
}
|
|
@@ -58,19 +58,22 @@ export type RecoverAllOptions = {
|
|
|
58
58
|
*/
|
|
59
59
|
onRecoveredWorkflow?: (info: RecoveredWorkflowInfo) => void | Promise<void>;
|
|
60
60
|
/**
|
|
61
|
-
* Policy for a recovered workflow whose
|
|
62
|
-
*
|
|
63
|
-
* {@link WorkflowDefinition.version}.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
61
|
+
* Policy for a recovered workflow whose persisted
|
|
62
|
+
* {@link WorkflowVersionTuple} or checkpoint `version` no longer matches the
|
|
63
|
+
* registered {@link WorkflowDefinition.version}. Recovery checks both
|
|
64
|
+
* persisted records against the registered version so current workflow-state
|
|
65
|
+
* metadata cannot mask a stale checkpoint version. The mismatch is detected
|
|
66
|
+
* before `resolveWorkflowServices` and `onRecoveredWorkflow` run for that
|
|
67
|
+
* workflow, so a mismatched run never re-provides services or invokes the
|
|
68
|
+
* hook.
|
|
66
69
|
*
|
|
67
70
|
* - `'fail-run'` (default): fail only the mismatched run to a terminal
|
|
68
71
|
* `failed` state with a `system` failure category carrying the
|
|
69
72
|
* {@link VersionMismatchError} message, then continue recovering its
|
|
70
73
|
* siblings. The run never advances user workflow code.
|
|
71
|
-
* - `'throw'`:
|
|
72
|
-
* {@link VersionMismatchError} out of `recoverAll()` immediately,
|
|
73
|
-
*
|
|
74
|
+
* - `'throw'`: use fail-fast recovery — rethrow the
|
|
75
|
+
* {@link VersionMismatchError} out of `recoverAll()` immediately, leaving
|
|
76
|
+
* every workflow not yet processed in this batch unresumed.
|
|
74
77
|
*/
|
|
75
78
|
versionMismatchPolicy?: 'fail-run' | 'throw';
|
|
76
79
|
};
|
|
@@ -19,6 +19,7 @@ export function buildStartBatchOperations(_internals, workflowId, state, checkpo
|
|
|
19
19
|
},
|
|
20
20
|
...visibilityIndexOperations,
|
|
21
21
|
...buildWorkflowTagIndexOperations(workflowId, void 0, state.tags),
|
|
22
|
+
...buildChildWorkflowIndexOperations(state),
|
|
22
23
|
...buildInitialSearchAttributeOperations(_internals, workflowId, registration, options?.searchAttributes, callbacks),
|
|
23
24
|
...buildPerRunScratchOperations(workflowId, options, workflowStartHeaders),
|
|
24
25
|
...additionalOperations ?? []
|
|
@@ -34,6 +35,17 @@ export function buildStartBatchOperations(_internals, workflowId, state, checkpo
|
|
|
34
35
|
operations.push(...buildTimerBatchOperations(delayedStartTimer));
|
|
35
36
|
return operations;
|
|
36
37
|
}
|
|
38
|
+
function buildChildWorkflowIndexOperations(state) {
|
|
39
|
+
if (state.parentWorkflowId === void 0)
|
|
40
|
+
return [];
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
type: "put",
|
|
44
|
+
key: KEYS.childWorkflowByParent(state.parentWorkflowId, state.parentWorkflowExecutionToken, state.id),
|
|
45
|
+
value: EMPTY_STORAGE_VALUE
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
}
|
|
37
49
|
function buildPerRunScratchOperations(workflowId, options, workflowStartHeaders) {
|
|
38
50
|
const hasHeaders = workflowStartHeaders !== void 0 && workflowStartHeaders.size > 0, hasServices = options?.services !== void 0, operations = [];
|
|
39
51
|
if (hasHeaders)
|
|
@@ -9,5 +9,5 @@ export declare function start(internals: EngineInternals, type: string, input: u
|
|
|
9
9
|
export declare function startWorkflow(internals: EngineInternals, type: string, input: unknown, options: StartWorkflowOptions | undefined, additionalStartOperations: BatchOperation[] | undefined, callbacks: LifecycleCallbacks, buildIdempotentStartOperations?: BuildIdempotentStartOperations): Promise<WorkflowHandle>;
|
|
10
10
|
export declare function resolveScheduledStartAt(internals: EngineInternals, options: StartOptions | undefined, submissionTime: number, callbacks: LifecycleCallbacks): number | undefined;
|
|
11
11
|
export declare function parseStartOptionDuration(_internals: EngineInternals, duration: Duration, fieldName: 'options.executionTimeout' | 'options.startAfter', _callbacks: LifecycleCallbacks): number;
|
|
12
|
-
export declare function createInitialWorkflowState(internals: EngineInternals, workflowId: string, type: string, input: unknown, versionTuple: WorkflowVersionTuple, options: StartOptions | undefined, tags: string[] | undefined, executionStateOwnerId: string | undefined, delayedStartTimer: TimerEntry | undefined, callbacks: LifecycleCallbacks): WorkflowState;
|
|
12
|
+
export declare function createInitialWorkflowState(internals: EngineInternals, workflowId: string, type: string, input: unknown, versionTuple: WorkflowVersionTuple, options: StartOptions | undefined, tags: string[] | undefined, executionStateOwnerId: string | undefined, parentWorkflowId: string | undefined, parentWorkflowExecutionToken: string | undefined, delayedStartTimer: TimerEntry | undefined, callbacks: LifecycleCallbacks): WorkflowState;
|
|
13
13
|
export declare function createInitialCheckpoint(internals: EngineInternals, workflowId: string, workflowVersion: string, options: StartOptions | undefined, _callbacks: LifecycleCallbacks): Checkpoint;
|
|
@@ -38,6 +38,10 @@ function prepareStartWorkflow(internals, options, callbacks) {
|
|
|
38
38
|
internals.pendingParentHeaders = void 0;
|
|
39
39
|
const pendingExecutionStateOwnerId = internals.pendingExecutionStateOwnerId, executionStateOwnerId = pendingExecutionStateOwnerId === null ? void 0 : pendingExecutionStateOwnerId ?? workflowId;
|
|
40
40
|
internals.pendingExecutionStateOwnerId = void 0;
|
|
41
|
+
const parentWorkflowId = internals.pendingParentWorkflowId;
|
|
42
|
+
internals.pendingParentWorkflowId = void 0;
|
|
43
|
+
const parentWorkflowExecutionToken = internals.pendingParentWorkflowExecutionToken;
|
|
44
|
+
internals.pendingParentWorkflowExecutionToken = void 0;
|
|
41
45
|
const submissionTime = internals.options.getNow(), scheduledStartAt = resolveScheduledStartAt(internals, options, submissionTime, callbacks), normalizedTags = normalizeStartWorkflowTags(internals, options?.tags, void 0, callbacks), delayedStartTimer = scheduledStartAt !== void 0 && scheduledStartAt > submissionTime ? createDelayedStartTimerEntry(internals, workflowId, scheduledStartAt, options, {
|
|
42
46
|
parseStartOptionDuration: (value, fieldName) => parseStartOptionDuration(internals, value, fieldName, callbacks)
|
|
43
47
|
}) : void 0;
|
|
@@ -46,6 +50,8 @@ function prepareStartWorkflow(internals, options, callbacks) {
|
|
|
46
50
|
callerProvidedId,
|
|
47
51
|
parentHeaders,
|
|
48
52
|
executionStateOwnerId,
|
|
53
|
+
parentWorkflowId,
|
|
54
|
+
parentWorkflowExecutionToken,
|
|
49
55
|
submissionTime,
|
|
50
56
|
delayedStartTimer,
|
|
51
57
|
normalizedTags
|
|
@@ -70,7 +76,15 @@ export async function startWorkflow(internals, type, input, options, additionalS
|
|
|
70
76
|
const workflowConcurrency = registration.concurrency;
|
|
71
77
|
assertServicesSupportedForMode(internals, options);
|
|
72
78
|
assertValidOnTerminalConflict(options);
|
|
73
|
-
const preparation = prepareStartWorkflow(internals, options, callbacks), {
|
|
79
|
+
const preparation = prepareStartWorkflow(internals, options, callbacks), {
|
|
80
|
+
workflowId,
|
|
81
|
+
callerProvidedId,
|
|
82
|
+
parentHeaders,
|
|
83
|
+
executionStateOwnerId,
|
|
84
|
+
parentWorkflowId,
|
|
85
|
+
parentWorkflowExecutionToken,
|
|
86
|
+
delayedStartTimer
|
|
87
|
+
} = preparation;
|
|
74
88
|
assertDeferSupported(internals, options, Boolean(delayedStartTimer));
|
|
75
89
|
if (internals.pendingStarts.has(workflowId))
|
|
76
90
|
throw new WorkflowAlreadyExistsError(workflowId);
|
|
@@ -79,7 +93,9 @@ export async function startWorkflow(internals, type, input, options, additionalS
|
|
|
79
93
|
try {
|
|
80
94
|
const terminalRunToPurge = callerProvidedId ? await resolveTerminalConflictForRestart(internals, workflowId, options) : null;
|
|
81
95
|
assertPayloadWithinLimit(input, internals.options.payloadSizePolicy.maxBytes, "workflow input");
|
|
82
|
-
const versionTuple = createWorkflowVersionTuple(internals, registration, callbacks), state = createInitialWorkflowState(internals, workflowId, type, input, versionTuple, options, preparation.normalizedTags, executionStateOwnerId,
|
|
96
|
+
const versionTuple = createWorkflowVersionTuple(internals, registration, callbacks), state = createInitialWorkflowState(internals, workflowId, type, input, versionTuple, options, preparation.normalizedTags, executionStateOwnerId, parentWorkflowId, parentWorkflowExecutionToken, delayedStartTimer, callbacks);
|
|
97
|
+
applyRestartLineage(state, terminalRunToPurge);
|
|
98
|
+
const checkpoint = createInitialCheckpoint(internals, workflowId, versionTuple.workflowVersion, options, callbacks), workflowStartHeaders = runWorkflowStartInterceptor(internals, workflowId, type, input, parentHeaders, callbacks), persistedWorkflowStartHeaders = selectPersistedWorkflowStartHeaders(workflowStartHeaders), purgeDeleteOperations = terminalRunToPurge !== null ? await prepareTerminalRunPurge(internals, terminalRunToPurge, callbacks) : void 0;
|
|
83
99
|
internals.checkpoints.set(workflowId, checkpoint);
|
|
84
100
|
setWorkflowStartHeaders(internals, workflowId, workflowStartHeaders, callbacks);
|
|
85
101
|
internals.workflowVersionTuples.set(workflowId, versionTuple);
|
|
@@ -121,6 +137,17 @@ export async function startWorkflow(internals, type, input, options, additionalS
|
|
|
121
137
|
rollbackTransientStartState(internals, workflowId);
|
|
122
138
|
}
|
|
123
139
|
}
|
|
140
|
+
function applyRestartLineage(state, displacedState) {
|
|
141
|
+
if (displacedState === null)
|
|
142
|
+
return;
|
|
143
|
+
state.restartedFrom = {
|
|
144
|
+
workflowId: displacedState.id,
|
|
145
|
+
...displacedState.workflowExecutionToken !== void 0 && {
|
|
146
|
+
workflowExecutionToken: displacedState.workflowExecutionToken
|
|
147
|
+
},
|
|
148
|
+
replacedAt: state.createdAt
|
|
149
|
+
};
|
|
150
|
+
}
|
|
124
151
|
export function resolveScheduledStartAt(internals, options, submissionTime, callbacks) {
|
|
125
152
|
assertExclusiveStartWorkflowOptions(options?.startAt, options?.startAfter);
|
|
126
153
|
if (options?.startAt !== void 0)
|
|
@@ -138,7 +165,7 @@ export function resolveScheduledStartAt(internals, options, submissionTime, call
|
|
|
138
165
|
export function parseStartOptionDuration(_internals, duration, fieldName, _callbacks) {
|
|
139
166
|
return parseStartWorkflowDuration(duration, fieldName);
|
|
140
167
|
}
|
|
141
|
-
function buildInitialIdentitySlice(workflowId, type, input, versionTuple, executionStateOwnerId, delayedStartTimer, now, tags) {
|
|
168
|
+
function buildInitialIdentitySlice(workflowId, type, input, versionTuple, executionStateOwnerId, parentWorkflowId, parentWorkflowExecutionToken, delayedStartTimer, now, tags) {
|
|
142
169
|
return {
|
|
143
170
|
id: workflowId,
|
|
144
171
|
type,
|
|
@@ -147,6 +174,8 @@ function buildInitialIdentitySlice(workflowId, type, input, versionTuple, execut
|
|
|
147
174
|
versionTuple,
|
|
148
175
|
workflowExecutionToken: crypto.randomUUID(),
|
|
149
176
|
...executionStateOwnerId !== void 0 && { executionStateOwnerId },
|
|
177
|
+
...parentWorkflowId !== void 0 && { parentWorkflowId },
|
|
178
|
+
...parentWorkflowExecutionToken !== void 0 && { parentWorkflowExecutionToken },
|
|
150
179
|
createdAt: now,
|
|
151
180
|
...!delayedStartTimer && { startedAt: now },
|
|
152
181
|
updatedAt: now,
|
|
@@ -163,8 +192,8 @@ function resolveInitialExecutionDeadline(internals, options, delayedStartTimer,
|
|
|
163
192
|
throw new StartWorkflowValidationError("options.executionTimeout must resolve to a finite, non-negative deadline");
|
|
164
193
|
}
|
|
165
194
|
}
|
|
166
|
-
export function createInitialWorkflowState(internals, workflowId, type, input, versionTuple, options, tags, executionStateOwnerId, delayedStartTimer, callbacks) {
|
|
167
|
-
const now = internals.options.getNow(), state = buildInitialIdentitySlice(workflowId, type, input, versionTuple, executionStateOwnerId, delayedStartTimer, now, tags), executionDeadline = resolveInitialExecutionDeadline(internals, options, delayedStartTimer, now, callbacks);
|
|
195
|
+
export function createInitialWorkflowState(internals, workflowId, type, input, versionTuple, options, tags, executionStateOwnerId, parentWorkflowId, parentWorkflowExecutionToken, delayedStartTimer, callbacks) {
|
|
196
|
+
const now = internals.options.getNow(), state = buildInitialIdentitySlice(workflowId, type, input, versionTuple, executionStateOwnerId, parentWorkflowId, parentWorkflowExecutionToken, delayedStartTimer, now, tags), executionDeadline = resolveInitialExecutionDeadline(internals, options, delayedStartTimer, now, callbacks);
|
|
168
197
|
if (executionDeadline !== void 0)
|
|
169
198
|
state.executionDeadline = executionDeadline;
|
|
170
199
|
return state;
|
|
@@ -36,18 +36,7 @@ export declare function processParallelOperation(internals: EngineInternals, wor
|
|
|
36
36
|
export declare function processRaceOperation(internals: EngineInternals, workflowId: string, operation: RaceOperation, callbacks: Pick<CoordinationOperationCallbacks, 'executeSubOperation' | 'runOperationWithResult'>): Promise<void>;
|
|
37
37
|
export declare function assertValidRaceBranchNames(operation: RaceOperation): void;
|
|
38
38
|
export declare function wrapRaceWinner(operation: RaceOperation, winnerIndex: number, value: unknown): unknown;
|
|
39
|
-
|
|
40
|
-
* Execute race branches while giving an already-buffered signal priority over a
|
|
41
|
-
* literal zero-duration sleep. The preflight is non-destructive: the selected
|
|
42
|
-
* wait-signal branch still returns its deferred-consume envelope, so the owning
|
|
43
|
-
* coordinator remains the only place that consumes the durable record.
|
|
44
|
-
*
|
|
45
|
-
* Positive-duration sleeps deliberately bypass this preflight, including when
|
|
46
|
-
* their deadline becomes past-due before dispatch. That keeps ordinary timeout
|
|
47
|
-
* races on the existing Promise.race path and limits the stronger ordering
|
|
48
|
-
* guarantee to the explicit non-blocking-drain idiom.
|
|
49
|
-
*/
|
|
50
|
-
export declare function executeRaceSubOperations(internals: EngineInternals, workflowId: string, operations: readonly ContextOperationRequest[], execute: (operation: ContextOperationRequest, signal: AbortSignal) => Promise<unknown>, parentSignal?: AbortSignal, wrapWinner?: (winnerIndex: number, value: unknown) => unknown): Promise<unknown>;
|
|
39
|
+
export declare function executeRaceSubOperations(internals: EngineInternals, workflowId: string, operations: readonly ContextOperationRequest[], execute: (operation: ContextOperationRequest, signal: AbortSignal) => Promise<unknown>, parentSignal?: AbortSignal, wrapWinner?: (winnerIndex: number, value: unknown) => unknown, observeWinner?: (winnerIndex: number, error?: unknown) => void): Promise<unknown>;
|
|
51
40
|
export declare function processRunAllOperation(internals: EngineInternals, workflowId: string, operation: RunAllOperation, callbacks: Pick<CoordinationOperationCallbacks, 'runOperationWithResult'>): Promise<void>;
|
|
52
41
|
export declare function isConfiguredInlineActivity(fn: Function): fn is RunAllOperation['branches'][string][0] & ActivityFunctionWithMetadata;
|
|
53
42
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isParallelOperationCacheEntry
|
|
3
|
-
} from "../context/parallel-
|
|
3
|
+
} from "../context/parallel-cache-entry.js";
|
|
4
4
|
import {
|
|
5
5
|
executeRunAllBranches,
|
|
6
6
|
executeRunAllBranchesSettled
|
|
@@ -21,6 +21,12 @@ import {
|
|
|
21
21
|
untrackWaiterKey
|
|
22
22
|
} from "./signals.js";
|
|
23
23
|
import { callActivityFunction } from "./state-utilities.js";
|
|
24
|
+
import {
|
|
25
|
+
parallelTimelineDetails,
|
|
26
|
+
raceTimelineDetails,
|
|
27
|
+
recordTimelineBranches,
|
|
28
|
+
runAllTimelineDetails
|
|
29
|
+
} from "./timeline-coordinator-detail.js";
|
|
24
30
|
export function assertSupportedSignalBranches(operations) {
|
|
25
31
|
const seen = new Set, walk = (subOperations) => {
|
|
26
32
|
for (const subOperation of subOperations)
|
|
@@ -74,6 +80,7 @@ export async function processParallelOperation(internals, workflowId, operation,
|
|
|
74
80
|
await finalizeFulfilledSlots(slots);
|
|
75
81
|
const entry = buildEntryFromSlots("all", slots);
|
|
76
82
|
writePartialEntry(internals, workflowId, operation.step, entry);
|
|
83
|
+
recordTimelineBranches(internals, workflowId, parallelTimelineDetails(operation.operations, slots));
|
|
77
84
|
if (hasFirstError)
|
|
78
85
|
throw firstError;
|
|
79
86
|
return valuesFromSlots(slots);
|
|
@@ -111,8 +118,22 @@ export async function processRaceOperation(internals, workflowId, operation, cal
|
|
|
111
118
|
return callbacks.runOperationWithResult(workflowId, operation, async () => {
|
|
112
119
|
assertSupportedSignalBranches(operation.operations);
|
|
113
120
|
assertValidRaceBranchNames(operation);
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
let winnerIndex, winnerError;
|
|
122
|
+
try {
|
|
123
|
+
const winner = await executeRaceSubOperations(internals, workflowId, operation.operations, (subOperation, signal) => callbacks.executeSubOperation(workflowId, subOperation, signal), void 0, (selectedIndex, value) => wrapRaceWinner(operation, selectedIndex, value), (selectedIndex, error) => {
|
|
124
|
+
winnerIndex = selectedIndex;
|
|
125
|
+
winnerError = error;
|
|
126
|
+
});
|
|
127
|
+
try {
|
|
128
|
+
return await finalizeAndUnwrap(winner);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
winnerError = error;
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
if (winnerIndex !== void 0)
|
|
135
|
+
recordTimelineBranches(internals, workflowId, raceTimelineDetails(operation.operations, winnerIndex, operation.branchNames, winnerError));
|
|
136
|
+
}
|
|
116
137
|
});
|
|
117
138
|
}
|
|
118
139
|
export function assertValidRaceBranchNames(operation) {
|
|
@@ -123,21 +144,46 @@ export function wrapRaceWinner(operation, winnerIndex, value) {
|
|
|
123
144
|
const key = operation.branchNames?.[winnerIndex];
|
|
124
145
|
return key === void 0 ? value : createKeyedRaceResultEnvelope(key, value);
|
|
125
146
|
}
|
|
126
|
-
|
|
147
|
+
function createRaceWinnerObservers(wrapWinner, observeWinner) {
|
|
148
|
+
const observers = {};
|
|
149
|
+
if (wrapWinner !== void 0)
|
|
150
|
+
observers.wrapWinner = wrapWinner;
|
|
151
|
+
if (observeWinner !== void 0)
|
|
152
|
+
observers.observeWinner = observeWinner;
|
|
153
|
+
return observers;
|
|
154
|
+
}
|
|
155
|
+
async function settleRaceBranch(operation, index, signal, execute) {
|
|
156
|
+
try {
|
|
157
|
+
return { index, status: "fulfilled", value: await execute(operation, signal) };
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return { error, index, status: "rejected" };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function resolveRaceSettlement(settlement, observers) {
|
|
163
|
+
if (settlement.status === "rejected") {
|
|
164
|
+
observers.observeWinner?.(settlement.index, settlement.error);
|
|
165
|
+
throw settlement.error;
|
|
166
|
+
}
|
|
167
|
+
observers.observeWinner?.(settlement.index);
|
|
168
|
+
return observers.wrapWinner === void 0 ? settlement.value : observers.wrapWinner(settlement.index, settlement.value);
|
|
169
|
+
}
|
|
170
|
+
async function executeBufferedRaceWinner(bufferedSignal, signal, execute, observers) {
|
|
171
|
+
const settlement = await settleRaceBranch(bufferedSignal.operation, bufferedSignal.index, signal, execute);
|
|
172
|
+
return resolveRaceSettlement(settlement, observers);
|
|
173
|
+
}
|
|
174
|
+
export async function executeRaceSubOperations(internals, workflowId, operations, execute, parentSignal, wrapWinner, observeWinner) {
|
|
127
175
|
parentSignal?.throwIfAborted();
|
|
128
|
-
const controller = new AbortController, abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
176
|
+
const controller = new AbortController, abortFromParent = () => controller.abort(parentSignal?.reason), observers = createRaceWinnerObservers(wrapWinner, observeWinner);
|
|
129
177
|
parentSignal?.addEventListener("abort", abortFromParent, { once: !0 });
|
|
130
178
|
try {
|
|
131
179
|
const bufferedSignal = await findBufferedSignalDrainBranch(internals, workflowId, operations);
|
|
132
180
|
parentSignal?.throwIfAborted();
|
|
133
|
-
if (bufferedSignal !== void 0)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
const subOperations = operations.map((operation, index) => execute(operation, controller.signal).then((value) => ({ index, value })));
|
|
181
|
+
if (bufferedSignal !== void 0)
|
|
182
|
+
return await executeBufferedRaceWinner(bufferedSignal, controller.signal, execute, observers);
|
|
183
|
+
const subOperations = operations.map((operation, index) => settleRaceBranch(operation, index, controller.signal, execute));
|
|
138
184
|
Promise.allSettled(subOperations);
|
|
139
185
|
const winner = await Promise.race(subOperations);
|
|
140
|
-
return
|
|
186
|
+
return resolveRaceSettlement(winner, observers);
|
|
141
187
|
} finally {
|
|
142
188
|
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
143
189
|
controller.abort();
|
|
@@ -156,6 +202,7 @@ async function findBufferedSignalDrainBranch(internals, workflowId, operations)
|
|
|
156
202
|
export async function processRunAllOperation(internals, workflowId, operation, callbacks) {
|
|
157
203
|
return callbacks.runOperationWithResult(workflowId, operation, async () => {
|
|
158
204
|
const branchNames = Object.keys(operation.branches), operationIds = branchNames.map((name) => `run-all:${operation.step}:${name}`), resumedSlots = extractResumedSlots(operation.resumedCacheEntry), resumedSlotsByName = mapResumedSlotsByName(resumedSlots, branchNames), branchesToRun = filterBranchesToRun(operation.branches, branchNames, resumedSlotsByName), { outcomes, hasFirstError, firstError } = await executeRunAllBranchesSettled(branchesToRun, (fn, input) => callActivityFunction(fn, input)), slots = mergeRunAllSlots(branchNames, operationIds, resumedSlotsByName, outcomes), entry = buildEntryFromSlots("run-all", slots, branchNames), partialEntryWritten = writePartialEntry(internals, workflowId, operation.step, entry);
|
|
205
|
+
recordTimelineBranches(internals, workflowId, runAllTimelineDetails(branchNames, slots, operation.branches));
|
|
159
206
|
if (hasFirstError) {
|
|
160
207
|
assertPartialFailurePersistenceSupported(partialEntryWritten, slots, "ctx.runAll", "worker execution mode");
|
|
161
208
|
throw firstError;
|
|
@@ -12,5 +12,5 @@ export type SpeculateOperationCallbacks = {
|
|
|
12
12
|
};
|
|
13
13
|
export declare function processSpeculateOperation(internals: EngineInternals, workflowId: string, operation: SpeculateOperation, callbacks: SpeculateOperationCallbacks): Promise<void>;
|
|
14
14
|
export declare function executeSpeculativeBranch(internals: EngineInternals, workflowId: string, operation: SpeculateOperation, callbacks: Pick<SpeculateOperationCallbacks, 'executeSubOperation'>): Promise<unknown>;
|
|
15
|
-
export declare function driveSpeculativeGenerator(workflowId: string, generator: SpeculativeOperationGenerator, speculativeState: SpeculativeExecutionState, callbacks: Pick<SpeculateOperationCallbacks, 'executeSubOperation'
|
|
15
|
+
export declare function driveSpeculativeGenerator(workflowId: string, generator: SpeculativeOperationGenerator, speculativeState: SpeculativeExecutionState, callbacks: Pick<SpeculateOperationCallbacks, 'executeSubOperation'>, observeChild?: (operation: ContextOperationRequest, index: number, error?: unknown) => void): Promise<unknown>;
|
|
16
16
|
export {};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { finalizeAndUnwrap } from "./deferred-consume-envelope.js";
|
|
2
2
|
import { assertSupportedSignalBranches } from "./operations-coordination.js";
|
|
3
3
|
import { SpeculativeExecutionState } from "./speculative-execution-state.js";
|
|
4
|
+
import {
|
|
5
|
+
operationTimelineDetail,
|
|
6
|
+
recordTimelineSpeculation
|
|
7
|
+
} from "./timeline-coordinator-detail.js";
|
|
4
8
|
export async function processSpeculateOperation(internals, workflowId, operation, callbacks) {
|
|
5
9
|
return callbacks.runOperationWithResult(workflowId, operation, () => executeSpeculativeBranch(internals, workflowId, operation, callbacks));
|
|
6
10
|
}
|
|
@@ -11,32 +15,42 @@ export async function executeSpeculativeBranch(internals, workflowId, operation,
|
|
|
11
15
|
const parentContext = inlineStrategy.getContext(workflowId);
|
|
12
16
|
if (!parentContext)
|
|
13
17
|
throw Error(`No active inline context for workflow "${workflowId}"`);
|
|
14
|
-
const speculativeContext = parentContext.createSpeculativeChild(), speculativeState = new SpeculativeExecutionState, generator = createSpeculativeOperationGenerator(operation, speculativeContext);
|
|
18
|
+
const speculativeContext = parentContext.createSpeculativeChild(), speculativeState = new SpeculativeExecutionState, generator = createSpeculativeOperationGenerator(operation, speculativeContext), children = [];
|
|
15
19
|
try {
|
|
16
|
-
const result = await driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks)
|
|
20
|
+
const result = await driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks, (child, index, error) => {
|
|
21
|
+
children.push(operationTimelineDetail(child, index, error === void 0 ? "fulfilled" : "rejected", error === void 0 ? {} : { error }));
|
|
22
|
+
});
|
|
17
23
|
await speculativeState.drainVerifications();
|
|
18
24
|
parentContext.commitSpeculativeChild(speculativeContext);
|
|
25
|
+
recordTimelineSpeculation(internals, workflowId, children, "committed");
|
|
19
26
|
return result;
|
|
20
27
|
} catch (error) {
|
|
21
|
-
|
|
28
|
+
try {
|
|
29
|
+
await speculativeState.rollback();
|
|
30
|
+
} finally {
|
|
31
|
+
recordTimelineSpeculation(internals, workflowId, children, "rolled-back");
|
|
32
|
+
}
|
|
22
33
|
throw error;
|
|
23
34
|
}
|
|
24
35
|
}
|
|
25
36
|
function createSpeculativeOperationGenerator(operation, speculativeContext) {
|
|
26
37
|
return operation.execute(speculativeContext);
|
|
27
38
|
}
|
|
28
|
-
export async function driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks) {
|
|
39
|
+
export async function driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks, observeChild) {
|
|
40
|
+
let childIndex = 0;
|
|
29
41
|
const advance = async (lastResult, errorToThrow) => {
|
|
30
42
|
const iterationResult = errorToThrow === void 0 ? await generator.next(lastResult) : await generator.throw(errorToThrow);
|
|
31
43
|
if (iterationResult.done)
|
|
32
44
|
return iterationResult.value;
|
|
33
|
-
const nextOperation = iterationResult.value
|
|
45
|
+
const nextOperation = iterationResult.value, currentChildIndex = childIndex++;
|
|
34
46
|
try {
|
|
35
47
|
if (nextOperation.type === "race" || nextOperation.type === "parallel")
|
|
36
48
|
assertSupportedSignalBranches(nextOperation.operations);
|
|
37
49
|
const nextResult = await callbacks.executeSubOperation(workflowId, nextOperation, void 0, speculativeState), finalizedResult = await finalizeAndUnwrap(nextResult);
|
|
50
|
+
observeChild?.(nextOperation, currentChildIndex);
|
|
38
51
|
return advance(finalizedResult, void 0);
|
|
39
52
|
} catch (error) {
|
|
53
|
+
observeChild?.(nextOperation, currentChildIndex, error);
|
|
40
54
|
return advance(lastResult, error instanceof Error ? error : Error(String(error)));
|
|
41
55
|
}
|
|
42
56
|
};
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* checkpoint-write machinery — see the `parallel-execution.md` guide
|
|
26
26
|
* for the precise durability contract.
|
|
27
27
|
*/
|
|
28
|
-
import type { ParallelBranchSlot, ParallelOperationCacheEntry } from '../context/parallel-
|
|
28
|
+
import type { ParallelBranchSlot, ParallelOperationCacheEntry } from '../context/parallel-cache-entry.ts';
|
|
29
29
|
export type ParallelDispatchResult = {
|
|
30
30
|
/** Final slot table — every entry is `fulfilled` or `rejected`. */
|
|
31
31
|
slots: ParallelBranchSlot[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createParallelOperationCacheEntry } from "../context/parallel-
|
|
1
|
+
import { createParallelOperationCacheEntry } from "../context/parallel-cache-entry.js";
|
|
2
2
|
export function createFirstRejectionCapture() {
|
|
3
3
|
return { hasFirstError: !1, firstError: void 0 };
|
|
4
4
|
}
|
|
@@ -59,8 +59,22 @@ function applyOptionalRegistrationFields(entry, registration) {
|
|
|
59
59
|
function buildRegistrationEntry(name, registration) {
|
|
60
60
|
const entry = buildBaseRegistrationEntry(name, registration);
|
|
61
61
|
applyOptionalRegistrationFields(entry, registration);
|
|
62
|
+
if (isBuilderWorkflowDefinition(registration)) {
|
|
63
|
+
entry.signals = normalizeMessageDefinitions(name, "signal", registration.signals);
|
|
64
|
+
entry.updates = normalizeMessageDefinitions(name, "update", registration.updates);
|
|
65
|
+
entry.queries = normalizeMessageDefinitions(name, "query", registration.queries);
|
|
66
|
+
}
|
|
62
67
|
return entry;
|
|
63
68
|
}
|
|
69
|
+
function normalizeMessageDefinitions(workflowType, messageKind, definitions) {
|
|
70
|
+
const normalized = Object.create(null), sortedDefinitions = Object.values(definitions).toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
71
|
+
for (const definition of sortedDefinitions) {
|
|
72
|
+
if (Object.hasOwn(normalized, definition.name))
|
|
73
|
+
throw Error(`Duplicate ${messageKind} runtime name "${definition.name}" in workflow "${workflowType}"`);
|
|
74
|
+
normalized[definition.name] = definition;
|
|
75
|
+
}
|
|
76
|
+
return normalized;
|
|
77
|
+
}
|
|
64
78
|
function commitWorkflowDefinition(internals, definition, callbacks) {
|
|
65
79
|
const name = definition.name;
|
|
66
80
|
validateWorkflowOrActivityName(name, "workflow");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type HumanReviewResult, type ReviewRequest } from '../review/index.ts';
|
|
2
2
|
import type { CompletedReviewEntry, PendingReviewEntry } from '../types.ts';
|
|
3
3
|
export declare function toPendingReviewEntry(review: ReviewRequest): PendingReviewEntry;
|
|
4
4
|
export declare function parseStoredReviewRequest(value: Uint8Array): ReviewRequest | null;
|