@lostgradient/weft 0.24.0 → 0.24.2
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 +2 -2
- package/dist/cli/conformance.js +5 -2
- package/dist/cli/generated/operation-catalog.snapshot.json +95 -1
- package/dist/cli/generated/operation-client.generated.d.ts +28 -2
- package/dist/cli/generated/operation-client.generated.js +2 -0
- package/dist/cli/parse-schedule-arguments.js +15 -3
- package/dist/cli/schedule.js +2 -1
- package/dist/cli/types.d.ts +2 -1
- package/dist/client/http-client-requests.js +6 -9
- package/dist/client/start-body.js +1 -0
- package/dist/core/catalog/index.d.ts +2 -2
- package/dist/core/catalog/index.js +9 -2
- package/dist/core/catalog/reference-counts.d.ts +29 -14
- package/dist/core/catalog/removal.d.ts +63 -10
- package/dist/core/catalog/removal.js +18 -2
- package/dist/core/catalog/storage-io.d.ts +23 -0
- package/dist/core/catalog/storage-io.js +1 -1
- package/dist/core/engine/activity-resolution.js +17 -17
- package/dist/core/engine/bulk-operations-purge.d.ts +1 -1
- package/dist/core/engine/bulk-operations-purge.js +17 -22
- package/dist/core/engine/bulk-operations-retry.d.ts +24 -0
- package/dist/core/engine/bulk-operations-retry.js +190 -0
- package/dist/core/engine/bulk-operations-shared.d.ts +18 -0
- package/dist/core/engine/bulk-operations-shared.js +14 -0
- package/dist/core/engine/bulk-operations.d.ts +3 -2
- package/dist/core/engine/bulk-operations.js +10 -169
- package/dist/core/engine/callback-creators-bundles.js +5 -3
- package/dist/core/engine/callback-creators-core.js +9 -1
- package/dist/core/engine/callback-creators-schedule.js +2 -2
- package/dist/core/engine/catalog-readiness.js +2 -0
- package/dist/core/engine/catalog-removal.d.ts +107 -3
- package/dist/core/engine/catalog-removal.js +66 -13
- package/dist/core/engine/catalog-tombstone-recovery.d.ts +56 -0
- package/dist/core/engine/catalog-tombstone-recovery.js +39 -0
- package/dist/core/engine/child-workflow.d.ts +13 -1
- package/dist/core/engine/child-workflow.js +22 -4
- package/dist/core/engine/constraints.js +5 -1
- package/dist/core/engine/construction.d.ts +1 -1
- package/dist/core/engine/decode-revision.d.ts +32 -0
- package/dist/core/engine/decode-revision.js +9 -0
- package/dist/core/engine/disposal.d.ts +6 -2
- package/dist/core/engine/disposal.js +24 -10
- package/dist/core/engine/dynamic-source-errors.d.ts +81 -0
- package/dist/core/engine/dynamic-source-errors.js +25 -0
- package/dist/core/engine/dynamic-source-execution.d.ts +173 -0
- package/dist/core/engine/dynamic-source-execution.js +108 -0
- package/dist/core/engine/engine-internal-types.d.ts +16 -0
- package/dist/core/engine/engine-workflows-namespace.d.ts +10 -0
- package/dist/core/engine/engine-workflows-namespace.js +3 -1
- package/dist/core/engine/generation-codec.d.ts +22 -0
- package/dist/core/engine/generation-codec.js +2 -0
- package/dist/core/engine/index.d.ts +18 -10
- package/dist/core/engine/index.js +22 -11
- package/dist/core/engine/inline-launch-queue.js +1 -1
- package/dist/core/engine/internals.d.ts +26 -30
- package/dist/core/engine/lifecycle/checkpoint-launch.d.ts +29 -0
- package/dist/core/engine/lifecycle/checkpoint-launch.js +75 -0
- package/dist/core/engine/lifecycle/delayed-start-registration.d.ts +30 -0
- package/dist/core/engine/lifecycle/delayed-start-registration.js +11 -0
- package/dist/core/engine/lifecycle/fork-helpers.d.ts +33 -1
- package/dist/core/engine/lifecycle/fork-helpers.js +3 -1
- package/dist/core/engine/lifecycle/recovery-isolation.d.ts +20 -0
- package/dist/core/engine/lifecycle/recovery-isolation.js +26 -0
- package/dist/core/engine/lifecycle/recovery-revision-groups.d.ts +75 -0
- package/dist/core/engine/lifecycle/recovery-revision-groups.js +64 -0
- package/dist/core/engine/lifecycle/resume-body.d.ts +44 -0
- package/dist/core/engine/lifecycle/resume-body.js +211 -0
- package/dist/core/engine/lifecycle/resume-generation-guard.d.ts +58 -0
- package/dist/core/engine/lifecycle/resume-generation-guard.js +11 -0
- package/dist/core/engine/lifecycle/resume.d.ts +46 -3
- package/dist/core/engine/lifecycle/resume.js +21 -201
- package/dist/core/engine/lifecycle/shared.d.ts +43 -0
- package/dist/core/engine/lifecycle/standalone-claim-acquire.d.ts +80 -2
- package/dist/core/engine/lifecycle/standalone-claim-acquire.js +20 -4
- package/dist/core/engine/lifecycle/start-commit-errors.d.ts +12 -0
- package/dist/core/engine/lifecycle/start-commit-errors.js +6 -0
- package/dist/core/engine/lifecycle/start-commit.d.ts +24 -7
- package/dist/core/engine/lifecycle/start-commit.js +57 -15
- package/dist/core/engine/lifecycle/start-exec.d.ts +18 -2
- package/dist/core/engine/lifecycle/start-exec.js +7 -5
- package/dist/core/engine/lifecycle/start-or-signal-create.js +1 -3
- package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +8 -2
- package/dist/core/engine/lifecycle/start-or-signal-resolution.js +11 -3
- package/dist/core/engine/lifecycle/start-or-signal.js +1 -1
- package/dist/core/engine/lifecycle/start-precondition-attribution.d.ts +55 -0
- package/dist/core/engine/lifecycle/start-precondition-attribution.js +13 -0
- package/dist/core/engine/lifecycle/start-revision-resolution.d.ts +34 -0
- package/dist/core/engine/lifecycle/start-revision-resolution.js +11 -0
- package/dist/core/engine/lifecycle/start-schedule-timing.d.ts +11 -0
- package/dist/core/engine/lifecycle/start-schedule-timing.js +21 -0
- package/dist/core/engine/lifecycle/start-state.d.ts +16 -0
- package/dist/core/engine/lifecycle/start-state.js +60 -0
- package/dist/core/engine/lifecycle/start-terminal-conflict-purge.d.ts +146 -4
- package/dist/core/engine/lifecycle/start-terminal-conflict-purge.js +33 -4
- package/dist/core/engine/lifecycle/start.d.ts +54 -7
- package/dist/core/engine/lifecycle/start.js +53 -90
- package/dist/core/engine/lifecycle/transition.d.ts +12 -4
- package/dist/core/engine/lifecycle/transition.js +40 -95
- package/dist/core/engine/lifecycle.d.ts +5 -2
- package/dist/core/engine/lifecycle.js +7 -6
- package/dist/core/engine/listing.js +15 -1
- package/dist/core/engine/memo-durable-activity.js +4 -1
- package/dist/core/engine/nonterminal-revision-count.d.ts +25 -0
- package/dist/core/engine/nonterminal-revision-count.js +16 -0
- package/dist/core/engine/operations-time.d.ts +5 -23
- package/dist/core/engine/operations-time.js +13 -11
- package/dist/core/engine/ownership-bootstrap.d.ts +1 -1
- package/dist/core/engine/pinned-schedule-revision-count.d.ts +26 -0
- package/dist/core/engine/pinned-schedule-revision-count.js +19 -0
- package/dist/core/engine/pinned-schedule-revision.d.ts +76 -0
- package/dist/core/engine/pinned-schedule-revision.js +53 -0
- package/dist/core/engine/registration.d.ts +30 -0
- package/dist/core/engine/registration.js +17 -2
- package/dist/core/engine/retention.js +9 -2
- package/dist/core/engine/revision-errors.d.ts +71 -0
- package/dist/core/engine/revision-errors.js +13 -0
- package/dist/core/engine/schedule-overlap.js +2 -1
- package/dist/core/engine/schedule-revision-fire.d.ts +17 -0
- package/dist/core/engine/schedule-revision-fire.js +3 -0
- package/dist/core/engine/schedule-run.d.ts +10 -0
- package/dist/core/engine/schedule-run.js +2 -1
- package/dist/core/engine/schedules.d.ts +11 -1
- package/dist/core/engine/schedules.js +28 -9
- package/dist/core/engine/source-diagnostics.d.ts +76 -0
- package/dist/core/engine/source-diagnostics.js +105 -0
- package/dist/core/engine/source-registration.js +4 -2
- package/dist/core/engine/source-resolution.d.ts +25 -9
- package/dist/core/engine/source-resolution.js +49 -14
- package/dist/core/engine/source-runtime-state.d.ts +92 -0
- package/dist/core/engine/source-runtime-state.js +36 -0
- package/dist/core/engine/storage-io.d.ts +19 -2
- package/dist/core/engine/storage-io.js +21 -2
- package/dist/core/engine/termination/complete.js +1 -1
- package/dist/core/engine/termination/finalizer-registration.d.ts +18 -0
- package/dist/core/engine/termination/finalizer-registration.js +21 -0
- package/dist/core/engine/termination/finalizer.d.ts +16 -19
- package/dist/core/engine/termination/finalizer.js +3 -3
- package/dist/core/engine/termination/suspend.js +3 -0
- package/dist/core/engine/time-operation-callbacks.d.ts +32 -0
- package/dist/core/engine/time-operation-callbacks.js +0 -0
- package/dist/core/engine/validation/schedule-cadence.d.ts +16 -0
- package/dist/core/engine/validation/schedule-cadence.js +20 -0
- package/dist/core/engine/validation/schedule-options.d.ts +4 -1
- package/dist/core/engine/validation/schedule-options.js +14 -0
- package/dist/core/engine/validation/schedule-revision.d.ts +24 -0
- package/dist/core/engine/validation/schedule-revision.js +21 -0
- package/dist/core/engine/validation/schedule-warnings.d.ts +11 -0
- package/dist/core/engine/validation/schedule-warnings.js +5 -0
- package/dist/core/engine/validation/schedule.d.ts +29 -4
- package/dist/core/engine/validation/schedule.js +19 -36
- package/dist/core/engine/validation.js +13 -17
- package/dist/core/engine/workflow-claim-reclaim-target.d.ts +1 -1
- package/dist/core/engine/workflow-claim-reclaim-target.js +22 -8
- package/dist/core/engine/workflow-claim-registry.d.ts +79 -75
- package/dist/core/engine/workflow-claim-registry.js +15 -2
- package/dist/core/engine/workflow-generation-fence.d.ts +74 -0
- package/dist/core/engine/workflow-generation-fence.js +19 -0
- package/dist/core/engine/workflow-retention-deadline.d.ts +16 -0
- package/dist/core/engine/workflow-retention-deadline.js +33 -0
- package/dist/core/events/event-map.d.ts +5 -0
- package/dist/core/events/index.d.ts +1 -0
- package/dist/core/events/index.js +1 -0
- package/dist/core/events/workflow-source-events.d.ts +102 -0
- package/dist/core/events/workflow-source-events.js +57 -0
- package/dist/core/execution-strategy.d.ts +7 -0
- package/dist/core/inline-execution-strategy.context-options.d.ts +1 -1
- package/dist/core/inline-execution-strategy.js +1 -1
- package/dist/core/start-workflow-validation.d.ts +15 -0
- package/dist/core/start-workflow-validation.js +11 -1
- package/dist/core/types/checkpoint.d.ts +14 -0
- package/dist/core/types/schedules.d.ts +52 -1
- package/dist/core/types/state.d.ts +16 -0
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +4 -1
- package/dist/core/worker-execution-strategy.d.ts +1 -0
- package/dist/core/worker-execution-strategy.js +13 -1
- package/dist/core/worker-fault-handling.d.ts +10 -0
- package/dist/core/worker-fault-handling.js +1 -0
- package/dist/core/worker-inbound-message.d.ts +1 -0
- package/dist/core/worker-inbound-message.js +1 -0
- package/dist/core/worker-protocol-guard.js +4 -0
- package/dist/core/worker-protocol.d.ts +7 -0
- package/dist/core/worker-protocol.js +2 -1
- package/dist/core/worker-turn-watchdog.d.ts +8 -1
- package/dist/core/worker-turn-watchdog.js +3 -2
- package/dist/core/workflow-identifiers.d.ts +37 -0
- package/dist/core/workflow-identifiers.js +19 -1
- package/dist/diagnostics/format.js +5 -0
- package/dist/diagnostics/types.d.ts +24 -0
- package/dist/diagnostics/version-check.js +8 -2
- package/dist/http.js +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +7 -0
- package/dist/indexeddb.js +1 -1
- package/dist/json-schema.js +3 -3
- package/dist/server/fault-to-json-rpc.js +2 -1
- package/dist/server/fleet-event-feed.js +6 -6
- package/dist/server/index.d.ts +11 -0
- package/dist/server/operation-fault.d.ts +2 -0
- package/dist/server/operation-fault.js +2 -1
- package/dist/server/operations/create-schedule.d.ts +2 -0
- package/dist/server/operations/create-schedule.js +10 -6
- package/dist/server/operations/get-catalog-diagnostics.d.ts +28 -0
- package/dist/server/operations/get-catalog-diagnostics.js +10 -2
- package/dist/server/operations/get-task-detail-schema.d.ts +8 -0
- package/dist/server/operations/get-task-detail-schema.js +1 -0
- package/dist/server/operations/get-task-detail.d.ts +8 -0
- package/dist/server/operations/get-task-detail.js +1 -0
- package/dist/server/operations/get-task-diagnostics.d.ts +4 -4
- package/dist/server/operations/preload-workflow-revision.d.ts +28 -0
- package/dist/server/operations/preload-workflow-revision.js +60 -0
- package/dist/server/operations/schedule-faults.d.ts +1 -0
- package/dist/server/operations/schedule-faults.js +32 -4
- package/dist/server/operations/schedule-rest-body.d.ts +1 -0
- package/dist/server/operations/schedule-rest-body.js +2 -1
- package/dist/server/operations/static-registrations.js +6 -0
- package/dist/server/operations/update-schedule.d.ts +2 -0
- package/dist/server/operations/update-schedule.js +4 -2
- package/dist/server/operations/workflow-catalog-operation-helpers.js +27 -0
- package/dist/server/rest-binding.d.ts +11 -0
- package/dist/server/runtime/task-dispatch-envelope.d.ts +41 -0
- package/dist/server/runtime/task-dispatch-envelope.js +54 -0
- package/dist/server/runtime/task-dispatch-revision.d.ts +29 -0
- package/dist/server/runtime/task-dispatch-revision.js +13 -0
- package/dist/server/runtime/task-dispatch.d.ts +3 -1
- package/dist/server/runtime/task-dispatch.js +20 -44
- package/dist/server/runtime/task-ledger-recovery.js +1 -1
- package/dist/server/runtime/task-ledger-runtime.d.ts +14 -2
- package/dist/server/runtime/task-ledger-runtime.js +5 -3
- package/dist/server/runtime/task-polling.js +9 -3
- package/dist/server/runtime/task-reconciliation.js +2 -1
- package/dist/server/runtime/websocket-worker.js +9 -0
- package/dist/server/task-ledger-codec.d.ts +28 -0
- package/dist/server/task-ledger-codec.js +7 -1
- package/dist/server/task-ledger-transition-helpers.js +1 -0
- package/dist/server/task-ledger-types.d.ts +9 -0
- package/dist/server/task-ledger.d.ts +1 -1
- package/dist/server/task-ledger.js +3 -1
- package/dist/server/task-queue-types.d.ts +2 -0
- package/dist/storage/bun-sql.js +135 -100
- package/dist/storage/catalog-keys.d.ts +19 -0
- package/dist/storage/catalog-keys.js +3 -1
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/generation-keys.d.ts +52 -0
- package/dist/storage/generation-keys.js +4 -0
- package/dist/storage/index.d.ts +76 -63
- package/dist/storage/interface.d.ts +24 -230
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +2 -0
- package/dist/storage/lease-keys.d.ts +48 -0
- package/dist/storage/lease-keys.js +8 -0
- package/dist/storage/lmdb.d.ts +30 -2
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +3 -3
- package/dist/storage/node-sqlite.js +135 -100
- package/dist/storage/postgres.js +3 -3
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/signal-keys.d.ts +23 -0
- package/dist/storage/signal-keys.js +9 -0
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +2 -2
- package/dist/storage/workflow-lifecycle-keys.d.ts +128 -0
- package/dist/storage/workflow-lifecycle-keys.js +46 -0
- package/dist/storage/workflow-record-keys.d.ts +98 -0
- package/dist/storage/workflow-record-keys.js +47 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web-extension.js +1 -1
- package/dist/worker/protocol-messages.d.ts +17 -0
- package/dist/worker/protocol-schemas.d.ts +32 -0
- package/dist/worker/protocol-schemas.js +7 -3
- package/dist/worker/protocol-task-result.js +35 -3
- package/dist/worker/protocol.js +1 -1
- package/dist/worker/registry/types.d.ts +2 -0
- package/dist/worker/registry.d.ts +1 -1
- package/dist/worker/registry.js +3 -1
- package/dist/workers/workflow-runner.d.ts +9 -0
- package/dist/workers/workflow-runner.js +9 -2
- package/dist/workers/workflow-worker-entry.js +9 -4
- package/package.json +2 -2
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { KEYS } from "../../storage/interface.js";
|
|
2
2
|
import { decode } from "../codec.js";
|
|
3
|
-
import { WorkflowNotRegisteredError } from "./errors.js";
|
|
4
3
|
import { commitFencedEngineWrite } from "./fenced-write.js";
|
|
4
|
+
import {
|
|
5
|
+
buildPinnedRevisionWriteOptions,
|
|
6
|
+
resolveScheduleCreationRevision,
|
|
7
|
+
resolveScheduleRevisionForPin
|
|
8
|
+
} from "./pinned-schedule-revision.js";
|
|
5
9
|
import { ScheduleHandle } from "./schedule-handle.js";
|
|
6
10
|
import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
|
|
7
11
|
import { getNextScheduleOccurrence } from "./schedule-occurrence.js";
|
|
@@ -31,9 +35,7 @@ import {
|
|
|
31
35
|
export { startScheduledRun } from "./schedule-run.js";
|
|
32
36
|
export { handleScheduleTimer } from "./schedule-timer.js";
|
|
33
37
|
export async function schedule(internals, type, input, spec, options) {
|
|
34
|
-
|
|
35
|
-
throw new WorkflowNotRegisteredError(type);
|
|
36
|
-
const normalizedSpec = normalizeScheduleSpec(spec), normalizedOptions = normalizeScheduleOptions(options), scheduleId = normalizedOptions.id ?? crypto.randomUUID();
|
|
38
|
+
const normalizedSpec = normalizeScheduleSpec(spec), normalizedOptions = normalizeScheduleOptions(options), scheduleId = normalizedOptions.id ?? crypto.randomUUID(), revisionPolicy = normalizedOptions.revisionPolicy ?? "active-at-fire", pinnedRevision = await resolveScheduleCreationRevision(internals, type, revisionPolicy);
|
|
37
39
|
if (internals.pendingScheduleCreations.has(scheduleId))
|
|
38
40
|
throw Error(`Schedule with id "${scheduleId}" already exists`);
|
|
39
41
|
internals.pendingScheduleCreations.add(scheduleId);
|
|
@@ -52,13 +54,15 @@ export async function schedule(internals, type, input, spec, options) {
|
|
|
52
54
|
overlap: normalizedOptions.overlap,
|
|
53
55
|
backfill: normalizedOptions.backfill,
|
|
54
56
|
...normalizedOptions.jitterMs !== void 0 && { jitterMs: normalizedOptions.jitterMs },
|
|
57
|
+
revisionPolicy,
|
|
58
|
+
...pinnedRevision !== void 0 && { pinnedRevision },
|
|
55
59
|
createdAt: now,
|
|
56
60
|
updatedAt: now,
|
|
57
61
|
nextFireAt: getNextScheduleOccurrence({ ...cadenceFields, createdAt: now }, now),
|
|
58
62
|
missedFireCount: 0,
|
|
59
63
|
queuedRuns: []
|
|
60
64
|
};
|
|
61
|
-
await writeScheduleState(internals, state);
|
|
65
|
+
await writeScheduleState(internals, state, await buildPinnedRevisionWriteOptions(internals, type, pinnedRevision));
|
|
62
66
|
return new ScheduleHandle(scheduleId, internals.engine);
|
|
63
67
|
} finally {
|
|
64
68
|
internals.pendingScheduleCreations.delete(scheduleId);
|
|
@@ -152,18 +156,33 @@ export async function updateSchedule(internals, scheduleId, newSpec, options) {
|
|
|
152
156
|
const state = await requireScheduleState(internals, normalizedScheduleId), now = internals.options.getNow(), {
|
|
153
157
|
cronExpression: _droppedCron,
|
|
154
158
|
intervalMs: _droppedInterval,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
pinnedRevision: _droppedPinnedRevision,
|
|
160
|
+
...stateWithoutCadenceOrPin
|
|
161
|
+
} = state, cadenceFields = normalizedSpec.kind === "interval" ? { intervalMs: normalizedSpec.intervalMs } : { cronExpression: normalizedSpec.cronExpression }, anchorFields = normalizedSpec.kind === "interval" ? { createdAt: now } : {};
|
|
162
|
+
let pinnedRevision, revisionFields;
|
|
163
|
+
if (normalizedOptions.revisionPolicy === void 0)
|
|
164
|
+
revisionFields = {
|
|
165
|
+
revisionPolicy: state.revisionPolicy,
|
|
166
|
+
...state.pinnedRevision !== void 0 && { pinnedRevision: state.pinnedRevision }
|
|
167
|
+
};
|
|
168
|
+
else if (normalizedOptions.revisionPolicy === "pinned") {
|
|
169
|
+
pinnedRevision = await resolveScheduleRevisionForPin(internals.engine, internals, state.workflowType);
|
|
170
|
+
revisionFields = { revisionPolicy: "pinned", pinnedRevision };
|
|
171
|
+
} else
|
|
172
|
+
revisionFields = { revisionPolicy: "active-at-fire" };
|
|
173
|
+
const updatedState = {
|
|
174
|
+
...stateWithoutCadenceOrPin,
|
|
158
175
|
...normalizedOptions,
|
|
159
176
|
...cadenceFields,
|
|
160
177
|
...anchorFields,
|
|
178
|
+
...revisionFields,
|
|
161
179
|
updatedAt: now,
|
|
162
180
|
nextFireAt: state.status === "cancelled" ? null : getNextScheduleOccurrence({ ...cadenceFields, createdAt: now }, now)
|
|
163
181
|
};
|
|
164
182
|
await writeScheduleState(internals, updatedState, {
|
|
165
183
|
includeTimer: state.status === "active",
|
|
166
|
-
replaceTimerFrom: state
|
|
184
|
+
replaceTimerFrom: state,
|
|
185
|
+
...await buildPinnedRevisionWriteOptions(internals, state.workflowType, pinnedRevision)
|
|
167
186
|
});
|
|
168
187
|
});
|
|
169
188
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded per-`(name, revision)` load-state diagnostics for dynamic
|
|
3
|
+
* workflow sources (WFT-15/16): the `idle -> loading -> ready|failed`
|
|
4
|
+
* state machine, waiter-count bookkeeping, and the `loading -> cancelled`
|
|
5
|
+
* transition. The `record*`/`begin*`/`end*` functions are pure state
|
|
6
|
+
* mutation, individually unit-testable; the `*AndDispatch` wrappers pair
|
|
7
|
+
* each with the matching `workflow-source:load-*` event, gated on the pure
|
|
8
|
+
* function's boolean return so a late, orphaned settle never re-dispatches
|
|
9
|
+
* `ready`/`failed` after the key already moved to `cancelled`, and a fresh
|
|
10
|
+
* attempt for the same key resets cleanly back to `loading` first.
|
|
11
|
+
* `source-resolution.ts` calls only the `*AndDispatch` wrappers.
|
|
12
|
+
*
|
|
13
|
+
* @module core/engine/source-diagnostics
|
|
14
|
+
*/
|
|
15
|
+
import type { WorkflowSourceKind } from '../source/index.ts';
|
|
16
|
+
import type { Engine } from './index.ts';
|
|
17
|
+
import type { EngineInternals } from './internals.ts';
|
|
18
|
+
import { type SourceLoadDiagnostics } from './source-runtime-state.ts';
|
|
19
|
+
/** Bounded diagnostics for one `(name, revision)`, or `undefined` when no load was ever attempted. */
|
|
20
|
+
export declare function readSourceLoadDiagnostics(internals: EngineInternals, name: string, revision: string): SourceLoadDiagnostics | undefined;
|
|
21
|
+
/** Record that a fresh shared load for `(name, revision)` just started. */
|
|
22
|
+
export declare function recordSourceLoadStarted(internals: EngineInternals, name: string, revision: string, kind: WorkflowSourceKind, now: number): void;
|
|
23
|
+
/**
|
|
24
|
+
* Record a successful load, UNLESS the key already moved on (a `cancelled`
|
|
25
|
+
* transition from the last waiter releasing, or a fresh attempt already
|
|
26
|
+
* reset it back to `loading` for a NEW load this settle does not belong
|
|
27
|
+
* to). Returns whether the caller should dispatch `WorkflowSourceLoadReadyEvent`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function recordSourceLoadReady(internals: EngineInternals, name: string, revision: string, now: number): boolean;
|
|
30
|
+
/** The failed-load counterpart of {@link recordSourceLoadReady}. */
|
|
31
|
+
export declare function recordSourceLoadFailed(internals: EngineInternals, name: string, revision: string, now: number, error: unknown): boolean;
|
|
32
|
+
/** Increment the outstanding-waiter count for `(name, revision)`. Call once per `resolveWorkflowSource()` call, before any await. */
|
|
33
|
+
export declare function beginSourceWaiter(internals: EngineInternals, name: string, revision: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Decrement the outstanding-waiter count for `(name, revision)`; call in
|
|
36
|
+
* every `resolveWorkflowSource()` call's own `finally`. Returns whether
|
|
37
|
+
* this was the LAST outstanding waiter releasing while the shared load is
|
|
38
|
+
* still `loading` — the caller should transition diagnostics to
|
|
39
|
+
* `cancelled` and dispatch `WorkflowSourceLoadCancelledEvent`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function endSourceWaiterAndCheckCancellation(internals: EngineInternals, name: string, revision: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* When a new caller joins a still-in-flight shared load whose diagnostics
|
|
44
|
+
* were marked `cancelled` (the last waiter released while the shared
|
|
45
|
+
* promise was still unsettled, and a later caller then joined that SAME
|
|
46
|
+
* orphaned promise instead of starting a fresh one — single-flight never
|
|
47
|
+
* aborts the shared load itself, only per-caller waiter interest), restore
|
|
48
|
+
* the state to `loading` so the eventual settle reaches its normal
|
|
49
|
+
* `ready`/`failed` transition instead of being silently suppressed by
|
|
50
|
+
* `recordSourceLoadReady`/`recordSourceLoadFailed`'s own `state !==
|
|
51
|
+
* 'loading'` guard. Never dispatches a fresh `WorkflowSourceLoadStartedEvent`
|
|
52
|
+
* — the load itself did not restart, only diagnostics visibility into it
|
|
53
|
+
* did. A no-op for any state other than `cancelled` (in particular,
|
|
54
|
+
* `getOrCreateSharedSourceLoad`'s cache-miss branch already sets `loading`
|
|
55
|
+
* itself via {@link recordSourceLoadStartedAndDispatch} for a genuinely new
|
|
56
|
+
* load, so this never fires there).
|
|
57
|
+
*/
|
|
58
|
+
export declare function reviveOrphanedSourceLoadDiagnostics(internals: EngineInternals, name: string, revision: string): void;
|
|
59
|
+
/** Current outstanding-waiter count for `(name, revision)`. */
|
|
60
|
+
export declare function readSourceWaiterCount(internals: EngineInternals, name: string, revision: string): number;
|
|
61
|
+
/** Bundles the identity a `source-resolution.ts` call site already has in scope, so the `*AndDispatch` wrappers below take one argument instead of five. */
|
|
62
|
+
export type SourceEventContext = {
|
|
63
|
+
engine: Engine;
|
|
64
|
+
internals: EngineInternals;
|
|
65
|
+
name: string;
|
|
66
|
+
revision: string;
|
|
67
|
+
kind: WorkflowSourceKind;
|
|
68
|
+
};
|
|
69
|
+
/** {@link recordSourceLoadStarted} plus dispatching `WorkflowSourceLoadStartedEvent`. */
|
|
70
|
+
export declare function recordSourceLoadStartedAndDispatch(ctx: SourceEventContext, now: number): void;
|
|
71
|
+
/** {@link recordSourceLoadReady} plus dispatching `WorkflowSourceLoadReadyEvent` when it returns `true`. */
|
|
72
|
+
export declare function recordSourceLoadReadyAndDispatch(ctx: SourceEventContext, now: number): void;
|
|
73
|
+
/** {@link recordSourceLoadFailed} plus dispatching `WorkflowSourceLoadFailedEvent` when it returns `true`. */
|
|
74
|
+
export declare function recordSourceLoadFailedAndDispatch(ctx: SourceEventContext, now: number, error: unknown): void;
|
|
75
|
+
/** {@link endSourceWaiterAndCheckCancellation} plus dispatching `WorkflowSourceLoadCancelledEvent` when it returns `true`. */
|
|
76
|
+
export declare function endSourceWaiterAndDispatchCancellation(engine: Engine, internals: EngineInternals, name: string, revision: string): void;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WorkflowSourceLoadCancelledEvent,
|
|
3
|
+
WorkflowSourceLoadFailedEvent,
|
|
4
|
+
WorkflowSourceLoadReadyEvent,
|
|
5
|
+
WorkflowSourceLoadStartedEvent
|
|
6
|
+
} from "../events/workflow-source-events.js";
|
|
7
|
+
import { classifyErrorAsFailureCategory } from "../failure-categories.js";
|
|
8
|
+
import {
|
|
9
|
+
decrementWaiterCount,
|
|
10
|
+
incrementWaiterCount,
|
|
11
|
+
readWaiterCount
|
|
12
|
+
} from "./source-runtime-state.js";
|
|
13
|
+
function getOrCreateDiagnosticsEntry(internals, name, revision) {
|
|
14
|
+
let byRevision = internals.sources.diagnostics.get(name);
|
|
15
|
+
if (byRevision === void 0) {
|
|
16
|
+
byRevision = new Map;
|
|
17
|
+
internals.sources.diagnostics.set(name, byRevision);
|
|
18
|
+
}
|
|
19
|
+
let entry = byRevision.get(revision);
|
|
20
|
+
if (entry === void 0) {
|
|
21
|
+
entry = {
|
|
22
|
+
state: "idle",
|
|
23
|
+
kind: "module",
|
|
24
|
+
requestedRevision: revision,
|
|
25
|
+
loadStartedAt: void 0,
|
|
26
|
+
loadDurationMs: void 0,
|
|
27
|
+
lastFailureCategory: void 0
|
|
28
|
+
};
|
|
29
|
+
byRevision.set(revision, entry);
|
|
30
|
+
}
|
|
31
|
+
return entry;
|
|
32
|
+
}
|
|
33
|
+
export function readSourceLoadDiagnostics(internals, name, revision) {
|
|
34
|
+
return internals.sources.diagnostics.get(name)?.get(revision);
|
|
35
|
+
}
|
|
36
|
+
export function recordSourceLoadStarted(internals, name, revision, kind, now) {
|
|
37
|
+
const entry = getOrCreateDiagnosticsEntry(internals, name, revision);
|
|
38
|
+
entry.state = "loading";
|
|
39
|
+
entry.kind = kind;
|
|
40
|
+
entry.loadStartedAt = now;
|
|
41
|
+
entry.loadDurationMs = void 0;
|
|
42
|
+
entry.lastFailureCategory = void 0;
|
|
43
|
+
}
|
|
44
|
+
export function recordSourceLoadReady(internals, name, revision, now) {
|
|
45
|
+
const entry = readSourceLoadDiagnostics(internals, name, revision);
|
|
46
|
+
if (entry === void 0 || entry.state !== "loading")
|
|
47
|
+
return !1;
|
|
48
|
+
entry.state = "ready";
|
|
49
|
+
entry.loadDurationMs = entry.loadStartedAt === void 0 ? void 0 : now - entry.loadStartedAt;
|
|
50
|
+
return !0;
|
|
51
|
+
}
|
|
52
|
+
export function recordSourceLoadFailed(internals, name, revision, now, error) {
|
|
53
|
+
const entry = readSourceLoadDiagnostics(internals, name, revision);
|
|
54
|
+
if (entry === void 0 || entry.state !== "loading")
|
|
55
|
+
return !1;
|
|
56
|
+
entry.state = "failed";
|
|
57
|
+
entry.loadDurationMs = entry.loadStartedAt === void 0 ? void 0 : now - entry.loadStartedAt;
|
|
58
|
+
entry.lastFailureCategory = classifyErrorAsFailureCategory(error, {
|
|
59
|
+
defaultErrorCategory: "application"
|
|
60
|
+
});
|
|
61
|
+
return !0;
|
|
62
|
+
}
|
|
63
|
+
export function beginSourceWaiter(internals, name, revision) {
|
|
64
|
+
incrementWaiterCount(internals.sources.waitersByKey, name, revision);
|
|
65
|
+
}
|
|
66
|
+
export function endSourceWaiterAndCheckCancellation(internals, name, revision) {
|
|
67
|
+
if (decrementWaiterCount(internals.sources.waitersByKey, name, revision) > 0)
|
|
68
|
+
return !1;
|
|
69
|
+
const entry = readSourceLoadDiagnostics(internals, name, revision);
|
|
70
|
+
if (entry === void 0 || entry.state !== "loading")
|
|
71
|
+
return !1;
|
|
72
|
+
entry.state = "cancelled";
|
|
73
|
+
return !0;
|
|
74
|
+
}
|
|
75
|
+
export function reviveOrphanedSourceLoadDiagnostics(internals, name, revision) {
|
|
76
|
+
const entry = readSourceLoadDiagnostics(internals, name, revision);
|
|
77
|
+
if (entry === void 0 || entry.state !== "cancelled")
|
|
78
|
+
return;
|
|
79
|
+
entry.state = "loading";
|
|
80
|
+
}
|
|
81
|
+
export function readSourceWaiterCount(internals, name, revision) {
|
|
82
|
+
return readWaiterCount(internals.sources.waitersByKey, name, revision);
|
|
83
|
+
}
|
|
84
|
+
export function recordSourceLoadStartedAndDispatch(ctx, now) {
|
|
85
|
+
recordSourceLoadStarted(ctx.internals, ctx.name, ctx.revision, ctx.kind, now);
|
|
86
|
+
ctx.engine.dispatchEvent(new WorkflowSourceLoadStartedEvent(ctx.name, ctx.revision, ctx.kind));
|
|
87
|
+
}
|
|
88
|
+
export function recordSourceLoadReadyAndDispatch(ctx, now) {
|
|
89
|
+
if (!recordSourceLoadReady(ctx.internals, ctx.name, ctx.revision, now))
|
|
90
|
+
return;
|
|
91
|
+
const loadDurationMs = readSourceLoadDiagnostics(ctx.internals, ctx.name, ctx.revision).loadDurationMs;
|
|
92
|
+
ctx.engine.dispatchEvent(new WorkflowSourceLoadReadyEvent(ctx.name, ctx.revision, ctx.kind, loadDurationMs));
|
|
93
|
+
}
|
|
94
|
+
export function recordSourceLoadFailedAndDispatch(ctx, now, error) {
|
|
95
|
+
if (!recordSourceLoadFailed(ctx.internals, ctx.name, ctx.revision, now, error))
|
|
96
|
+
return;
|
|
97
|
+
const diagnostics = readSourceLoadDiagnostics(ctx.internals, ctx.name, ctx.revision);
|
|
98
|
+
ctx.engine.dispatchEvent(new WorkflowSourceLoadFailedEvent(ctx.name, ctx.revision, ctx.kind, diagnostics.loadDurationMs ?? 0, diagnostics.lastFailureCategory));
|
|
99
|
+
}
|
|
100
|
+
export function endSourceWaiterAndDispatchCancellation(engine, internals, name, revision) {
|
|
101
|
+
if (!endSourceWaiterAndCheckCancellation(internals, name, revision))
|
|
102
|
+
return;
|
|
103
|
+
const kind = readSourceLoadDiagnostics(internals, name, revision)?.kind ?? "module";
|
|
104
|
+
engine.dispatchEvent(new WorkflowSourceLoadCancelledEvent(name, revision, kind));
|
|
105
|
+
}
|
|
@@ -14,16 +14,18 @@ function assertWellFormedSourceDescriptor(source) {
|
|
|
14
14
|
throw TypeError(`registerSource(): source.descriptor.revision is ${revisionBytes} bytes, exceeding the maximum identifier size of ${MAX_CONTRACT_IDENTIFIER_BYTES}`);
|
|
15
15
|
}
|
|
16
16
|
export function registerSource(internals, source) {
|
|
17
|
+
if (internals.inlineStrategy === null)
|
|
18
|
+
throw Error('registerSource() is only supported in inline execution mode; a dynamically-loaded definition cannot be shipped to a Worker realm. Use workflowExecutionMode: "inline", or register this workflow eagerly with engine.register() instead.');
|
|
17
19
|
assertWellFormedSourceDescriptor(source);
|
|
18
20
|
const { name, revision } = source.descriptor;
|
|
19
21
|
validateWorkflowOrActivityName(name, "workflow");
|
|
20
22
|
Object.freeze(source.descriptor);
|
|
21
23
|
if (internals.registrations.has(name))
|
|
22
24
|
throw Error(`Cannot registerSource("${name}"): "${name}" is already registered as an eager workflow via engine.register(). A workflow name may not be both eagerly registered and a dynamic source.`);
|
|
23
|
-
let byRevision = internals.
|
|
25
|
+
let byRevision = internals.sources.byName.get(name);
|
|
24
26
|
if (byRevision === void 0) {
|
|
25
27
|
byRevision = new Map;
|
|
26
|
-
internals.
|
|
28
|
+
internals.sources.byName.set(name, byRevision);
|
|
27
29
|
}
|
|
28
30
|
const existing = byRevision.get(revision);
|
|
29
31
|
if (existing === void 0) {
|
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Single-flight per `(name, revision)`: the underlying load+validate+install
|
|
7
7
|
* work is shared by every concurrent caller for the same key via
|
|
8
|
-
* `internals.
|
|
8
|
+
* `internals.sources.resolutionsInFlight`, but each caller races that shared
|
|
9
9
|
* work against its OWN per-call cancellation interest — a cancelled waiter
|
|
10
|
-
* never aborts a load another waiter still needs, and the shared load
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* point it naturally settles.
|
|
10
|
+
* never aborts a load another waiter still needs, and the shared load itself
|
|
11
|
+
* is never tied to any individual caller's lifetime. Disposal aborts every
|
|
12
|
+
* outstanding waiter (rejecting each pending `resolveWorkflowSource()` call)
|
|
13
|
+
* without touching the shared load, which keeps running to its own settle.
|
|
15
14
|
*
|
|
16
15
|
* @module core/engine/source-resolution
|
|
17
16
|
*/
|
|
@@ -41,12 +40,15 @@ export type ResolveWorkflowSourceOptions = {
|
|
|
41
40
|
* {@link WorkflowRevisionRecord} immediately, without invoking the loader
|
|
42
41
|
* at all, when `(name, revision)` is already durably installed — including
|
|
43
42
|
* a revision installed by a different process. When that is the case,
|
|
44
|
-
* `internals.
|
|
43
|
+
* `internals.sources.resolved` is NOT populated for this key (there
|
|
45
44
|
* is no locally-loaded `WorkflowDefinition` to stash — only the durable
|
|
46
45
|
* manifest was ever read) even though the catalog itself considers the
|
|
47
|
-
* revision installed; a
|
|
46
|
+
* revision installed; a caller reading `internals.sources.resolved` must
|
|
48
47
|
* account for that gap rather than assuming every installed revision has a
|
|
49
|
-
* live definition available in this process
|
|
48
|
+
* live definition available in this process — see
|
|
49
|
+
* {@link resolveWorkflowSourceForExecution} for the internal-only variant
|
|
50
|
+
* that closes exactly this gap for callers that need a local definition.
|
|
51
|
+
* This fast path still requires
|
|
50
52
|
* `registerSource()` to have been called for this exact key first (see
|
|
51
53
|
* {@link resolveCachedOrHandle}), and still validates a pinned
|
|
52
54
|
* `workflowVersion`/`contractHash` against the cached manifest — the ONLY
|
|
@@ -83,3 +85,17 @@ export type ResolveWorkflowSourceOptions = {
|
|
|
83
85
|
* ```
|
|
84
86
|
*/
|
|
85
87
|
export declare function resolveWorkflowSource(engine: Engine, name: string, revision: string, options?: ResolveWorkflowSourceOptions): Promise<WorkflowRevisionRecord>;
|
|
88
|
+
/**
|
|
89
|
+
* Internal-only variant of {@link resolveWorkflowSource} used exclusively by
|
|
90
|
+
* `resolveExecutableRegistration()` (`dynamic-source-execution.ts`, WFT-15).
|
|
91
|
+
* Identical cancellation, disposal, and single-flight contract, with one
|
|
92
|
+
* difference: a cache hit whose local resolved-definition cache
|
|
93
|
+
* (`internals.sources.resolved`) is still empty for this exact key falls
|
|
94
|
+
* through to a real load instead of returning the cached manifest —
|
|
95
|
+
* closing the cross-process-restart gap {@link resolveWorkflowSource}'s own
|
|
96
|
+
* doc calls out. `catalog.install()` is idempotent on byte-identical
|
|
97
|
+
* content, so re-installing an already-installed manifest never throws.
|
|
98
|
+
* Not exported from the package root — reached only via
|
|
99
|
+
* `resolveExecutableRegistration`.
|
|
100
|
+
*/
|
|
101
|
+
export declare function resolveWorkflowSourceForExecution(engine: Engine, name: string, revision: string, options?: ResolveWorkflowSourceOptions): Promise<WorkflowRevisionRecord>;
|
|
@@ -9,8 +9,17 @@ import {
|
|
|
9
9
|
WorkflowSourceValidationError
|
|
10
10
|
} from "../source/index.js";
|
|
11
11
|
import { ensureWorkflowCatalogReady, isWorkflowCatalogReady } from "./catalog-readiness.js";
|
|
12
|
+
import { WorkflowSourceNotRegisteredError } from "./dynamic-source-errors.js";
|
|
12
13
|
import { EngineDisposedError } from "./errors.js";
|
|
13
14
|
import { getInternals, getWorkflowCatalog } from "./internals.js";
|
|
15
|
+
import {
|
|
16
|
+
beginSourceWaiter,
|
|
17
|
+
endSourceWaiterAndDispatchCancellation,
|
|
18
|
+
recordSourceLoadFailedAndDispatch,
|
|
19
|
+
recordSourceLoadReadyAndDispatch,
|
|
20
|
+
recordSourceLoadStartedAndDispatch,
|
|
21
|
+
reviveOrphanedSourceLoadDiagnostics
|
|
22
|
+
} from "./source-diagnostics.js";
|
|
14
23
|
function toAbortError(signal) {
|
|
15
24
|
const reason = signal.reason;
|
|
16
25
|
return reason instanceof Error ? reason : Error("resolveWorkflowSource() aborted");
|
|
@@ -31,10 +40,10 @@ async function runSharedSourceLoad(engine, internals, name, revision, handle) {
|
|
|
31
40
|
throw new EngineDisposedError;
|
|
32
41
|
const installed = await getWorkflowCatalog(engine).install(outcome.manifest, outcome.definition);
|
|
33
42
|
if (!internals.disposed) {
|
|
34
|
-
let resolvedByRevision = internals.
|
|
43
|
+
let resolvedByRevision = internals.sources.resolved.get(name);
|
|
35
44
|
if (resolvedByRevision === void 0) {
|
|
36
45
|
resolvedByRevision = new Map;
|
|
37
|
-
internals.
|
|
46
|
+
internals.sources.resolved.set(name, resolvedByRevision);
|
|
38
47
|
}
|
|
39
48
|
resolvedByRevision.set(revision, {
|
|
40
49
|
definition: outcome.loadedDefinition,
|
|
@@ -44,21 +53,37 @@ async function runSharedSourceLoad(engine, internals, name, revision, handle) {
|
|
|
44
53
|
return { manifest: installed.manifest, installedAt: installed.installedAt };
|
|
45
54
|
}
|
|
46
55
|
function getOrCreateSharedSourceLoad(engine, internals, name, revision, handle) {
|
|
47
|
-
let byRevision = internals.
|
|
56
|
+
let byRevision = internals.sources.resolutionsInFlight.get(name);
|
|
48
57
|
if (byRevision === void 0) {
|
|
49
58
|
byRevision = new Map;
|
|
50
|
-
internals.
|
|
59
|
+
internals.sources.resolutionsInFlight.set(name, byRevision);
|
|
51
60
|
}
|
|
52
61
|
const existing = byRevision.get(revision);
|
|
53
|
-
if (existing !== void 0)
|
|
62
|
+
if (existing !== void 0) {
|
|
63
|
+
reviveOrphanedSourceLoadDiagnostics(internals, name, revision);
|
|
54
64
|
return existing;
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
}
|
|
66
|
+
const eventContext = {
|
|
67
|
+
engine,
|
|
68
|
+
internals,
|
|
69
|
+
name,
|
|
70
|
+
revision,
|
|
71
|
+
kind: handle.descriptor.kind
|
|
72
|
+
};
|
|
73
|
+
recordSourceLoadStartedAndDispatch(eventContext, internals.options.getNow());
|
|
74
|
+
const shared = runSharedSourceLoad(engine, internals, name, revision, handle).then((record) => {
|
|
75
|
+
recordSourceLoadReadyAndDispatch(eventContext, internals.options.getNow());
|
|
76
|
+
return record;
|
|
77
|
+
}, (error) => {
|
|
78
|
+
recordSourceLoadFailedAndDispatch(eventContext, internals.options.getNow(), error);
|
|
79
|
+
throw error;
|
|
80
|
+
}).finally(() => {
|
|
81
|
+
const currentByRevision = internals.sources.resolutionsInFlight.get(name);
|
|
57
82
|
if (currentByRevision?.get(revision) !== shared)
|
|
58
83
|
return;
|
|
59
84
|
currentByRevision.delete(revision);
|
|
60
85
|
if (currentByRevision.size === 0)
|
|
61
|
-
internals.
|
|
86
|
+
internals.sources.resolutionsInFlight.delete(name);
|
|
62
87
|
});
|
|
63
88
|
shared.catch(() => {});
|
|
64
89
|
byRevision.set(revision, shared);
|
|
@@ -72,10 +97,10 @@ function createWaiterAbort(options) {
|
|
|
72
97
|
options?.signal?.addEventListener("abort", forwardAbort, { once: !0 });
|
|
73
98
|
return { controller, detach: () => options?.signal?.removeEventListener("abort", forwardAbort) };
|
|
74
99
|
}
|
|
75
|
-
async function resolveCachedOrHandle(engine, internals, name, revision) {
|
|
76
|
-
const handle = internals.
|
|
100
|
+
async function resolveCachedOrHandle(engine, internals, name, revision, requireLocalDefinition) {
|
|
101
|
+
const handle = internals.sources.byName.get(name)?.get(revision);
|
|
77
102
|
if (handle === void 0)
|
|
78
|
-
throw
|
|
103
|
+
throw new WorkflowSourceNotRegisteredError(name, revision);
|
|
79
104
|
if (!isWorkflowCatalogReady(engine))
|
|
80
105
|
await ensureWorkflowCatalogReady(engine);
|
|
81
106
|
const existing = await getWorkflowCatalog(engine).resolveEntry(name, revision);
|
|
@@ -83,11 +108,19 @@ async function resolveCachedOrHandle(engine, internals, name, revision) {
|
|
|
83
108
|
const expectedManifest = buildExpectedManifest(handle.descriptor, existing.manifest), verdict = checkWorkflowCompatibility(expectedManifest, existing.manifest, DEFAULT_WORKFLOW_COMPATIBILITY_POLICY);
|
|
84
109
|
if (!verdict.compatible)
|
|
85
110
|
throw new WorkflowSourceValidationError(name, revision, verdict.reasons);
|
|
111
|
+
if (requireLocalDefinition && internals.sources.resolved.get(name)?.get(revision) === void 0)
|
|
112
|
+
return { handle };
|
|
86
113
|
return { cached: existing };
|
|
87
114
|
}
|
|
88
115
|
return { handle };
|
|
89
116
|
}
|
|
90
117
|
export async function resolveWorkflowSource(engine, name, revision, options) {
|
|
118
|
+
return resolveWorkflowSourceCore(engine, name, revision, options, !1);
|
|
119
|
+
}
|
|
120
|
+
export async function resolveWorkflowSourceForExecution(engine, name, revision, options) {
|
|
121
|
+
return resolveWorkflowSourceCore(engine, name, revision, options, !0);
|
|
122
|
+
}
|
|
123
|
+
async function resolveWorkflowSourceCore(engine, name, revision, options, requireLocalDefinition) {
|
|
91
124
|
const internals = getInternals(engine), waiter = createWaiterAbort(options);
|
|
92
125
|
if (waiter.controller.signal.aborted) {
|
|
93
126
|
waiter.detach();
|
|
@@ -97,9 +130,10 @@ export async function resolveWorkflowSource(engine, name, revision, options) {
|
|
|
97
130
|
waiter.detach();
|
|
98
131
|
throw new EngineDisposedError;
|
|
99
132
|
}
|
|
100
|
-
internals.
|
|
133
|
+
internals.sources.waiterControllers.add(waiter.controller);
|
|
134
|
+
beginSourceWaiter(internals, name, revision);
|
|
101
135
|
try {
|
|
102
|
-
const cachedOrHandle = resolveCachedOrHandle(engine, internals, name, revision), cachedOrHandleAbort = abortRejection(waiter.controller.signal);
|
|
136
|
+
const cachedOrHandle = resolveCachedOrHandle(engine, internals, name, revision, requireLocalDefinition), cachedOrHandleAbort = abortRejection(waiter.controller.signal);
|
|
103
137
|
cachedOrHandleAbort.catch(() => {});
|
|
104
138
|
let outcome;
|
|
105
139
|
try {
|
|
@@ -114,6 +148,7 @@ export async function resolveWorkflowSource(engine, name, revision, options) {
|
|
|
114
148
|
return await Promise.race([shared, waiterAbort]);
|
|
115
149
|
} finally {
|
|
116
150
|
waiter.detach();
|
|
117
|
-
internals.
|
|
151
|
+
internals.sources.waiterControllers.delete(waiter.controller);
|
|
152
|
+
endSourceWaiterAndDispatchCancellation(engine, internals, name, revision);
|
|
118
153
|
}
|
|
119
154
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `EngineInternals.sources` — the single consolidated container for every
|
|
3
|
+
* piece of process-local, in-memory state the dynamic workflow-source
|
|
4
|
+
* pipeline (WFT-13/14, WFT-15/16) needs: registered candidates, in-flight
|
|
5
|
+
* single-flight loads, per-caller waiter controllers, locally-resolved
|
|
6
|
+
* definitions, and bounded per-`(name, revision)` diagnostics.
|
|
7
|
+
*
|
|
8
|
+
* WFT-13/14 originally spread this across five flat `EngineInternals`
|
|
9
|
+
* fields (`workflowSourcesByName`, `sourceResolutionsInFlight`,
|
|
10
|
+
* `sourceResolutionWaiterControllers`, `resolvedWorkflowSources`, plus the
|
|
11
|
+
* `ResolvedSource` type alias). WFT-15/16 folds them into one field —
|
|
12
|
+
* `internals.sources: WorkflowSourceRuntimeState` — both because
|
|
13
|
+
* `internals.ts` and `lifecycle/start.ts` are already at the repository's
|
|
14
|
+
* 500-line implementation-file ceiling (no headroom for four more
|
|
15
|
+
* documented fields) and because the diagnostics state this batch adds
|
|
16
|
+
* (`diagnostics`, `waitersByKey`, `lastResolvedRevisionByName`) is the same
|
|
17
|
+
* kind of per-`(name, revision)` bookkeeping the original four fields
|
|
18
|
+
* already were — one container, one owner.
|
|
19
|
+
*
|
|
20
|
+
* Every consumer reaches this state via `internals.sources.<field>` rather
|
|
21
|
+
* than a flat `internals.<field>` — see `scripts/check-engine-internals-field-access.ts`,
|
|
22
|
+
* which is updated in the same commit to the single `sources` name.
|
|
23
|
+
*
|
|
24
|
+
* @module core/engine/source-runtime-state
|
|
25
|
+
*/
|
|
26
|
+
import type { ActivityRegistry } from '../activity-registry.ts';
|
|
27
|
+
import type { WorkflowDefinition } from '../types.ts';
|
|
28
|
+
import type { FailureCategory } from '../types/identity.ts';
|
|
29
|
+
type SourceHandle = import('../source/index.ts').WorkflowSourceHandle;
|
|
30
|
+
type CatalogRevisionRecord = import('../catalog/index.ts').WorkflowRevisionRecord;
|
|
31
|
+
type WorkflowSourceKind = import('../source/index.ts').WorkflowSourceKind;
|
|
32
|
+
/** A successful `resolveWorkflowSource()`'s live, in-process definition and activity registry. */
|
|
33
|
+
export type ResolvedSource = {
|
|
34
|
+
definition: WorkflowDefinition;
|
|
35
|
+
activityRegistry: ActivityRegistry;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Load-state machine for one `(name, revision)`, backing both the
|
|
39
|
+
* `weft.catalog.diagnostics` `source` extension and the
|
|
40
|
+
* `workflow-source:load-*` events. `'idle'` is the type-level starting
|
|
41
|
+
* point before the first `resolveWorkflowSource()` call for a key, and IS
|
|
42
|
+
* observable externally: `weft.catalog.diagnostics` reports it for any
|
|
43
|
+
* `registerSource()`-registered revision that has never had a load start
|
|
44
|
+
* (see `buildSourceDiagnostics()` in `catalog-removal.ts`), and no
|
|
45
|
+
* `workflow-source:load-*` event is emitted for that state.
|
|
46
|
+
*/
|
|
47
|
+
export type SourceLoadState = 'idle' | 'loading' | 'ready' | 'failed' | 'cancelled';
|
|
48
|
+
/** Bounded, per-`(name, revision)` diagnostics — no manifest or contract content, only identity and state. */
|
|
49
|
+
export type SourceLoadDiagnostics = {
|
|
50
|
+
state: SourceLoadState;
|
|
51
|
+
kind: WorkflowSourceKind;
|
|
52
|
+
requestedRevision: string;
|
|
53
|
+
loadStartedAt: number | undefined;
|
|
54
|
+
loadDurationMs: number | undefined;
|
|
55
|
+
lastFailureCategory: FailureCategory | undefined;
|
|
56
|
+
};
|
|
57
|
+
/** The consolidated dynamic workflow-source runtime state — see the module doc. */
|
|
58
|
+
export type WorkflowSourceRuntimeState = {
|
|
59
|
+
/** `registerSource()` candidates, keyed name then revision. Registering never invokes the loader. */
|
|
60
|
+
byName: Map<string, Map<string, SourceHandle>>;
|
|
61
|
+
/** In-flight `resolveWorkflowSource` single-flight load per `(name, revision)`. */
|
|
62
|
+
resolutionsInFlight: Map<string, Map<string, Promise<CatalogRevisionRecord>>>;
|
|
63
|
+
/** Per-caller `AbortController`s from in-flight `resolveWorkflowSource()` calls; disposal aborts every waiter without touching the shared load. */
|
|
64
|
+
waiterControllers: Set<AbortController>;
|
|
65
|
+
/**
|
|
66
|
+
* Outstanding-waiter count per `(name, revision)`, keyed name then
|
|
67
|
+
* revision — a flat composite-string key is not safe here, since unlike a
|
|
68
|
+
* workflow `name`, a source `revision` is only byte-length-bounded, not
|
|
69
|
+
* grammar-restricted, so it may contain whatever separator character a
|
|
70
|
+
* flat key would pick. Incremented/decremented inside
|
|
71
|
+
* `resolveWorkflowSource`'s own try/finally. Drives the `loading` ->
|
|
72
|
+
* `cancelled` diagnostics transition: that transition fires only when
|
|
73
|
+
* the LAST outstanding waiter for a key releases while the shared load
|
|
74
|
+
* is still unsettled.
|
|
75
|
+
*/
|
|
76
|
+
waitersByKey: Map<string, Map<string, number>>;
|
|
77
|
+
/** A successful `resolveWorkflowSource()`'s live definition, keyed name then revision. */
|
|
78
|
+
resolved: Map<string, Map<string, ResolvedSource>>;
|
|
79
|
+
/** The revision `resolveExecutableRegistration()` most recently resolved for a name — used by the sync-only fallback readers (`finalizer.ts`, `constraints.ts`). */
|
|
80
|
+
lastResolvedRevisionByName: Map<string, string>;
|
|
81
|
+
/** Bounded per-`(name, revision)` load-state diagnostics, keyed name then revision. */
|
|
82
|
+
diagnostics: Map<string, Map<string, SourceLoadDiagnostics>>;
|
|
83
|
+
};
|
|
84
|
+
/** Build an empty {@link WorkflowSourceRuntimeState}. */
|
|
85
|
+
export declare function createWorkflowSourceRuntimeState(): WorkflowSourceRuntimeState;
|
|
86
|
+
/** Read the nested `name -> revision -> value` count at `(name, revision)` from {@link WorkflowSourceRuntimeState.waitersByKey}, defaulting to `0`. */
|
|
87
|
+
export declare function readWaiterCount(waitersByKey: WorkflowSourceRuntimeState['waitersByKey'], name: string, revision: string): number;
|
|
88
|
+
/** Increment the waiter count at `(name, revision)`, creating intermediate maps as needed. Returns the new count. */
|
|
89
|
+
export declare function incrementWaiterCount(waitersByKey: WorkflowSourceRuntimeState['waitersByKey'], name: string, revision: string): number;
|
|
90
|
+
/** Decrement the waiter count at `(name, revision)`, removing empty intermediate maps. Returns the new count (never negative). */
|
|
91
|
+
export declare function decrementWaiterCount(waitersByKey: WorkflowSourceRuntimeState['waitersByKey'], name: string, revision: string): number;
|
|
92
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function createWorkflowSourceRuntimeState() {
|
|
2
|
+
return {
|
|
3
|
+
byName: new Map,
|
|
4
|
+
resolutionsInFlight: new Map,
|
|
5
|
+
waiterControllers: new Set,
|
|
6
|
+
waitersByKey: new Map,
|
|
7
|
+
resolved: new Map,
|
|
8
|
+
lastResolvedRevisionByName: new Map,
|
|
9
|
+
diagnostics: new Map
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function readWaiterCount(waitersByKey, name, revision) {
|
|
13
|
+
return waitersByKey.get(name)?.get(revision) ?? 0;
|
|
14
|
+
}
|
|
15
|
+
export function incrementWaiterCount(waitersByKey, name, revision) {
|
|
16
|
+
let byRevision = waitersByKey.get(name);
|
|
17
|
+
if (byRevision === void 0) {
|
|
18
|
+
byRevision = new Map;
|
|
19
|
+
waitersByKey.set(name, byRevision);
|
|
20
|
+
}
|
|
21
|
+
const next = (byRevision.get(revision) ?? 0) + 1;
|
|
22
|
+
byRevision.set(revision, next);
|
|
23
|
+
return next;
|
|
24
|
+
}
|
|
25
|
+
export function decrementWaiterCount(waitersByKey, name, revision) {
|
|
26
|
+
const byRevision = waitersByKey.get(name), current = byRevision?.get(revision) ?? 0, next = Math.max(0, current - 1);
|
|
27
|
+
if (byRevision === void 0)
|
|
28
|
+
return next;
|
|
29
|
+
if (next === 0) {
|
|
30
|
+
byRevision.delete(revision);
|
|
31
|
+
if (byRevision.size === 0)
|
|
32
|
+
waitersByKey.delete(name);
|
|
33
|
+
} else
|
|
34
|
+
byRevision.set(revision, next);
|
|
35
|
+
return next;
|
|
36
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BatchOperation } from '../../storage/interface.ts';
|
|
1
|
+
import { type BatchOperation, type ConditionalBatchCondition } from '../../storage/interface.ts';
|
|
2
2
|
import type { ScheduleState, WorkflowState } from '../types.ts';
|
|
3
3
|
import type { EngineInternals } from './internals.ts';
|
|
4
4
|
import { type WorkflowClaimTransitionFragment } from './workflow-claim-transitions.ts';
|
|
@@ -84,11 +84,28 @@ export declare function commitExternalTerminalWorkflowStateOperations(internals:
|
|
|
84
84
|
export declare function loadScheduleState(internals: EngineInternals, scheduleId: string): Promise<ScheduleState | null>;
|
|
85
85
|
/** Load a schedule state. */
|
|
86
86
|
export declare function requireScheduleState(internals: EngineInternals, scheduleId: string): Promise<ScheduleState>;
|
|
87
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* Persist schedule state and optionally write the next schedule timer.
|
|
89
|
+
*
|
|
90
|
+
* `extraConditions` (WFT-20) additionally fences the commit on caller-supplied
|
|
91
|
+
* preconditions — used by a `revisionPolicy: 'pinned'` create/update to fence
|
|
92
|
+
* the write on `buildCatalogEntryRevisionCondition(type, pinnedRevision)` so a
|
|
93
|
+
* concurrent `removeWorkflowRevision()` landing between pin capture and this
|
|
94
|
+
* commit cannot let a pin to an already-removed revision durably land. When
|
|
95
|
+
* the commit's `extraConditions` are the ones that lost the CAS,
|
|
96
|
+
* `onExtraConditionsLost` (required whenever `extraConditions` is non-empty)
|
|
97
|
+
* supplies the thrown error instead of this function's own generic
|
|
98
|
+
* lost-precondition error, so the caller sees an actionable, typed failure
|
|
99
|
+
* (e.g. `WorkflowRevisionUnavailableError`) rather than a bare "lost its
|
|
100
|
+
* precondition" message. Every existing caller passes neither option and gets
|
|
101
|
+
* byte-for-byte the pre-WFT-20 commit shape.
|
|
102
|
+
*/
|
|
88
103
|
export declare function writeScheduleState(internals: EngineInternals, state: ScheduleState, options?: {
|
|
89
104
|
includeTimer?: boolean;
|
|
90
105
|
replaceTimerFrom?: ScheduleState;
|
|
91
106
|
additionalOperations?: BatchOperation[];
|
|
107
|
+
extraConditions?: ConditionalBatchCondition[];
|
|
108
|
+
onExtraConditionsLost?: () => Error;
|
|
92
109
|
}): Promise<void>;
|
|
93
110
|
/** Load persisted workflow start headers by workflow ID. */
|
|
94
111
|
export declare function loadWorkflowStartHeaders(internals: EngineInternals, workflowId: string): Promise<Map<string, string> | undefined>;
|