@lostgradient/weft 0.12.0 → 0.13.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/operation-catalog-snapshot.d.ts +11 -2
- package/dist/cli/operation-catalog-snapshot.js +14 -5
- package/dist/cli/operation-client-runtime.d.ts +29 -0
- 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 +110 -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 +15 -8
- package/dist/client/http-request.js +19 -7
- 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/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-creators-schedule.d.ts +2 -1
- package/dist/core/engine/callback-creators-schedule.js +3 -3
- 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 +57 -10
- package/dist/core/engine/operations-speculate.d.ts +1 -1
- package/dist/core/engine/operations-speculate.js +19 -5
- package/dist/core/engine/registration.js +14 -0
- 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 +4 -4
- 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/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 +55 -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-fault.d.ts +24 -7
- package/dist/server/operation-fault.js +65 -13
- 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-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 +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/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/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.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
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { KEYS } from "../../storage/interface.js";
|
|
2
2
|
import { decode } from "../codec.js";
|
|
3
3
|
import { WorkflowNotRegisteredError } from "./errors.js";
|
|
4
|
+
import { commitFencedEngineWrite } from "./fenced-write.js";
|
|
4
5
|
import { ScheduleHandle } from "./schedule-handle.js";
|
|
5
6
|
import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
|
|
6
7
|
import { getNextScheduleOccurrence } from "./schedule-occurrence.js";
|
|
8
|
+
import { applyBlockedScheduleOccurrence, drainQueuedScheduleRun } from "./schedule-overlap.js";
|
|
7
9
|
import { decodeScheduleRunMetadata } from "./schedule-run-metadata.js";
|
|
8
10
|
import {
|
|
9
11
|
clearScheduleCurrentWorkflow,
|
|
@@ -11,14 +13,20 @@ import {
|
|
|
11
13
|
matchesScheduleFilter,
|
|
12
14
|
paginateScheduleSummaries
|
|
13
15
|
} from "./state-utilities.js";
|
|
14
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
loadScheduleState,
|
|
18
|
+
requireScheduleState,
|
|
19
|
+
runSerializedScheduleStateOperation,
|
|
20
|
+
writeScheduleState
|
|
21
|
+
} from "./storage-io.js";
|
|
15
22
|
import {
|
|
16
23
|
coerceScheduleId,
|
|
17
24
|
decodeScheduleState,
|
|
18
25
|
isValidScheduleIdentifier,
|
|
19
26
|
normalizeScheduleFilter,
|
|
20
27
|
normalizeScheduleOptions,
|
|
21
|
-
normalizeScheduleSpec
|
|
28
|
+
normalizeScheduleSpec,
|
|
29
|
+
normalizeScheduleUpdateOptions
|
|
22
30
|
} from "./validation/schedule.js";
|
|
23
31
|
export { startScheduledRun } from "./schedule-run.js";
|
|
24
32
|
export { handleScheduleTimer } from "./schedule-timer.js";
|
|
@@ -48,7 +56,7 @@ export async function schedule(internals, type, input, spec, options) {
|
|
|
48
56
|
updatedAt: now,
|
|
49
57
|
nextFireAt: getNextScheduleOccurrence({ ...cadenceFields, createdAt: now }, now),
|
|
50
58
|
missedFireCount: 0,
|
|
51
|
-
queuedRuns:
|
|
59
|
+
queuedRuns: []
|
|
52
60
|
};
|
|
53
61
|
await writeScheduleState(internals, state);
|
|
54
62
|
return new ScheduleHandle(scheduleId, internals.engine);
|
|
@@ -64,8 +72,7 @@ export async function listSchedules(internals, filter) {
|
|
|
64
72
|
const state = decodeScheduleState(value);
|
|
65
73
|
if (!state || !matchesScheduleFilter(state, normalizedFilter))
|
|
66
74
|
continue;
|
|
67
|
-
|
|
68
|
-
items.push(summary);
|
|
75
|
+
items.push(toScheduleSummary(state));
|
|
69
76
|
}
|
|
70
77
|
return paginateScheduleSummaries(items, normalizedFilter);
|
|
71
78
|
}
|
|
@@ -93,7 +100,7 @@ async function hasCurrentScheduleTimer(internals, state) {
|
|
|
93
100
|
}
|
|
94
101
|
export function toScheduleSummary(state) {
|
|
95
102
|
const { input: _input, ...summary } = state;
|
|
96
|
-
return summary;
|
|
103
|
+
return { ...summary, queuedRuns: summary.queuedRuns.map((queuedRun) => ({ ...queuedRun })) };
|
|
97
104
|
}
|
|
98
105
|
export async function pauseSchedule(internals, scheduleId) {
|
|
99
106
|
const normalizedScheduleId = coerceScheduleId(scheduleId, "scheduleId"), state = await requireScheduleState(internals, normalizedScheduleId);
|
|
@@ -105,7 +112,7 @@ export async function pauseSchedule(internals, scheduleId) {
|
|
|
105
112
|
status: "paused",
|
|
106
113
|
updatedAt: now,
|
|
107
114
|
nextFireAt: getNextScheduleOccurrence(state, now),
|
|
108
|
-
queuedRuns:
|
|
115
|
+
queuedRuns: []
|
|
109
116
|
};
|
|
110
117
|
await writeScheduleState(internals, updatedState, { includeTimer: !1 });
|
|
111
118
|
}
|
|
@@ -132,23 +139,30 @@ export async function cancelSchedule(internals, scheduleId) {
|
|
|
132
139
|
status: "cancelled",
|
|
133
140
|
updatedAt: internals.options.getNow(),
|
|
134
141
|
nextFireAt: null,
|
|
135
|
-
queuedRuns:
|
|
142
|
+
queuedRuns: []
|
|
136
143
|
};
|
|
137
144
|
await writeScheduleState(internals, updatedState, { includeTimer: !1 });
|
|
138
145
|
}
|
|
139
|
-
export async function updateSchedule(internals, scheduleId, newSpec) {
|
|
140
|
-
const normalizedScheduleId = coerceScheduleId(scheduleId, "scheduleId"), normalizedSpec = normalizeScheduleSpec(newSpec),
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
146
|
+
export async function updateSchedule(internals, scheduleId, newSpec, options) {
|
|
147
|
+
const normalizedScheduleId = coerceScheduleId(scheduleId, "scheduleId"), normalizedSpec = normalizeScheduleSpec(newSpec), normalizedOptions = normalizeScheduleUpdateOptions(options);
|
|
148
|
+
await runSerializedScheduleStateOperation(internals, normalizedScheduleId, async () => {
|
|
149
|
+
const state = await requireScheduleState(internals, normalizedScheduleId), now = internals.options.getNow(), {
|
|
150
|
+
cronExpression: _droppedCron,
|
|
151
|
+
intervalMs: _droppedInterval,
|
|
152
|
+
...stateWithoutCadence
|
|
153
|
+
} = state, cadenceFields = normalizedSpec.kind === "interval" ? { intervalMs: normalizedSpec.intervalMs } : { cronExpression: normalizedSpec.cronExpression }, anchorFields = normalizedSpec.kind === "interval" ? { createdAt: now } : {}, updatedState = {
|
|
154
|
+
...stateWithoutCadence,
|
|
155
|
+
...normalizedOptions,
|
|
156
|
+
...cadenceFields,
|
|
157
|
+
...anchorFields,
|
|
158
|
+
updatedAt: now,
|
|
159
|
+
nextFireAt: state.status === "cancelled" ? null : getNextScheduleOccurrence({ ...cadenceFields, createdAt: now }, now)
|
|
160
|
+
};
|
|
161
|
+
await writeScheduleState(internals, updatedState, {
|
|
162
|
+
includeTimer: state.status === "active",
|
|
163
|
+
replaceTimerFrom: state
|
|
164
|
+
});
|
|
165
|
+
});
|
|
152
166
|
}
|
|
153
167
|
function scheduledRunOccupiesSlot(currentWorkflowState) {
|
|
154
168
|
const status = currentWorkflowState?.status;
|
|
@@ -169,31 +183,20 @@ export async function refreshScheduledWorkflowState(internals, state, callbacks)
|
|
|
169
183
|
function hasActiveScheduledWorkflow(currentWorkflowState) {
|
|
170
184
|
return scheduledRunOccupiesSlot(currentWorkflowState);
|
|
171
185
|
}
|
|
172
|
-
async function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
await callbacks.cancelWorkflow(state.currentWorkflowId);
|
|
179
|
-
}
|
|
180
|
-
const stateForStart = clearScheduleCurrentWorkflow(state);
|
|
181
|
-
return {
|
|
182
|
-
...state,
|
|
183
|
-
currentWorkflowId: await callbacks.startScheduledRun(stateForStart, occurrence)
|
|
184
|
-
};
|
|
186
|
+
export async function applyScheduleOccurrence(internals, state, callbacks, occurrence) {
|
|
187
|
+
const { state: refreshedState, currentWorkflowState } = await callbacks.refreshScheduledWorkflowState(state);
|
|
188
|
+
let stateForOccurrence = refreshedState, hasActiveWorkflow = hasActiveScheduledWorkflow(currentWorkflowState);
|
|
189
|
+
if (!hasActiveWorkflow && stateForOccurrence.queuedRuns.length > 0) {
|
|
190
|
+
stateForOccurrence = await drainQueuedScheduleRun(stateForOccurrence, callbacks);
|
|
191
|
+
hasActiveWorkflow = !0;
|
|
185
192
|
}
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const { state: refreshedState, currentWorkflowState } = await callbacks.refreshScheduledWorkflowState(state), hasActiveWorkflow = hasActiveScheduledWorkflow(currentWorkflowState);
|
|
192
|
-
if (refreshedState.overlap === "allow") {
|
|
193
|
-
await callbacks.startScheduledRun(refreshedState, occurrence);
|
|
194
|
-
return refreshedState;
|
|
193
|
+
if (stateForOccurrence.overlap === "allow") {
|
|
194
|
+
await callbacks.startScheduledRun(stateForOccurrence, {
|
|
195
|
+
...occurrence !== void 0 && { occurrence }
|
|
196
|
+
});
|
|
197
|
+
return stateForOccurrence;
|
|
195
198
|
}
|
|
196
|
-
return applyBlockedScheduleOccurrence(
|
|
199
|
+
return applyBlockedScheduleOccurrence(internals, stateForOccurrence, hasActiveWorkflow, callbacks, occurrence);
|
|
197
200
|
}
|
|
198
201
|
export async function settleBackfillScheduleState(internals, state, callbacks) {
|
|
199
202
|
if (!state.currentWorkflowId)
|
|
@@ -208,26 +211,29 @@ export async function handleScheduledWorkflowTerminal(internals, workflowId, cal
|
|
|
208
211
|
const scheduleRunBytes = await internals.storage.get(KEYS.scheduleRun(workflowId));
|
|
209
212
|
if (!scheduleRunBytes)
|
|
210
213
|
return;
|
|
211
|
-
await internals.storage.delete(KEYS.scheduleRun(workflowId));
|
|
212
214
|
const metadata = decodeScheduleRunMetadata(scheduleRunBytes);
|
|
213
|
-
if (metadata === null || !isValidScheduleIdentifier(metadata.id))
|
|
215
|
+
if (metadata === null || !isValidScheduleIdentifier(metadata.id)) {
|
|
216
|
+
await deleteTransientScheduleRunMetadata(internals, workflowId);
|
|
214
217
|
return;
|
|
218
|
+
}
|
|
215
219
|
const scheduleId = metadata.id, state = await loadScheduleState(internals, scheduleId);
|
|
216
|
-
if (!state || state.currentWorkflowId !== workflowId)
|
|
220
|
+
if (!state || state.currentWorkflowId !== workflowId) {
|
|
221
|
+
await deleteTransientScheduleRunMetadata(internals, workflowId);
|
|
217
222
|
return;
|
|
223
|
+
}
|
|
218
224
|
const now = internals.options.getNow(), clearedState = {
|
|
219
225
|
...clearScheduleCurrentWorkflow(state),
|
|
220
226
|
updatedAt: now
|
|
221
227
|
};
|
|
222
|
-
if (clearedState.status === "active" && clearedState.
|
|
223
|
-
|
|
224
|
-
await writeScheduleState(internals, {
|
|
225
|
-
...clearedState,
|
|
226
|
-
currentWorkflowId: nextWorkflowId,
|
|
227
|
-
queuedRuns: clearedState.queuedRuns - 1,
|
|
228
|
-
updatedAt: now
|
|
229
|
-
}, { includeTimer: !1 });
|
|
228
|
+
if (clearedState.status === "active" && clearedState.queuedRuns.length > 0) {
|
|
229
|
+
await drainQueuedScheduleRun({ ...clearedState, updatedAt: now }, callbacks, workflowId);
|
|
230
230
|
return;
|
|
231
231
|
}
|
|
232
|
-
await writeScheduleState(internals, clearedState, {
|
|
232
|
+
await writeScheduleState(internals, clearedState, {
|
|
233
|
+
includeTimer: !1,
|
|
234
|
+
additionalOperations: [{ type: "delete", key: KEYS.scheduleRun(workflowId) }]
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async function deleteTransientScheduleRunMetadata(internals, workflowId) {
|
|
238
|
+
await commitFencedEngineWrite(internals, [{ type: "delete", key: KEYS.scheduleRun(workflowId) }], [], () => Error(`Schedule-run cleanup for workflow "${workflowId}" lost its precondition.`));
|
|
233
239
|
}
|
|
@@ -223,7 +223,7 @@ function matchesListFilterStatus(state, filter) {
|
|
|
223
223
|
return (Array.isArray(filter.status) ? filter.status : [filter.status]).includes(state.status);
|
|
224
224
|
}
|
|
225
225
|
function matchesListFilterIdentity(state, filter) {
|
|
226
|
-
return matchesListFilterStatus(state, filter) && (filter.type === void 0 || state.type === filter.type) && (filter.idPrefix === void 0 || state.id.startsWith(filter.idPrefix));
|
|
226
|
+
return matchesListFilterStatus(state, filter) && (filter.type === void 0 || state.type === filter.type) && (filter.parentWorkflowId === void 0 || state.parentWorkflowId === filter.parentWorkflowId) && (filter.parentWorkflowExecutionToken === void 0 || state.parentWorkflowExecutionToken === filter.parentWorkflowExecutionToken) && (filter.idPrefix === void 0 || state.id.startsWith(filter.idPrefix));
|
|
227
227
|
}
|
|
228
228
|
function matchesListFilterTimeRanges(state, filter) {
|
|
229
229
|
if (filter.createdAt !== void 0 && !timestampInRange(state.createdAt, filter.createdAt))
|
|
@@ -3,6 +3,16 @@ import type { ScheduleState, WorkflowState } from '../types.ts';
|
|
|
3
3
|
import type { EngineInternals } from './internals.ts';
|
|
4
4
|
/** Run a workflow-state write after any earlier write for that workflow has settled. */
|
|
5
5
|
export declare function runSerializedWorkflowStateWrite<TResult>(internals: EngineInternals, workflowId: string, writeOperation: () => Promise<TResult>): Promise<TResult>;
|
|
6
|
+
/**
|
|
7
|
+
* Serialize a schedule read-modify-write operation with its timer callback.
|
|
8
|
+
*
|
|
9
|
+
* Unlike workflow-state write serialization, a failed predecessor is propagated
|
|
10
|
+
* to callers that were already queued behind it. In particular, an update that
|
|
11
|
+
* arrives while a scanned timer callback is rearming must not delete the fired
|
|
12
|
+
* timer after that callback fails; rejecting the queued update leaves the durable
|
|
13
|
+
* timer available for the scheduler (or a lease successor) to retry.
|
|
14
|
+
*/
|
|
15
|
+
export declare function runSerializedScheduleStateOperation<TResult>(internals: EngineInternals, scheduleId: string, operation: () => Promise<TResult>): Promise<TResult>;
|
|
6
16
|
/** Load and decode persisted workflow state by workflow ID. */
|
|
7
17
|
export declare function loadWorkflowState(internals: EngineInternals, workflowId: string): Promise<WorkflowState | null>;
|
|
8
18
|
/** Load a terminal workflow result or throw the persisted terminal error. */
|
|
@@ -29,6 +39,8 @@ export declare function requireScheduleState(internals: EngineInternals, schedul
|
|
|
29
39
|
/** Persist schedule state and optionally write the next schedule timer. */
|
|
30
40
|
export declare function writeScheduleState(internals: EngineInternals, state: ScheduleState, options?: {
|
|
31
41
|
includeTimer?: boolean;
|
|
42
|
+
replaceTimerFrom?: ScheduleState;
|
|
43
|
+
additionalOperations?: BatchOperation[];
|
|
32
44
|
}): Promise<void>;
|
|
33
45
|
/** Load persisted workflow start headers by workflow ID. */
|
|
34
46
|
export declare function loadWorkflowStartHeaders(internals: EngineInternals, workflowId: string): Promise<Map<string, string> | undefined>;
|
|
@@ -30,6 +30,21 @@ export async function runSerializedWorkflowStateWrite(internals, workflowId, wri
|
|
|
30
30
|
internals.workflowStateWriteChains.delete(workflowId);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
export async function runSerializedScheduleStateOperation(internals, scheduleId, operation) {
|
|
34
|
+
const execution = (internals.scheduleStateOperationChains.get(scheduleId) ?? Promise.resolve()).then(operation), trackedExecution = execution.then(() => {
|
|
35
|
+
return;
|
|
36
|
+
});
|
|
37
|
+
trackedExecution.catch(() => {
|
|
38
|
+
return;
|
|
39
|
+
});
|
|
40
|
+
internals.scheduleStateOperationChains.set(scheduleId, trackedExecution);
|
|
41
|
+
try {
|
|
42
|
+
return await execution;
|
|
43
|
+
} finally {
|
|
44
|
+
if (internals.scheduleStateOperationChains.get(scheduleId) === trackedExecution)
|
|
45
|
+
internals.scheduleStateOperationChains.delete(scheduleId);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
33
48
|
export async function loadWorkflowState(internals, workflowId) {
|
|
34
49
|
const bytes = await internals.storage.get(KEYS.workflow(workflowId));
|
|
35
50
|
if (!bytes)
|
|
@@ -80,19 +95,43 @@ export async function requireScheduleState(internals, scheduleId) {
|
|
|
80
95
|
throw Error(`Schedule "${scheduleId}" not found`);
|
|
81
96
|
return state;
|
|
82
97
|
}
|
|
98
|
+
function buildPreviousScheduleTimerDeleteOperations(previousState) {
|
|
99
|
+
if (previousState?.status !== "active" || previousState.nextFireAt === null)
|
|
100
|
+
return [];
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
type: "delete",
|
|
104
|
+
key: KEYS.scheduleTick(resolveEffectiveScheduleFireAt(previousState, previousState.nextFireAt), previousState.id)
|
|
105
|
+
}
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
function buildNextScheduleTimerOperations(state, includeTimer) {
|
|
109
|
+
if (!includeTimer || state.status !== "active" || state.nextFireAt === null)
|
|
110
|
+
return [];
|
|
111
|
+
return buildTimerBatchOperations({
|
|
112
|
+
id: createScheduleTimerId(state.id),
|
|
113
|
+
workflowId: state.id,
|
|
114
|
+
fireAt: resolveEffectiveScheduleFireAt(state, state.nextFireAt),
|
|
115
|
+
kind: "schedule"
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function buildScheduleTimerReplacementOperations(state, includeTimer, previousState) {
|
|
119
|
+
const deleteOperations = buildPreviousScheduleTimerDeleteOperations(previousState), nextTimerOperations = buildNextScheduleTimerOperations(state, includeTimer);
|
|
120
|
+
if (nextTimerOperations.length > 0)
|
|
121
|
+
return [...deleteOperations, ...nextTimerOperations];
|
|
122
|
+
if (previousState === void 0)
|
|
123
|
+
return deleteOperations;
|
|
124
|
+
return [
|
|
125
|
+
...deleteOperations,
|
|
126
|
+
{ type: "delete", key: `timer-idx:${createScheduleTimerId(state.id)}` }
|
|
127
|
+
];
|
|
128
|
+
}
|
|
83
129
|
export async function writeScheduleState(internals, state, options) {
|
|
84
130
|
const operations = [
|
|
85
131
|
{ type: "put", key: KEYS.schedule(state.id), value: encode(state) }
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
operations.push(...buildTimerBatchOperations({
|
|
90
|
-
id: createScheduleTimerId(state.id),
|
|
91
|
-
workflowId: state.id,
|
|
92
|
-
fireAt: effectiveFireAt,
|
|
93
|
-
kind: "schedule"
|
|
94
|
-
}));
|
|
95
|
-
}
|
|
132
|
+
], includeTimer = options?.includeTimer ?? state.status === "active";
|
|
133
|
+
operations.push(...buildScheduleTimerReplacementOperations(state, includeTimer, options?.replaceTimerFrom));
|
|
134
|
+
operations.push(...options?.additionalOperations ?? []);
|
|
96
135
|
await commitFencedEngineWrite(internals, operations, [], () => Error(`Schedule state commit for schedule "${state.id}" lost its precondition.`));
|
|
97
136
|
}
|
|
98
137
|
export async function loadWorkflowStartHeaders(internals, workflowId) {
|
|
@@ -37,11 +37,11 @@ export declare function runningClaimIsStale(internals: EngineInternals, claim: T
|
|
|
37
37
|
/**
|
|
38
38
|
* Durable audit record written to {@link KEYS.teardownDeadLetter} when a workflow's
|
|
39
39
|
* finalizer permanently fails — the retry horizon was reached, or the recorded resource
|
|
40
|
-
* state vanished so the finalizer can never run. It is the supported operator
|
|
41
|
-
* for a leaked external resource
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
40
|
+
* state vanished so the finalizer can never run. It is the supported durable operator
|
|
41
|
+
* record for a leaked external resource; {@link Engine.getFinalizerStatus} and the matching
|
|
42
|
+
* transport operation expose it without relying on best-effort teardown events. The record
|
|
43
|
+
* is excluded from the workflow purge delete-set so it survives after the workflow record
|
|
44
|
+
* is gone.
|
|
45
45
|
*/
|
|
46
46
|
export interface TeardownDeadLetterRecord {
|
|
47
47
|
/** The workflow type whose finalizer leaked. */
|
|
@@ -52,6 +52,8 @@ export interface TeardownDeadLetterRecord {
|
|
|
52
52
|
attempts: number;
|
|
53
53
|
/** Engine clock when the record was written. */
|
|
54
54
|
deadLetteredAt: number;
|
|
55
|
+
/** Concrete run identity, present on records written from current workflow state. */
|
|
56
|
+
workflowExecutionToken?: string;
|
|
55
57
|
/** The decoded `ctx.setFinalizerState` payload, when it was still recoverable. */
|
|
56
58
|
finalizerInput?: unknown;
|
|
57
59
|
}
|
|
@@ -107,4 +109,4 @@ export declare function settleOnRunningClaim(internals: EngineInternals, workflo
|
|
|
107
109
|
export declare function deadLetterTeardown(internals: EngineInternals, workflowId: string, workflowType: string, attempts: number, expectedBytes: Uint8Array, details: {
|
|
108
110
|
lastError: string;
|
|
109
111
|
finalizerInput: unknown;
|
|
110
|
-
}): Promise<boolean>;
|
|
112
|
+
}, workflowExecutionToken?: string): Promise<boolean>;
|
|
@@ -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-operations.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,
|