@lostgradient/weft 0.9.0 → 0.11.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 +12 -4
- package/dist/alerting/alert-manager.d.ts +3 -1
- package/dist/alerting/alert-manager.js +5 -2
- package/dist/cli-main.js +63 -63
- package/dist/core/checkpoint/serialization.js +2 -2
- package/dist/core/context/index.d.ts +2 -1
- package/dist/core/context/index.js +8 -0
- package/dist/core/context/operation-request.d.ts +2 -0
- package/dist/core/context/parallel-cache-entry.d.ts +1 -1
- package/dist/core/context/parallel-cache-entry.js +12 -8
- package/dist/core/context/parallel-operations.d.ts +1 -1
- package/dist/core/context/parallel-operations.js +50 -8
- package/dist/core/engine/async-activity-completion.d.ts +24 -54
- package/dist/core/engine/async-activity-completion.js +25 -90
- package/dist/core/engine/async-activity-records.d.ts +106 -0
- package/dist/core/engine/async-activity-records.js +124 -0
- package/dist/core/engine/bulk-operations-purge.js +2 -1
- package/dist/core/engine/callback-creators-core.js +1 -0
- package/dist/core/engine/construction.d.ts +1 -3
- package/dist/core/engine/construction.js +2 -5
- package/dist/core/engine/deferred-consume-envelope.d.ts +10 -1
- package/dist/core/engine/deferred-consume-envelope.js +9 -1
- package/dist/core/engine/disposal.js +4 -0
- package/dist/core/engine/engine-internal-types.d.ts +1 -0
- package/dist/core/engine/engine-leak-warnings.d.ts +2 -0
- package/dist/core/engine/engine-leak-warnings.js +7 -1
- package/dist/core/engine/engine-runtime-helpers.d.ts +8 -0
- package/dist/core/engine/engine-runtime-helpers.js +9 -0
- package/dist/core/engine/errors.js +1 -1
- package/dist/core/engine/index.d.ts +18 -5
- package/dist/core/engine/index.js +41 -10
- package/dist/core/engine/inline-parking.js +14 -1
- package/dist/core/engine/internals.d.ts +23 -2
- package/dist/core/engine/lifecycle/recovered-services.d.ts +4 -2
- package/dist/core/engine/lifecycle/recovered-services.js +37 -7
- package/dist/core/engine/lifecycle/resume.d.ts +2 -2
- package/dist/core/engine/lifecycle/resume.js +39 -7
- package/dist/core/engine/lifecycle/shared.d.ts +34 -0
- package/dist/core/engine/lifecycle/transition.d.ts +1 -1
- package/dist/core/engine/lifecycle/transition.js +3 -3
- package/dist/core/engine/lifecycle.d.ts +1 -1
- package/dist/core/engine/memo-durable-activity.js +7 -9
- package/dist/core/engine/operations-activity.js +1 -1
- package/dist/core/engine/operations-coordination.d.ts +15 -1
- package/dist/core/engine/operations-coordination.js +43 -10
- package/dist/core/engine/operations-time.js +8 -28
- package/dist/core/engine/ownership-options.d.ts +1 -0
- package/dist/core/engine/ownership-options.js +14 -0
- package/dist/core/engine/retention.js +4 -0
- package/dist/core/engine/schedule-run-metadata.d.ts +3 -0
- package/dist/core/engine/schedule-run-metadata.js +29 -0
- package/dist/core/engine/schedule-run.js +19 -4
- package/dist/core/engine/schedules.js +4 -3
- package/dist/core/engine/sleep-timer-acknowledgements.d.ts +15 -0
- package/dist/core/engine/sleep-timer-acknowledgements.js +144 -0
- package/dist/core/engine/sub-operation.js +6 -13
- package/dist/core/engine/termination/cleanup.js +6 -1
- package/dist/core/types/options.d.ts +12 -0
- package/dist/core/types/services-resolution.d.ts +8 -3
- package/dist/core/types/workflow-builder.d.ts +2 -2
- package/dist/core/types/workflow-context.d.ts +27 -0
- package/dist/index.d.ts +2 -2
- package/dist/mcp/cli.js +17 -17
- package/dist/server/authorization.d.ts +3 -3
- package/dist/server/fault-to-json-rpc.d.ts +2 -1
- package/dist/server/handler.js +22 -22
- package/dist/server/index.js +17 -17
- package/dist/server/json-rpc-dispatch.d.ts +2 -2
- package/dist/server/json-rpc-parse.d.ts +1 -1
- package/dist/server/json-rpc-protocol.d.ts +2 -2
- package/dist/server/json-rpc-websocket.d.ts +1 -1
- package/dist/server/operation-fault.d.ts +3 -1
- package/dist/server/operations/async-activity.js +2 -2
- package/dist/server/principal.d.ts +2 -2
- package/dist/server/stdio-session.d.ts +4 -4
- package/dist/service-worker/index.js +13 -13
- package/dist/service-worker/scheduler.js +1 -0
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/http.js +2 -2
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/indexeddb.js +1 -1
- package/dist/storage/interface.d.ts +1 -0
- package/dist/storage/interface.js +1 -1
- package/dist/storage/lazy-postgres-pool.d.ts +38 -0
- package/dist/storage/lazy-postgres-pool.js +38 -0
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon-batch.d.ts +8 -8
- package/dist/storage/neon.d.ts +26 -79
- package/dist/storage/neon.js +3 -3
- package/dist/storage/postgres-key-value-queries.d.ts +1 -1
- package/dist/storage/postgres-key-value-queries.js +1 -1
- package/dist/storage/postgres-key-value-storage.d.ts +110 -0
- package/dist/storage/postgres-key-value-storage.js +204 -0
- package/dist/storage/postgres.d.ts +59 -0
- package/dist/storage/postgres.js +15 -0
- 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 +1 -1
- package/dist/storage/typed-storage.js +1 -1
- package/dist/storage/web-extension.js +1 -1
- package/dist/testing/index.js +17 -17
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +17 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WorkflowState } from '../../types.ts';
|
|
1
|
+
import type { WorkflowServicesResolverInfo, WorkflowState } from '../../types.ts';
|
|
2
2
|
import type { EngineInternals } from '../internals.ts';
|
|
3
3
|
/**
|
|
4
4
|
* Re-provide a recovered inline workflow's non-serialized `services` before its
|
|
@@ -36,7 +36,9 @@ import type { EngineInternals } from '../internals.ts';
|
|
|
36
36
|
* @param onCommitError - Records a fail-warn when `failRun` itself throws, so the
|
|
37
37
|
* swallowed terminal-commit fault is still observable.
|
|
38
38
|
*/
|
|
39
|
-
export declare function reprovideRecoveredServices(internals: EngineInternals, state: WorkflowState, failRun: (workflowId: string, error: Error) => Promise<void>, onCommitError: (source: string, error: unknown, workflowId: string) => void, dispatchDiagnostic?: (event: Event) => void): Promise<boolean>;
|
|
39
|
+
export declare function reprovideRecoveredServices(internals: EngineInternals, state: WorkflowState, failRun: (workflowId: string, error: Error) => Promise<void>, onCommitError: (source: string, error: unknown, workflowId: string) => void, dispatchDiagnostic?: (event: Event) => void, resolverInfo?: WorkflowServicesResolverInfo): Promise<boolean>;
|
|
40
|
+
/** Build the durable recovery context shared by the services resolver and recovery hook. */
|
|
41
|
+
export declare function workflowServicesResolverInfoFromState(internals: EngineInternals, state: WorkflowState): Promise<WorkflowServicesResolverInfo>;
|
|
40
42
|
/**
|
|
41
43
|
* The canonical terminal error for any run whose services could not be provided —
|
|
42
44
|
* both recovery re-provision paths and a scheduled-occurrence launch. Shared so
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { KEYS } from "../../../storage/interface.js";
|
|
2
2
|
import { DevelopmentWarningEvent } from "../../events.js";
|
|
3
|
+
import { decodeScheduleRunMetadata } from "../schedule-run-metadata.js";
|
|
4
|
+
import { loadScheduleState } from "../storage-io.js";
|
|
3
5
|
const RESOLVE_WORKFLOW_SERVICES_OPTION_PATH = "EngineOptions.resolveWorkflowServices";
|
|
4
|
-
export async function reprovideRecoveredServices(internals, state, failRun, onCommitError, dispatchDiagnostic = () => {}) {
|
|
6
|
+
export async function reprovideRecoveredServices(internals, state, failRun, onCommitError, dispatchDiagnostic = () => {}, resolverInfo) {
|
|
5
7
|
const resolver = internals.options.resolveWorkflowServices;
|
|
6
8
|
if (internals.inlineStrategy === null)
|
|
7
9
|
return !1;
|
|
@@ -20,13 +22,9 @@ export async function reprovideRecoveredServices(internals, state, failRun, onCo
|
|
|
20
22
|
return !0;
|
|
21
23
|
}
|
|
22
24
|
let reason;
|
|
25
|
+
const info = resolverInfo ?? await workflowServicesResolverInfoFromState(internals, state);
|
|
23
26
|
try {
|
|
24
|
-
const resolution = await resolver(
|
|
25
|
-
workflowId: state.id,
|
|
26
|
-
workflowType: state.type,
|
|
27
|
-
input: state.input,
|
|
28
|
-
launchOptions: launchOptionsFromWorkflowState(state)
|
|
29
|
-
});
|
|
27
|
+
const resolution = await resolver(info);
|
|
30
28
|
if (resolution.status === "available") {
|
|
31
29
|
internals.workflowServices.set(state.id, resolution.services);
|
|
32
30
|
return !1;
|
|
@@ -42,6 +40,16 @@ export async function reprovideRecoveredServices(internals, state, failRun, onCo
|
|
|
42
40
|
}
|
|
43
41
|
return !0;
|
|
44
42
|
}
|
|
43
|
+
export async function workflowServicesResolverInfoFromState(internals, state) {
|
|
44
|
+
const schedule = await scheduleFromWorkflowState(internals, state);
|
|
45
|
+
return {
|
|
46
|
+
workflowId: state.id,
|
|
47
|
+
workflowType: state.type,
|
|
48
|
+
input: state.input,
|
|
49
|
+
launchOptions: launchOptionsFromWorkflowState(state),
|
|
50
|
+
...schedule !== null ? { schedule } : {}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
45
53
|
export function unavailableServicesError(workflowId, reason) {
|
|
46
54
|
return Error(`Workflow "${workflowId}" services unavailable: ${reason}`);
|
|
47
55
|
}
|
|
@@ -54,3 +62,25 @@ function launchOptionsFromWorkflowState(state) {
|
|
|
54
62
|
...state.tags !== void 0 && state.tags.length > 0 ? { tags: [...state.tags] } : {}
|
|
55
63
|
};
|
|
56
64
|
}
|
|
65
|
+
async function scheduleFromWorkflowState(internals, state) {
|
|
66
|
+
const bytes = await internals.storage.get(KEYS.scheduleRun(state.id));
|
|
67
|
+
if (bytes === null)
|
|
68
|
+
return null;
|
|
69
|
+
const metadata = decodeScheduleRunMetadata(bytes);
|
|
70
|
+
if (metadata === null)
|
|
71
|
+
return null;
|
|
72
|
+
const scheduleState = await loadScheduleState(internals, metadata.id);
|
|
73
|
+
if (scheduleState === null)
|
|
74
|
+
return null;
|
|
75
|
+
if (scheduleState.workflowType !== state.type)
|
|
76
|
+
return null;
|
|
77
|
+
if (scheduleState.overlap === "allow")
|
|
78
|
+
return metadata.occurrence !== void 0 ? metadata : null;
|
|
79
|
+
if (scheduleState.currentWorkflowId === state.id)
|
|
80
|
+
return metadata;
|
|
81
|
+
if (metadata.occurrence !== void 0 && scheduleState.nextFireAt === metadata.occurrence)
|
|
82
|
+
return metadata;
|
|
83
|
+
if (metadata.occurrence === void 0 && scheduleState.overlap === "queue" && scheduleState.queuedRuns > 0)
|
|
84
|
+
return metadata;
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type WorkflowHandle } from '../handles.ts';
|
|
2
2
|
import type { EngineInternals } from '../internals.ts';
|
|
3
|
-
import { type LifecycleCallbacks } from './shared.ts';
|
|
4
|
-
export declare function resumeWorkflowFromStorage(internals: EngineInternals, workflowId: string, dispatchResumedEvent: boolean, callbacks: LifecycleCallbacks): Promise<WorkflowHandle>;
|
|
3
|
+
import { type LifecycleCallbacks, type RecoverAllOptions } from './shared.ts';
|
|
4
|
+
export declare function resumeWorkflowFromStorage(internals: EngineInternals, workflowId: string, dispatchResumedEvent: boolean, callbacks: LifecycleCallbacks, onRecoveredWorkflow?: RecoverAllOptions['onRecoveredWorkflow']): Promise<WorkflowHandle>;
|
|
@@ -15,15 +15,47 @@ import { getComposedWorkflowInterceptor } from "../strategy-helpers.js";
|
|
|
15
15
|
import { decodeWorkflowState } from "../validation.js";
|
|
16
16
|
import { buildWorkflowVisibilityIndexTransition } from "../workflow-indexes.js";
|
|
17
17
|
import { prepareResumeState } from "./persist.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
reprovideRecoveredServices,
|
|
20
|
+
workflowServicesResolverInfoFromState
|
|
21
|
+
} from "./recovered-services.js";
|
|
19
22
|
import {
|
|
20
23
|
enforceHistoryPolicyBeforeReplay,
|
|
21
24
|
loadTerminalCleanupTrackedState,
|
|
22
25
|
loadWorkflowStartHeaders,
|
|
23
26
|
setWorkflowStartHeaders
|
|
24
27
|
} from "./shared.js";
|
|
25
|
-
async function prepareRecoveredServicesOrFail(internals, state, callbacks) {
|
|
26
|
-
return reprovideRecoveredServices(internals, state, callbacks.failWorkflowForUnavailableServices, callbacks.handleCleanupError, callbacks.dispatchEvent);
|
|
28
|
+
async function prepareRecoveredServicesOrFail(internals, state, callbacks, resolverInfo) {
|
|
29
|
+
return reprovideRecoveredServices(internals, state, callbacks.failWorkflowForUnavailableServices, callbacks.handleCleanupError, callbacks.dispatchEvent, resolverInfo);
|
|
30
|
+
}
|
|
31
|
+
async function runRecoveredWorkflowHookOrFail(internals, state, handle, resolverInfo, onRecoveredWorkflow, callbacks) {
|
|
32
|
+
const info = {
|
|
33
|
+
...resolverInfo,
|
|
34
|
+
handle,
|
|
35
|
+
launchOptions: resolverInfo.launchOptions ?? { id: state.id },
|
|
36
|
+
services: internals.workflowServices.get(state.id)
|
|
37
|
+
};
|
|
38
|
+
try {
|
|
39
|
+
await onRecoveredWorkflow(info);
|
|
40
|
+
return !1;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const reason = error instanceof Error ? error.message : String(error), hookError = Error(`Recovery hook failed for workflow "${state.id}": ${reason}`);
|
|
43
|
+
try {
|
|
44
|
+
await callbacks.failWorkflowForRecoveryHook(state.id, hookError);
|
|
45
|
+
} catch (commitError) {
|
|
46
|
+
callbacks.handleCleanupError("onRecoveredWorkflow", commitError, state.id);
|
|
47
|
+
}
|
|
48
|
+
return !0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function prepareRecoveredWorkflowOrFail(internals, state, callbacks, onRecoveredWorkflow) {
|
|
52
|
+
const resolverInfo = onRecoveredWorkflow === void 0 ? void 0 : await workflowServicesResolverInfoFromState(internals, state), handle = callbacks.getHandle(state.id);
|
|
53
|
+
if (await prepareRecoveredServicesOrFail(internals, state, callbacks, resolverInfo))
|
|
54
|
+
return { handle, shouldStop: !0 };
|
|
55
|
+
if (onRecoveredWorkflow === void 0 || resolverInfo === void 0)
|
|
56
|
+
return { handle, shouldStop: !1 };
|
|
57
|
+
const shouldStop = await runRecoveredWorkflowHookOrFail(internals, state, handle, resolverInfo, onRecoveredWorkflow, callbacks);
|
|
58
|
+
return { handle, shouldStop };
|
|
27
59
|
}
|
|
28
60
|
function assertResumeNotTerminating(internals, workflowId) {
|
|
29
61
|
if (internals.terminalizingWorkflows.has(workflowId))
|
|
@@ -138,7 +170,7 @@ async function reactivateSuspendedWorkflowState(internals, state) {
|
|
|
138
170
|
}) : []
|
|
139
171
|
], [], () => Error(`Resume of workflow "${state.id}" lost its CAS race.`));
|
|
140
172
|
}
|
|
141
|
-
export async function resumeWorkflowFromStorage(internals, workflowId, dispatchResumedEvent, callbacks) {
|
|
173
|
+
export async function resumeWorkflowFromStorage(internals, workflowId, dispatchResumedEvent, callbacks, onRecoveredWorkflow) {
|
|
142
174
|
const stateBytes = await internals.storage.get(KEYS.workflow(workflowId));
|
|
143
175
|
if (!stateBytes)
|
|
144
176
|
throw Error(`Workflow "${workflowId}" not found in storage`);
|
|
@@ -156,9 +188,9 @@ export async function resumeWorkflowFromStorage(internals, workflowId, dispatchR
|
|
|
156
188
|
return callbacks.getHandle(workflowId);
|
|
157
189
|
const workflowStartHeaders = await loadWorkflowStartHeaders(internals, workflowId, callbacks);
|
|
158
190
|
await loadTerminalCleanupTrackedState(internals, workflowId, callbacks);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
191
|
+
const { handle, shouldStop } = await prepareRecoveredWorkflowOrFail(internals, state, callbacks, onRecoveredWorkflow);
|
|
192
|
+
if (shouldStop)
|
|
193
|
+
return handle;
|
|
162
194
|
await callbacks.runSerializedWorkflowStateWrite(workflowId, () => performSerializedResume(internals, {
|
|
163
195
|
workflowId,
|
|
164
196
|
resumeCheckpoint,
|
|
@@ -1,10 +1,35 @@
|
|
|
1
1
|
import type { ComposedWorkflowInterceptor } from '../../interceptor.ts';
|
|
2
|
+
import type { WorkflowServicesResolverLaunchOptions, WorkflowServicesResolverScheduleInfo } from '../../types.ts';
|
|
2
3
|
import type { QueuedInlineWorkflowExecutionStart } from '../engine-internal-types.ts';
|
|
3
4
|
import { type WorkflowHandle } from '../handles.ts';
|
|
4
5
|
import type { EngineInternals } from '../internals.ts';
|
|
5
6
|
export type RegistrationEntry = EngineInternals['registrations'] extends Map<string, infer Entry> ? Entry : never;
|
|
6
7
|
export declare const FORK_LINEAGE_ATTRIBUTE = "weft:forkedFrom";
|
|
7
8
|
export declare const EMPTY_STORAGE_VALUE: Uint8Array<ArrayBuffer>;
|
|
9
|
+
/**
|
|
10
|
+
* Durable run context passed to {@link RecoverAllOptions.onRecoveredWorkflow}
|
|
11
|
+
* before recovered user code advances.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import type { RecoveredWorkflowInfo } from '@lostgradient/weft';
|
|
16
|
+
*
|
|
17
|
+
* function registerSurface(info: RecoveredWorkflowInfo): void {
|
|
18
|
+
* console.log(`Recovered ${info.workflowType}/${info.workflowId}`);
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export interface RecoveredWorkflowInfo {
|
|
23
|
+
/** Handle for the recovered run. */
|
|
24
|
+
handle: WorkflowHandle;
|
|
25
|
+
workflowId: string;
|
|
26
|
+
workflowType: string;
|
|
27
|
+
input: unknown;
|
|
28
|
+
launchOptions: WorkflowServicesResolverLaunchOptions;
|
|
29
|
+
schedule?: WorkflowServicesResolverScheduleInfo;
|
|
30
|
+
/** Re-provided host services, or `undefined` when the run did not use services. */
|
|
31
|
+
services: unknown;
|
|
32
|
+
}
|
|
8
33
|
/**
|
|
9
34
|
* Options for {@link Engine.recoverAll}. The acknowledgement flag is an
|
|
10
35
|
* explicit escape hatch for deployments that intentionally skip stored
|
|
@@ -25,6 +50,13 @@ export type RecoverAllOptions = {
|
|
|
25
50
|
* rolling deploys or explicit operator storage repair.
|
|
26
51
|
*/
|
|
27
52
|
acknowledgeUnknownWorkflowTypes?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Register consumer-owned live state for each recovered run after services
|
|
55
|
+
* are re-provided but before the workflow generator advances. The callback is
|
|
56
|
+
* awaited. If it throws, only that run fails with a `system` failure and
|
|
57
|
+
* recovery continues with its siblings.
|
|
58
|
+
*/
|
|
59
|
+
onRecoveredWorkflow?: (info: RecoveredWorkflowInfo) => void | Promise<void>;
|
|
28
60
|
};
|
|
29
61
|
export type LifecycleCallbacks = {
|
|
30
62
|
dispatchEvent: (event: Event) => void;
|
|
@@ -56,6 +88,8 @@ export type LifecycleCallbacks = {
|
|
|
56
88
|
* `error` is the canonical {@link unavailableServicesError}.
|
|
57
89
|
*/
|
|
58
90
|
failWorkflowForUnavailableServices: (workflowId: string, error: Error) => Promise<void>;
|
|
91
|
+
/** Fail one recovered workflow whose pre-resume consumer hook threw. */
|
|
92
|
+
failWorkflowForRecoveryHook: (workflowId: string, error: Error) => Promise<void>;
|
|
59
93
|
/**
|
|
60
94
|
* Force a recovered workflow to a terminal `failed` state because its
|
|
61
95
|
* persisted checkpoint cannot be decoded on this runtime. The failure is
|
|
@@ -3,6 +3,6 @@ import { type WorkflowHandle } from '../handles.ts';
|
|
|
3
3
|
import type { EngineInternals } from '../internals.ts';
|
|
4
4
|
import { type LifecycleCallbacks, type RecoverAllOptions, type RegistrationEntry } from './shared.ts';
|
|
5
5
|
export declare function recoverAll(internals: EngineInternals, callbacks: LifecycleCallbacks, options?: RecoverAllOptions): Promise<WorkflowHandle[]>;
|
|
6
|
-
export declare function resume(internals: EngineInternals, workflowId: string, callbacks: LifecycleCallbacks): Promise<WorkflowHandle>;
|
|
6
|
+
export declare function resume(internals: EngineInternals, workflowId: string, callbacks: LifecycleCallbacks, onRecoveredWorkflow?: RecoverAllOptions['onRecoveredWorkflow']): Promise<WorkflowHandle>;
|
|
7
7
|
export declare function fork(internals: EngineInternals, sourceWorkflowId: string, options: ForkOptions | undefined, callbacks: LifecycleCallbacks): Promise<WorkflowHandle>;
|
|
8
8
|
export declare function launchWorkflowFromCheckpoint(internals: EngineInternals, workflowId: string, state: WorkflowState, checkpoint: Checkpoint, registration: RegistrationEntry, callbacks: LifecycleCallbacks): WorkflowHandle;
|
|
@@ -77,7 +77,7 @@ export async function recoverAll(internals, callbacks, options) {
|
|
|
77
77
|
continue;
|
|
78
78
|
}
|
|
79
79
|
try {
|
|
80
|
-
handles.push(await resume(internals, entry.workflowId, callbacks));
|
|
80
|
+
handles.push(await resume(internals, entry.workflowId, callbacks, options?.onRecoveredWorkflow));
|
|
81
81
|
} catch (error) {
|
|
82
82
|
if (error instanceof RegExpExtensionDecodeError) {
|
|
83
83
|
await callbacks.failWorkflowForCheckpointDecodeError(entry.workflowId, error);
|
|
@@ -88,7 +88,7 @@ export async function recoverAll(internals, callbacks, options) {
|
|
|
88
88
|
}
|
|
89
89
|
return handles;
|
|
90
90
|
}
|
|
91
|
-
export async function resume(internals, workflowId, callbacks) {
|
|
91
|
+
export async function resume(internals, workflowId, callbacks, onRecoveredWorkflow) {
|
|
92
92
|
const workflowState = await loadWorkflowState(internals, workflowId);
|
|
93
93
|
if (workflowState !== null) {
|
|
94
94
|
if (callbacks.isInlineWorkflowLocallyOwned(workflowId, workflowState.status) || callbacks.hasLocalCheckpointOwnership(workflowId, workflowState.status)) {
|
|
@@ -96,7 +96,7 @@ export async function resume(internals, workflowId, callbacks) {
|
|
|
96
96
|
return callbacks.getHandle(workflowId);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
return resumeWorkflowFromStorage(internals, workflowId, !0, callbacks);
|
|
99
|
+
return resumeWorkflowFromStorage(internals, workflowId, !0, callbacks, onRecoveredWorkflow);
|
|
100
100
|
}
|
|
101
101
|
export async function fork(internals, sourceWorkflowId, options, callbacks) {
|
|
102
102
|
const sourceState = await loadWorkflowState(internals, sourceWorkflowId);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { EMPTY_STORAGE_VALUE, createWorkflowHandle, loadTerminalCleanupTrackedState, loadWorkflowStartHeaders, normalizeStartWorkflowTags, processPendingUpdatesAfterReplay, setWorkflowStartHeaders, type LifecycleCallbacks, type RecoverAllOptions, } from './lifecycle/shared.ts';
|
|
1
|
+
export { EMPTY_STORAGE_VALUE, createWorkflowHandle, loadTerminalCleanupTrackedState, loadWorkflowStartHeaders, normalizeStartWorkflowTags, processPendingUpdatesAfterReplay, setWorkflowStartHeaders, type LifecycleCallbacks, type RecoverAllOptions, type RecoveredWorkflowInfo, } from './lifecycle/shared.ts';
|
|
2
2
|
export { createWorkflowVersionTuple, derivePreparedExecutionState, prepareResumeState, throwVersionMismatch, workflowStateWithVersionTuple, workflowVersionTupleFromState, } from './lifecycle/persist.ts';
|
|
3
3
|
export { createInitialCheckpoint, createInitialWorkflowState, parseStartOptionDuration, resolveScheduledStartAt, start, startWorkflow, } from './lifecycle/start.ts';
|
|
4
4
|
export { startOrSignal, startWithIdempotency, type StartOrSignalCallbacks, type StartOrSignalResult, } from './lifecycle/start-or-signal.ts';
|
|
@@ -198,15 +198,13 @@ class MemoDurableActivityScope {
|
|
|
198
198
|
onAbort();
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
|
-
operation.then((value) => {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
});
|
|
209
|
-
}).catch((error) => {
|
|
201
|
+
operation.then((value) => settle(() => {
|
|
202
|
+
if (this.#closed || signal.aborted) {
|
|
203
|
+
reject(this.#abortError());
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
resolve(value);
|
|
207
|
+
})).catch((error) => {
|
|
210
208
|
settle(() => reject(error));
|
|
211
209
|
});
|
|
212
210
|
});
|
|
@@ -18,10 +18,10 @@ import {
|
|
|
18
18
|
import { getActivityFunctionWithMetadata, resolveActivityFunction } from "./activity-resolution.js";
|
|
19
19
|
import {
|
|
20
20
|
AsyncActivityDeferral,
|
|
21
|
-
deriveAsyncActivityToken,
|
|
22
21
|
driveWorkflowInterceptorGenerator,
|
|
23
22
|
parkDeferredAsyncActivity
|
|
24
23
|
} from "./async-activity-completion.js";
|
|
24
|
+
import { deriveAsyncActivityToken } from "./async-activity-records.js";
|
|
25
25
|
import { callActivityFunction } from "./state-utilities.js";
|
|
26
26
|
export function buildActivityVerification(_internals, activityName, verify, context, result) {
|
|
27
27
|
return (async () => {
|
|
@@ -33,7 +33,21 @@ export type CoordinationOperationCallbacks = {
|
|
|
33
33
|
};
|
|
34
34
|
export declare function processWaitSignalOperation(internals: EngineInternals, workflowId: string, operation: WaitSignalOperation, callbacks: Pick<CoordinationOperationCallbacks, 'completeOperation'>): Promise<void>;
|
|
35
35
|
export declare function processParallelOperation(internals: EngineInternals, workflowId: string, operation: ParallelOperation, callbacks: Pick<CoordinationOperationCallbacks, 'executeSubOperation' | 'runOperationWithResult'>): Promise<void>;
|
|
36
|
-
export declare function processRaceOperation(
|
|
36
|
+
export declare function processRaceOperation(internals: EngineInternals, workflowId: string, operation: RaceOperation, callbacks: Pick<CoordinationOperationCallbacks, 'executeSubOperation' | 'runOperationWithResult'>): Promise<void>;
|
|
37
|
+
export declare function assertValidRaceBranchNames(operation: RaceOperation): void;
|
|
38
|
+
export declare function wrapRaceWinner(operation: RaceOperation, winnerIndex: number, value: unknown): unknown;
|
|
39
|
+
/**
|
|
40
|
+
* Execute race branches while giving an already-buffered signal priority over a
|
|
41
|
+
* literal zero-duration sleep. The preflight is non-destructive: the selected
|
|
42
|
+
* wait-signal branch still returns its deferred-consume envelope, so the owning
|
|
43
|
+
* coordinator remains the only place that consumes the durable record.
|
|
44
|
+
*
|
|
45
|
+
* Positive-duration sleeps deliberately bypass this preflight, including when
|
|
46
|
+
* their deadline becomes past-due before dispatch. That keeps ordinary timeout
|
|
47
|
+
* races on the existing Promise.race path and limits the stronger ordering
|
|
48
|
+
* guarantee to the explicit non-blocking-drain idiom.
|
|
49
|
+
*/
|
|
50
|
+
export declare function executeRaceSubOperations(internals: EngineInternals, workflowId: string, operations: readonly ContextOperationRequest[], execute: (operation: ContextOperationRequest, signal: AbortSignal) => Promise<unknown>, parentSignal?: AbortSignal, wrapWinner?: (winnerIndex: number, value: unknown) => unknown): Promise<unknown>;
|
|
37
51
|
export declare function processRunAllOperation(internals: EngineInternals, workflowId: string, operation: RunAllOperation, callbacks: Pick<CoordinationOperationCallbacks, 'runOperationWithResult'>): Promise<void>;
|
|
38
52
|
export declare function isConfiguredInlineActivity(fn: Function): fn is RunAllOperation['branches'][string][0] & ActivityFunctionWithMetadata;
|
|
39
53
|
/**
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
executeRunAllBranches,
|
|
6
6
|
executeRunAllBranchesSettled
|
|
7
7
|
} from "../engine-helpers.js";
|
|
8
|
-
import { finalizeAndUnwrap } from "./deferred-consume-envelope.js";
|
|
8
|
+
import { createKeyedRaceResultEnvelope, finalizeAndUnwrap } from "./deferred-consume-envelope.js";
|
|
9
9
|
import {
|
|
10
10
|
executeActivityOperationResult as executeActivityOperationResultFromInternals
|
|
11
11
|
} from "./operations-activity.js";
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "./parallel-dispatch.js";
|
|
17
17
|
import {
|
|
18
18
|
consumeSignalWithAtomicWorkflowCommit,
|
|
19
|
+
peekSignal,
|
|
19
20
|
trackWaiterKey,
|
|
20
21
|
untrackWaiterKey
|
|
21
22
|
} from "./signals.js";
|
|
@@ -106,20 +107,52 @@ function assertPartialFailurePersistenceSupported(partialEntryWritten, slots, op
|
|
|
106
107
|
return;
|
|
107
108
|
throw Error(`${operationName} partial-failure preservation is not supported in ${executionMode}: the engine cannot persist fulfilled branch slots after a sibling branch fails. Run the workflow inline or make branch side effects idempotent.`);
|
|
108
109
|
}
|
|
109
|
-
export async function processRaceOperation(
|
|
110
|
+
export async function processRaceOperation(internals, workflowId, operation, callbacks) {
|
|
110
111
|
return callbacks.runOperationWithResult(workflowId, operation, async () => {
|
|
111
112
|
assertSupportedSignalBranches(operation.operations);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
let winner;
|
|
115
|
-
try {
|
|
116
|
-
winner = await Promise.race(subOperations);
|
|
117
|
-
} finally {
|
|
118
|
-
controller.abort();
|
|
119
|
-
}
|
|
113
|
+
assertValidRaceBranchNames(operation);
|
|
114
|
+
const winner = await executeRaceSubOperations(internals, workflowId, operation.operations, (subOperation, signal) => callbacks.executeSubOperation(workflowId, subOperation, signal), void 0, (winnerIndex, value) => wrapRaceWinner(operation, winnerIndex, value));
|
|
120
115
|
return finalizeAndUnwrap(winner);
|
|
121
116
|
});
|
|
122
117
|
}
|
|
118
|
+
export function assertValidRaceBranchNames(operation) {
|
|
119
|
+
if (operation.branchNames !== void 0 && operation.branchNames.length !== operation.operations.length)
|
|
120
|
+
throw Error("ctx.raceKeyed branch names must match its operation count");
|
|
121
|
+
}
|
|
122
|
+
export function wrapRaceWinner(operation, winnerIndex, value) {
|
|
123
|
+
const key = operation.branchNames?.[winnerIndex];
|
|
124
|
+
return key === void 0 ? value : createKeyedRaceResultEnvelope(key, value);
|
|
125
|
+
}
|
|
126
|
+
export async function executeRaceSubOperations(internals, workflowId, operations, execute, parentSignal, wrapWinner) {
|
|
127
|
+
parentSignal?.throwIfAborted();
|
|
128
|
+
const controller = new AbortController, abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
129
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: !0 });
|
|
130
|
+
try {
|
|
131
|
+
const bufferedSignal = await findBufferedSignalDrainBranch(internals, workflowId, operations);
|
|
132
|
+
parentSignal?.throwIfAborted();
|
|
133
|
+
if (bufferedSignal !== void 0) {
|
|
134
|
+
const value = await execute(bufferedSignal.operation, controller.signal);
|
|
135
|
+
return wrapWinner === void 0 ? value : wrapWinner(bufferedSignal.index, value);
|
|
136
|
+
}
|
|
137
|
+
const subOperations = operations.map((operation, index) => execute(operation, controller.signal).then((value) => ({ index, value })));
|
|
138
|
+
Promise.allSettled(subOperations);
|
|
139
|
+
const winner = await Promise.race(subOperations);
|
|
140
|
+
return wrapWinner === void 0 ? winner.value : wrapWinner(winner.index, winner.value);
|
|
141
|
+
} finally {
|
|
142
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
143
|
+
controller.abort();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function findBufferedSignalDrainBranch(internals, workflowId, operations) {
|
|
147
|
+
if (operations.length !== 2)
|
|
148
|
+
return;
|
|
149
|
+
const waitSignalOperation = operations.find((operation) => operation.type === "wait-signal"), zeroDurationSleep = operations.find((operation) => operation.type === "sleep" && operation.duration === 0);
|
|
150
|
+
if (waitSignalOperation === void 0 || zeroDurationSleep === void 0)
|
|
151
|
+
return;
|
|
152
|
+
if (!(await peekSignal(internals, workflowId, waitSignalOperation.signalName)).found)
|
|
153
|
+
return;
|
|
154
|
+
return { index: operations.indexOf(waitSignalOperation), operation: waitSignalOperation };
|
|
155
|
+
}
|
|
123
156
|
export async function processRunAllOperation(internals, workflowId, operation, callbacks) {
|
|
124
157
|
return callbacks.runOperationWithResult(workflowId, operation, async () => {
|
|
125
158
|
const branchNames = Object.keys(operation.branches), operationIds = branchNames.map((name) => `run-all:${operation.step}:${name}`), resumedSlots = extractResumedSlots(operation.resumedCacheEntry), resumedSlotsByName = mapResumedSlotsByName(resumedSlots, branchNames), branchesToRun = filterBranchesToRun(operation.branches, branchNames, resumedSlotsByName), { outcomes, hasFirstError, firstError } = await executeRunAllBranchesSettled(branchesToRun, (fn, input) => callActivityFunction(fn, input)), slots = mergeRunAllSlots(branchNames, operationIds, resumedSlotsByName, outcomes), entry = buildEntryFromSlots("run-all", slots, branchNames), partialEntryWritten = writePartialEntry(internals, workflowId, operation.step, entry);
|
|
@@ -4,6 +4,11 @@ import { encode } from "../codec.js";
|
|
|
4
4
|
import { buildTimerBatchOperations, normalizeStorageTimestamp } from "../scheduler.js";
|
|
5
5
|
import { commitFencedEngineWrite } from "./fenced-write.js";
|
|
6
6
|
import { reprovideRecoveredServices } from "./lifecycle/recovered-services.js";
|
|
7
|
+
import {
|
|
8
|
+
acknowledgeSupersededSleepTimers,
|
|
9
|
+
handleSleepTimerWithAcknowledgement,
|
|
10
|
+
resolveSleepTimer
|
|
11
|
+
} from "./sleep-timer-acknowledgements.js";
|
|
7
12
|
import { buildWorkflowVisibilityIndexTransition } from "./workflow-indexes.js";
|
|
8
13
|
export function createDelayedStartTimerEntry(_internals, workflowId, scheduledStartAt, options, callbacks) {
|
|
9
14
|
return {
|
|
@@ -26,6 +31,8 @@ function sleepTimerFiredEarly(internals, workflowId, operation) {
|
|
|
26
31
|
workflowMarkers.delete(operation.operationId);
|
|
27
32
|
if (workflowMarkers.size === 0)
|
|
28
33
|
internals.sleepTimersFiredWithoutResolver.delete(workflowId);
|
|
34
|
+
if (markedFireAt < operation.scheduledFireAt)
|
|
35
|
+
acknowledgeSupersededSleepTimers(internals, workflowId, operation.scheduledFireAt);
|
|
29
36
|
return markedFireAt >= operation.scheduledFireAt;
|
|
30
37
|
}
|
|
31
38
|
export async function processSleepOperation(internals, workflowId, operation, callbacks) {
|
|
@@ -166,7 +173,7 @@ export async function handleTimerFired(internals, entry, callbacks) {
|
|
|
166
173
|
return;
|
|
167
174
|
}
|
|
168
175
|
if (entry.kind === "sleep")
|
|
169
|
-
|
|
176
|
+
await handleSleepTimerWithAcknowledgement(internals, entry, callbacks.loadWorkflowState);
|
|
170
177
|
else if (entry.kind === "wait-condition")
|
|
171
178
|
resolveConditionTimer(internals, entry);
|
|
172
179
|
else if (entry.kind === "execution-deadline")
|
|
@@ -189,30 +196,3 @@ async function handleReviewTimer(internals, entry, callbacks) {
|
|
|
189
196
|
return;
|
|
190
197
|
await handler(entry);
|
|
191
198
|
}
|
|
192
|
-
function resolveSleepTimer(internals, entry) {
|
|
193
|
-
const operationId = entry.id.replace("sleep:", ""), resolverKey = `${entry.workflowId}:${operationId}`, resolver = internals.sleepResolvers.get(resolverKey);
|
|
194
|
-
if (!resolver) {
|
|
195
|
-
let workflowMarkers = internals.sleepTimersFiredWithoutResolver.get(entry.workflowId);
|
|
196
|
-
if (!workflowMarkers) {
|
|
197
|
-
workflowMarkers = new Map;
|
|
198
|
-
internals.sleepTimersFiredWithoutResolver.set(entry.workflowId, workflowMarkers);
|
|
199
|
-
}
|
|
200
|
-
const existing = workflowMarkers.get(operationId);
|
|
201
|
-
if (existing === void 0 || entry.fireAt > existing)
|
|
202
|
-
workflowMarkers.set(operationId, entry.fireAt);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (entry.fireAt < resolver.fireAt)
|
|
206
|
-
return;
|
|
207
|
-
internals.sleepResolvers.delete(resolverKey);
|
|
208
|
-
untrackSleepResolver(internals, entry.workflowId, operationId);
|
|
209
|
-
resolver.resolve();
|
|
210
|
-
}
|
|
211
|
-
function untrackSleepResolver(internals, workflowId, operationId) {
|
|
212
|
-
const workflowOperations = internals.sleepResolversByWorkflow.get(workflowId);
|
|
213
|
-
if (!workflowOperations)
|
|
214
|
-
return;
|
|
215
|
-
workflowOperations.delete(operationId);
|
|
216
|
-
if (workflowOperations.size === 0)
|
|
217
|
-
internals.sleepResolversByWorkflow.delete(workflowId);
|
|
218
|
-
}
|
|
@@ -13,6 +13,7 @@ export declare const DEFAULT_LEASE_TTL_MS = 30000;
|
|
|
13
13
|
export declare const DEFAULT_LEASE_RENEW_INTERVAL_MS = 5000;
|
|
14
14
|
/** Default boot-time lease acquisition wait window for `ownership: 'lease'`. */
|
|
15
15
|
export declare const DEFAULT_LEASE_WAIT_TIMEOUT_MS = 60000;
|
|
16
|
+
export declare function resolveBackgroundTaskMode(options: EngineConstructorOptions | undefined): ResolvedOptions['backgroundTaskMode'];
|
|
16
17
|
/**
|
|
17
18
|
* Resolve the ownership posture and lease tuning into their `ResolvedOptions`
|
|
18
19
|
* fields. Defaults to `'none'`. The lease durations are documented as "ignored
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { normalizeRetentionDuration } from "./validation.js";
|
|
2
2
|
export const DEFAULT_LEASE_TTL_MS = 30000, DEFAULT_LEASE_RENEW_INTERVAL_MS = 5000, DEFAULT_LEASE_WAIT_TIMEOUT_MS = 60000;
|
|
3
|
+
export function resolveBackgroundTaskMode(options) {
|
|
4
|
+
const mode = options?.backgroundTasks ?? "automatic";
|
|
5
|
+
if (mode !== "automatic" && mode !== "manual")
|
|
6
|
+
throw Error('options.backgroundTasks must be "automatic" or "manual" when provided');
|
|
7
|
+
if (mode === "manual")
|
|
8
|
+
assertManualBackgroundTaskCompatibility(options);
|
|
9
|
+
return mode;
|
|
10
|
+
}
|
|
11
|
+
function assertManualBackgroundTaskCompatibility(options) {
|
|
12
|
+
if (options?.detectSecondInstance === !0)
|
|
13
|
+
throw Error('detectSecondInstance cannot be enabled when backgroundTasks is "manual"');
|
|
14
|
+
if (options?.ownership === "lease")
|
|
15
|
+
throw Error('ownership cannot be "lease" when backgroundTasks is "manual"');
|
|
16
|
+
}
|
|
3
17
|
export function resolveOwnershipFields(options) {
|
|
4
18
|
const ownershipMode = assertKnownOwnershipMode(options?.ownership ?? "none");
|
|
5
19
|
if (ownershipMode !== "lease")
|
|
@@ -11,6 +11,10 @@ export function setNextRetentionSweepAt(internals) {
|
|
|
11
11
|
internals.nextRetentionSweepAt = internals.options.getNow() + internals.options.retentionSweepIntervalMs;
|
|
12
12
|
}
|
|
13
13
|
export function ensureRetentionSweepInterval(internals, callbacks) {
|
|
14
|
+
if (internals.options.backgroundTaskMode === "manual") {
|
|
15
|
+
internals.nextRetentionSweepAt = null;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
14
18
|
if (!callbacks.hasConfiguredRetention()) {
|
|
15
19
|
if (internals.retentionSweepInterval !== null) {
|
|
16
20
|
clearInterval(internals.retentionSweepInterval ?? void 0);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { WorkflowServicesResolverScheduleInfo } from '../types.ts';
|
|
2
|
+
export declare function encodeScheduleRunMetadata(scheduleId: string, occurrence: number | undefined): Uint8Array;
|
|
3
|
+
export declare function decodeScheduleRunMetadata(bytes: Uint8Array): WorkflowServicesResolverScheduleInfo | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { decode, encode } from "../codec.js";
|
|
2
|
+
import { isRecord } from "../debug-output.js";
|
|
3
|
+
import { isValidScheduleIdentifier } from "./validation/schedule.js";
|
|
4
|
+
export function encodeScheduleRunMetadata(scheduleId, occurrence) {
|
|
5
|
+
return encode({
|
|
6
|
+
id: scheduleId,
|
|
7
|
+
...occurrence !== void 0 ? { occurrence } : {}
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function decodeScheduleRunMetadata(bytes) {
|
|
11
|
+
let decoded;
|
|
12
|
+
try {
|
|
13
|
+
decoded = decode(bytes);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
if (typeof decoded === "string")
|
|
18
|
+
return isValidScheduleIdentifier(decoded) ? { id: decoded } : null;
|
|
19
|
+
if (!isRecord(decoded))
|
|
20
|
+
return null;
|
|
21
|
+
const { id, occurrence } = decoded;
|
|
22
|
+
if (!isValidScheduleIdentifier(id))
|
|
23
|
+
return null;
|
|
24
|
+
if (occurrence === void 0)
|
|
25
|
+
return { id };
|
|
26
|
+
if (typeof occurrence !== "number" || !Number.isSafeInteger(occurrence))
|
|
27
|
+
return null;
|
|
28
|
+
return { id, occurrence };
|
|
29
|
+
}
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { KEYS } from "../../storage/interface.js";
|
|
2
|
-
import { encode } from "../codec.js";
|
|
3
2
|
import { ScheduleFiredEvent } from "../events.js";
|
|
4
3
|
import { unavailableServicesError } from "./lifecycle/recovered-services.js";
|
|
5
4
|
import { EMPTY_STORAGE_VALUE } from "./lifecycle/shared.js";
|
|
5
|
+
import { encodeScheduleRunMetadata } from "./schedule-run-metadata.js";
|
|
6
6
|
export async function startScheduledRun(internals, state, callbacks, occurrence) {
|
|
7
|
-
const workflowId = crypto.randomUUID(), scheduleRunOperations =
|
|
7
|
+
const workflowId = crypto.randomUUID(), scheduleRunOperations = [
|
|
8
|
+
{
|
|
9
|
+
type: "put",
|
|
10
|
+
key: KEYS.scheduleRun(workflowId),
|
|
11
|
+
value: encodeScheduleRunMetadata(state.id, occurrence)
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
type: "put",
|
|
15
|
+
key: KEYS.terminalCleanupNeeded(workflowId),
|
|
16
|
+
value: EMPTY_STORAGE_VALUE
|
|
17
|
+
}
|
|
18
|
+
], resolution = await resolveScheduledRunServices(internals, workflowId, state, occurrence);
|
|
8
19
|
if (resolution !== null) {
|
|
9
|
-
scheduleRunOperations.push({
|
|
10
|
-
|
|
20
|
+
scheduleRunOperations.push({
|
|
21
|
+
type: "put",
|
|
22
|
+
key: KEYS.workflowHasServices(workflowId),
|
|
23
|
+
value: EMPTY_STORAGE_VALUE
|
|
24
|
+
});
|
|
11
25
|
if (resolution.status === "available")
|
|
12
26
|
internals.workflowServices.set(workflowId, resolution.services);
|
|
13
27
|
}
|
|
28
|
+
internals.workflowsNeedingTerminalCleanup.add(workflowId);
|
|
14
29
|
await callbacks.startWorkflow(state.workflowType, state.input, { id: workflowId }, scheduleRunOperations);
|
|
15
30
|
internals.engine.dispatchEvent(new ScheduleFiredEvent(state.id, workflowId, internals.options.getNow(), occurrence));
|
|
16
31
|
if (resolution !== null && resolution.status === "unavailable") {
|
|
@@ -4,6 +4,7 @@ import { WorkflowNotRegisteredError } from "./errors.js";
|
|
|
4
4
|
import { ScheduleHandle } from "./schedule-handle.js";
|
|
5
5
|
import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
|
|
6
6
|
import { getNextScheduleOccurrence } from "./schedule-occurrence.js";
|
|
7
|
+
import { decodeScheduleRunMetadata } from "./schedule-run-metadata.js";
|
|
7
8
|
import {
|
|
8
9
|
clearScheduleCurrentWorkflow,
|
|
9
10
|
createScheduleTimerId,
|
|
@@ -208,10 +209,10 @@ export async function handleScheduledWorkflowTerminal(internals, workflowId, cal
|
|
|
208
209
|
if (!scheduleRunBytes)
|
|
209
210
|
return;
|
|
210
211
|
await internals.storage.delete(KEYS.scheduleRun(workflowId));
|
|
211
|
-
const
|
|
212
|
-
if (!isValidScheduleIdentifier(
|
|
212
|
+
const metadata = decodeScheduleRunMetadata(scheduleRunBytes);
|
|
213
|
+
if (metadata === null || !isValidScheduleIdentifier(metadata.id))
|
|
213
214
|
return;
|
|
214
|
-
const state = await loadScheduleState(internals,
|
|
215
|
+
const scheduleId = metadata.id, state = await loadScheduleState(internals, scheduleId);
|
|
215
216
|
if (!state || state.currentWorkflowId !== workflowId)
|
|
216
217
|
return;
|
|
217
218
|
const now = internals.options.getNow(), clearedState = {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ContextOperationRequest } from '../context.ts';
|
|
2
|
+
import type { TimerEntry, WorkflowState } from '../types.ts';
|
|
3
|
+
import type { EngineInternals } from './internals.ts';
|
|
4
|
+
export type SleepTimerAcknowledgement = {
|
|
5
|
+
cancel: () => void;
|
|
6
|
+
promise: Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
export declare function handleSleepTimerWithAcknowledgement(internals: EngineInternals, entry: TimerEntry, loadWorkflowState: (workflowId: string) => Promise<WorkflowState | null>): Promise<void>;
|
|
9
|
+
export declare function createSleepTimerAcknowledgement(internals: EngineInternals, workflowId: string, operationId: string, fireAt: number): SleepTimerAcknowledgement;
|
|
10
|
+
export declare function recordDurableInlineOperation(internals: EngineInternals, workflowId: string, operation: ContextOperationRequest): void;
|
|
11
|
+
export declare function settleSleepTimerAcknowledgements(internals: EngineInternals, workflowId: string, disposition: 'suspended' | 'terminal'): void;
|
|
12
|
+
export declare function acknowledgeSupersededSleepTimers(internals: EngineInternals, workflowId: string, currentDeadline: number): void;
|
|
13
|
+
export declare function rejectSleepTimerAcknowledgements(internals: EngineInternals, workflowId: string, error: unknown): void;
|
|
14
|
+
export declare function rejectAllSleepTimerAcknowledgements(internals: EngineInternals, error: Error): void;
|
|
15
|
+
export declare function resolveSleepTimer(internals: EngineInternals, entry: TimerEntry): boolean;
|