@ai-digital-factory/state-flow-runtime 0.0.0-stage → 0.1.1
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/LICENSE +18 -0
- package/README.md +300 -2
- package/dist/index.d.ts +1024 -0
- package/dist/index.js +4405 -0
- package/package.json +45 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/** Shared runtime contract for manual actions, also used by companion transports. */
|
|
4
|
+
declare const resolveInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
5
|
+
action: z.ZodLiteral<"retry">;
|
|
6
|
+
operationId: z.ZodPipe<z.ZodString, z.ZodTransform<OperationId, string>>;
|
|
7
|
+
expectedAttempts: z.ZodNumber;
|
|
8
|
+
reason: z.ZodString;
|
|
9
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
10
|
+
action: z.ZodLiteral<"terminate">;
|
|
11
|
+
operationId: z.ZodPipe<z.ZodString, z.ZodTransform<OperationId, string>>;
|
|
12
|
+
expectedAttempts: z.ZodNumber;
|
|
13
|
+
reason: z.ZodString;
|
|
14
|
+
}, z.core.$strict>, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
15
|
+
action: z.ZodLiteral<"confirm-completed">;
|
|
16
|
+
callback: z.ZodLiteral<"guard">;
|
|
17
|
+
result: z.ZodBoolean;
|
|
18
|
+
operationId: z.ZodPipe<z.ZodString, z.ZodTransform<OperationId, string>>;
|
|
19
|
+
expectedAttempts: z.ZodNumber;
|
|
20
|
+
reason: z.ZodString;
|
|
21
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
22
|
+
action: z.ZodLiteral<"confirm-completed">;
|
|
23
|
+
callback: z.ZodLiteral<"resolve-start-state">;
|
|
24
|
+
result: z.ZodString;
|
|
25
|
+
operationId: z.ZodPipe<z.ZodString, z.ZodTransform<OperationId, string>>;
|
|
26
|
+
expectedAttempts: z.ZodNumber;
|
|
27
|
+
reason: z.ZodString;
|
|
28
|
+
}, z.core.$strict>, z.ZodPipe<z.ZodObject<{
|
|
29
|
+
action: z.ZodLiteral<"confirm-completed">;
|
|
30
|
+
callback: z.ZodLiteral<"on-transition">;
|
|
31
|
+
result: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
32
|
+
operationId: z.ZodPipe<z.ZodString, z.ZodTransform<OperationId, string>>;
|
|
33
|
+
expectedAttempts: z.ZodNumber;
|
|
34
|
+
reason: z.ZodString;
|
|
35
|
+
}, z.core.$strict>, z.ZodTransform<{
|
|
36
|
+
action: "confirm-completed";
|
|
37
|
+
callback: "on-transition";
|
|
38
|
+
operationId: OperationId;
|
|
39
|
+
expectedAttempts: number;
|
|
40
|
+
reason: string;
|
|
41
|
+
} | {
|
|
42
|
+
result: string;
|
|
43
|
+
action: "confirm-completed";
|
|
44
|
+
callback: "on-transition";
|
|
45
|
+
operationId: OperationId;
|
|
46
|
+
expectedAttempts: number;
|
|
47
|
+
reason: string;
|
|
48
|
+
}, {
|
|
49
|
+
action: "confirm-completed";
|
|
50
|
+
callback: "on-transition";
|
|
51
|
+
operationId: OperationId;
|
|
52
|
+
expectedAttempts: number;
|
|
53
|
+
reason: string;
|
|
54
|
+
result?: string | null | undefined;
|
|
55
|
+
}>>, z.ZodObject<{
|
|
56
|
+
action: z.ZodLiteral<"confirm-completed">;
|
|
57
|
+
callback: z.ZodLiteral<"children">;
|
|
58
|
+
result: z.ZodJSONSchema;
|
|
59
|
+
operationId: z.ZodPipe<z.ZodString, z.ZodTransform<OperationId, string>>;
|
|
60
|
+
expectedAttempts: z.ZodNumber;
|
|
61
|
+
reason: z.ZodString;
|
|
62
|
+
}, z.core.$strict>, z.ZodPipe<z.ZodObject<{
|
|
63
|
+
action: z.ZodLiteral<"confirm-completed">;
|
|
64
|
+
callback: z.ZodEnum<{
|
|
65
|
+
"on-enter": "on-enter";
|
|
66
|
+
"on-exit": "on-exit";
|
|
67
|
+
}>;
|
|
68
|
+
result: z.ZodOptional<z.ZodNull>;
|
|
69
|
+
operationId: z.ZodPipe<z.ZodString, z.ZodTransform<OperationId, string>>;
|
|
70
|
+
expectedAttempts: z.ZodNumber;
|
|
71
|
+
reason: z.ZodString;
|
|
72
|
+
}, z.core.$strict>, z.ZodTransform<{
|
|
73
|
+
action: "confirm-completed";
|
|
74
|
+
callback: "on-enter" | "on-exit";
|
|
75
|
+
operationId: OperationId;
|
|
76
|
+
expectedAttempts: number;
|
|
77
|
+
reason: string;
|
|
78
|
+
}, {
|
|
79
|
+
action: "confirm-completed";
|
|
80
|
+
callback: "on-enter" | "on-exit";
|
|
81
|
+
operationId: OperationId;
|
|
82
|
+
expectedAttempts: number;
|
|
83
|
+
reason: string;
|
|
84
|
+
result?: null | undefined;
|
|
85
|
+
}>>], "callback">], "action">;
|
|
86
|
+
type ParsedResolveInput = z.output<typeof resolveInputSchema>;
|
|
87
|
+
/** Parses untrusted input; graph membership and stale-target validation follow at execution. */
|
|
88
|
+
declare function parseResolveInput(value: unknown): ResolveInput<string>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Public types for the durable runtime (ADRs 0046–0075).
|
|
92
|
+
*
|
|
93
|
+
* Handler authors use the callback types (`Guard`, `TransitionCallback`, …).
|
|
94
|
+
* Services use the outcome-handler, snapshot, and resolve types through `Runtime`.
|
|
95
|
+
*/
|
|
96
|
+
declare const durableIdKind: unique symbol;
|
|
97
|
+
type BrandedId<Kind extends string> = string & {
|
|
98
|
+
readonly [durableIdKind]: Kind;
|
|
99
|
+
};
|
|
100
|
+
/** Application-supplied flow instance identity. Build with `instanceId(...)`. */
|
|
101
|
+
type InstanceId = BrandedId<"instance">;
|
|
102
|
+
/** Application-supplied dispatch request identity. Build with `requestId(...)`. */
|
|
103
|
+
type RequestId = BrandedId<"request">;
|
|
104
|
+
/** State Flow-generated logical callback operation identity, stable across attempts. */
|
|
105
|
+
type OperationId = BrandedId<"operation">;
|
|
106
|
+
/** State Flow-generated identity of one execution or recovery attempt. */
|
|
107
|
+
type AttemptId = BrandedId<"attempt">;
|
|
108
|
+
type JsonPrimitive = string | number | boolean | null;
|
|
109
|
+
type JsonValue = JsonPrimitive | readonly JsonValue[] | {
|
|
110
|
+
readonly [key: string]: JsonValue;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Maps `T` to its JSON-compatible shape. Members that cannot be persisted
|
|
114
|
+
* (functions, symbols, bigint, `undefined`, class instances with methods)
|
|
115
|
+
* become `never`, so `T` is only assignable to the result when it is JSON-compatible.
|
|
116
|
+
*/
|
|
117
|
+
type JsonCompatible<T> = T extends JsonPrimitive ? T : T extends (...args: never[]) => unknown ? never : T extends bigint | symbol | undefined ? never : T extends readonly (infer Item)[] ? readonly JsonCompatible<Item>[] : T extends object ? {
|
|
118
|
+
[K in keyof T]: JsonCompatible<T[K]>;
|
|
119
|
+
} : never;
|
|
120
|
+
type IsJsonCompatible<T> = [unknown] extends [T] ? false : [T] extends [JsonCompatible<T>] ? true : false;
|
|
121
|
+
/** Transition names whose payload types are not JSON-compatible. */
|
|
122
|
+
type NonJsonPayloadTransitions<Payloads> = {
|
|
123
|
+
[T in keyof Payloads]: IsJsonCompatible<Payloads[T]> extends true ? never : T;
|
|
124
|
+
}[keyof Payloads];
|
|
125
|
+
type RecoveryStrategy = "safe-replay" | "reconcile" | "manual";
|
|
126
|
+
type OperationInfo = {
|
|
127
|
+
readonly operationId: OperationId;
|
|
128
|
+
readonly attemptId: AttemptId;
|
|
129
|
+
/** 1 for the first run; increases with each run or reconciliation attempt. */
|
|
130
|
+
readonly attempt: number;
|
|
131
|
+
};
|
|
132
|
+
/** Fields State Flow adds to every durable callback context. */
|
|
133
|
+
type ExecutionContext = {
|
|
134
|
+
/** The root instance, also for callbacks of nested scopes. */
|
|
135
|
+
readonly instanceId: InstanceId;
|
|
136
|
+
readonly operation: OperationInfo;
|
|
137
|
+
/**
|
|
138
|
+
* Cooperative cancellation: aborted when the runtime closes (ADR 0068) and when a parent
|
|
139
|
+
* Transition interrupts this nested scope (ADR 0095). Unrelated scopes are not affected.
|
|
140
|
+
*/
|
|
141
|
+
readonly signal: AbortSignal;
|
|
142
|
+
/** Where this callback runs: `null` for the root graph, otherwise its nested scope. */
|
|
143
|
+
readonly scope: ScopeRef | null;
|
|
144
|
+
};
|
|
145
|
+
/** Identity of one nested activation (ADR 0096). */
|
|
146
|
+
type ScopeRef = {
|
|
147
|
+
/** Composition path from the root, for example `authenticated` or `workspace:uploads`. */
|
|
148
|
+
readonly path: string;
|
|
149
|
+
/** Distinct per activation: re-entering the same path creates a new one. */
|
|
150
|
+
readonly activationId: string;
|
|
151
|
+
/** FlowList member key; `null` outside a FlowList. */
|
|
152
|
+
readonly memberKey: string | null;
|
|
153
|
+
};
|
|
154
|
+
type CallbackContext<Context> = Context & ExecutionContext;
|
|
155
|
+
type RecoveryCause = "failed" | "interrupted";
|
|
156
|
+
type ReconcileContext<Context> = CallbackContext<Context> & {
|
|
157
|
+
readonly recovery: {
|
|
158
|
+
/** `interrupted` when the process stopped while `run` was in flight. */
|
|
159
|
+
readonly cause: RecoveryCause;
|
|
160
|
+
readonly lastError: string | null;
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
/** `completed` carries the callback's result, except for callbacks that return nothing. */
|
|
164
|
+
type CompletedDecision<Result> = [Result] extends [void] ? {
|
|
165
|
+
readonly decision: "completed";
|
|
166
|
+
readonly result?: never;
|
|
167
|
+
} : {
|
|
168
|
+
readonly decision: "completed";
|
|
169
|
+
readonly result: Result;
|
|
170
|
+
};
|
|
171
|
+
/** Explicit reconciliation decisions (ADR 0064). */
|
|
172
|
+
type ReconcileDecision<Result> = CompletedDecision<Result> | {
|
|
173
|
+
readonly decision: "safe-to-retry";
|
|
174
|
+
} | {
|
|
175
|
+
readonly decision: "pending";
|
|
176
|
+
} | {
|
|
177
|
+
readonly decision: "needs-attention";
|
|
178
|
+
readonly reason: string;
|
|
179
|
+
};
|
|
180
|
+
/** Discriminated recovery declaration: only `reconcile` carries a reconcile function. */
|
|
181
|
+
type RecoveryDeclaration<Context, Args extends readonly unknown[], Result> = {
|
|
182
|
+
readonly strategy: "safe-replay";
|
|
183
|
+
readonly reconcile?: never;
|
|
184
|
+
} | {
|
|
185
|
+
readonly strategy: "manual";
|
|
186
|
+
readonly reconcile?: never;
|
|
187
|
+
} | {
|
|
188
|
+
readonly strategy: "reconcile";
|
|
189
|
+
readonly reconcile: (context: ReconcileContext<Context>, ...args: Args) => ReconcileDecision<Result> | Promise<ReconcileDecision<Result>>;
|
|
190
|
+
};
|
|
191
|
+
/** One implemented callback: its execution and its recovery declared together. */
|
|
192
|
+
type Callback<Context, Args extends readonly unknown[], Result> = {
|
|
193
|
+
readonly run: (context: CallbackContext<Context>, ...args: Args) => Result | Promise<Result>;
|
|
194
|
+
readonly recovery: RecoveryDeclaration<Context, Args, Result>;
|
|
195
|
+
};
|
|
196
|
+
/** `onEnter` / `onExit` in a durable state handler. */
|
|
197
|
+
type StateCallback<Context> = Callback<Context, [], void>;
|
|
198
|
+
/** One FlowList member: a key unique within the activation and the member's invocation input. */
|
|
199
|
+
type ListMember<Input> = {
|
|
200
|
+
readonly key: string;
|
|
201
|
+
readonly input: Input;
|
|
202
|
+
};
|
|
203
|
+
/**
|
|
204
|
+
* What a composite State's `children` callback returns (ADR 0096): the input of each placed
|
|
205
|
+
* Flow that declares one, and the members of each FlowList. Placed Flows without input are omitted.
|
|
206
|
+
*/
|
|
207
|
+
type ChildInputsOf<Regions extends Record<string, {
|
|
208
|
+
readonly kind: "flow" | "list";
|
|
209
|
+
readonly input: unknown;
|
|
210
|
+
}>> = {
|
|
211
|
+
readonly [R in keyof Regions as Regions[R]["kind"] extends "list" ? R : [Regions[R]["input"]] extends [null] ? never : R]: Regions[R]["kind"] extends "list" ? readonly ListMember<Regions[R]["input"]>[] : Regions[R]["input"];
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* `children` in a composite State handler: checkpointed once per activation. It receives the
|
|
215
|
+
* entering Transition's payload, or `null` when the State is entered at a default.
|
|
216
|
+
*/
|
|
217
|
+
type ChildrenCallback<Context, Inputs, EntryPayload = null> = Callback<Context, [
|
|
218
|
+
payload: EntryPayload
|
|
219
|
+
], Inputs>;
|
|
220
|
+
/** Handlers of a composite State: `children` is required when the result has required fields. */
|
|
221
|
+
type CompositeStateHandlers<EntryContext, ExitContext, ChildrenContext, Inputs, EntryPayload = null> = StateHandlers<EntryContext, ExitContext> & (Record<never, never> extends Inputs ? {
|
|
222
|
+
readonly children?: ChildrenCallback<ChildrenContext, Inputs, EntryPayload>;
|
|
223
|
+
} : {
|
|
224
|
+
readonly children: ChildrenCallback<ChildrenContext, Inputs, EntryPayload>;
|
|
225
|
+
});
|
|
226
|
+
/** `guard` in a durable transition handler. */
|
|
227
|
+
type Guard<Context, Payload> = Callback<Context, [
|
|
228
|
+
payload: Payload
|
|
229
|
+
], boolean>;
|
|
230
|
+
/**
|
|
231
|
+
* `onTransition` in a durable transition handler. `Result` is `void` for a fixed destination and
|
|
232
|
+
* the generated union of destination names when the transition branches (ADR 0087).
|
|
233
|
+
*/
|
|
234
|
+
type TransitionCallback<Context, Payload, Result = void> = Callback<Context, [
|
|
235
|
+
payload: Payload
|
|
236
|
+
], Result>;
|
|
237
|
+
/** `resolveStartState` in a durable creation handler. Return the initial state for ordinary creation. */
|
|
238
|
+
type StartStateResolver<Context, StateName extends string> = Callback<Context, [
|
|
239
|
+
], StateName>;
|
|
240
|
+
/**
|
|
241
|
+
* `onEnter` receives the incoming transition (or `"default"` at creation); `onExit` receives the
|
|
242
|
+
* outgoing transition, so the two callbacks have different context types.
|
|
243
|
+
*/
|
|
244
|
+
type StateHandlers<EntryContext, ExitContext> = {
|
|
245
|
+
readonly onEnter?: StateCallback<EntryContext>;
|
|
246
|
+
readonly onExit?: StateCallback<ExitContext>;
|
|
247
|
+
};
|
|
248
|
+
/** Handlers of a transition with one fixed destination: both callbacks are optional. */
|
|
249
|
+
type TransitionHandlers<Context, Payload, Result = void> = {
|
|
250
|
+
readonly guard?: Guard<Context, Payload>;
|
|
251
|
+
readonly onTransition?: TransitionCallback<Context, Payload, Result>;
|
|
252
|
+
};
|
|
253
|
+
/** Handlers of a branching transition: `onTransition` is required and selects the destination. */
|
|
254
|
+
type BranchingTransitionHandlers<Context, Payload, Result> = {
|
|
255
|
+
readonly guard?: Guard<Context, Payload>;
|
|
256
|
+
readonly onTransition: TransitionCallback<Context, Payload, Result>;
|
|
257
|
+
};
|
|
258
|
+
type CreationHandlers<Context, StateName extends string> = {
|
|
259
|
+
readonly resolveStartState?: StartStateResolver<Context, StateName>;
|
|
260
|
+
};
|
|
261
|
+
type EndpointMapFor<StateName extends string> = Record<string, {
|
|
262
|
+
fromState: StateName;
|
|
263
|
+
destinations: readonly StateName[];
|
|
264
|
+
}>;
|
|
265
|
+
type HandlerTable = {
|
|
266
|
+
readonly states: Readonly<Record<string, StateHandlers<any, any> | CompositeStateHandlers<any, any, any, any, any> | null>>;
|
|
267
|
+
readonly transitions: Readonly<Record<string, TransitionHandlers<any, any> | TransitionHandlers<any, any, string> | null>>;
|
|
268
|
+
readonly creation: CreationHandlers<any, string> | null;
|
|
269
|
+
};
|
|
270
|
+
/** Contract of one nested scope reachable through `instance.scope(...)` (ADR 0096). */
|
|
271
|
+
type ScopeEntry<StateName extends string, EndpointMap, Payloads, List extends boolean, Scopes> = {
|
|
272
|
+
readonly states: StateName;
|
|
273
|
+
readonly endpoints: EndpointMap;
|
|
274
|
+
readonly payloads: Payloads;
|
|
275
|
+
/** A FlowList: address members with their key. */
|
|
276
|
+
readonly list: List;
|
|
277
|
+
readonly scopes: Scopes;
|
|
278
|
+
};
|
|
279
|
+
/** Erased scope map: composition-path segment → scope contract. */
|
|
280
|
+
type ScopeMap = Record<string, ScopeEntry<string, EndpointMapFor<string>, Record<string, unknown>, boolean, unknown>>;
|
|
281
|
+
type CompiledFlowModule<StateName extends string, TransitionPayloads extends Record<string, unknown>, Dependencies, EndpointMap extends EndpointMapFor<StateName>, Handlers extends HandlerTable, Scopes = Record<never, never>, Input = null> = {
|
|
282
|
+
readonly execution: "durable";
|
|
283
|
+
readonly flowName: string;
|
|
284
|
+
readonly initialStateId: string;
|
|
285
|
+
readonly states: ReadonlyArray<{
|
|
286
|
+
readonly id: string;
|
|
287
|
+
readonly name: StateName;
|
|
288
|
+
/** `final` for a FinalState; reaching it completes the instance. */
|
|
289
|
+
readonly kind?: "state" | "final";
|
|
290
|
+
/** Nested States name their composite parent and region (ADR 0095). */
|
|
291
|
+
readonly parentId?: string;
|
|
292
|
+
readonly region?: string;
|
|
293
|
+
}>;
|
|
294
|
+
readonly transitions: ReadonlyArray<{
|
|
295
|
+
readonly fromId: string;
|
|
296
|
+
readonly fromName: StateName;
|
|
297
|
+
readonly name: Extract<keyof TransitionPayloads, string>;
|
|
298
|
+
readonly description: string;
|
|
299
|
+
readonly destinations: ReadonlyArray<{
|
|
300
|
+
readonly toId: string;
|
|
301
|
+
readonly toName: StateName;
|
|
302
|
+
readonly description: string;
|
|
303
|
+
}>;
|
|
304
|
+
readonly childWork?: "block" | "interrupt";
|
|
305
|
+
readonly trigger?: "dispatch" | "done";
|
|
306
|
+
}>;
|
|
307
|
+
/** Composite States and their regions (ADR 0096); absent for a flat flow. */
|
|
308
|
+
readonly compositions?: ReadonlyArray<{
|
|
309
|
+
readonly stateId: string;
|
|
310
|
+
readonly kind: "compound" | "parallel";
|
|
311
|
+
readonly regions: ReadonlyArray<{
|
|
312
|
+
readonly name: string;
|
|
313
|
+
readonly kind: "inline";
|
|
314
|
+
readonly defaultId: string;
|
|
315
|
+
} | {
|
|
316
|
+
readonly name: string;
|
|
317
|
+
readonly kind: "flow" | "list";
|
|
318
|
+
readonly flow: string;
|
|
319
|
+
}>;
|
|
320
|
+
}>;
|
|
321
|
+
/** Compiled modules of the Flows this one places, by flow name. */
|
|
322
|
+
readonly placedFlows?: Readonly<Record<string, unknown>>;
|
|
323
|
+
/** Invocation input contract when this Flow runs as a nested child. */
|
|
324
|
+
readonly inputSchema?: z.ZodType<Input, Input>;
|
|
325
|
+
/** Dispatchable Transitions of the root graph. */
|
|
326
|
+
readonly transitionEndpointMap: EndpointMap;
|
|
327
|
+
/** Runtime payload contracts; generated from each transition module's payloadSchema. */
|
|
328
|
+
readonly payloadSchemas: {
|
|
329
|
+
readonly [T in keyof TransitionPayloads]: z.ZodType<TransitionPayloads[T], TransitionPayloads[T]>;
|
|
330
|
+
};
|
|
331
|
+
readonly handlers: Handlers;
|
|
332
|
+
/**
|
|
333
|
+
* Hash of the flow's handler sources, written by compile. Unresolved work started under a
|
|
334
|
+
* different revision is not resumed (ADR 0049, 0075). `null` for hand-built modules.
|
|
335
|
+
*/
|
|
336
|
+
readonly handlerRevision?: string | null;
|
|
337
|
+
readonly __dependenciesBrand?: Dependencies;
|
|
338
|
+
readonly __transitionPayloadsBrand?: TransitionPayloads;
|
|
339
|
+
readonly __scopesBrand?: Scopes;
|
|
340
|
+
} & PayloadCheck<TransitionPayloads>;
|
|
341
|
+
/** Rejects a compiled module whose transition payloads are not JSON-compatible. */
|
|
342
|
+
type PayloadCheck<TransitionPayloads> = [
|
|
343
|
+
NonJsonPayloadTransitions<TransitionPayloads>
|
|
344
|
+
] extends [never] ? unknown : {
|
|
345
|
+
readonly "State Flow: transition payloads must be JSON-compatible": NonJsonPayloadTransitions<TransitionPayloads>;
|
|
346
|
+
};
|
|
347
|
+
/** Allowed destinations of transition `T`. */
|
|
348
|
+
type DestinationOf<EndpointMap extends EndpointMapFor<string>, T extends keyof EndpointMap> = EndpointMap[T]["destinations"][number];
|
|
349
|
+
/** States that have at least one outgoing transition. */
|
|
350
|
+
type StatesWithOutgoing<StateName extends string, EndpointMap extends EndpointMapFor<StateName>> = EndpointMap[keyof EndpointMap]["fromState"];
|
|
351
|
+
type OutgoingFrom<EndpointMap extends EndpointMapFor<string>, From extends string> = {
|
|
352
|
+
[K in keyof EndpointMap]: EndpointMap[K]["fromState"] extends From ? K & string : never;
|
|
353
|
+
}[keyof EndpointMap];
|
|
354
|
+
/** Dispatch input for a scoped dispatcher. Narrowing `transition` narrows `payload`. */
|
|
355
|
+
type DispatchInput<TransitionPayloads extends Record<string, unknown>, EndpointMap extends EndpointMapFor<string>, From extends string> = {
|
|
356
|
+
[T in OutgoingFrom<EndpointMap, From>]: {
|
|
357
|
+
readonly requestId: RequestId;
|
|
358
|
+
readonly transition: T;
|
|
359
|
+
readonly payload: TransitionPayloads[T];
|
|
360
|
+
};
|
|
361
|
+
}[OutgoingFrom<EndpointMap, From>];
|
|
362
|
+
type CallbackRef<StateName extends string, TransitionName extends string> = {
|
|
363
|
+
readonly kind: "resolve-start-state";
|
|
364
|
+
} | {
|
|
365
|
+
readonly kind: "on-enter";
|
|
366
|
+
readonly state: StateName;
|
|
367
|
+
} | {
|
|
368
|
+
readonly kind: "on-exit";
|
|
369
|
+
readonly state: StateName;
|
|
370
|
+
}
|
|
371
|
+
/** A composite State's `children` callback (ADR 0096). */
|
|
372
|
+
| {
|
|
373
|
+
readonly kind: "children";
|
|
374
|
+
readonly state: StateName;
|
|
375
|
+
} | {
|
|
376
|
+
readonly kind: "guard";
|
|
377
|
+
readonly transition: TransitionName;
|
|
378
|
+
} | {
|
|
379
|
+
readonly kind: "on-transition";
|
|
380
|
+
readonly transition: TransitionName;
|
|
381
|
+
};
|
|
382
|
+
type CallbackKind = CallbackRef<string, string>["kind"];
|
|
383
|
+
/** The unresolved callback blocking an instance. */
|
|
384
|
+
type UnresolvedWork<StateName extends string, TransitionName extends string> = {
|
|
385
|
+
readonly operationId: OperationId;
|
|
386
|
+
/** `null` while the instance is being created. */
|
|
387
|
+
readonly requestId: RequestId | null;
|
|
388
|
+
readonly callback: CallbackRef<StateName, TransitionName>;
|
|
389
|
+
readonly strategy: RecoveryStrategy;
|
|
390
|
+
readonly attempts: number;
|
|
391
|
+
readonly lastError: string | null;
|
|
392
|
+
/** ISO timestamp of the next automatic attempt, or `null` when none is scheduled. */
|
|
393
|
+
readonly nextAttemptAt: string | null;
|
|
394
|
+
};
|
|
395
|
+
type ExecutionStatus<StateName extends string, TransitionName extends string> = {
|
|
396
|
+
readonly kind: "ready";
|
|
397
|
+
} | {
|
|
398
|
+
readonly kind: "running";
|
|
399
|
+
readonly requestId: RequestId | null;
|
|
400
|
+
/**
|
|
401
|
+
* The transition in progress, `null` during creation. The committed `state` remains its
|
|
402
|
+
* source until the selected destination commits, so UIs show progress from this (ADR 0087).
|
|
403
|
+
*/
|
|
404
|
+
readonly transition: TransitionName | null;
|
|
405
|
+
/**
|
|
406
|
+
* The destination this execution recorded once `onTransition` selected it (or a fixed
|
|
407
|
+
* destination advanced); `null` before selection.
|
|
408
|
+
*/
|
|
409
|
+
readonly destination: StateName | null;
|
|
410
|
+
} | {
|
|
411
|
+
readonly kind: "recovering";
|
|
412
|
+
readonly work: UnresolvedWork<StateName, TransitionName>;
|
|
413
|
+
} | {
|
|
414
|
+
readonly kind: "needs-attention";
|
|
415
|
+
readonly work: UnresolvedWork<StateName, TransitionName>;
|
|
416
|
+
readonly reason: string;
|
|
417
|
+
} | {
|
|
418
|
+
readonly kind: "terminated";
|
|
419
|
+
readonly reason: string;
|
|
420
|
+
}
|
|
421
|
+
/** The instance reached a FinalState. Distinct from operational termination (ADR 0087). */
|
|
422
|
+
| {
|
|
423
|
+
readonly kind: "completed";
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* A nested scope whose activation ended (ADR 0095). `interrupted` when a parent Transition
|
|
427
|
+
* interrupted its work. Retained unresolved work shows as `recovering` or `needs-attention`.
|
|
428
|
+
*/
|
|
429
|
+
| {
|
|
430
|
+
readonly kind: "exited";
|
|
431
|
+
readonly interrupted: boolean;
|
|
432
|
+
};
|
|
433
|
+
type InstanceSnapshot<StateName extends string, TransitionName extends string> = {
|
|
434
|
+
readonly instanceId: InstanceId;
|
|
435
|
+
readonly flowName: string;
|
|
436
|
+
/** The committed authored state. Stays visible while post-commit work recovers (ADR 0060). */
|
|
437
|
+
readonly state: StateName;
|
|
438
|
+
/** Increases with every committed state or status change. */
|
|
439
|
+
readonly revision: number;
|
|
440
|
+
readonly status: ExecutionStatus<StateName, TransitionName>;
|
|
441
|
+
/**
|
|
442
|
+
* Set when an infrastructure error (for example a failing disk) stopped this instance's
|
|
443
|
+
* execution in this process. `status` then shows where it stopped; call `restartRecovery()`
|
|
444
|
+
* or open the instance again to resume from persisted state (ADR 0076).
|
|
445
|
+
*/
|
|
446
|
+
readonly stall: {
|
|
447
|
+
readonly error: string;
|
|
448
|
+
readonly at: string;
|
|
449
|
+
} | null;
|
|
450
|
+
/**
|
|
451
|
+
* Nested scopes of a root instance (ADR 0096): every active scope, and exited scopes that
|
|
452
|
+
* still retain unresolved work. Empty for a flat flow and for scope snapshots.
|
|
453
|
+
*/
|
|
454
|
+
readonly scopes: readonly ScopeSnapshot[];
|
|
455
|
+
};
|
|
456
|
+
/** One nested activation in a root snapshot. */
|
|
457
|
+
type ScopeSnapshot = ScopeRef & {
|
|
458
|
+
/** Composition-path segment relative to the parent scope. */
|
|
459
|
+
readonly segment: string;
|
|
460
|
+
/** Activation of the parent scope, `null` when the parent is the root. */
|
|
461
|
+
readonly parentActivationId: string | null;
|
|
462
|
+
/** The Flow whose graph this scope executes. */
|
|
463
|
+
readonly flowName: string;
|
|
464
|
+
readonly state: string;
|
|
465
|
+
/** False once the scope's activation exited. */
|
|
466
|
+
readonly active: boolean;
|
|
467
|
+
readonly status: ExecutionStatus<string, string>;
|
|
468
|
+
};
|
|
469
|
+
type HistoryEventType = "instance-created" | "execution-started" | "callback-started" | "callback-completed" | "callback-failed" | "recovery-scheduled" | "recovery-interrupted" | "state-committed" | "execution-applied" | "execution-blocked" | "needs-attention" | "manual-resolution" | "instance-terminated" | "instance-completed"
|
|
470
|
+
/** A composite State activated its child scopes (ADR 0096). */
|
|
471
|
+
| "scope-activated"
|
|
472
|
+
/** A nested scope exited because an ancestor Transition left its State. */
|
|
473
|
+
| "scope-exited"
|
|
474
|
+
/** A parent Transition interrupted a scope's running or unresolved work. */
|
|
475
|
+
| "scope-interrupted"
|
|
476
|
+
/** A nested scope reached a FinalState. */
|
|
477
|
+
| "scope-completed"
|
|
478
|
+
/** An interrupted operation's outcome was established after its activation exited. */
|
|
479
|
+
| "late-outcome";
|
|
480
|
+
type HistoryEvent = {
|
|
481
|
+
readonly sequence: number;
|
|
482
|
+
readonly at: string;
|
|
483
|
+
readonly type: HistoryEventType;
|
|
484
|
+
readonly requestId: RequestId | null;
|
|
485
|
+
readonly operationId: OperationId | null;
|
|
486
|
+
readonly attemptId: AttemptId | null;
|
|
487
|
+
readonly data: JsonValue;
|
|
488
|
+
};
|
|
489
|
+
/** Bounded history query. Pages are in sequence order. */
|
|
490
|
+
type HistoryQuery = {
|
|
491
|
+
/** Return events after this sequence number (exclusive). Defaults to 0. */
|
|
492
|
+
readonly afterSequence?: number;
|
|
493
|
+
/** Defaults to 100; at most 1000. */
|
|
494
|
+
readonly limit?: number;
|
|
495
|
+
};
|
|
496
|
+
type HistoryPage = {
|
|
497
|
+
readonly events: readonly HistoryEvent[];
|
|
498
|
+
/** Pass as `afterSequence` for the next page; `null` when this page is the last. */
|
|
499
|
+
readonly nextAfterSequence: number | null;
|
|
500
|
+
};
|
|
501
|
+
type ExecutionStatusKind = ExecutionStatus<string, string>["kind"];
|
|
502
|
+
/** Statuses a root instance can have: only nested scopes exit (ADR 0096). */
|
|
503
|
+
type InstanceStatusKind = Exclude<ExecutionStatusKind, "exited">;
|
|
504
|
+
/** One row of an instance listing, read from storage without opening the instance. */
|
|
505
|
+
type InstanceSummary = {
|
|
506
|
+
readonly instanceId: InstanceId;
|
|
507
|
+
readonly flowName: string;
|
|
508
|
+
readonly state: string;
|
|
509
|
+
readonly status: InstanceStatusKind;
|
|
510
|
+
/** Needs-attention or termination reason. */
|
|
511
|
+
readonly statusReason: string | null;
|
|
512
|
+
readonly revision: number;
|
|
513
|
+
readonly createdAt: string;
|
|
514
|
+
readonly updatedAt: string;
|
|
515
|
+
};
|
|
516
|
+
type InstanceQuery = {
|
|
517
|
+
readonly flowName?: string;
|
|
518
|
+
readonly status?: InstanceStatusKind | readonly InstanceStatusKind[];
|
|
519
|
+
/** Cursor from a previous page's `next`. */
|
|
520
|
+
readonly after?: string;
|
|
521
|
+
/** Defaults to 50; at most 500. */
|
|
522
|
+
readonly limit?: number;
|
|
523
|
+
};
|
|
524
|
+
type InstancePage = {
|
|
525
|
+
readonly instances: readonly InstanceSummary[];
|
|
526
|
+
/** Pass as `after` for the next page; `null` when this page is the last. */
|
|
527
|
+
readonly next: string | null;
|
|
528
|
+
};
|
|
529
|
+
type OutcomeHandler<Context> = (context: Context) => unknown;
|
|
530
|
+
/** Rejects handler keys outside the expected outcome set. */
|
|
531
|
+
type ExactHandlers<Handlers, Expected> = Handlers & {
|
|
532
|
+
readonly [K in Exclude<keyof Handlers, keyof Expected>]: never;
|
|
533
|
+
};
|
|
534
|
+
type HandlerResult<Handlers> = Awaited<{
|
|
535
|
+
[K in keyof Handlers]: Handlers[K] extends (...args: never[]) => infer R ? R : never;
|
|
536
|
+
}[keyof Handlers]>;
|
|
537
|
+
/** A rejected or historic payload has not been validated against the current contract. */
|
|
538
|
+
type UnvalidatedDispatchInput<Input> = Input extends unknown ? Omit<Input, "payload"> & {
|
|
539
|
+
readonly payload: unknown;
|
|
540
|
+
} : never;
|
|
541
|
+
type CompletedDispatchContext<Input, StateName extends string, TransitionName extends string> = {
|
|
542
|
+
readonly snapshot: InstanceSnapshot<StateName, TransitionName>;
|
|
543
|
+
} & ({
|
|
544
|
+
readonly duplicate: false;
|
|
545
|
+
readonly input: Input;
|
|
546
|
+
} | {
|
|
547
|
+
readonly duplicate: true;
|
|
548
|
+
readonly input: UnvalidatedDispatchInput<Input>;
|
|
549
|
+
});
|
|
550
|
+
/**
|
|
551
|
+
* Applied dispatch: `destination` is the State this request's execution recorded, which stays the
|
|
552
|
+
* same for duplicates even after the instance moves on; `snapshot` is the current condition.
|
|
553
|
+
*/
|
|
554
|
+
type AppliedContext<Input, StateName extends string, TransitionName extends string> = CompletedDispatchContext<Input, StateName, TransitionName> & {
|
|
555
|
+
readonly destination: StateName;
|
|
556
|
+
};
|
|
557
|
+
type DispatchOutcomeHandlers<StateName extends string, TransitionName extends string, Input, From extends StateName> = {
|
|
558
|
+
readonly applied: OutcomeHandler<AppliedContext<Input, StateName, TransitionName>>;
|
|
559
|
+
readonly "invalid-payload": OutcomeHandler<{
|
|
560
|
+
readonly input: UnvalidatedDispatchInput<Input>;
|
|
561
|
+
readonly issues: readonly {
|
|
562
|
+
readonly path: readonly PropertyKey[];
|
|
563
|
+
readonly message: string;
|
|
564
|
+
}[];
|
|
565
|
+
}>;
|
|
566
|
+
readonly blocked: OutcomeHandler<CompletedDispatchContext<Input, StateName, TransitionName>>;
|
|
567
|
+
readonly busy: OutcomeHandler<{
|
|
568
|
+
readonly input: Input;
|
|
569
|
+
readonly snapshot: InstanceSnapshot<StateName, TransitionName>;
|
|
570
|
+
}>;
|
|
571
|
+
readonly "recovery-pending": OutcomeHandler<{
|
|
572
|
+
readonly input: Input;
|
|
573
|
+
readonly snapshot: InstanceSnapshot<StateName, TransitionName>;
|
|
574
|
+
readonly work: UnresolvedWork<StateName, TransitionName>;
|
|
575
|
+
}>;
|
|
576
|
+
readonly "needs-attention": OutcomeHandler<{
|
|
577
|
+
readonly input: Input;
|
|
578
|
+
readonly snapshot: InstanceSnapshot<StateName, TransitionName>;
|
|
579
|
+
readonly work: UnresolvedWork<StateName, TransitionName>;
|
|
580
|
+
readonly reason: string;
|
|
581
|
+
}>;
|
|
582
|
+
readonly "state-mismatch": OutcomeHandler<{
|
|
583
|
+
readonly input: Input;
|
|
584
|
+
readonly expectedState: From;
|
|
585
|
+
readonly actualState: StateName;
|
|
586
|
+
readonly snapshot: InstanceSnapshot<StateName, TransitionName>;
|
|
587
|
+
}>;
|
|
588
|
+
readonly "request-conflict": OutcomeHandler<{
|
|
589
|
+
readonly input: UnvalidatedDispatchInput<Input>;
|
|
590
|
+
readonly recorded: {
|
|
591
|
+
readonly fromState: StateName;
|
|
592
|
+
readonly transition: TransitionName;
|
|
593
|
+
readonly payload: JsonValue;
|
|
594
|
+
};
|
|
595
|
+
}>;
|
|
596
|
+
readonly terminated: OutcomeHandler<{
|
|
597
|
+
readonly input: Input;
|
|
598
|
+
readonly snapshot: InstanceSnapshot<StateName, TransitionName>;
|
|
599
|
+
readonly reason: string;
|
|
600
|
+
}>;
|
|
601
|
+
/** The instance reached a FinalState; this new request ran no callbacks. */
|
|
602
|
+
readonly completed: OutcomeHandler<{
|
|
603
|
+
readonly input: Input;
|
|
604
|
+
readonly snapshot: InstanceSnapshot<StateName, TransitionName>;
|
|
605
|
+
}>;
|
|
606
|
+
};
|
|
607
|
+
type DispatchOutcome = keyof DispatchOutcomeHandlers<string, string, unknown, string>;
|
|
608
|
+
/**
|
|
609
|
+
* Outcomes of a dispatch to a nested scope (ADR 0095, 0096): the root outcomes, plus
|
|
610
|
+
* `interrupted` when a parent Transition interrupted this request's execution, and `inactive`
|
|
611
|
+
* when no activation exists at the addressed scope.
|
|
612
|
+
*/
|
|
613
|
+
type ScopedDispatchOutcomeHandlers<StateName extends string, TransitionName extends string, Input, From extends StateName> = DispatchOutcomeHandlers<StateName, TransitionName, Input, From> & {
|
|
614
|
+
/** The execution was interrupted; nothing further commits for it. Not success, not rejection. */
|
|
615
|
+
readonly interrupted: OutcomeHandler<{
|
|
616
|
+
readonly input: UnvalidatedDispatchInput<Input>;
|
|
617
|
+
readonly snapshot: InstanceSnapshot<StateName, TransitionName>;
|
|
618
|
+
readonly duplicate: boolean;
|
|
619
|
+
}>;
|
|
620
|
+
/** The addressed scope or FlowList member is not active, for example after its parent exited. */
|
|
621
|
+
readonly inactive: OutcomeHandler<{
|
|
622
|
+
readonly input: Input;
|
|
623
|
+
}>;
|
|
624
|
+
};
|
|
625
|
+
type ScopedDispatchOutcome = keyof ScopedDispatchOutcomeHandlers<string, string, unknown, string>;
|
|
626
|
+
/** Why `ensureInstance` rejected with `DefinitionMismatchError`; the stored instance is untouched. */
|
|
627
|
+
type DefinitionMismatchContext = {
|
|
628
|
+
/** The flow graph or recovery declarations changed. */
|
|
629
|
+
readonly reason: "definition";
|
|
630
|
+
readonly instanceId: InstanceId;
|
|
631
|
+
readonly message: string;
|
|
632
|
+
readonly storedFlowName: string;
|
|
633
|
+
readonly storedDefinitionId: string;
|
|
634
|
+
readonly currentDefinitionId: string;
|
|
635
|
+
} | {
|
|
636
|
+
/** Handler code changed while this instance had unresolved work. */
|
|
637
|
+
readonly reason: "handler-revision";
|
|
638
|
+
readonly instanceId: InstanceId;
|
|
639
|
+
readonly message: string;
|
|
640
|
+
readonly storedHandlerRevision: string | null;
|
|
641
|
+
readonly currentHandlerRevision: string | null;
|
|
642
|
+
};
|
|
643
|
+
type ResolveInput<StateName extends string> = Exclude<ParsedResolveInput, {
|
|
644
|
+
callback: "resolve-start-state" | "on-transition";
|
|
645
|
+
}> | (Omit<Extract<ParsedResolveInput, {
|
|
646
|
+
callback: "resolve-start-state";
|
|
647
|
+
}>, "result"> & {
|
|
648
|
+
readonly result: StateName;
|
|
649
|
+
}) | (Omit<Extract<ParsedResolveInput, {
|
|
650
|
+
callback: "on-transition";
|
|
651
|
+
}>, "result"> & {
|
|
652
|
+
/** The selected destination when the transition branches; omitted for a fixed destination. */
|
|
653
|
+
readonly result?: StateName;
|
|
654
|
+
});
|
|
655
|
+
type ResolveRejection =
|
|
656
|
+
/** Inspector only: no registered flow can load this instance (unknown flow or definition mismatch). */
|
|
657
|
+
"unavailable"
|
|
658
|
+
/**
|
|
659
|
+
* The operation is not the instance's current unresolved work awaiting attention, or it has
|
|
660
|
+
* had attempts since `expectedAttempts`.
|
|
661
|
+
*/
|
|
662
|
+
| "stale"
|
|
663
|
+
/** `callback` does not match the unresolved callback. */
|
|
664
|
+
| "callback-mismatch"
|
|
665
|
+
/** `result` does not satisfy the callback's result contract. */
|
|
666
|
+
| "invalid-result"
|
|
667
|
+
/**
|
|
668
|
+
* Retry of interrupted work whose activation exited (ADR 0095): it is never replayed. Only a
|
|
669
|
+
* reconcile check, Confirm it completed, or Terminate are possible.
|
|
670
|
+
*/
|
|
671
|
+
| "not-replayable";
|
|
672
|
+
type ResolveResult<StateName extends string, TransitionName extends string> = {
|
|
673
|
+
readonly outcome: "accepted";
|
|
674
|
+
readonly snapshot: InstanceSnapshot<StateName, TransitionName>;
|
|
675
|
+
} | {
|
|
676
|
+
readonly outcome: "rejected";
|
|
677
|
+
readonly reason: ResolveRejection;
|
|
678
|
+
readonly message: string;
|
|
679
|
+
};
|
|
680
|
+
/** Application mapping for each manual-resolution outcome. */
|
|
681
|
+
type ResolveOutcomeHandlers<StateName extends string, TransitionName extends string> = {
|
|
682
|
+
readonly accepted: (context: Extract<ResolveResult<StateName, TransitionName>, {
|
|
683
|
+
outcome: "accepted";
|
|
684
|
+
}>) => unknown;
|
|
685
|
+
readonly rejected: (context: Extract<ResolveResult<StateName, TransitionName>, {
|
|
686
|
+
outcome: "rejected";
|
|
687
|
+
}>) => unknown;
|
|
688
|
+
};
|
|
689
|
+
type RestartOutcomeHandlers = {
|
|
690
|
+
readonly restarted: () => unknown;
|
|
691
|
+
readonly "not-restarted": () => unknown;
|
|
692
|
+
};
|
|
693
|
+
/** Execution statuses that block a deployment: the instance has unfinished execution. */
|
|
694
|
+
type BlockingStatusKind = Extract<ExecutionStatusKind, "running" | "recovering" | "needs-attention">;
|
|
695
|
+
/** One instance whose unfinished execution blocks a deployment. */
|
|
696
|
+
type DeploymentBlocker = {
|
|
697
|
+
/** The root instance, also when the unfinished work belongs to a nested scope. */
|
|
698
|
+
readonly instanceId: InstanceId;
|
|
699
|
+
readonly flowName: string;
|
|
700
|
+
readonly state: string;
|
|
701
|
+
readonly status: BlockingStatusKind;
|
|
702
|
+
/** Needs-attention reason; `null` otherwise. */
|
|
703
|
+
readonly statusReason: string | null;
|
|
704
|
+
/** The nested scope holding the work, including exited activations (ADR 0095); else `null`. */
|
|
705
|
+
readonly scope: (ScopeRef & {
|
|
706
|
+
readonly active: boolean;
|
|
707
|
+
}) | null;
|
|
708
|
+
};
|
|
709
|
+
/**
|
|
710
|
+
* The established outcome of interrupted work after its activation exited: a late result, a
|
|
711
|
+
* post-exit reconciliation, or an operator's confirmation. Durable until acknowledged, because
|
|
712
|
+
* the exited graph can no longer react and the external effect may be live.
|
|
713
|
+
*/
|
|
714
|
+
type LateOutcome = ScopeRef & {
|
|
715
|
+
readonly sequence: number;
|
|
716
|
+
readonly instanceId: InstanceId;
|
|
717
|
+
readonly operationId: OperationId;
|
|
718
|
+
readonly callback: CallbackRef<string, string>;
|
|
719
|
+
readonly source: "late-result" | "reconciled" | "confirmed";
|
|
720
|
+
readonly result: JsonValue;
|
|
721
|
+
readonly recordedAt: string;
|
|
722
|
+
};
|
|
723
|
+
/** Every persisted instance with unfinished execution, ordered by instance ID. */
|
|
724
|
+
type DeploymentBlocked = {
|
|
725
|
+
readonly outcome: "blocked";
|
|
726
|
+
readonly blockers: readonly DeploymentBlocker[];
|
|
727
|
+
};
|
|
728
|
+
/**
|
|
729
|
+
* `runtime.handover()`: either the runtime closed and released the database, or nothing changed
|
|
730
|
+
* and the old application keeps running.
|
|
731
|
+
*/
|
|
732
|
+
type HandoverResult = {
|
|
733
|
+
readonly outcome: "handed-over";
|
|
734
|
+
} | DeploymentBlocked;
|
|
735
|
+
/** `checkDeployment()` against a database no runtime owns. */
|
|
736
|
+
type DeploymentCheck = {
|
|
737
|
+
readonly outcome: "ready";
|
|
738
|
+
} | DeploymentBlocked | {
|
|
739
|
+
readonly outcome: "owned";
|
|
740
|
+
readonly message: string;
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
type UnmanagedRuntimeOptions = {
|
|
744
|
+
/** Persistent SQLite file owned by State Flow, or `":memory:"` for disposable runs. */
|
|
745
|
+
readonly databasePath: string;
|
|
746
|
+
/** Receives subscriber failures, which never affect execution (ADR 0059). Defaults to console.error. */
|
|
747
|
+
readonly onObserverError?: (error: unknown) => void;
|
|
748
|
+
/**
|
|
749
|
+
* Receives infrastructure errors from background recovery, which has no caller to reject.
|
|
750
|
+
* The affected instance resumes recovery when it is opened again. Defaults to console.error.
|
|
751
|
+
*/
|
|
752
|
+
readonly onRecoveryError?: (error: unknown) => void;
|
|
753
|
+
};
|
|
754
|
+
type Runtime = {
|
|
755
|
+
/**
|
|
756
|
+
* Validates a compiled module, including every handler's recovery declaration, and registers it
|
|
757
|
+
* with its dependencies before any handler runs (ADR 0046, 0090). Rejects with `DefinitionError`
|
|
758
|
+
* for an invalid module and `InputError` when the flow is already registered.
|
|
759
|
+
*/
|
|
760
|
+
createFlow<StateName extends string, TransitionPayloads extends Record<string, unknown>, Dependencies, EndpointMap extends EndpointMapFor<StateName>, Handlers extends HandlerTable, Scopes = Record<never, never>, Input = null>(compiled: CompiledFlowModule<StateName, TransitionPayloads, Dependencies, EndpointMap, Handlers, Scopes, Input> & PayloadCheck<TransitionPayloads>,
|
|
761
|
+
/**
|
|
762
|
+
* Implementations of the handler-owned dependency interfaces (`context.dependencies`). A
|
|
763
|
+
* Flow that places other Flows uses each placed Flow's registered dependencies, so register
|
|
764
|
+
* placed Flows first (ADR 0096).
|
|
765
|
+
*/
|
|
766
|
+
dependencies: Dependencies): Promise<RegisteredFlow<StateName, TransitionPayloads, EndpointMap, Scopes>>;
|
|
767
|
+
/**
|
|
768
|
+
* Stops admission and recovery scheduling, aborts handler signals, lets active work checkpoint,
|
|
769
|
+
* and releases the database (ADR 0061). With `timeoutMs`, rejects with
|
|
770
|
+
* `ShutdownTimeoutError` if callbacks are still running; the lock is kept until they
|
|
771
|
+
* settle or the process exits.
|
|
772
|
+
*/
|
|
773
|
+
close(options?: {
|
|
774
|
+
readonly timeoutMs?: number;
|
|
775
|
+
}): Promise<void>;
|
|
776
|
+
/**
|
|
777
|
+
* The deployment gate (ADR 0091, 0094). Resolves `blocked`, listing every persisted instance
|
|
778
|
+
* that is running, recovering or needs attention, and changes nothing: the application keeps
|
|
779
|
+
* running. Otherwise it closes the runtime in the same step and resolves `handed-over` once the
|
|
780
|
+
* database is released; a deployment abandoned after that restarts this build. Rejects when the
|
|
781
|
+
* check fails, and with `ShutdownTimeoutError` when the close times out: neither permits a
|
|
782
|
+
* deployment.
|
|
783
|
+
*/
|
|
784
|
+
handover(options?: {
|
|
785
|
+
readonly timeoutMs?: number;
|
|
786
|
+
}): Promise<HandoverResult>;
|
|
787
|
+
/**
|
|
788
|
+
* Cross-instance access for operator tools such as the companion UI: listing, snapshots and
|
|
789
|
+
* history straight from storage, runtime-wide subscriptions, and manual resolution. The
|
|
790
|
+
* application authorizes every resolve call it forwards (ADR 0062, 0063).
|
|
791
|
+
*/
|
|
792
|
+
readonly inspector: RuntimeInspector;
|
|
793
|
+
/** Explicit operator capability; pass inspector alone to read-only tools. */
|
|
794
|
+
readonly controls: RuntimeControls;
|
|
795
|
+
};
|
|
796
|
+
type RuntimeInspector = {
|
|
797
|
+
/** Root instances; nested scopes appear in their root's snapshot (`scopes`). */
|
|
798
|
+
listInstances(query?: InstanceQuery): InstancePage;
|
|
799
|
+
/** `null` when no instance has this ID. */
|
|
800
|
+
getSnapshot(instanceId: InstanceId): InstanceSnapshot<string, string> | null;
|
|
801
|
+
history(instanceId: InstanceId, query?: HistoryQuery): HistoryPage;
|
|
802
|
+
/** Snapshots of every instance this runtime executes, after each change. */
|
|
803
|
+
subscribe(listener: (snapshot: InstanceSnapshot<string, string>) => unknown): () => void;
|
|
804
|
+
/**
|
|
805
|
+
* Late outcomes of interrupted work the application has not acknowledged, oldest first
|
|
806
|
+
* (ADR 0095, 0096). Durable: they survive restarts until `controls.acknowledgeLateOutcome`.
|
|
807
|
+
*/
|
|
808
|
+
lateOutcomes(query?: {
|
|
809
|
+
readonly instanceId?: InstanceId;
|
|
810
|
+
}): readonly LateOutcome[];
|
|
811
|
+
/** Called after each newly recorded late outcome commits. */
|
|
812
|
+
subscribeLateOutcomes(listener: (outcome: LateOutcome) => unknown): () => void;
|
|
813
|
+
};
|
|
814
|
+
type RuntimeControls = {
|
|
815
|
+
/** Marks a late outcome handled. False when it was already acknowledged or does not exist. */
|
|
816
|
+
acknowledgeLateOutcome(sequence: number): boolean;
|
|
817
|
+
/**
|
|
818
|
+
* Resolves unresolved work using the flow registered for the instance's flow, opening the
|
|
819
|
+
* instance if needed. When no registered flow can load it (unknown flow, definition or
|
|
820
|
+
* handler-revision mismatch), only `terminate` is accepted, and it runs no handler; other
|
|
821
|
+
* actions return `unavailable`.
|
|
822
|
+
*/
|
|
823
|
+
resolve(instanceId: InstanceId, input: ResolveInput<string>): Promise<ResolveResult<string, string>>;
|
|
824
|
+
/** Restarts recovery of a stalled instance this runtime has loaded. False if nothing to restart. */
|
|
825
|
+
restartRecovery(instanceId: InstanceId): boolean;
|
|
826
|
+
};
|
|
827
|
+
type TransitionNameOf<TransitionPayloads> = Extract<keyof TransitionPayloads, string>;
|
|
828
|
+
type RegisteredFlow<StateName extends string, TransitionPayloads extends Record<string, unknown>, EndpointMap extends EndpointMapFor<StateName>, Scopes = Record<never, never>> = {
|
|
829
|
+
readonly flowName: string;
|
|
830
|
+
readonly definitionId: string;
|
|
831
|
+
/**
|
|
832
|
+
* The only way to acquire an instance (ADR 0090): opens it, or creates it once when absent, and
|
|
833
|
+
* starts required recovery. Acquiring does not imply readiness. Rejects with
|
|
834
|
+
* `DefinitionMismatchError` when the stored instance belongs to another definition or has
|
|
835
|
+
* unresolved work from other handler code. To avoid creating, check
|
|
836
|
+
* `runtime.inspector.getSnapshot(instanceId)` first.
|
|
837
|
+
*/
|
|
838
|
+
ensureInstance(input: {
|
|
839
|
+
readonly instanceId: InstanceId;
|
|
840
|
+
}): Promise<FlowInstance<StateName, TransitionPayloads, EndpointMap, Scopes>>;
|
|
841
|
+
};
|
|
842
|
+
/** Composition-path segments that address a single activation (not a FlowList). */
|
|
843
|
+
type SingleSegment<Scopes> = {
|
|
844
|
+
[K in keyof Scopes & string]: Scopes[K] extends {
|
|
845
|
+
readonly list: false;
|
|
846
|
+
} ? K : never;
|
|
847
|
+
}[keyof Scopes & string];
|
|
848
|
+
/** Composition-path segments of FlowLists, addressed with a member key. */
|
|
849
|
+
type ListSegment<Scopes> = {
|
|
850
|
+
[K in keyof Scopes & string]: Scopes[K] extends {
|
|
851
|
+
readonly list: true;
|
|
852
|
+
} ? K : never;
|
|
853
|
+
}[keyof Scopes & string];
|
|
854
|
+
type ScopeHandleFor<Entry> = Entry extends {
|
|
855
|
+
readonly states: infer StateName extends string;
|
|
856
|
+
readonly endpoints: infer EndpointMap extends EndpointMapFor<string>;
|
|
857
|
+
readonly payloads: infer Payloads extends Record<string, unknown>;
|
|
858
|
+
readonly scopes: infer Nested;
|
|
859
|
+
} ? ScopeHandle<StateName, Payloads, EndpointMap, Nested> : never;
|
|
860
|
+
/** States of a scope with at least one dispatchable Transition. */
|
|
861
|
+
type ScopeFromStates<EndpointMap> = {
|
|
862
|
+
[K in keyof EndpointMap]: EndpointMap[K] extends {
|
|
863
|
+
readonly fromState: infer From extends string;
|
|
864
|
+
} ? From : never;
|
|
865
|
+
}[keyof EndpointMap];
|
|
866
|
+
/** Typed access to nested scopes (ADR 0096): one level per call, chained for deeper scopes. */
|
|
867
|
+
type ScopeNavigation<Scopes> = {
|
|
868
|
+
/**
|
|
869
|
+
* The active activation at `segment` (`state` for a compound State, `state:region` for a
|
|
870
|
+
* parallel region). Resolved at each call, so a handle never reaches an exited activation.
|
|
871
|
+
*/
|
|
872
|
+
scope<Segment extends SingleSegment<Scopes>>(segment: Segment): ScopeHandleFor<Scopes[Segment]>;
|
|
873
|
+
/** One FlowList member by key. */
|
|
874
|
+
scope<Segment extends ListSegment<Scopes>>(segment: Segment, memberKey: string): ScopeHandleFor<Scopes[Segment]>;
|
|
875
|
+
};
|
|
876
|
+
/** A nested scope addressed by composition path; every call resolves its current activation. */
|
|
877
|
+
type ScopeHandle<StateName extends string, TransitionPayloads extends Record<string, unknown>, EndpointMap extends EndpointMapFor<string>, Scopes> = ScopeNavigation<Scopes> & {
|
|
878
|
+
/** Composition path from the root, for example `workspace:uploads`. */
|
|
879
|
+
readonly path: string;
|
|
880
|
+
readonly memberKey: string | null;
|
|
881
|
+
/** The active activation's snapshot, or `null` when none is active. */
|
|
882
|
+
getSnapshot(): InstanceSnapshot<StateName, TransitionNameOf<TransitionPayloads>> | null;
|
|
883
|
+
/** History of the active activation; empty when none is active. */
|
|
884
|
+
history(query?: HistoryQuery): HistoryPage;
|
|
885
|
+
/**
|
|
886
|
+
* Dispatch to the active activation from `state`. Besides the root outcomes, `interrupted`
|
|
887
|
+
* reports an execution a parent Transition interrupted and `inactive` an absent activation.
|
|
888
|
+
* A duplicate request ID always refers to its original activation (ADR 0095).
|
|
889
|
+
*/
|
|
890
|
+
when<From extends ScopeFromStates<EndpointMap> & StateName>(state: From): {
|
|
891
|
+
handle<Handlers extends ScopedDispatchOutcomeHandlers<StateName, TransitionNameOf<TransitionPayloads>, DispatchInput<TransitionPayloads, EndpointMap, From>, From>>(handlers: ExactHandlers<Handlers, ScopedDispatchOutcomeHandlers<StateName, string, unknown, From>>): {
|
|
892
|
+
dispatch(input: DispatchInput<TransitionPayloads, EndpointMap, From>): Promise<HandlerResult<Handlers>>;
|
|
893
|
+
};
|
|
894
|
+
};
|
|
895
|
+
};
|
|
896
|
+
type FlowInstance<StateName extends string, TransitionPayloads extends Record<string, unknown>, EndpointMap extends EndpointMapFor<StateName>, Scopes = Record<never, never>> = ScopeNavigation<Scopes> & {
|
|
897
|
+
readonly instanceId: InstanceId;
|
|
898
|
+
getSnapshot(): InstanceSnapshot<StateName, TransitionNameOf<TransitionPayloads>>;
|
|
899
|
+
/** Called after every committed state or status change. Returns an unsubscribe function. */
|
|
900
|
+
subscribe(listener: (snapshot: InstanceSnapshot<StateName, TransitionNameOf<TransitionPayloads>>) => unknown): () => void;
|
|
901
|
+
/** One page of this instance's execution history (ADR 0072). */
|
|
902
|
+
history(query?: HistoryQuery): HistoryPage;
|
|
903
|
+
/**
|
|
904
|
+
* Dispatch from `state`: `handle` takes one callback per outcome, checked exhaustively at compile
|
|
905
|
+
* time, and returns a reusable dispatcher. The promise resolves to the selected callback's value;
|
|
906
|
+
* only programmer misuse and infrastructure failures reject (ADR 0090).
|
|
907
|
+
*/
|
|
908
|
+
when<From extends StatesWithOutgoing<StateName, EndpointMap>>(state: From): {
|
|
909
|
+
handle<Handlers extends DispatchOutcomeHandlers<StateName, TransitionNameOf<TransitionPayloads>, DispatchInput<TransitionPayloads, EndpointMap, From>, From>>(handlers: ExactHandlers<Handlers, DispatchOutcomeHandlers<StateName, string, unknown, From>>): {
|
|
910
|
+
dispatch(input: DispatchInput<TransitionPayloads, EndpointMap, From>): Promise<HandlerResult<Handlers>>;
|
|
911
|
+
};
|
|
912
|
+
};
|
|
913
|
+
/** Exhaustive outcome mapping for manual actions; `runtime.controls` holds the raw primitive. */
|
|
914
|
+
handleResolve<Handlers extends ResolveOutcomeHandlers<StateName, TransitionNameOf<TransitionPayloads>>>(handlers: ExactHandlers<Handlers, ResolveOutcomeHandlers<StateName, TransitionNameOf<TransitionPayloads>>>): {
|
|
915
|
+
resolve(input: ResolveInput<StateName>): Promise<HandlerResult<Handlers>>;
|
|
916
|
+
};
|
|
917
|
+
handleRestart<Handlers extends RestartOutcomeHandlers>(handlers: ExactHandlers<Handlers, RestartOutcomeHandlers>): {
|
|
918
|
+
restart(): HandlerResult<Handlers> | Promise<HandlerResult<Handlers>>;
|
|
919
|
+
};
|
|
920
|
+
};
|
|
921
|
+
declare function createUnmanagedRuntime(options: UnmanagedRuntimeOptions): Runtime;
|
|
922
|
+
|
|
923
|
+
/** Validates an application-supplied flow instance identity. */
|
|
924
|
+
declare function instanceId(value: string): InstanceId;
|
|
925
|
+
/** Validates an application-supplied dispatch request identity. */
|
|
926
|
+
declare function requestId(value: string): RequestId;
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* The one built-in bounded recovery policy (ADR 0066, values in ADR 0075).
|
|
930
|
+
* An operation gets at most this many attempts in total, counting its first run,
|
|
931
|
+
* replays, and reconciliation checks. Attempts survive restarts.
|
|
932
|
+
*/
|
|
933
|
+
declare const MAX_ATTEMPTS = 4;
|
|
934
|
+
/** Delay before the next automatic attempt, indexed by attempts already used minus one. */
|
|
935
|
+
declare const RECOVERY_DELAYS_MS: readonly [1000, 5000, 30000];
|
|
936
|
+
|
|
937
|
+
/** Base class for durable runtime rejections (infrastructure, defects, invalid input). */
|
|
938
|
+
declare class RuntimeError extends Error {
|
|
939
|
+
constructor(message: string, options?: {
|
|
940
|
+
cause?: unknown;
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
/** Another runtime already owns the database (ADR 0047). */
|
|
944
|
+
declare class OwnershipError extends RuntimeError {
|
|
945
|
+
}
|
|
946
|
+
/** The runtime is closing or closed and admits no new work (ADR 0061). */
|
|
947
|
+
declare class RuntimeClosedError extends RuntimeError {
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* `close({ timeoutMs })` elapsed while callbacks were still running. The runtime keeps the
|
|
951
|
+
* database lock; exit the process to release it (ADR 0075).
|
|
952
|
+
*/
|
|
953
|
+
declare class ShutdownTimeoutError extends RuntimeError {
|
|
954
|
+
}
|
|
955
|
+
/** A compiled module failed startup validation before any handler ran (ADR 0046). */
|
|
956
|
+
declare class DefinitionError extends RuntimeError {
|
|
957
|
+
readonly problems: readonly string[];
|
|
958
|
+
constructor(flowName: string, problems: readonly string[]);
|
|
959
|
+
}
|
|
960
|
+
/** Input that cannot become trusted durable data (IDs, payloads, transitions). */
|
|
961
|
+
declare class InputError extends RuntimeError {
|
|
962
|
+
}
|
|
963
|
+
/** The database was written by a newer State Flow schema. */
|
|
964
|
+
declare class SchemaError extends RuntimeError {
|
|
965
|
+
}
|
|
966
|
+
/** Stored instances remain untouched when their definition or handler revision is incompatible. */
|
|
967
|
+
declare class DefinitionMismatchError extends RuntimeError {
|
|
968
|
+
readonly context: DefinitionMismatchContext;
|
|
969
|
+
constructor(context: DefinitionMismatchContext);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* The deployment gate for a database no runtime owns, for example after a crash or a manual
|
|
974
|
+
* stop (ADR 0094). It takes ownership for the check and releases it before returning, so it
|
|
975
|
+
* reports `owned` rather than readiness while a runtime is live. It never creates or upgrades the
|
|
976
|
+
* database or changes its data; a missing or foreign file throws, and other failures throw too: only
|
|
977
|
+
* `ready` permits a deployment.
|
|
978
|
+
*/
|
|
979
|
+
declare function checkDeployment(options: {
|
|
980
|
+
readonly databasePath: string;
|
|
981
|
+
}): DeploymentCheck;
|
|
982
|
+
|
|
983
|
+
type RuntimeOptions = UnmanagedRuntimeOptions & {
|
|
984
|
+
readonly shutdownTimeoutMs?: number;
|
|
985
|
+
};
|
|
986
|
+
/** A standalone runtime whose asynchronous disposal releases its database ownership. */
|
|
987
|
+
type ManagedRuntime = Runtime & {
|
|
988
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
989
|
+
};
|
|
990
|
+
/** Use `await using runtime = createRuntime(options)` for automatic cleanup at scope exit. */
|
|
991
|
+
declare function createRuntime(options: RuntimeOptions): ManagedRuntime;
|
|
992
|
+
|
|
993
|
+
/** Owns runtime cleanup even when registration, execution, or response handling throws. */
|
|
994
|
+
declare function withRuntime<Result>(options: RuntimeOptions, run: (runtime: Runtime) => Result | Promise<Result>): Promise<Awaited<Result>>;
|
|
995
|
+
|
|
996
|
+
type TargetBase<StateName extends string> = {
|
|
997
|
+
retry(reason: string): ResolveInput<StateName>;
|
|
998
|
+
terminate(reason: string): ResolveInput<StateName>;
|
|
999
|
+
};
|
|
1000
|
+
/** Immutable captured target: refreshing a snapshot cannot silently retarget an operator's action. */
|
|
1001
|
+
type RecoveryTarget<StateName extends string> = TargetBase<StateName> & ({
|
|
1002
|
+
readonly callback: "guard";
|
|
1003
|
+
confirm(result: boolean, reason: string): ResolveInput<StateName>;
|
|
1004
|
+
} | {
|
|
1005
|
+
readonly callback: "resolve-start-state";
|
|
1006
|
+
confirm(result: StateName, reason: string): ResolveInput<StateName>;
|
|
1007
|
+
} | {
|
|
1008
|
+
readonly callback: "on-enter" | "on-exit";
|
|
1009
|
+
confirm(reason: string): ResolveInput<StateName>;
|
|
1010
|
+
} | {
|
|
1011
|
+
readonly callback: "children";
|
|
1012
|
+
/** The child inputs and FlowList members the callback produced (ADR 0096). */
|
|
1013
|
+
confirm(result: JsonValue, reason: string): ResolveInput<StateName>;
|
|
1014
|
+
} | {
|
|
1015
|
+
readonly callback: "on-transition";
|
|
1016
|
+
/**
|
|
1017
|
+
* A branching transition needs the destination the operation selected; a fixed
|
|
1018
|
+
* destination takes none. The runtime rejects an undeclared or missing destination.
|
|
1019
|
+
*/
|
|
1020
|
+
confirm(reason: string, destination?: StateName): ResolveInput<StateName>;
|
|
1021
|
+
});
|
|
1022
|
+
declare function recoveryTarget<StateName extends string>(work: UnresolvedWork<StateName, string>): RecoveryTarget<StateName>;
|
|
1023
|
+
|
|
1024
|
+
export { type AttemptId, type BlockingStatusKind, type BranchingTransitionHandlers, type Callback, type CallbackContext, type CallbackKind, type CallbackRef, type ChildInputsOf, type ChildrenCallback, type CompiledFlowModule, type CompletedDecision, type CompositeStateHandlers, type CreationHandlers, DefinitionError, type DefinitionMismatchContext, DefinitionMismatchError, type DeploymentBlocked, type DeploymentBlocker, type DeploymentCheck, type DestinationOf, type DispatchInput, type DispatchOutcome, type DispatchOutcomeHandlers, type EndpointMapFor, type ExactHandlers, type ExecutionContext, type ExecutionStatus, type ExecutionStatusKind, type FlowInstance, type Guard, type HandlerResult, type HandlerTable, type HandoverResult, type HistoryEvent, type HistoryEventType, type HistoryPage, type HistoryQuery, InputError, type InstanceId, type InstancePage, type InstanceQuery, type InstanceSnapshot, type InstanceStatusKind, type InstanceSummary, type IsJsonCompatible, type JsonCompatible, type JsonPrimitive, type JsonValue, type LateOutcome, type ListMember, MAX_ATTEMPTS, type ManagedRuntime, type NonJsonPayloadTransitions, type OperationId, type OperationInfo, type OutgoingFrom, OwnershipError, type PayloadCheck, RECOVERY_DELAYS_MS, type ReconcileContext, type ReconcileDecision, type RecoveryCause, type RecoveryDeclaration, type RecoveryStrategy, type RecoveryTarget, type RegisteredFlow, type RequestId, type ResolveInput, type ResolveOutcomeHandlers, type ResolveRejection, type ResolveResult, type RestartOutcomeHandlers, type Runtime, RuntimeClosedError, type RuntimeControls, RuntimeError, type RuntimeInspector, type RuntimeOptions, SchemaError, type ScopeEntry, type ScopeMap, type ScopeRef, type ScopeSnapshot, type ScopedDispatchOutcome, type ScopedDispatchOutcomeHandlers, ShutdownTimeoutError, type StartStateResolver, type StateCallback, type StateHandlers, type StatesWithOutgoing, type TransitionCallback, type TransitionHandlers, type UnmanagedRuntimeOptions, type UnresolvedWork, type UnvalidatedDispatchInput, checkDeployment, createRuntime, createUnmanagedRuntime, instanceId, parseResolveInput, recoveryTarget, requestId, resolveInputSchema, withRuntime };
|