@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
|
@@ -64,12 +64,13 @@ export async function settleOnRunningClaim(internals, workflowId, runningBytes,
|
|
|
64
64
|
{ key: KEYS.teardownOwed(workflowId), expectedValue: runningBytes }
|
|
65
65
|
]);
|
|
66
66
|
}
|
|
67
|
-
export async function deadLetterTeardown(internals, workflowId, workflowType, attempts, expectedBytes, details) {
|
|
67
|
+
export async function deadLetterTeardown(internals, workflowId, workflowType, attempts, expectedBytes, details, workflowExecutionToken) {
|
|
68
68
|
const deadLetter = {
|
|
69
69
|
type: workflowType,
|
|
70
70
|
lastError: details.lastError,
|
|
71
71
|
attempts,
|
|
72
72
|
deadLetteredAt: internals.options.getNow(),
|
|
73
|
+
...workflowExecutionToken === void 0 ? {} : { workflowExecutionToken },
|
|
73
74
|
...details.finalizerInput === void 0 ? {} : { finalizerInput: details.finalizerInput }
|
|
74
75
|
};
|
|
75
76
|
return settleOnRunningClaim(internals, workflowId, expectedBytes, [
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { KEYS } from "../../../storage/interface.js";
|
|
2
2
|
import { decode } from "../../codec.js";
|
|
3
3
|
import { WorkflowTeardownEvent } from "../../events.js";
|
|
4
|
+
import { buildTeardownSuccessOperations } from "../finalizer-status.js";
|
|
4
5
|
import { isTeardownClaim, parseTeardownTimerId } from "../state-utilities.js";
|
|
5
6
|
import { runFinalizerActivity } from "./finalizer-activity.js";
|
|
6
7
|
import {
|
|
@@ -21,10 +22,7 @@ export { teardownStaleThresholdMs } from "./finalizer-claim.js";
|
|
|
21
22
|
const TERMINAL_STATUSES_OWED_TEARDOWN = new Set([
|
|
22
23
|
"cancelled",
|
|
23
24
|
"timed-out"
|
|
24
|
-
]);
|
|
25
|
-
function errorMessage(error) {
|
|
26
|
-
return error instanceof Error ? error.message : String(error);
|
|
27
|
-
}
|
|
25
|
+
]), errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
28
26
|
async function rearmOnLostSettle(internals, workflowId, token) {
|
|
29
27
|
await rearmTeardownTimer(internals, workflowId, token, TEARDOWN_SELF_HEAL_DELAY_MS);
|
|
30
28
|
}
|
|
@@ -63,7 +61,7 @@ async function resolveTeardownDrive(internals, workflowId, token, callbacks) {
|
|
|
63
61
|
return { kind: "rearm", token };
|
|
64
62
|
const finalizerStateBytes = await internals.storage.get(KEYS.finalizerState(workflowId));
|
|
65
63
|
if (finalizerStateBytes === null) {
|
|
66
|
-
await deadLetterMissingState(internals, workflowId, state.type, claim.attempts, markerBytes, callbacks);
|
|
64
|
+
await deadLetterMissingState(internals, workflowId, state.type, state.workflowExecutionToken, claim.attempts, markerBytes, callbacks);
|
|
67
65
|
return { kind: "cleared" };
|
|
68
66
|
}
|
|
69
67
|
return {
|
|
@@ -71,12 +69,12 @@ async function resolveTeardownDrive(internals, workflowId, token, callbacks) {
|
|
|
71
69
|
drive: { state, claim, markerBytes, finalizer, finalizerInput: decode(finalizerStateBytes) }
|
|
72
70
|
};
|
|
73
71
|
}
|
|
74
|
-
async function deadLetterMissingState(internals, workflowId, workflowType, attempts, expectedBytes, callbacks) {
|
|
72
|
+
async function deadLetterMissingState(internals, workflowId, workflowType, workflowExecutionToken, attempts, expectedBytes, callbacks) {
|
|
75
73
|
const lastError = "finalizer state missing \u2014 recorded resource cannot be torn down";
|
|
76
74
|
if (await deadLetterTeardown(internals, workflowId, workflowType, attempts, expectedBytes, {
|
|
77
75
|
lastError,
|
|
78
76
|
finalizerInput: void 0
|
|
79
|
-
}))
|
|
77
|
+
}, workflowExecutionToken))
|
|
80
78
|
callbacks.dispatchEvent(new WorkflowTeardownEvent(workflowId, workflowType, "dead-lettered", attempts, lastError));
|
|
81
79
|
}
|
|
82
80
|
export async function runWorkflowFinalizer(internals, workflowId, timerId, callbacks) {
|
|
@@ -105,7 +103,7 @@ async function driveResolvedTeardown(internals, workflowId, token, drive, callba
|
|
|
105
103
|
}
|
|
106
104
|
const result = await runFinalizerActivity(finalizer, finalizerInput, attempt, internals.abortController.signal, state.workflowExecutionToken);
|
|
107
105
|
if (result.ok) {
|
|
108
|
-
await settleTeardownSuccess(internals, workflowId, state.type, token, attempt, runningBytes, callbacks);
|
|
106
|
+
await settleTeardownSuccess(internals, workflowId, state.type, state.workflowExecutionToken, token, attempt, runningBytes, callbacks);
|
|
109
107
|
return;
|
|
110
108
|
}
|
|
111
109
|
if (result.abortedByShutdown) {
|
|
@@ -123,11 +121,9 @@ async function driveResolvedTeardown(internals, workflowId, token, drive, callba
|
|
|
123
121
|
}
|
|
124
122
|
await settleTeardownFailure(internals, workflowId, state, attempt, token, runningBytes, result.error, callbacks);
|
|
125
123
|
}
|
|
126
|
-
async function settleTeardownSuccess(internals, workflowId, workflowType, token, attempt, runningBytes, callbacks) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{ type: "delete", key: KEYS.finalizerState(workflowId) }
|
|
130
|
-
])) {
|
|
124
|
+
async function settleTeardownSuccess(internals, workflowId, workflowType, workflowExecutionToken, token, attempt, runningBytes, callbacks) {
|
|
125
|
+
const operations = buildTeardownSuccessOperations(workflowId, attempt, internals.options.getNow(), workflowExecutionToken);
|
|
126
|
+
if (!await settleOnRunningClaim(internals, workflowId, runningBytes, operations)) {
|
|
131
127
|
await rearmOnLostSettle(internals, workflowId, token);
|
|
132
128
|
return;
|
|
133
129
|
}
|
|
@@ -140,7 +136,7 @@ async function settleTeardownFailure(internals, workflowId, state, attempt, toke
|
|
|
140
136
|
if (!await deadLetterTeardown(internals, workflowId, state.type, attempt, runningBytes, {
|
|
141
137
|
lastError: message,
|
|
142
138
|
finalizerInput: finalizerStateBytes === null ? void 0 : decode(finalizerStateBytes)
|
|
143
|
-
})) {
|
|
139
|
+
}, state.workflowExecutionToken)) {
|
|
144
140
|
await rearmOnLostSettle(internals, workflowId, token);
|
|
145
141
|
return;
|
|
146
142
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MAX_TIMELINE_COORDINATOR_DETAILS = 100, MAX_TIMELINE_DETAIL_STRING_LENGTH = 512;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ContextOperationRequest } from '../context.ts';
|
|
2
|
+
import type { ParallelBranchSlot } from '../context/parallel-cache-entry.ts';
|
|
3
|
+
import type { WorkflowTimelineOperationDetail } from '../types.ts';
|
|
4
|
+
import type { EngineInternals } from './internals.ts';
|
|
5
|
+
export declare function operationTimelineDetail(operation: ContextOperationRequest, index: number, outcome: WorkflowTimelineOperationDetail['outcome'], options?: {
|
|
6
|
+
error?: unknown;
|
|
7
|
+
key?: string;
|
|
8
|
+
}): WorkflowTimelineOperationDetail;
|
|
9
|
+
export declare function parallelTimelineDetails(operations: readonly ContextOperationRequest[], slots: readonly ParallelBranchSlot[], branchNames?: readonly string[]): WorkflowTimelineOperationDetail[];
|
|
10
|
+
export declare function raceTimelineDetails(operations: readonly ContextOperationRequest[], winnerIndex: number, branchNames?: readonly string[], winnerError?: unknown): WorkflowTimelineOperationDetail[];
|
|
11
|
+
export declare function runAllTimelineDetails(branchNames: readonly string[], slots: readonly ParallelBranchSlot[], branches: Record<string, readonly [Function] | readonly [Function, unknown]>): WorkflowTimelineOperationDetail[];
|
|
12
|
+
export declare function recordTimelineBranches(internals: EngineInternals, workflowId: string, details: readonly WorkflowTimelineOperationDetail[]): void;
|
|
13
|
+
export declare function recordTimelineSpeculation(internals: EngineInternals, workflowId: string, details: readonly WorkflowTimelineOperationDetail[], outcome: 'committed' | 'rolled-back'): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getTimelineOperationLabel, summarizeTimelineValue } from "./state-utilities.js";
|
|
2
|
+
import {
|
|
3
|
+
MAX_TIMELINE_COORDINATOR_DETAILS,
|
|
4
|
+
MAX_TIMELINE_DETAIL_STRING_LENGTH
|
|
5
|
+
} from "./timeline-coordinator-constants.js";
|
|
6
|
+
function boundedString(value) {
|
|
7
|
+
return value.length <= MAX_TIMELINE_DETAIL_STRING_LENGTH ? value : `${value.slice(0, MAX_TIMELINE_DETAIL_STRING_LENGTH - 1)}\u2026`;
|
|
8
|
+
}
|
|
9
|
+
function boundedErrorSummary(error) {
|
|
10
|
+
return boundedString(summarizeTimelineValue(error));
|
|
11
|
+
}
|
|
12
|
+
export function operationTimelineDetail(operation, index, outcome, options = {}) {
|
|
13
|
+
return {
|
|
14
|
+
index,
|
|
15
|
+
...options.key === void 0 ? {} : { key: boundedString(options.key) },
|
|
16
|
+
operationId: boundedString(operation.operationId),
|
|
17
|
+
operationType: operation.type,
|
|
18
|
+
operationLabel: boundedString(getTimelineOperationLabel(operation)),
|
|
19
|
+
outcome,
|
|
20
|
+
...options.error === void 0 ? {} : { errorSummary: boundedErrorSummary(options.error) }
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function parallelTimelineDetails(operations, slots, branchNames) {
|
|
24
|
+
return operations.map((operation, index) => {
|
|
25
|
+
const slot = slots[index];
|
|
26
|
+
return operationTimelineDetail(operation, index, slot?.status === "fulfilled" ? "fulfilled" : "rejected", {
|
|
27
|
+
...branchNames?.[index] === void 0 ? {} : { key: branchNames[index] },
|
|
28
|
+
...slot?.status === "rejected" ? { error: slot.reason } : {}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function raceTimelineDetails(operations, winnerIndex, branchNames, winnerError) {
|
|
33
|
+
return operations.map((operation, index) => operationTimelineDetail(operation, index, index === winnerIndex ? "won" : "lost", {
|
|
34
|
+
...branchNames?.[index] === void 0 ? {} : { key: branchNames[index] },
|
|
35
|
+
...index === winnerIndex && winnerError !== void 0 ? { error: winnerError } : {}
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
export function runAllTimelineDetails(branchNames, slots, branches) {
|
|
39
|
+
return branchNames.map((key, index) => {
|
|
40
|
+
const slot = slots[index], branchFunction = branches[key]?.[0];
|
|
41
|
+
return {
|
|
42
|
+
index,
|
|
43
|
+
key: boundedString(key),
|
|
44
|
+
operationId: boundedString(slot?.operationId ?? `run-all:${String(index)}:${key}`),
|
|
45
|
+
operationType: "activity",
|
|
46
|
+
operationLabel: boundedString(branchFunction?.name || key),
|
|
47
|
+
outcome: slot?.status === "fulfilled" ? "fulfilled" : "rejected",
|
|
48
|
+
...slot?.status === "rejected" ? { errorSummary: boundedErrorSummary(slot.reason) } : {}
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function boundedDetails(details) {
|
|
53
|
+
return {
|
|
54
|
+
details: details.slice(0, MAX_TIMELINE_COORDINATOR_DETAILS),
|
|
55
|
+
omitted: Math.max(0, details.length - MAX_TIMELINE_COORDINATOR_DETAILS)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function recordTimelineBranches(internals, workflowId, details) {
|
|
59
|
+
const entry = internals.pendingTimelineEntries?.get(workflowId)?.entry;
|
|
60
|
+
if (entry === void 0)
|
|
61
|
+
return;
|
|
62
|
+
const bounded = boundedDetails(details);
|
|
63
|
+
entry.branches = bounded.details;
|
|
64
|
+
if (bounded.omitted > 0)
|
|
65
|
+
entry.branchesOmitted = bounded.omitted;
|
|
66
|
+
}
|
|
67
|
+
export function recordTimelineSpeculation(internals, workflowId, details, outcome) {
|
|
68
|
+
const entry = internals.pendingTimelineEntries?.get(workflowId)?.entry;
|
|
69
|
+
if (entry === void 0)
|
|
70
|
+
return;
|
|
71
|
+
const bounded = boundedDetails(details);
|
|
72
|
+
entry.children = bounded.details;
|
|
73
|
+
if (bounded.omitted > 0)
|
|
74
|
+
entry.childrenOmitted = bounded.omitted;
|
|
75
|
+
entry.speculationOutcome = outcome;
|
|
76
|
+
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import type { ScheduleOptions, ScheduleOverlapPolicy } from '../../types.ts';
|
|
1
|
+
import type { ScheduleOptions, ScheduleOverlapPolicy, ScheduleUpdateOptions } from '../../types.ts';
|
|
2
2
|
export declare const SCHEDULE_OVERLAP_POLICIES: Set<ScheduleOverlapPolicy>;
|
|
3
3
|
type NormalizedScheduleOptions = Required<Pick<ScheduleOptions, 'overlap' | 'backfill'>> & {
|
|
4
4
|
id?: string;
|
|
5
5
|
description?: string;
|
|
6
6
|
jitterMs?: number;
|
|
7
7
|
};
|
|
8
|
+
type NormalizedScheduleUpdateOptions = {
|
|
9
|
+
description?: string;
|
|
10
|
+
overlap?: ScheduleOverlapPolicy;
|
|
11
|
+
backfill?: boolean;
|
|
12
|
+
jitterMs?: number;
|
|
13
|
+
};
|
|
8
14
|
export declare function normalizeScheduleOptions(options: ScheduleOptions | undefined): NormalizedScheduleOptions;
|
|
15
|
+
export declare function normalizeScheduleUpdateOptions(options: ScheduleUpdateOptions | undefined): NormalizedScheduleUpdateOptions;
|
|
9
16
|
export {};
|
|
@@ -23,6 +23,24 @@ export function normalizeScheduleOptions(options) {
|
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
+
export function normalizeScheduleUpdateOptions(options) {
|
|
27
|
+
if (options === void 0)
|
|
28
|
+
return {};
|
|
29
|
+
if (typeof options !== "object" || options === null)
|
|
30
|
+
throw Error("options must be an object when provided");
|
|
31
|
+
return {
|
|
32
|
+
...options.description !== void 0 && {
|
|
33
|
+
description: normalizeScheduleDescription(options.description)
|
|
34
|
+
},
|
|
35
|
+
...options.overlap !== void 0 && { overlap: normalizeScheduleOverlap(options.overlap) },
|
|
36
|
+
...options.backfill !== void 0 && {
|
|
37
|
+
backfill: normalizeScheduleBackfill(options.backfill)
|
|
38
|
+
},
|
|
39
|
+
...options.jitter !== void 0 && {
|
|
40
|
+
jitterMs: normalizeScheduleJitter(options.jitter, "options.jitter")
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
26
44
|
function normalizeScheduleDescription(description) {
|
|
27
45
|
if (typeof description !== "string")
|
|
28
46
|
throw Error("options.description must be a string when provided");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ScheduleFilter, ScheduleOverlapPolicy, ScheduleSpec, ScheduleState, ScheduleStatus } from '../../types.ts';
|
|
2
|
-
export { normalizeScheduleOptions, SCHEDULE_OVERLAP_POLICIES } from './schedule-options.ts';
|
|
2
|
+
export { normalizeScheduleOptions, normalizeScheduleUpdateOptions, SCHEDULE_OVERLAP_POLICIES, } from './schedule-options.ts';
|
|
3
3
|
export declare const SCHEDULE_STATUSES: Set<ScheduleStatus>;
|
|
4
4
|
export declare function isValidScheduleTimestamp(value: unknown): value is number;
|
|
5
5
|
export declare function isValidScheduleStatus(value: unknown): value is ScheduleStatus;
|
|
@@ -4,7 +4,11 @@ import { parseCronExpression } from "../../schedule.js";
|
|
|
4
4
|
import { parseDuration } from "../../scheduler.js";
|
|
5
5
|
import { coerceStartWorkflowId } from "../../start-workflow-validation.js";
|
|
6
6
|
import { SCHEDULE_OVERLAP_POLICIES } from "./schedule-options.js";
|
|
7
|
-
export {
|
|
7
|
+
export {
|
|
8
|
+
normalizeScheduleOptions,
|
|
9
|
+
normalizeScheduleUpdateOptions,
|
|
10
|
+
SCHEDULE_OVERLAP_POLICIES
|
|
11
|
+
} from "./schedule-options.js";
|
|
8
12
|
export const SCHEDULE_STATUSES = new Set(["active", "paused", "cancelled"]);
|
|
9
13
|
export function isValidScheduleTimestamp(value) {
|
|
10
14
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
@@ -206,11 +210,29 @@ function decodeScheduleCurrentWorkflowId(decoded, scheduleId) {
|
|
|
206
210
|
}
|
|
207
211
|
function decodeScheduleQueuedRuns(decoded, scheduleId) {
|
|
208
212
|
const queuedRuns = decoded.queuedRuns;
|
|
209
|
-
if (
|
|
213
|
+
if (!Array.isArray(queuedRuns)) {
|
|
210
214
|
rejectInvalidScheduleRecord(scheduleId, "with invalid queuedRuns");
|
|
211
215
|
return null;
|
|
212
216
|
}
|
|
213
|
-
|
|
217
|
+
const result = [], workflowIds = new Set;
|
|
218
|
+
for (const queuedRun of queuedRuns) {
|
|
219
|
+
if (!isRecord(queuedRun)) {
|
|
220
|
+
rejectInvalidScheduleRecord(scheduleId, "with invalid queuedRuns");
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const { workflowId, queuedAt, occurrence } = queuedRun;
|
|
224
|
+
if (!isValidScheduleIdentifier(workflowId) || !isValidScheduleTimestamp(queuedAt) || occurrence !== void 0 && !isValidScheduleTimestamp(occurrence)) {
|
|
225
|
+
rejectInvalidScheduleRecord(scheduleId, "with invalid queuedRuns");
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
if (workflowIds.has(workflowId)) {
|
|
229
|
+
rejectInvalidScheduleRecord(scheduleId, "with duplicate queued workflow ids");
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
workflowIds.add(workflowId);
|
|
233
|
+
result.push({ workflowId, queuedAt, ...occurrence !== void 0 && { occurrence } });
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
214
236
|
}
|
|
215
237
|
function decodeScheduleMissedFireCount(decoded, scheduleId) {
|
|
216
238
|
const missedFireCount = decoded.missedFireCount;
|
|
@@ -263,6 +285,8 @@ export function decodeScheduleRuntimeFields(decoded, scheduleId) {
|
|
|
263
285
|
const queueFields = decodeScheduleQueueFields(decoded, scheduleId);
|
|
264
286
|
if (queueFields === null)
|
|
265
287
|
return null;
|
|
288
|
+
if (currentWorkflow.value !== void 0 && queueFields.queuedRuns.some((queuedRun) => queuedRun.workflowId === currentWorkflow.value))
|
|
289
|
+
return rejectInvalidScheduleRecord(scheduleId, "whose current workflow is also queued");
|
|
266
290
|
return {
|
|
267
291
|
...optionFields,
|
|
268
292
|
...timestamps,
|
|
@@ -4,6 +4,10 @@ import { normalizeFailureCategory } from "../failure-categories.js";
|
|
|
4
4
|
import { coerceStartWorkflowId, parseStartWorkflowDuration } from "../start-workflow-validation.js";
|
|
5
5
|
import { DEFAULT_WORKFLOW_VERSION } from "../versioning.js";
|
|
6
6
|
import { isWorkflowTagArray } from "../workflow-tags.js";
|
|
7
|
+
import {
|
|
8
|
+
MAX_TIMELINE_COORDINATOR_DETAILS,
|
|
9
|
+
MAX_TIMELINE_DETAIL_STRING_LENGTH
|
|
10
|
+
} from "./timeline-coordinator-constants.js";
|
|
7
11
|
const WORKFLOW_TIMELINE_STATUSES = new Set([
|
|
8
12
|
"running",
|
|
9
13
|
"completed",
|
|
@@ -28,12 +32,15 @@ const WORKFLOW_STATE_FIELD_NAMES = new Set(defineCompleteWorkflowStateFieldNames
|
|
|
28
32
|
"versionTuple",
|
|
29
33
|
"workflowExecutionToken",
|
|
30
34
|
"executionStateOwnerId",
|
|
35
|
+
"parentWorkflowId",
|
|
36
|
+
"parentWorkflowExecutionToken",
|
|
31
37
|
"createdAt",
|
|
32
38
|
"startedAt",
|
|
33
39
|
"updatedAt",
|
|
34
40
|
"terminalCleanupToken",
|
|
35
41
|
"executionDeadline",
|
|
36
|
-
"forkedFrom"
|
|
42
|
+
"forkedFrom",
|
|
43
|
+
"restartedFrom"
|
|
37
44
|
]));
|
|
38
45
|
export function isSanitizedSearchAttributeValue(value) {
|
|
39
46
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
|
|
@@ -53,6 +60,30 @@ export function isFiniteNumber(value) {
|
|
|
53
60
|
export function isTimelineStep(value) {
|
|
54
61
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
55
62
|
}
|
|
63
|
+
const WORKFLOW_TIMELINE_DETAIL_OUTCOMES = new Set(["fulfilled", "rejected", "won", "lost"]);
|
|
64
|
+
function isBoundedTimelineDetailString(value) {
|
|
65
|
+
return typeof value === "string" && value.length <= MAX_TIMELINE_DETAIL_STRING_LENGTH;
|
|
66
|
+
}
|
|
67
|
+
const TIMELINE_DETAIL_FIELD_CHECKS = [
|
|
68
|
+
(detail) => typeof detail.index === "number" && Number.isSafeInteger(detail.index) && detail.index >= 0,
|
|
69
|
+
(detail) => detail.key === void 0 || isBoundedTimelineDetailString(detail.key),
|
|
70
|
+
(detail) => isBoundedTimelineDetailString(detail.operationId),
|
|
71
|
+
(detail) => isBoundedTimelineDetailString(detail.operationType),
|
|
72
|
+
(detail) => isBoundedTimelineDetailString(detail.operationLabel),
|
|
73
|
+
(detail) => WORKFLOW_TIMELINE_DETAIL_OUTCOMES.has(detail.outcome),
|
|
74
|
+
(detail) => detail.errorSummary === void 0 || isBoundedTimelineDetailString(detail.errorSummary)
|
|
75
|
+
];
|
|
76
|
+
function isWorkflowTimelineOperationDetail(value) {
|
|
77
|
+
if (!isRecord(value))
|
|
78
|
+
return !1;
|
|
79
|
+
return TIMELINE_DETAIL_FIELD_CHECKS.every((check) => check(value));
|
|
80
|
+
}
|
|
81
|
+
function isWorkflowTimelineOperationDetails(value) {
|
|
82
|
+
return value === void 0 || Array.isArray(value) && value.length <= MAX_TIMELINE_COORDINATOR_DETAILS && value.every(isWorkflowTimelineOperationDetail);
|
|
83
|
+
}
|
|
84
|
+
function isOmittedTimelineDetailCount(value) {
|
|
85
|
+
return value === void 0 || typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
86
|
+
}
|
|
56
87
|
const TIMELINE_ENTRY_FIELD_CHECKS = [
|
|
57
88
|
(entry) => isTimelineStep(entry.step),
|
|
58
89
|
(entry) => typeof entry.operationType === "string",
|
|
@@ -62,7 +93,12 @@ const TIMELINE_ENTRY_FIELD_CHECKS = [
|
|
|
62
93
|
(entry) => WORKFLOW_TIMELINE_STATUSES.has(entry.status),
|
|
63
94
|
(entry) => entry.outputSummary === void 0 || typeof entry.outputSummary === "string",
|
|
64
95
|
(entry) => entry.duration === void 0 || isFiniteNumber(entry.duration),
|
|
65
|
-
(entry) => entry.versionTuple === void 0 || isWorkflowVersionTuple(entry.versionTuple)
|
|
96
|
+
(entry) => entry.versionTuple === void 0 || isWorkflowVersionTuple(entry.versionTuple),
|
|
97
|
+
(entry) => isWorkflowTimelineOperationDetails(entry.branches),
|
|
98
|
+
(entry) => isOmittedTimelineDetailCount(entry.branchesOmitted),
|
|
99
|
+
(entry) => isWorkflowTimelineOperationDetails(entry.children),
|
|
100
|
+
(entry) => isOmittedTimelineDetailCount(entry.childrenOmitted),
|
|
101
|
+
(entry) => entry.speculationOutcome === void 0 || entry.speculationOutcome === "committed" || entry.speculationOutcome === "rolled-back"
|
|
66
102
|
];
|
|
67
103
|
export function isWorkflowTimelineEntry(value) {
|
|
68
104
|
if (!isRecord(value))
|
|
@@ -102,8 +138,36 @@ export function decodeWorkflowState(bytes) {
|
|
|
102
138
|
console.warn(`[weft] Decoded workflow state for "${String(state.id)}" has an invalid executionStateOwnerId field; falling back to the workflow id as the execution owner. This usually indicates corruption or tampering of the storage record.`);
|
|
103
139
|
delete state.executionStateOwnerId;
|
|
104
140
|
}
|
|
141
|
+
sanitizeDecodedParentLineage(state);
|
|
142
|
+
sanitizeDecodedRestartLineage(state);
|
|
105
143
|
return decodedRecord === void 0 ? state : stripUnknownWorkflowStateFields(state, decodedRecord);
|
|
106
144
|
}
|
|
145
|
+
function sanitizeDecodedParentLineage(state) {
|
|
146
|
+
if (state.parentWorkflowId !== void 0)
|
|
147
|
+
try {
|
|
148
|
+
coerceStartWorkflowId(state.parentWorkflowId, "parentWorkflowId");
|
|
149
|
+
} catch {
|
|
150
|
+
delete state.parentWorkflowId;
|
|
151
|
+
}
|
|
152
|
+
if (typeof state.parentWorkflowExecutionToken !== "string" || state.parentWorkflowExecutionToken.length === 0 || state.parentWorkflowId === void 0)
|
|
153
|
+
delete state.parentWorkflowExecutionToken;
|
|
154
|
+
}
|
|
155
|
+
function sanitizeDecodedRestartLineage(state) {
|
|
156
|
+
const restartedFrom = state.restartedFrom;
|
|
157
|
+
if (!isRecord(restartedFrom)) {
|
|
158
|
+
delete state.restartedFrom;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
coerceStartWorkflowId(restartedFrom.workflowId, "restartedFrom.workflowId");
|
|
163
|
+
} catch {
|
|
164
|
+
delete state.restartedFrom;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const { workflowExecutionToken, replacedAt } = restartedFrom;
|
|
168
|
+
if (workflowExecutionToken !== void 0 && (typeof workflowExecutionToken !== "string" || workflowExecutionToken.length === 0) || typeof replacedAt !== "number" || !Number.isSafeInteger(replacedAt) || replacedAt < 0)
|
|
169
|
+
delete state.restartedFrom;
|
|
170
|
+
}
|
|
107
171
|
function stripUnknownWorkflowStateFields(state, stateFields) {
|
|
108
172
|
for (const fieldName of Object.keys(stateFields))
|
|
109
173
|
if (!WORKFLOW_STATE_FIELD_NAMES.has(fieldName))
|
|
@@ -2,8 +2,12 @@ import type { AttributeFilter, ListFilter, WorkflowState } from '../types.ts';
|
|
|
2
2
|
import type { EngineInternals } from './internals.ts';
|
|
3
3
|
/** Stream decoded workflow states that match a list filter. */
|
|
4
4
|
export declare function streamMatchingWorkflowStates(internals: EngineInternals, filter?: ListFilter): AsyncGenerator<WorkflowState>;
|
|
5
|
-
/** Resolve
|
|
5
|
+
/** Resolve indexed workflow IDs implied by lineage, schedule, tag, and search-attribute filters. */
|
|
6
6
|
export declare function resolveConstrainedIds(internals: EngineInternals, filter: ListFilter | undefined, normalizedTagFilters: readonly string[] | undefined): Promise<Set<string> | null>;
|
|
7
7
|
/** Query a single search-attribute index filter and return matching workflow IDs. */
|
|
8
8
|
export declare function queryAttributeIndex(internals: EngineInternals, filter: AttributeFilter): Promise<Set<string>>;
|
|
9
|
+
/** Resolve the durable reverse index from one concrete parent run to its direct children. */
|
|
10
|
+
export declare function queryChildWorkflowIndex(internals: EngineInternals, parentWorkflowId: string, parentWorkflowExecutionToken: string | undefined): Promise<Set<string>>;
|
|
11
|
+
/** Resolve the durable reverse index from a schedule id to launched workflow ids. */
|
|
12
|
+
export declare function queryScheduleRunIndex(internals: EngineInternals, scheduleId: string): Promise<Set<string>>;
|
|
9
13
|
export declare function isTopLevelWorkflowStateKey(key: string): boolean;
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
matchesListFilter
|
|
16
16
|
} from "./state-utilities.js";
|
|
17
17
|
import { decodeWorkflowState } from "./validation.js";
|
|
18
|
+
import { MAX_LIST_SCAN_ROWS, WorkflowListScanCapExceededError } from "./workflow-indexes.js";
|
|
18
19
|
const ATTRIBUTE_SCAN_CONCURRENCY = 8;
|
|
19
20
|
export async function* streamMatchingWorkflowStates(internals, filter) {
|
|
20
21
|
const normalizedTagFilters = normalizeWorkflowTags(filter?.tags), constrainedIds = await resolveConstrainedIds(internals, filter, normalizedTagFilters);
|
|
@@ -54,10 +55,9 @@ async function readSearchAttributesForFilter(internals, workflowId, filter) {
|
|
|
54
55
|
return decodeSearchAttributeRecord(await internals.storage.get(KEYS.attribute(workflowId)));
|
|
55
56
|
}
|
|
56
57
|
export async function resolveConstrainedIds(internals, filter, normalizedTagFilters) {
|
|
57
|
-
const attributeFilters = filter?.attributes,
|
|
58
|
-
if (
|
|
58
|
+
const attributeFilters = filter?.attributes, queries = buildConstrainedIdQueries(internals, normalizedTagFilters, attributeFilters, filter?.scheduleId, filter?.parentWorkflowId, filter?.parentWorkflowExecutionToken);
|
|
59
|
+
if (queries.length === 0)
|
|
59
60
|
return null;
|
|
60
|
-
const queries = buildConstrainedIdQueries(internals, normalizedTagFilters, attributeFilters);
|
|
61
61
|
return intersectIdentifierSets(await runConstrainedIdQueries(queries));
|
|
62
62
|
}
|
|
63
63
|
export async function queryAttributeIndex(internals, filter) {
|
|
@@ -68,29 +68,52 @@ export async function queryAttributeIndex(internals, filter) {
|
|
|
68
68
|
await collectRangeAttributeMatches(internals, filter, prefix, ids);
|
|
69
69
|
return ids;
|
|
70
70
|
}
|
|
71
|
-
function buildConstrainedIdQueries(internals, normalizedTagFilters, attributeFilters) {
|
|
71
|
+
function buildConstrainedIdQueries(internals, normalizedTagFilters, attributeFilters, scheduleId, parentWorkflowId, parentWorkflowExecutionToken) {
|
|
72
72
|
return [
|
|
73
|
+
...scheduleId === void 0 ? [] : [() => queryScheduleRunIndex(internals, scheduleId)],
|
|
74
|
+
...parentWorkflowId === void 0 ? [] : [() => queryChildWorkflowIndex(internals, parentWorkflowId, parentWorkflowExecutionToken)],
|
|
73
75
|
...normalizedTagFilters?.map((tag) => () => queryTagIndex(internals, tag)) ?? [],
|
|
74
76
|
...attributeFilters?.map((attributeFilter) => () => queryAttributeIndex(internals, attributeFilter)) ?? []
|
|
75
77
|
];
|
|
76
78
|
}
|
|
79
|
+
export async function queryChildWorkflowIndex(internals, parentWorkflowId, parentWorkflowExecutionToken) {
|
|
80
|
+
const workflowIds = new Set, prefix = KEYS.childWorkflowByParentPrefix(parentWorkflowId, parentWorkflowExecutionToken);
|
|
81
|
+
for await (const [key] of internals.storage.scan(prefix)) {
|
|
82
|
+
if (workflowIds.size >= MAX_LIST_SCAN_ROWS)
|
|
83
|
+
throw new WorkflowListScanCapExceededError(MAX_LIST_SCAN_ROWS);
|
|
84
|
+
const encodedWorkflowId = key.slice(prefix.length).split(":").at(-1);
|
|
85
|
+
if (encodedWorkflowId === void 0)
|
|
86
|
+
continue;
|
|
87
|
+
const workflowId = tryDecodeStorageKeyComponent(encodedWorkflowId);
|
|
88
|
+
if (workflowId !== null)
|
|
89
|
+
workflowIds.add(workflowId);
|
|
90
|
+
}
|
|
91
|
+
return workflowIds;
|
|
92
|
+
}
|
|
93
|
+
export async function queryScheduleRunIndex(internals, scheduleId) {
|
|
94
|
+
const workflowIds = new Set, prefix = KEYS.scheduleRunBySchedulePrefix(scheduleId);
|
|
95
|
+
for await (const [key] of internals.storage.scan(prefix)) {
|
|
96
|
+
if (workflowIds.size >= MAX_LIST_SCAN_ROWS)
|
|
97
|
+
throw new WorkflowListScanCapExceededError(MAX_LIST_SCAN_ROWS);
|
|
98
|
+
const encodedWorkflowId = key.slice(prefix.length), workflowId = tryDecodeStorageKeyComponent(encodedWorkflowId);
|
|
99
|
+
if (workflowId !== null)
|
|
100
|
+
workflowIds.add(workflowId);
|
|
101
|
+
}
|
|
102
|
+
return workflowIds;
|
|
103
|
+
}
|
|
77
104
|
async function runConstrainedIdQueries(queries) {
|
|
78
|
-
const idSets = Array.from({ length: queries.length });
|
|
79
105
|
let nextIndex = 0;
|
|
80
106
|
const runWorker = async () => {
|
|
107
|
+
const workerResults = [];
|
|
81
108
|
while (nextIndex < queries.length) {
|
|
82
109
|
const currentIndex = nextIndex;
|
|
83
110
|
nextIndex += 1;
|
|
84
|
-
|
|
111
|
+
workerResults.push([currentIndex, await queries[currentIndex]()]);
|
|
85
112
|
}
|
|
86
|
-
|
|
87
|
-
await Promise.all(Array.from({ length: workerLimit }, () => runWorker()));
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
function requireConstrainedIdSet(idSet) {
|
|
91
|
-
if (idSet === void 0)
|
|
92
|
-
throw Error("Attribute index query did not produce a workflow ID set.");
|
|
93
|
-
return idSet;
|
|
113
|
+
return workerResults;
|
|
114
|
+
}, workerLimit = Math.max(1, Math.min(ATTRIBUTE_SCAN_CONCURRENCY, queries.length)), completedResults = (await Promise.all(Array.from({ length: workerLimit }, () => runWorker()))).flat();
|
|
115
|
+
completedResults.sort(([leftIndex], [rightIndex]) => leftIndex - rightIndex);
|
|
116
|
+
return completedResults.map(([, idSet]) => idSet);
|
|
94
117
|
}
|
|
95
118
|
async function collectExactAttributeMatches(internals, filter, attributeName, ids) {
|
|
96
119
|
const exactValues = attributeFilterExactValues(filter);
|
|
@@ -38,10 +38,9 @@ export declare class ScheduleFiredEvent extends Event {
|
|
|
38
38
|
/** Wall-clock time the run was launched, from the engine's injected clock. */
|
|
39
39
|
readonly firedAt: number;
|
|
40
40
|
/**
|
|
41
|
-
* The scheduled grid timestamp the occurrence was due.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* and cannot be reported when the run finally launches.
|
|
41
|
+
* The scheduled grid timestamp the occurrence was due. Queue overlap retains
|
|
42
|
+
* this timestamp on the durable queue entry and reports it when the run drains.
|
|
43
|
+
* It is undefined only when an internal caller did not supply an occurrence.
|
|
45
44
|
*/
|
|
46
45
|
readonly occurrence: number | undefined;
|
|
47
46
|
constructor(scheduleId: string, workflowId: string, firedAt: number, occurrence?: number);
|
|
@@ -241,7 +241,8 @@ export declare class WorkflowRecoverySkippedEvent extends Event {
|
|
|
241
241
|
* dead-letter horizon is reached. Per-attempt observability — fires on every retry.
|
|
242
242
|
* - `'dead-lettered'`: the retry horizon was reached without success; the external
|
|
243
243
|
* resource may be leaked. The durable record is `KEYS.teardownDeadLetter`, which
|
|
244
|
-
* survives purge so
|
|
244
|
+
* survives purge so `getFinalizerStatus()` remains auditable after the workflow
|
|
245
|
+
* record is gone.
|
|
245
246
|
*
|
|
246
247
|
* @example
|
|
247
248
|
* ```ts
|
|
@@ -20,6 +20,9 @@ import { WeftError } from './weft-error.ts';
|
|
|
20
20
|
export declare const listFilterObjectSchema: z.ZodObject<{
|
|
21
21
|
status: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<WorkflowStatus, unknown, z.core.$ZodTypeInternals<WorkflowStatus, unknown>>, z.ZodArray<z.ZodType<WorkflowStatus, unknown, z.core.$ZodTypeInternals<WorkflowStatus, unknown>>>]>>;
|
|
22
22
|
type: z.ZodOptional<z.ZodString>;
|
|
23
|
+
scheduleId: z.ZodOptional<z.ZodString>;
|
|
24
|
+
parentWorkflowId: z.ZodOptional<z.ZodString>;
|
|
25
|
+
parentWorkflowExecutionToken: z.ZodOptional<z.ZodString>;
|
|
23
26
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
24
27
|
attributes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
25
28
|
key: z.ZodUnion<readonly [z.ZodString, z.ZodAny]>;
|
|
@@ -35,6 +35,9 @@ const WORKFLOW_STATUSES = [
|
|
|
35
35
|
export const listFilterObjectSchema = z.object({
|
|
36
36
|
status: z.union([workflowStatusSchema, z.array(workflowStatusSchema)]).optional(),
|
|
37
37
|
type: z.string().min(1).optional(),
|
|
38
|
+
scheduleId: z.string().min(1).optional(),
|
|
39
|
+
parentWorkflowId: z.string().min(1).optional(),
|
|
40
|
+
parentWorkflowExecutionToken: z.string().min(1).optional(),
|
|
38
41
|
tags: z.array(z.string()).optional(),
|
|
39
42
|
attributes: z.array(attributeFilterSchema).optional(),
|
|
40
43
|
limit: z.number().int().min(1).max(1000).optional(),
|
|
@@ -70,6 +73,14 @@ export function normalizeListFilter(input) {
|
|
|
70
73
|
const result = listFilterObjectSchema.safeParse(input ?? {});
|
|
71
74
|
if (!result.success)
|
|
72
75
|
throw new ListFilterValidationError(result.error.issues.map(flattenIssue));
|
|
76
|
+
if (result.data.parentWorkflowExecutionToken !== void 0 && result.data.parentWorkflowId === void 0)
|
|
77
|
+
throw new ListFilterValidationError([
|
|
78
|
+
{
|
|
79
|
+
path: ["parentWorkflowExecutionToken"],
|
|
80
|
+
message: "parentWorkflowExecutionToken requires parentWorkflowId",
|
|
81
|
+
code: "custom"
|
|
82
|
+
}
|
|
83
|
+
]);
|
|
73
84
|
return result.data;
|
|
74
85
|
}
|
|
75
86
|
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure builder that captures a snapshot of the engine's locally-registered
|
|
3
|
+
* workflows and activities, with their JSON Schemas. This is the data source
|
|
4
|
+
* behind the `GET /v1/registry` REST endpoint and (later) the MCP server.
|
|
5
|
+
*
|
|
6
|
+
* The output is a plain object designed to be safe for JSON serialization.
|
|
7
|
+
* Absent metadata fields are omitted (never `null`, never `{}`); converter
|
|
8
|
+
* exceptions surface as a typed {@link RegistrySchemaConversionError} (see
|
|
9
|
+
* its JSDoc for what reaches the wire vs server logs).
|
|
10
|
+
*
|
|
11
|
+
* **Ordering guarantee.** Builder code inserts workflow, activity, signal,
|
|
12
|
+
* update, and query keys in alphabetical (codepoint) order. Workflow and
|
|
13
|
+
* activity names cannot be integer-like (the name grammar requires a leading
|
|
14
|
+
* letter or underscore), but signal/update/query names accept any string, so
|
|
15
|
+
* integer-like message names could theoretically be reordered by JS engines
|
|
16
|
+
* despite the explicit sort. Clients that want to protect themselves from
|
|
17
|
+
* future registry sources should still sort `Object.keys(...)` before
|
|
18
|
+
* presenting or diffing snapshot entries.
|
|
19
|
+
*
|
|
20
|
+
* @module core/registry-snapshot
|
|
21
|
+
*/
|
|
22
|
+
import type { ActivityMetadata } from './activity-registry.ts';
|
|
1
23
|
import type { Engine } from './engine.ts';
|
|
2
24
|
import { WeftError } from './weft-error.ts';
|
|
3
25
|
/**
|
|
@@ -6,12 +28,20 @@ import { WeftError } from './weft-error.ts';
|
|
|
6
28
|
* versions with a clear upgrade message.
|
|
7
29
|
*/
|
|
8
30
|
export declare const REGISTRY_VERSION = 1;
|
|
31
|
+
/** Schema metadata reported for a statically registered workflow message. */
|
|
32
|
+
export type RegistryMessageEntry = {
|
|
33
|
+
inputSchema?: Record<string, unknown>;
|
|
34
|
+
outputSchema?: Record<string, unknown>;
|
|
35
|
+
};
|
|
9
36
|
/** Metadata reported per workflow in a registry snapshot. */
|
|
10
37
|
export type RegistryWorkflowEntry = {
|
|
11
38
|
inputSchema?: Record<string, unknown>;
|
|
12
39
|
outputSchema?: Record<string, unknown>;
|
|
13
40
|
description?: string;
|
|
14
41
|
tags?: ReadonlyArray<string>;
|
|
42
|
+
signals?: Record<string, RegistryMessageEntry>;
|
|
43
|
+
updates?: Record<string, RegistryMessageEntry>;
|
|
44
|
+
queries?: Record<string, RegistryMessageEntry>;
|
|
15
45
|
};
|
|
16
46
|
/**
|
|
17
47
|
* Metadata reported per activity in a registry snapshot.
|
|
@@ -31,6 +61,8 @@ export type RegistryActivityEntry = {
|
|
|
31
61
|
/** Engine assigns a default queue when none is specified, so this is always populated. */
|
|
32
62
|
queue: string;
|
|
33
63
|
description?: string;
|
|
64
|
+
retry?: ActivityMetadata['retry'];
|
|
65
|
+
timeout?: ActivityMetadata['timeout'];
|
|
34
66
|
};
|
|
35
67
|
/**
|
|
36
68
|
* Snapshot of every locally-registered workflow and activity, suitable for
|