@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
|
@@ -1,32 +1,8 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { decode } from "../codec.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
reviewType: z.string(),
|
|
8
|
-
reviewers: z.array(z.string()),
|
|
9
|
-
allowPartial: z.boolean(),
|
|
10
|
-
timeout: z.number().optional(),
|
|
11
|
-
webhookUrl: z.string().optional(),
|
|
12
|
-
createdAt: z.number()
|
|
13
|
-
}), persistedCompletedReviewEntrySchema = z.object({
|
|
14
|
-
status: z.literal("completed"),
|
|
15
|
-
reviewId: z.string(),
|
|
16
|
-
workflowId: z.string(),
|
|
17
|
-
artifact: z.unknown().nonoptional(),
|
|
18
|
-
reviewType: z.string(),
|
|
19
|
-
reviewers: z.array(z.string()),
|
|
20
|
-
allowPartial: z.boolean(),
|
|
21
|
-
timeout: z.number().optional(),
|
|
22
|
-
webhookUrl: z.string().optional(),
|
|
23
|
-
createdAt: z.number(),
|
|
24
|
-
decision: z.enum(["approved", "rejected", "needs-changes"]),
|
|
25
|
-
reviewer: z.string(),
|
|
26
|
-
feedback: z.string().optional(),
|
|
27
|
-
sectionDecisions: z.record(z.string(), z.enum(["approved", "rejected"])).optional(),
|
|
28
|
-
timestamp: z.number()
|
|
29
|
-
});
|
|
2
|
+
import {
|
|
3
|
+
completedReviewEntrySchema,
|
|
4
|
+
reviewRequestSchema
|
|
5
|
+
} from "../review/index.js";
|
|
30
6
|
function assignWhenDefined(target, key, value) {
|
|
31
7
|
if (value !== void 0)
|
|
32
8
|
target[key] = value;
|
|
@@ -58,7 +34,7 @@ export function parseStoredReviewRequest(value) {
|
|
|
58
34
|
} catch {
|
|
59
35
|
return null;
|
|
60
36
|
}
|
|
61
|
-
const parsedReview =
|
|
37
|
+
const parsedReview = reviewRequestSchema.safeParse(decodedValue);
|
|
62
38
|
if (!parsedReview.success)
|
|
63
39
|
return null;
|
|
64
40
|
return normalizeStoredReviewRequest(parsedReview.data);
|
|
@@ -110,7 +86,7 @@ export function parseCompletedReviewEntry(value) {
|
|
|
110
86
|
} catch {
|
|
111
87
|
return null;
|
|
112
88
|
}
|
|
113
|
-
const parsedReview =
|
|
89
|
+
const parsedReview = completedReviewEntrySchema.safeParse(decodedValue);
|
|
114
90
|
if (!parsedReview.success)
|
|
115
91
|
return null;
|
|
116
92
|
return normalizeCompletedReviewEntry(parsedReview.data);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScheduleSpec, ScheduleSummary } from '../types.ts';
|
|
1
|
+
import type { ScheduleSpec, ScheduleSummary, ScheduleUpdateOptions } from '../types.ts';
|
|
2
2
|
/**
|
|
3
3
|
* Narrow engine view a {@link ScheduleHandle} delegates to. The full
|
|
4
4
|
* {@link Engine} implements it; the handle only depends on these schedule
|
|
@@ -8,13 +8,13 @@ export interface ScheduleHandleEngine {
|
|
|
8
8
|
pauseSchedule(scheduleId: string): Promise<void>;
|
|
9
9
|
resumeSchedule(scheduleId: string): Promise<void>;
|
|
10
10
|
cancelSchedule(scheduleId: string): Promise<void>;
|
|
11
|
-
updateSchedule(scheduleId: string, newSpec: string | ScheduleSpec): Promise<void>;
|
|
11
|
+
updateSchedule(scheduleId: string, newSpec: string | ScheduleSpec, options?: ScheduleUpdateOptions): Promise<void>;
|
|
12
12
|
getSchedule(scheduleId: string): Promise<ScheduleSummary | null>;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Handle to a recurring schedule created by {@link Engine.schedule}. Use
|
|
16
16
|
* `handle.pause()`, `handle.resume()`, `handle.cancel()`, or
|
|
17
|
-
* `handle.update(
|
|
17
|
+
* `handle.update(spec, options?)` to manage the schedule lifecycle.
|
|
18
18
|
* `handle.describe()` returns the current {@link ScheduleSummary}.
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
@@ -40,6 +40,6 @@ export declare class ScheduleHandle {
|
|
|
40
40
|
pause(): Promise<void>;
|
|
41
41
|
resume(): Promise<void>;
|
|
42
42
|
cancel(): Promise<void>;
|
|
43
|
-
update(newSpec: string | ScheduleSpec): Promise<void>;
|
|
43
|
+
update(newSpec: string | ScheduleSpec, options?: ScheduleUpdateOptions): Promise<void>;
|
|
44
44
|
describe(): Promise<ScheduleSummary>;
|
|
45
45
|
}
|
|
@@ -14,8 +14,8 @@ export class ScheduleHandle {
|
|
|
14
14
|
async cancel() {
|
|
15
15
|
await this.#engine.cancelSchedule(this.id);
|
|
16
16
|
}
|
|
17
|
-
async update(newSpec) {
|
|
18
|
-
await this.#engine.updateSchedule(this.id, newSpec);
|
|
17
|
+
async update(newSpec, options) {
|
|
18
|
+
await this.#engine.updateSchedule(this.id, newSpec, options);
|
|
19
19
|
}
|
|
20
20
|
async describe() {
|
|
21
21
|
const schedule = await this.#engine.getSchedule(this.id);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ScheduleState } from '../types.ts';
|
|
2
|
+
import type { EngineInternals } from './internals.ts';
|
|
3
|
+
import type { ScheduledRunStartOptions } from './schedule-run.ts';
|
|
4
|
+
type ScheduleOverlapCallbacks = {
|
|
5
|
+
cancelWorkflow: (workflowId: string) => Promise<void>;
|
|
6
|
+
getWorkflowResult: (workflowId: string) => Promise<unknown>;
|
|
7
|
+
startScheduledRun: (state: ScheduleState, options?: ScheduledRunStartOptions) => Promise<string>;
|
|
8
|
+
};
|
|
9
|
+
export declare function applyBlockedScheduleOccurrence(internals: EngineInternals, state: ScheduleState, hasActiveWorkflow: boolean, callbacks: ScheduleOverlapCallbacks, occurrence?: number): Promise<ScheduleState>;
|
|
10
|
+
export declare function drainQueuedScheduleRun(state: ScheduleState, callbacks: Pick<ScheduleOverlapCallbacks, 'startScheduledRun'>, completedWorkflowId?: string): Promise<ScheduleState>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { clearScheduleCurrentWorkflow } from "./state-utilities.js";
|
|
2
|
+
export async function applyBlockedScheduleOccurrence(internals, state, hasActiveWorkflow, callbacks, occurrence) {
|
|
3
|
+
if (!hasActiveWorkflow)
|
|
4
|
+
return startScheduleRunInOccupiedSlot(state, callbacks, occurrence);
|
|
5
|
+
if (state.overlap === "cancel-running") {
|
|
6
|
+
if (state.currentWorkflowId) {
|
|
7
|
+
callbacks.getWorkflowResult(state.currentWorkflowId).catch(() => {});
|
|
8
|
+
await callbacks.cancelWorkflow(state.currentWorkflowId);
|
|
9
|
+
}
|
|
10
|
+
return startScheduleRunInOccupiedSlot(clearScheduleCurrentWorkflow(state), callbacks, occurrence);
|
|
11
|
+
}
|
|
12
|
+
if (state.overlap === "queue")
|
|
13
|
+
return {
|
|
14
|
+
...state,
|
|
15
|
+
queuedRuns: [
|
|
16
|
+
...state.queuedRuns,
|
|
17
|
+
{
|
|
18
|
+
workflowId: crypto.randomUUID(),
|
|
19
|
+
queuedAt: internals.options.getNow(),
|
|
20
|
+
...occurrence !== void 0 && { occurrence }
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
};
|
|
24
|
+
return state;
|
|
25
|
+
}
|
|
26
|
+
async function startScheduleRunInOccupiedSlot(state, callbacks, occurrence) {
|
|
27
|
+
const workflowId = crypto.randomUUID(), stateAfterStart = { ...state, currentWorkflowId: workflowId };
|
|
28
|
+
await callbacks.startScheduledRun(state, {
|
|
29
|
+
workflowId,
|
|
30
|
+
...occurrence !== void 0 && { occurrence }
|
|
31
|
+
});
|
|
32
|
+
return stateAfterStart;
|
|
33
|
+
}
|
|
34
|
+
export async function drainQueuedScheduleRun(state, callbacks, completedWorkflowId) {
|
|
35
|
+
const [queuedRun, ...remainingQueuedRuns] = state.queuedRuns;
|
|
36
|
+
if (queuedRun === void 0)
|
|
37
|
+
return state;
|
|
38
|
+
const stateAfterStart = {
|
|
39
|
+
...state,
|
|
40
|
+
currentWorkflowId: queuedRun.workflowId,
|
|
41
|
+
queuedRuns: remainingQueuedRuns
|
|
42
|
+
};
|
|
43
|
+
await callbacks.startScheduledRun(state, {
|
|
44
|
+
workflowId: queuedRun.workflowId,
|
|
45
|
+
...queuedRun.occurrence !== void 0 && { occurrence: queuedRun.occurrence },
|
|
46
|
+
scheduleStateAfterStart: stateAfterStart,
|
|
47
|
+
...completedWorkflowId !== void 0 && { completedWorkflowId }
|
|
48
|
+
});
|
|
49
|
+
return stateAfterStart;
|
|
50
|
+
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { ScheduleState } from '../types.ts';
|
|
2
2
|
import type { EngineInternals } from './internals.ts';
|
|
3
3
|
import type { ScheduleCallbacks } from './schedules.ts';
|
|
4
|
+
export type ScheduledRunStartOptions = {
|
|
5
|
+
occurrence?: number;
|
|
6
|
+
/** Reserved queue identity. A fresh id is minted when omitted. */
|
|
7
|
+
workflowId?: string;
|
|
8
|
+
/** Schedule state committed atomically with the workflow start. */
|
|
9
|
+
scheduleStateAfterStart?: ScheduleState;
|
|
10
|
+
/** Prior terminal run whose transient schedule metadata is now settled. */
|
|
11
|
+
completedWorkflowId?: string;
|
|
12
|
+
};
|
|
4
13
|
/**
|
|
5
14
|
* Launch one scheduled occurrence's workflow run and emit `schedule:fired`.
|
|
6
15
|
*
|
|
@@ -11,7 +20,6 @@ import type { ScheduleCallbacks } from './schedules.ts';
|
|
|
11
20
|
* active) never reach here, so they correctly do not fire.
|
|
12
21
|
*
|
|
13
22
|
* `occurrence` is the scheduled grid timestamp the run was due, threaded from the
|
|
14
|
-
* timer loop
|
|
15
|
-
* tracked only as a count and whose original timestamp is therefore gone.
|
|
23
|
+
* timer loop or retained on its durable queue entry until drain.
|
|
16
24
|
*/
|
|
17
|
-
export declare function startScheduledRun(internals: EngineInternals, state: ScheduleState, callbacks: Pick<ScheduleCallbacks, 'startWorkflow' | 'failWorkflow' | 'handleCleanupError'>,
|
|
25
|
+
export declare function startScheduledRun(internals: EngineInternals, state: ScheduleState, callbacks: Pick<ScheduleCallbacks, 'startWorkflow' | 'failWorkflow' | 'handleCleanupError'>, options?: ScheduledRunStartOptions): Promise<string>;
|
|
@@ -1,21 +1,44 @@
|
|
|
1
1
|
import { KEYS } from "../../storage/interface.js";
|
|
2
|
+
import { encode } from "../codec.js";
|
|
2
3
|
import { ScheduleFiredEvent } from "../events.js";
|
|
3
4
|
import { unavailableServicesError } from "./lifecycle/recovered-services.js";
|
|
4
5
|
import { EMPTY_STORAGE_VALUE } from "./lifecycle/shared.js";
|
|
5
6
|
import { encodeScheduleRunMetadata } from "./schedule-run-metadata.js";
|
|
6
|
-
export async function startScheduledRun(internals, state, callbacks,
|
|
7
|
-
const workflowId = crypto.randomUUID(), scheduleRunOperations = [
|
|
7
|
+
export async function startScheduledRun(internals, state, callbacks, options = {}) {
|
|
8
|
+
const workflowId = options.workflowId ?? crypto.randomUUID(), occurrence = options.occurrence, metadata = encodeScheduleRunMetadata(state.id, occurrence), scheduleRunOperations = [
|
|
8
9
|
{
|
|
9
10
|
type: "put",
|
|
10
11
|
key: KEYS.scheduleRun(workflowId),
|
|
11
|
-
value:
|
|
12
|
+
value: metadata
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
type: "put",
|
|
16
|
+
key: KEYS.scheduleRunLink(workflowId),
|
|
17
|
+
value: metadata
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type: "put",
|
|
21
|
+
key: KEYS.scheduleRunBySchedule(state.id, workflowId),
|
|
22
|
+
value: EMPTY_STORAGE_VALUE
|
|
12
23
|
},
|
|
13
24
|
{
|
|
14
25
|
type: "put",
|
|
15
26
|
key: KEYS.terminalCleanupNeeded(workflowId),
|
|
16
27
|
value: EMPTY_STORAGE_VALUE
|
|
17
28
|
}
|
|
18
|
-
]
|
|
29
|
+
];
|
|
30
|
+
if (options.scheduleStateAfterStart !== void 0)
|
|
31
|
+
scheduleRunOperations.push({
|
|
32
|
+
type: "put",
|
|
33
|
+
key: KEYS.schedule(options.scheduleStateAfterStart.id),
|
|
34
|
+
value: encode(options.scheduleStateAfterStart)
|
|
35
|
+
});
|
|
36
|
+
if (options.completedWorkflowId !== void 0)
|
|
37
|
+
scheduleRunOperations.push({
|
|
38
|
+
type: "delete",
|
|
39
|
+
key: KEYS.scheduleRun(options.completedWorkflowId)
|
|
40
|
+
});
|
|
41
|
+
const resolution = await resolveScheduledRunServices(internals, workflowId, state, occurrence);
|
|
19
42
|
if (resolution !== null) {
|
|
20
43
|
scheduleRunOperations.push({
|
|
21
44
|
type: "put",
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { ScheduleMissedFireEvent } from "../events/schedule-events.js";
|
|
2
2
|
import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
|
|
3
3
|
import { collectDueScheduleOccurrences, getNextScheduleOccurrence } from "./schedule-occurrence.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
loadScheduleState,
|
|
6
|
+
runSerializedScheduleStateOperation,
|
|
7
|
+
writeScheduleState
|
|
8
|
+
} from "./storage-io.js";
|
|
5
9
|
const SCHEDULE_LATE_GRACE_MILLISECONDS = 1000, MAX_SCHEDULE_BACKFILL_OCCURRENCES_PER_TICK = 256, MAX_MISSED_FIRE_COUNT_PER_TICK = MAX_SCHEDULE_BACKFILL_OCCURRENCES_PER_TICK;
|
|
6
10
|
|
|
7
11
|
class ScheduleStatePersistenceError extends Error {
|
|
@@ -12,6 +16,11 @@ class ScheduleStatePersistenceError extends Error {
|
|
|
12
16
|
}
|
|
13
17
|
}
|
|
14
18
|
export async function handleScheduleTimer(internals, entry, callbacks) {
|
|
19
|
+
await runSerializedScheduleStateOperation(internals, entry.workflowId, async () => {
|
|
20
|
+
await handleSerializedScheduleTimer(internals, entry, callbacks);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function handleSerializedScheduleTimer(internals, entry, callbacks) {
|
|
15
24
|
if (internals.deposed)
|
|
16
25
|
return;
|
|
17
26
|
const state = await loadScheduleState(internals, entry.workflowId);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { BatchOperation } from '../../storage/interface.ts';
|
|
2
|
-
import type { PaginatedResult, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleState, ScheduleSummary, WorkflowState } from '../types.ts';
|
|
2
|
+
import type { PaginatedResult, ScheduleFilter, ScheduleOptions, ScheduleSpec, ScheduleState, ScheduleSummary, ScheduleUpdateOptions, WorkflowState } from '../types.ts';
|
|
3
3
|
import type { EngineInternals } from './internals.ts';
|
|
4
4
|
import { ScheduleHandle } from './schedule-handle.ts';
|
|
5
|
+
import type { ScheduledRunStartOptions } from './schedule-run.ts';
|
|
5
6
|
export { startScheduledRun } from './schedule-run.ts';
|
|
6
7
|
export { handleScheduleTimer } from './schedule-timer.ts';
|
|
7
8
|
export type RefreshedScheduleState = {
|
|
@@ -16,7 +17,7 @@ export type ScheduleCallbacks = {
|
|
|
16
17
|
cancelWorkflow: (workflowId: string) => Promise<void>;
|
|
17
18
|
getWorkflowResult: (workflowId: string) => Promise<unknown>;
|
|
18
19
|
refreshScheduledWorkflowState: (state: ScheduleState) => Promise<RefreshedScheduleState>;
|
|
19
|
-
startScheduledRun: (state: ScheduleState,
|
|
20
|
+
startScheduledRun: (state: ScheduleState, options?: ScheduledRunStartOptions) => Promise<string>;
|
|
20
21
|
applyScheduleOccurrence: (state: ScheduleState, occurrence?: number) => Promise<ScheduleState>;
|
|
21
22
|
settleBackfillScheduleState: (state: ScheduleState) => Promise<ScheduleState>;
|
|
22
23
|
flushQueuedInlineWorkflowStartsDirectly: () => Promise<void>;
|
|
@@ -30,8 +31,8 @@ export declare function toScheduleSummary(state: ScheduleState): ScheduleSummary
|
|
|
30
31
|
export declare function pauseSchedule(internals: EngineInternals, scheduleId: string): Promise<void>;
|
|
31
32
|
export declare function resumeSchedule(internals: EngineInternals, scheduleId: string): Promise<void>;
|
|
32
33
|
export declare function cancelSchedule(internals: EngineInternals, scheduleId: string): Promise<void>;
|
|
33
|
-
export declare function updateSchedule(internals: EngineInternals, scheduleId: string, newSpec: string | ScheduleSpec): Promise<void>;
|
|
34
|
+
export declare function updateSchedule(internals: EngineInternals, scheduleId: string, newSpec: string | ScheduleSpec, options?: ScheduleUpdateOptions): Promise<void>;
|
|
34
35
|
export declare function refreshScheduledWorkflowState(internals: EngineInternals, state: ScheduleState, callbacks: Pick<ScheduleCallbacks, 'loadWorkflowState'>): Promise<RefreshedScheduleState>;
|
|
35
|
-
export declare function applyScheduleOccurrence(
|
|
36
|
+
export declare function applyScheduleOccurrence(internals: EngineInternals, state: ScheduleState, callbacks: ScheduleCallbacks, occurrence?: number): Promise<ScheduleState>;
|
|
36
37
|
export declare function settleBackfillScheduleState(internals: EngineInternals, state: ScheduleState, callbacks: Pick<ScheduleCallbacks, 'flushQueuedInlineWorkflowStartsDirectly' | 'refreshScheduledWorkflowState'>): Promise<ScheduleState>;
|
|
37
38
|
export declare function handleScheduledWorkflowTerminal(internals: EngineInternals, workflowId: string, callbacks: ScheduleCallbacks): Promise<void>;
|
|
@@ -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>;
|