@geonosis/workflows 0.2.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/LICENSE +202 -0
- package/README.md +110 -0
- package/dist/index.cjs +1751 -0
- package/dist/index.d.cts +1153 -0
- package/dist/index.d.ts +1153 -0
- package/dist/index.js +1734 -0
- package/package.json +58 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,1153 @@
|
|
|
1
|
+
import { Extensions, EventEnvelope, SeenBefore } from '@geonosis/events';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Facts the engine states about the run itself, and a body never does. A consumer can rely on seeing
|
|
5
|
+
* exactly one of these per closed run.
|
|
6
|
+
*/
|
|
7
|
+
declare const LIFECYCLE_EVENTS: {
|
|
8
|
+
readonly compensated: "workflow.compensated";
|
|
9
|
+
readonly completed: "workflow.completed";
|
|
10
|
+
};
|
|
11
|
+
type LifecycleEventType = (typeof LIFECYCLE_EVENTS)[keyof typeof LIFECYCLE_EVENTS];
|
|
12
|
+
/** What a run is, to whoever names the extensions its envelopes carry. */
|
|
13
|
+
type RunFacts = {
|
|
14
|
+
actor: string | null;
|
|
15
|
+
name: string;
|
|
16
|
+
runId: string;
|
|
17
|
+
tenantId: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* What the emit door is built from, at the composition root and nowhere else.
|
|
21
|
+
*
|
|
22
|
+
* `source` is the one CloudEvents concept nothing in a run can stand in for — it identifies the
|
|
23
|
+
* PRODUCER — so it is configuration, like the clock beside it, and it is spelled once. The tenant,
|
|
24
|
+
* the actor and the run are not fields here: they are FLAT EXTENSIONS the repo names in its own
|
|
25
|
+
* vocabulary, because a name one product needed in every envelope is what makes a package
|
|
26
|
+
* unadoptable by the next one.
|
|
27
|
+
*/
|
|
28
|
+
type EmitConfig<Carried extends Extensions = Extensions> = {
|
|
29
|
+
extensionsOf?: (run: RunFacts) => Carried;
|
|
30
|
+
/** The clock, injected. */
|
|
31
|
+
now?: () => number;
|
|
32
|
+
source: string;
|
|
33
|
+
};
|
|
34
|
+
type WorkflowExecution = 'durable' | 'inline';
|
|
35
|
+
/**
|
|
36
|
+
* The statuses and the endings are const tuples rather than bare unions so the type and the value
|
|
37
|
+
* cannot drift: a conformance suite, a journal migration and a consumer's switch all need to
|
|
38
|
+
* ENUMERATE them, and a union alone leaves each of the three to write the list out again.
|
|
39
|
+
*/
|
|
40
|
+
declare const RUN_STATUSES: readonly ["cancelled", "compensated", "completed", "failed", "running"];
|
|
41
|
+
type RunStatus = (typeof RUN_STATUSES)[number];
|
|
42
|
+
/** How a run ended. `running` is the only status that is not an ending. */
|
|
43
|
+
declare const RUN_OUTCOMES: readonly ["cancelled", "compensated", "completed", "failed"];
|
|
44
|
+
type RunOutcome = (typeof RUN_OUTCOMES)[number];
|
|
45
|
+
declare const STEP_STATUSES: readonly ["compensated", "completed", "failed"];
|
|
46
|
+
type StepStatus = (typeof STEP_STATUSES)[number];
|
|
47
|
+
/**
|
|
48
|
+
* How far the undo got. A run that could not be fully reversed is `failed`, not `compensated` —
|
|
49
|
+
* the difference is the whole point of writing the trail down.
|
|
50
|
+
*/
|
|
51
|
+
type CompensationOutcome = Exclude<RunOutcome, 'completed'>;
|
|
52
|
+
/**
|
|
53
|
+
* Names the engine uses for its own steps. A caller's step under one of these would be handed the
|
|
54
|
+
* engine's memoised result on a replay — or would hand the engine its own.
|
|
55
|
+
*/
|
|
56
|
+
declare const RESERVED_STEP_NAMES: {
|
|
57
|
+
readonly emitEvents: "emit-events";
|
|
58
|
+
readonly finishRun: "finish-run";
|
|
59
|
+
};
|
|
60
|
+
declare const COMPENSATION_PREFIX = "compensate:";
|
|
61
|
+
/** What the engine calls the step that undoes another one. Declared here, used everywhere. */
|
|
62
|
+
declare const compensationStepName: (stepName: string) => string;
|
|
63
|
+
/**
|
|
64
|
+
* What the engine accepts wherever it is going to `await` the answer anyway. Plenty of work is not
|
|
65
|
+
* asynchronous — totalling a basket, deriving a reference — and a step exists to record that it
|
|
66
|
+
* happened and to hang an undo on it, not because it waits for anybody.
|
|
67
|
+
*/
|
|
68
|
+
type MaybePromise<Value> = Value | Promise<Value>;
|
|
69
|
+
type StepBackoff = 'constant' | 'exponential' | 'linear';
|
|
70
|
+
/**
|
|
71
|
+
* What a step is allowed to spend before it gives up. `delay` is a duration the inline executor
|
|
72
|
+
* reads and a durable platform parses itself, so both a number of milliseconds and the platforms'
|
|
73
|
+
* own `'10 seconds'` spelling are accepted.
|
|
74
|
+
*/
|
|
75
|
+
type StepRetry = {
|
|
76
|
+
backoff?: StepBackoff;
|
|
77
|
+
delay?: number | string;
|
|
78
|
+
limit: number;
|
|
79
|
+
};
|
|
80
|
+
type StepBudget = {
|
|
81
|
+
retry?: StepRetry;
|
|
82
|
+
timeout?: number | string;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* What a step is worth spending when it declared nothing.
|
|
86
|
+
*
|
|
87
|
+
* The OBJECT is the signal, not its contents: the inline executor tests a budget for identity with
|
|
88
|
+
* this one to tell "the caller asked for retries" from "the caller said nothing", because an inline
|
|
89
|
+
* run is holding a request open and a step that cannot finish now should compensate and say so.
|
|
90
|
+
* The numbers are what a durable platform is told, where waiting costs nothing.
|
|
91
|
+
*/
|
|
92
|
+
declare const DEFAULT_STEP_BUDGET: StepBudget;
|
|
93
|
+
/**
|
|
94
|
+
* Structurally a Cloudflare Queue producer, so a Queue binding IS an EventSink with no adapter at
|
|
95
|
+
* all. Batches only, deliberately: a run emits several events and a sweep delivers many, and one
|
|
96
|
+
* call per message is a round trip per event on the mutation path.
|
|
97
|
+
*/
|
|
98
|
+
type EventSink = {
|
|
99
|
+
sendBatch: (messages: {
|
|
100
|
+
body: EventEnvelope;
|
|
101
|
+
}[]) => Promise<unknown>;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The run record, as the executors need it.
|
|
105
|
+
*
|
|
106
|
+
* `getRun` and `listRunSteps` are REQUIRED here and were optional in the study — see CONTRACT.md,
|
|
107
|
+
* "Divergences". Every property the TLA+ model states holds only when the trail is read at entry;
|
|
108
|
+
* an optional read is a second configuration nobody model-checks and every implementer skips.
|
|
109
|
+
*/
|
|
110
|
+
type RunJournal = {
|
|
111
|
+
/**
|
|
112
|
+
* MUST throw when the idempotency key is already HELD — that is, when a run with the same
|
|
113
|
+
* `(tenantId, idempotencyKey)` is `running` or `completed`. The throw IS the dedup signal: the
|
|
114
|
+
* engine answers it by looking the held run up rather than doing the work twice. A run that
|
|
115
|
+
* failed, compensated or was cancelled releases its key, so the work can be asked for again.
|
|
116
|
+
*/
|
|
117
|
+
insertRun: (params: {
|
|
118
|
+
execution: WorkflowExecution;
|
|
119
|
+
idempotencyKey: string | null;
|
|
120
|
+
input: unknown;
|
|
121
|
+
name: string;
|
|
122
|
+
/**
|
|
123
|
+
* The run this one was started from, when a step started it. Provenance and nothing more:
|
|
124
|
+
* without it a child run is an orphan in the table and nobody can answer "what caused this?".
|
|
125
|
+
*/
|
|
126
|
+
parentRunId?: string | null;
|
|
127
|
+
/** The run this one was started to do again, when it is one. Null for nearly every run. */
|
|
128
|
+
replayOf?: string | null;
|
|
129
|
+
tenantId: string;
|
|
130
|
+
}) => Promise<string>;
|
|
131
|
+
/**
|
|
132
|
+
* Idempotent on `(runId, seq, attempt)` — a retried step writes one row, not two.
|
|
133
|
+
*
|
|
134
|
+
* Answers with the run's cancellation flag, in the SAME round trip that writes the step.
|
|
135
|
+
* Cooperative cancellation is then free: the engine already had to talk to the journal here.
|
|
136
|
+
*/
|
|
137
|
+
recordStep: (params: {
|
|
138
|
+
attempt: number;
|
|
139
|
+
error?: string | null;
|
|
140
|
+
name: string;
|
|
141
|
+
output?: unknown;
|
|
142
|
+
runId: string;
|
|
143
|
+
seq: number;
|
|
144
|
+
status: StepStatus;
|
|
145
|
+
tenantId: string;
|
|
146
|
+
}) => Promise<{
|
|
147
|
+
cancellationRequested: boolean;
|
|
148
|
+
}>;
|
|
149
|
+
/**
|
|
150
|
+
* ONE atomic write. Closing the run and writing down the events it emitted are one call because
|
|
151
|
+
* they are one batch underneath: a run is completed if and only if its events are durably queued
|
|
152
|
+
* for delivery. A journal that took them separately could be interrupted between the two, and
|
|
153
|
+
* "completed, audit trail lost" is the state that must not exist.
|
|
154
|
+
*/
|
|
155
|
+
finishRun: (params: {
|
|
156
|
+
error?: string | null;
|
|
157
|
+
events?: EventEnvelope[];
|
|
158
|
+
output?: unknown;
|
|
159
|
+
runId: string;
|
|
160
|
+
status: RunOutcome;
|
|
161
|
+
tenantId: string;
|
|
162
|
+
}) => Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Delivered, and recorded as delivered so nothing sweeps it again. Failing this is survivable —
|
|
165
|
+
* the sweeper re-sends, and the consumer recognises the message by its id.
|
|
166
|
+
*/
|
|
167
|
+
markEventsDispatched: (params: {
|
|
168
|
+
ids: string[];
|
|
169
|
+
tenantId: string;
|
|
170
|
+
}) => Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Raise the cancellation flag on a run. MUST answer true only if the run was `running` — a run
|
|
173
|
+
* that has already ended cannot be stopped.
|
|
174
|
+
*/
|
|
175
|
+
requestCancellation: (params: {
|
|
176
|
+
runId: string;
|
|
177
|
+
tenantId: string;
|
|
178
|
+
}) => Promise<boolean>;
|
|
179
|
+
/**
|
|
180
|
+
* Every tenant's undispatched events, oldest first — the sweeper's query, and the one place in
|
|
181
|
+
* the contract that is deliberately not tenant-scoped, because nobody is asking on a tenant's
|
|
182
|
+
* behalf.
|
|
183
|
+
*/
|
|
184
|
+
listUndispatchedEvents: (params: {
|
|
185
|
+
before: number;
|
|
186
|
+
limit: number;
|
|
187
|
+
}) => Promise<{
|
|
188
|
+
envelope: EventEnvelope;
|
|
189
|
+
tenantId: string;
|
|
190
|
+
}[]>;
|
|
191
|
+
/**
|
|
192
|
+
* The runs of the given kind that were started before the cutoff and are still `running`.
|
|
193
|
+
*
|
|
194
|
+
* A list rather than a bulk update, because closing a run is not only a status change: every
|
|
195
|
+
* closed run announces itself, and an announcement needs the run's name and its tenant.
|
|
196
|
+
*/
|
|
197
|
+
listAbandonedRuns: (params: {
|
|
198
|
+
execution: 'inline';
|
|
199
|
+
limit: number;
|
|
200
|
+
startedBefore: number;
|
|
201
|
+
}) => Promise<{
|
|
202
|
+
name: string;
|
|
203
|
+
runId: string;
|
|
204
|
+
tenantId: string;
|
|
205
|
+
}[]>;
|
|
206
|
+
/** The run itself, for whoever is asking what happened. */
|
|
207
|
+
getRun: (params: {
|
|
208
|
+
runId: string;
|
|
209
|
+
tenantId: string;
|
|
210
|
+
}) => Promise<{
|
|
211
|
+
error: string | null;
|
|
212
|
+
execution: string;
|
|
213
|
+
finishedAt: number | null;
|
|
214
|
+
id: string;
|
|
215
|
+
input: unknown;
|
|
216
|
+
name: string;
|
|
217
|
+
output: unknown;
|
|
218
|
+
parentRunId: string | null;
|
|
219
|
+
replayOf: string | null;
|
|
220
|
+
startedAt: number;
|
|
221
|
+
status: string;
|
|
222
|
+
} | null>;
|
|
223
|
+
/** The run's trail, oldest first. */
|
|
224
|
+
listRunSteps: (params: {
|
|
225
|
+
runId: string;
|
|
226
|
+
tenantId: string;
|
|
227
|
+
}) => Promise<{
|
|
228
|
+
attempt: number;
|
|
229
|
+
error: string | null;
|
|
230
|
+
name: string;
|
|
231
|
+
seq: number;
|
|
232
|
+
status: string;
|
|
233
|
+
}[]>;
|
|
234
|
+
/** Held runs only, by the same rule `insertRun` refuses by. */
|
|
235
|
+
findRunByIdempotencyKey: (params: {
|
|
236
|
+
idempotencyKey: string;
|
|
237
|
+
tenantId: string;
|
|
238
|
+
}) => Promise<{
|
|
239
|
+
id: string;
|
|
240
|
+
output: unknown;
|
|
241
|
+
status: RunStatus;
|
|
242
|
+
} | null>;
|
|
243
|
+
};
|
|
244
|
+
/** Why a compensation is running. */
|
|
245
|
+
type CompensationReason = {
|
|
246
|
+
/** The error that unwound the run. A `RunCancelledError` when somebody asked it to stop. */
|
|
247
|
+
cause: unknown;
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
250
|
+
* What a step is handed. `idempotencyKey` is the step's own — `${runId}:${seq}`, and
|
|
251
|
+
* `${runId}:${seq}:undo` for a compensation — stable across every attempt and every replay of that
|
|
252
|
+
* step, and different for every other step in the run.
|
|
253
|
+
*/
|
|
254
|
+
type StepContext<Scope> = Scope & {
|
|
255
|
+
/** Which attempt this is, from one. The idempotency key deliberately does NOT move with it. */
|
|
256
|
+
attempt: number;
|
|
257
|
+
idempotencyKey: string;
|
|
258
|
+
runId: string;
|
|
259
|
+
tenantId: string;
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* A step, as the engine holds it.
|
|
263
|
+
*
|
|
264
|
+
* There is ONE rule about compensation data: the undo is handed exactly what the step returned. A
|
|
265
|
+
* step that needs something extra to undo itself returns it, and then its value says everything
|
|
266
|
+
* about what it did — which is also what the run record holds and what the body was given.
|
|
267
|
+
*
|
|
268
|
+
* `invoke` and `compensate` are declared as METHODS rather than function properties so TypeScript
|
|
269
|
+
* checks their parameters bivariantly, which is what lets a heterogeneous list of steps be held in
|
|
270
|
+
* one array without every caller reaching for a cast.
|
|
271
|
+
*/
|
|
272
|
+
type Step<Scope, Input, Output> = {
|
|
273
|
+
budget: StepBudget;
|
|
274
|
+
compensate?(output: Output, ctx: StepContext<Scope>, reason: CompensationReason): MaybePromise<void>;
|
|
275
|
+
invoke(input: Input, ctx: StepContext<Scope>): MaybePromise<Output>;
|
|
276
|
+
name: string;
|
|
277
|
+
};
|
|
278
|
+
/**
|
|
279
|
+
* The one difference between the two executors: how a unit of work is carried out. Inline calls
|
|
280
|
+
* it; durable hands it to the platform's step primitive, which checkpoints and retries it.
|
|
281
|
+
* Everything else — ordering, the trail, the reverse compensation, the held events — is one
|
|
282
|
+
* implementation, proven once.
|
|
283
|
+
*/
|
|
284
|
+
type StepRunner = <Output>(name: string, budget: StepBudget, run: (ctx: {
|
|
285
|
+
attempt: number;
|
|
286
|
+
}) => Promise<Output>) => Promise<Output>;
|
|
287
|
+
/**
|
|
288
|
+
* The seam over a durable engine's step primitives. Cloudflare Workflows implements it against a
|
|
289
|
+
* real `WorkflowStep`; a suite implements it against an array of calls. Inngest, Restate and
|
|
290
|
+
* Temporal expose the same three capabilities under other names.
|
|
291
|
+
*/
|
|
292
|
+
type StepPrimitive = {
|
|
293
|
+
do: <Output>(name: string, budget: StepBudget, run: (ctx: {
|
|
294
|
+
attempt: number;
|
|
295
|
+
}) => Promise<Output>) => Promise<Output>;
|
|
296
|
+
sleep: (name: string, duration: number | string) => Promise<void>;
|
|
297
|
+
waitForEvent: <Payload>(name: string, options: {
|
|
298
|
+
timeout?: number | string;
|
|
299
|
+
type: string;
|
|
300
|
+
}) => Promise<Payload>;
|
|
301
|
+
};
|
|
302
|
+
/**
|
|
303
|
+
* Everything a run needs that is not the run itself.
|
|
304
|
+
*
|
|
305
|
+
* Built once per isolate for what is static — where records go, where events go, who is watching —
|
|
306
|
+
* and narrowed per request for who is asking. `context` is whatever else the caller scoped this
|
|
307
|
+
* request with, and it reaches every step through its `StepContext`.
|
|
308
|
+
*/
|
|
309
|
+
type WorkflowRuntime<Scope extends object = Record<string, unknown>> = {
|
|
310
|
+
actor?: string | null;
|
|
311
|
+
context?: Scope;
|
|
312
|
+
emit?: EmitConfig;
|
|
313
|
+
events?: EventSink;
|
|
314
|
+
journal: RunJournal;
|
|
315
|
+
observer?: RunObserver;
|
|
316
|
+
scope?: RunScope;
|
|
317
|
+
tenantId: string;
|
|
318
|
+
};
|
|
319
|
+
/** What a unit of work is wrapped in. `withConnection`, `withCells`, a span — the host's own. */
|
|
320
|
+
type StepScope = <Output>(body: () => Promise<Output>) => Promise<Output>;
|
|
321
|
+
/**
|
|
322
|
+
* What a DURABLE step's body and its undo are each wrapped in, and nothing else is.
|
|
323
|
+
*
|
|
324
|
+
* A durable instance hibernates between steps, so a handle opened once per run is dead by the time
|
|
325
|
+
* the second step reaches for it: each unit of work opens the one it uses. The engine's own writes —
|
|
326
|
+
* the trail, the closing batch — are not that unit; they are the runner's, and a scope stretched
|
|
327
|
+
* over them would put a journal write inside a connection the step is about to close.
|
|
328
|
+
*
|
|
329
|
+
* `perStep` is asked for a scope per unit rather than handed one, because a scope is a resource and
|
|
330
|
+
* the whole point is that a retried step gets its own. An inline run ignores it: it holds one request
|
|
331
|
+
* open from the first step to the last, so there is nothing to reopen.
|
|
332
|
+
*/
|
|
333
|
+
type RunScope = {
|
|
334
|
+
perStep: () => StepScope;
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* Plain facts about a run going by, for whoever wants to count or trace them. Deliberately plain:
|
|
338
|
+
* no objects from inside the engine, so an adapter for OpenTelemetry or a log line cannot come to
|
|
339
|
+
* depend on the engine's shape. Every hook is called defensively — an observability backend having
|
|
340
|
+
* a bad day is not a reason to refuse somebody's invoice.
|
|
341
|
+
*/
|
|
342
|
+
type RunObserver = {
|
|
343
|
+
onCompensationEnd?(fact: {
|
|
344
|
+
attempt: number;
|
|
345
|
+
durationMs: number;
|
|
346
|
+
name: string;
|
|
347
|
+
runId: string;
|
|
348
|
+
seq: number;
|
|
349
|
+
status: StepStatus;
|
|
350
|
+
}): void;
|
|
351
|
+
onCompensationStart?(fact: {
|
|
352
|
+
attempt: number;
|
|
353
|
+
name: string;
|
|
354
|
+
runId: string;
|
|
355
|
+
seq: number;
|
|
356
|
+
}): void;
|
|
357
|
+
/**
|
|
358
|
+
* `events` are the TYPES the run queued in the batch that closed it, in order — never their
|
|
359
|
+
* payloads. A payload is somebody's invoice; it belongs where a person went looking for it, not
|
|
360
|
+
* in a log line that scrolls past.
|
|
361
|
+
*/
|
|
362
|
+
onRunEnd?(fact: {
|
|
363
|
+
durationMs: number;
|
|
364
|
+
events: string[];
|
|
365
|
+
name: string;
|
|
366
|
+
runId: string;
|
|
367
|
+
status: RunOutcome;
|
|
368
|
+
}): void;
|
|
369
|
+
onRunStart?(fact: {
|
|
370
|
+
name: string;
|
|
371
|
+
runId: string;
|
|
372
|
+
tenantId: string;
|
|
373
|
+
}): void;
|
|
374
|
+
onStepEnd?(fact: {
|
|
375
|
+
attempt: number;
|
|
376
|
+
durationMs: number;
|
|
377
|
+
name: string;
|
|
378
|
+
runId: string;
|
|
379
|
+
seq: number;
|
|
380
|
+
status: StepStatus;
|
|
381
|
+
}): void;
|
|
382
|
+
onStepStart?(fact: {
|
|
383
|
+
attempt: number;
|
|
384
|
+
name: string;
|
|
385
|
+
runId: string;
|
|
386
|
+
seq: number;
|
|
387
|
+
}): void;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* How the inline executor carries out a step.
|
|
392
|
+
*
|
|
393
|
+
* By default it runs it once. An inline run is holding a request open, and a run that cannot finish
|
|
394
|
+
* now should compensate and say so rather than spend a budget nobody asked it to. A step that DID
|
|
395
|
+
* ask — one that declares a retry itself — is believed: a flaky provider call inside a 200 ms
|
|
396
|
+
* mutation is exactly the case where two quick attempts beat compensating the whole run.
|
|
397
|
+
*
|
|
398
|
+
* "Asked" is IDENTITY with `DEFAULT_STEP_BUDGET`, which is the object `createStep` uses when the
|
|
399
|
+
* caller named neither a retry nor a timeout. That is what lets one definition mean "once" inline
|
|
400
|
+
* and "the platform's default" durably.
|
|
401
|
+
*/
|
|
402
|
+
declare const createInlineRunner: (options?: {
|
|
403
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
404
|
+
}) => StepRunner;
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* The identities the engine derives from the run rather than from the clock or a random source.
|
|
408
|
+
*
|
|
409
|
+
* A durable body invoked twice for one run walks the same steps in the same order, so it arrives
|
|
410
|
+
* at the same identities: a step retried after a provider already accepted the work presents the
|
|
411
|
+
* key it presented the first time, and a second write of a run's closure lands on the row that
|
|
412
|
+
* already exists. They are together here because they are a WIRE FORMAT — they appear in database
|
|
413
|
+
* rows, in delivered messages and in other people's idempotency records — so changing either is a
|
|
414
|
+
* breaking change and the two must never quietly drift apart.
|
|
415
|
+
*/
|
|
416
|
+
/** What a step presents to the outside world. Stable across attempts and replays. */
|
|
417
|
+
declare const stepIdempotencyKey: (runId: string, seq: number) => string;
|
|
418
|
+
/**
|
|
419
|
+
* What a compensation presents. Undoing a charge is a refund — a different side effect, and so a
|
|
420
|
+
* different key, derived from the step it reverses.
|
|
421
|
+
*/
|
|
422
|
+
declare const compensationIdempotencyKey: (runId: string, seq: number) => string;
|
|
423
|
+
/** What identifies one emission of one run, to the outbox and to every consumer. */
|
|
424
|
+
declare const envelopeId: (runId: string, ordinal: number) => string;
|
|
425
|
+
type RunClosure = 'compensated' | 'completed' | 'start-refused' | 'swept';
|
|
426
|
+
/**
|
|
427
|
+
* What identifies a fact about the RUN rather than an emission from its body.
|
|
428
|
+
*
|
|
429
|
+
* Deliberately not an ordinal from the emission sequence. An ordinal is a function of how far the
|
|
430
|
+
* body walked, and a re-invoked body can walk further than the invocation that closed the run did —
|
|
431
|
+
* which minted the announcement at a higher ordinal, under a different id, which the outbox's
|
|
432
|
+
* conflict clause could not recognise as a repeat. A run closes once, so its closure has one id,
|
|
433
|
+
* whoever writes it and however far anybody walked.
|
|
434
|
+
*/
|
|
435
|
+
declare const lifecycleEnvelopeId: (runId: string, closure: RunClosure) => string;
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* What a run puts on the table: one envelope per announcement, plus the fact that the run closed.
|
|
439
|
+
*
|
|
440
|
+
* Every field an envelope has that is not its type and its payload is minted by
|
|
441
|
+
* `@geonosis/events`' emit door and nowhere else — `id`, `occurredAt`, the required `source`, and
|
|
442
|
+
* the flat extensions the repo declared. This engine spells none of them. What it does supply is
|
|
443
|
+
* the door's MINTER, and it supplies one derived from the RUN rather than from the clock: a body
|
|
444
|
+
* invoked twice for one run walks the same emissions in the same order and arrives at the same ids,
|
|
445
|
+
* so a second write lands on rows that already exist instead of handing a consumer copies it has no
|
|
446
|
+
* way to recognise. `formal/WorkflowsRandomIds.cfg` is what any other minter costs.
|
|
447
|
+
*/
|
|
448
|
+
type RunEmitter = {
|
|
449
|
+
/** A fact about the RUN. Identified by what it is, never by a position in the emission sequence:
|
|
450
|
+
* an ordinal is a function of how far the body walked, and a re-invoked body can walk further. */
|
|
451
|
+
closure: (closure: RunClosure, type: string, payload: unknown) => EventEnvelope;
|
|
452
|
+
emission: (type: string, payload: unknown) => EventEnvelope;
|
|
453
|
+
};
|
|
454
|
+
declare const createRunEmitter: (config: EmitConfig, run: RunFacts) => RunEmitter;
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* What one batched send carries. A drain larger than this is more than one call, and the number
|
|
458
|
+
* lives beside the only code that makes those calls rather than in a journal, which has no business
|
|
459
|
+
* knowing what a queue is.
|
|
460
|
+
*/
|
|
461
|
+
declare const EVENT_BATCH_LIMIT = 100;
|
|
462
|
+
/**
|
|
463
|
+
* The one way an event leaves the process, shared by the drain a run does for itself and the sweep a
|
|
464
|
+
* cron does for whatever the drain could not finish. Sending and recording the send are one act per
|
|
465
|
+
* batch: a batch that was sent but not recorded is sent again by the next sweep, which the consumer
|
|
466
|
+
* recognises by the envelope's id and discards — the safe direction of an at-least-once delivery.
|
|
467
|
+
*/
|
|
468
|
+
declare const dispatchEvents: (options: {
|
|
469
|
+
envelopes: readonly EventEnvelope[];
|
|
470
|
+
markDispatched: (ids: string[]) => Promise<void>;
|
|
471
|
+
sink: EventSink;
|
|
472
|
+
}) => Promise<number>;
|
|
473
|
+
/** How many rows one sweep considers. A sweep takes a bounded bite and lets the next take the rest. */
|
|
474
|
+
declare const EVENT_SWEEP_LIMIT = 500;
|
|
475
|
+
/**
|
|
476
|
+
* How long a row is left for the run that made it. A row written a moment ago almost certainly
|
|
477
|
+
* belongs to a run whose own drain is still in flight; waiting a minute costs a minute and saves a
|
|
478
|
+
* duplicate delivery.
|
|
479
|
+
*/
|
|
480
|
+
declare const EVENT_SWEEP_GRACE_MS = 60000;
|
|
481
|
+
/**
|
|
482
|
+
* The other half of the outbox: what comes back for the events a run's own drain could not deliver.
|
|
483
|
+
* The drain is best-effort by design — the mutation committed, and a queue that could not be reached
|
|
484
|
+
* is not the caller's problem — and this is what makes that true.
|
|
485
|
+
*
|
|
486
|
+
* It reads across every tenant, because nobody is asking on a tenant's behalf, and delivers each
|
|
487
|
+
* tenant's rows under that tenant so a journal that scopes its writes still can.
|
|
488
|
+
*/
|
|
489
|
+
declare const sweepEventOutbox: (options: {
|
|
490
|
+
journal: RunJournal;
|
|
491
|
+
limit?: number;
|
|
492
|
+
now?: number;
|
|
493
|
+
olderThanMs?: number;
|
|
494
|
+
sink: EventSink;
|
|
495
|
+
}) => Promise<number>;
|
|
496
|
+
/** How many abandoned runs one sweep closes. The next sweep takes the rest. */
|
|
497
|
+
declare const ABANDONED_SWEEP_LIMIT = 200;
|
|
498
|
+
/**
|
|
499
|
+
* Close the runs nobody is going to close.
|
|
500
|
+
*
|
|
501
|
+
* An inline run lives inside one request. If the process carrying it dies — a deploy, a crash, a
|
|
502
|
+
* timeout — nothing is left to finish it: it is not running, it was never compensated, and the
|
|
503
|
+
* record says `running` for as long as the table exists. Those are the runs an operator finds a year
|
|
504
|
+
* later and cannot explain, and the only honest thing to do with them is say so.
|
|
505
|
+
*
|
|
506
|
+
* Durable runs are deliberately not touched at any age. One may be asleep for a week or waiting on a
|
|
507
|
+
* human, and failing it because it is old would be the sweep inventing an incident.
|
|
508
|
+
*
|
|
509
|
+
* Each run is closed through `finishRun` carrying its own announcement, because every closed run
|
|
510
|
+
* announces itself and a sweeper is not an exception. Run it on a schedule with a window comfortably
|
|
511
|
+
* longer than your longest inline request.
|
|
512
|
+
*/
|
|
513
|
+
declare const sweepAbandonedRuns: (options: {
|
|
514
|
+
/** The door's configuration. Without it a swept run closes with nothing to announce. */
|
|
515
|
+
emit?: EmitConfig;
|
|
516
|
+
journal: RunJournal;
|
|
517
|
+
limit?: number;
|
|
518
|
+
now?: number;
|
|
519
|
+
olderThanMs: number;
|
|
520
|
+
}) => Promise<number>;
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Wait for several step calls at once.
|
|
524
|
+
*
|
|
525
|
+
* It is `Promise.all` under Medusa's name, and it is thin on purpose: the calls are ARGUMENTS, so
|
|
526
|
+
* every one of them has already been made — in array order, synchronously — by the time this
|
|
527
|
+
* function has anything to wait for. That is what makes start order the array order, and start order
|
|
528
|
+
* is what the run is unwound by.
|
|
529
|
+
*
|
|
530
|
+
* Reverse START order rather than reverse completion order, because completion order is not stable
|
|
531
|
+
* across a durable re-invocation: a memoised step comes back from the journal instantly, so the same
|
|
532
|
+
* body would unwind one way the first time and another way the second. Start order is a property of
|
|
533
|
+
* the body; completion order is a property of the weather.
|
|
534
|
+
*/
|
|
535
|
+
declare const parallelize: <const Calls extends readonly PromiseLike<unknown>[]>(...calls: Calls) => Promise<{ -readonly [Index in keyof Calls]: Awaited<Calls[Index]>; }>;
|
|
536
|
+
|
|
537
|
+
declare const messageOf: (error: unknown) => string;
|
|
538
|
+
/**
|
|
539
|
+
* The ancestor of everything this package throws, so one `catch` can recognise all of it. Without
|
|
540
|
+
* it a caller has to know every concrete name, and every error type added in a later version walks
|
|
541
|
+
* silently past the catch block somebody wrote carefully.
|
|
542
|
+
*/
|
|
543
|
+
declare class WorkflowsError extends Error {
|
|
544
|
+
constructor(message: string, options?: {
|
|
545
|
+
cause?: unknown;
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* What a failed run throws. It carries the run id because the run RECORD — not the stack — is where
|
|
550
|
+
* a failure is explained: input, step trail, compensation trail, timings.
|
|
551
|
+
*/
|
|
552
|
+
declare class RunFailedError extends WorkflowsError {
|
|
553
|
+
/** A literal tag beside `name`, so this slots into tagged-union error handling with no adapter. */
|
|
554
|
+
readonly _tag: "RunFailedError";
|
|
555
|
+
/** The steps whose undo came back, in the order they were undone. */
|
|
556
|
+
readonly compensated: string[];
|
|
557
|
+
/** The steps whose undo refused. Non-empty means something is still standing. */
|
|
558
|
+
readonly failedCompensations: string[];
|
|
559
|
+
readonly failedStep: string | null;
|
|
560
|
+
readonly outcome: CompensationOutcome;
|
|
561
|
+
readonly runId: string;
|
|
562
|
+
readonly workflowName: string;
|
|
563
|
+
constructor(params: {
|
|
564
|
+
cause: unknown;
|
|
565
|
+
compensated: string[];
|
|
566
|
+
failedCompensations: string[];
|
|
567
|
+
failedStep: string | null;
|
|
568
|
+
outcome: CompensationOutcome;
|
|
569
|
+
runId: string;
|
|
570
|
+
workflowName: string;
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* What the engine throws when it finds that the run has been asked to stop. It is not a failure:
|
|
575
|
+
* the run is unwound exactly as a failure would be, but it closes `cancelled` and says so, because
|
|
576
|
+
* "somebody changed their mind" and "something broke" are different facts.
|
|
577
|
+
*/
|
|
578
|
+
declare class RunCancelledError extends WorkflowsError {
|
|
579
|
+
readonly _tag: "RunCancelledError";
|
|
580
|
+
readonly runId: string;
|
|
581
|
+
constructor(runId: string);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* What a journal throws when the idempotency key is already held.
|
|
585
|
+
*
|
|
586
|
+
* A typed refusal rather than a message to match on: a journal built on a store this package has
|
|
587
|
+
* never heard of can say "this key is taken" unambiguously, and the engine can tell that apart
|
|
588
|
+
* from the database being on fire.
|
|
589
|
+
*/
|
|
590
|
+
declare class IdempotencyKeyHeldError extends WorkflowsError {
|
|
591
|
+
readonly idempotencyKey: string;
|
|
592
|
+
readonly tenantId: string;
|
|
593
|
+
constructor(params: {
|
|
594
|
+
idempotencyKey: string;
|
|
595
|
+
tenantId: string;
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* What a run answers with when the caller would rather decide than catch. A run that was undone is a
|
|
601
|
+
* normal outcome — the undo ran, the record is written, and there is a decision to make — and
|
|
602
|
+
* requiring a try/catch and an `instanceof` to reach it makes the ordinary case look like the broken
|
|
603
|
+
* one.
|
|
604
|
+
*/
|
|
605
|
+
type RunResult<Output> = {
|
|
606
|
+
deduplicated: boolean;
|
|
607
|
+
ok: true;
|
|
608
|
+
result: Output;
|
|
609
|
+
runId: string;
|
|
610
|
+
} | {
|
|
611
|
+
error: RunFailedError;
|
|
612
|
+
ok: false;
|
|
613
|
+
result: undefined;
|
|
614
|
+
runId: string;
|
|
615
|
+
};
|
|
616
|
+
type WorkflowBody<Input, Output, Scope extends object> = (input: Input, ctx: {
|
|
617
|
+
runId: string;
|
|
618
|
+
tenantId: string;
|
|
619
|
+
} & Scope) => Promise<Output>;
|
|
620
|
+
/** One event: its type and its payload. Everything else on the envelope is minted at the door. */
|
|
621
|
+
type Announcement = readonly [type: string, payload: unknown];
|
|
622
|
+
type WorkflowOptions<Input, Output> = {
|
|
623
|
+
/**
|
|
624
|
+
* Which door carries this workflow out: `inline` (default) is one request holding it open,
|
|
625
|
+
* `durable` is a platform re-invoking it. #210: the engine hardcoded `inline` and the lint rule
|
|
626
|
+
* read a declaration off the call that no consumer could write, so the two disagreed about what a
|
|
627
|
+
* durable workflow looks like. The definition says it, once, and each door refuses what is not
|
|
628
|
+
* its own — a body carried the other way replays where it was written not to, or holds a request
|
|
629
|
+
* open for a run meant to survive one.
|
|
630
|
+
*/
|
|
631
|
+
execution?: WorkflowExecution;
|
|
632
|
+
/**
|
|
633
|
+
* What this run announces when it completes, given what it returned.
|
|
634
|
+
*
|
|
635
|
+
* A run knows what it did, so the run says it — in the same place it says its name. A body that
|
|
636
|
+
* reaches for an emit halfway through is announcing something that has not happened yet: the run
|
|
637
|
+
* can still fail after that line, and the whole point of the outbox is that nothing is announced
|
|
638
|
+
* until the run is a fact. Held until the run closes, written in the batch that closes it, and
|
|
639
|
+
* dropped entirely if the run is undone.
|
|
640
|
+
*/
|
|
641
|
+
announce?: (output: Output) => Announcement | readonly Announcement[] | null | undefined;
|
|
642
|
+
/**
|
|
643
|
+
* How this run is recognised as one somebody already asked for. `true` derives the key from the
|
|
644
|
+
* input itself — key order is not meaning, so it is the canonical rendering that is hashed, and
|
|
645
|
+
* the workflow's name is part of the key so two workflows given the same input do not collide. A
|
|
646
|
+
* function is there for when the key means something to somebody else.
|
|
647
|
+
*/
|
|
648
|
+
idempotent?: true | ((input: Input) => string);
|
|
649
|
+
};
|
|
650
|
+
type RunOptions<Input, Scope extends object> = {
|
|
651
|
+
input: Input;
|
|
652
|
+
/** Overrides whatever the definition's `idempotent` rule would have derived. */
|
|
653
|
+
idempotencyKey?: string;
|
|
654
|
+
/** The run this one was started from, when a step started it. Provenance only. */
|
|
655
|
+
parentRunId?: string | null;
|
|
656
|
+
runtime: WorkflowRuntime<Scope>;
|
|
657
|
+
throwOnError?: boolean;
|
|
658
|
+
};
|
|
659
|
+
type Workflow<Input, Output, Scope extends object> = {
|
|
660
|
+
readonly execution: WorkflowExecution;
|
|
661
|
+
readonly name: string;
|
|
662
|
+
run(options: RunOptions<Input, Scope> & {
|
|
663
|
+
throwOnError?: true;
|
|
664
|
+
}): Promise<Extract<RunResult<Output>, {
|
|
665
|
+
ok: true;
|
|
666
|
+
}>>;
|
|
667
|
+
run(options: RunOptions<Input, Scope> & {
|
|
668
|
+
throwOnError: false;
|
|
669
|
+
}): Promise<RunResult<Output>>;
|
|
670
|
+
};
|
|
671
|
+
/**
|
|
672
|
+
* What a workflow IS, behind the callable that opens an inline run for it. A durable executor needs
|
|
673
|
+
* the body and the announcement rule and has no use for `run`, which opens a run of its own.
|
|
674
|
+
*/
|
|
675
|
+
type WorkflowDefinition<Input, Output, Scope extends object> = {
|
|
676
|
+
announce: (output: Output) => readonly Announcement[];
|
|
677
|
+
body: WorkflowBody<Input, Output, Scope>;
|
|
678
|
+
/** What the definition declared (#210). The durable door reads it before it carries anything. */
|
|
679
|
+
execution: WorkflowExecution;
|
|
680
|
+
name: string;
|
|
681
|
+
};
|
|
682
|
+
declare const workflowDefinitionOf: <Input, Output, Scope extends object>(workflow: Workflow<Input, Output, Scope>) => WorkflowDefinition<Input, Output, Scope>;
|
|
683
|
+
/**
|
|
684
|
+
* Declare a workflow: a name, and a body that is an ordinary async function.
|
|
685
|
+
*
|
|
686
|
+
* The body's values are REAL. A step call answers with what the step returned, an `if` is an `if`,
|
|
687
|
+
* and a loop is a loop — which is why there is no `transform` and no `when` here. Medusa needs both
|
|
688
|
+
* because its body runs once at definition time to build a graph, so everything inside it is a
|
|
689
|
+
* placeholder; a body that is executed rather than compiled has nothing to transform.
|
|
690
|
+
*
|
|
691
|
+
* What the body must NOT do is depend on anything but its input and its step results. A durable
|
|
692
|
+
* platform memoises steps by name and a replay has to arrive at the same names, so a free read that
|
|
693
|
+
* gates a branch changes the branch on the replay — see the durable-body law in the README.
|
|
694
|
+
*/
|
|
695
|
+
declare const createWorkflow: <Input, Output, Scope extends object = Record<string, unknown>>(name: string, body: WorkflowBody<Input, Output, Scope>, options?: WorkflowOptions<Input, Output>) => Workflow<Input, Output, Scope>;
|
|
696
|
+
|
|
697
|
+
type RuntimeConfig<Scope extends object> = {
|
|
698
|
+
actor?: string | null;
|
|
699
|
+
/** Whatever the caller scoped this request with beyond the tenant and the actor. */
|
|
700
|
+
context?: Scope;
|
|
701
|
+
/** The emit door's configuration: the producer's `source`, the clock, and the extension names. */
|
|
702
|
+
emit?: EmitConfig;
|
|
703
|
+
events?: EventSink;
|
|
704
|
+
journal: RunJournal;
|
|
705
|
+
observer?: RunObserver;
|
|
706
|
+
/** What a DURABLE step's body and its undo are each wrapped in. Inline runs ignore it. */
|
|
707
|
+
scope?: RunScope;
|
|
708
|
+
tenantId?: string;
|
|
709
|
+
};
|
|
710
|
+
/** What a caller may say about one call once the runtime is already known. */
|
|
711
|
+
type BoundRunOptions<Input> = {
|
|
712
|
+
input: Input;
|
|
713
|
+
idempotencyKey?: string;
|
|
714
|
+
parentRunId?: string | null;
|
|
715
|
+
throwOnError?: boolean;
|
|
716
|
+
};
|
|
717
|
+
type BoundWorkflow<Input, Output> = {
|
|
718
|
+
readonly name: string;
|
|
719
|
+
run(options: BoundRunOptions<Input> & {
|
|
720
|
+
throwOnError?: true;
|
|
721
|
+
}): Promise<Extract<RunResult<Output>, {
|
|
722
|
+
ok: true;
|
|
723
|
+
}>>;
|
|
724
|
+
run(options: BoundRunOptions<Input> & {
|
|
725
|
+
throwOnError: false;
|
|
726
|
+
}): Promise<RunResult<Output>>;
|
|
727
|
+
};
|
|
728
|
+
type Definitions<Scope extends object> = Record<string, Workflow<never, unknown, Scope>>;
|
|
729
|
+
type Bound<Given> = {
|
|
730
|
+
[Name in keyof Given]: Given[Name] extends Workflow<infer Input, infer Output, never> ? BoundWorkflow<Input, Output> : never;
|
|
731
|
+
};
|
|
732
|
+
type Runtime<Scope extends object> = {
|
|
733
|
+
/** The same runtime, knowing one more thing. Adds; never replaces. */
|
|
734
|
+
for(narrower: {
|
|
735
|
+
actor?: string | null;
|
|
736
|
+
context?: Scope;
|
|
737
|
+
tenantId?: string;
|
|
738
|
+
}): Runtime<Scope>;
|
|
739
|
+
bind<Given extends Record<string, Workflow<never, unknown, Scope>>>(definitions: Given): Bound<Given>;
|
|
740
|
+
readonly runtime: WorkflowRuntime<Scope>;
|
|
741
|
+
};
|
|
742
|
+
/**
|
|
743
|
+
* Wire the engine once, and narrow it per request.
|
|
744
|
+
*
|
|
745
|
+
* Two lifetimes, kept apart. What the workflows ARE — their steps, their undos, their announcements
|
|
746
|
+
* — is static and is built once per isolate by `createStep` and `createWorkflow`. WHO is asking, and
|
|
747
|
+
* on which tenant, belongs to one request and is bound here. The consequence worth having is that a
|
|
748
|
+
* definition can be declared at module scope with nothing attached to it, which is why a test can
|
|
749
|
+
* build a throwaway runtime around the very same definitions a deployment uses.
|
|
750
|
+
*/
|
|
751
|
+
declare const createRuntime: <Scope extends object = Record<string, unknown>>(config: RuntimeConfig<Scope>) => Runtime<Scope>;
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* What a body is inside while it runs: the one thing a step call needs in order to reach the run it
|
|
755
|
+
* belongs to.
|
|
756
|
+
*
|
|
757
|
+
* AsyncLocalStorage is the only thing that answers "which run am I in" correctly when two runs are
|
|
758
|
+
* in flight at once, which in a server is always. It needs Node 16+, Bun, Deno, or a Cloudflare
|
|
759
|
+
* Worker with `nodejs_compat` — the same requirement the Workers runtime already puts on most
|
|
760
|
+
* libraries, and the reason the import is at the top rather than behind a fallback that would be
|
|
761
|
+
* quietly wrong under concurrency.
|
|
762
|
+
*/
|
|
763
|
+
type RunFrame = {
|
|
764
|
+
call: <Input, Output>(step: Step<never, Input, Output>, input: Input) => Promise<Output>;
|
|
765
|
+
runId: string;
|
|
766
|
+
workflowName: string;
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
type RunExecution<Output> = {
|
|
770
|
+
/**
|
|
771
|
+
* Which executor is driving. It decides one thing: whether `scope.perStep` is honoured, because a
|
|
772
|
+
* durable instance hibernates between steps and an inline run does not.
|
|
773
|
+
*/
|
|
774
|
+
/** What the run announces once it is a fact, given what the body returned. */
|
|
775
|
+
announce?: (output: Output) => readonly (readonly [type: string, payload: unknown])[];
|
|
776
|
+
execution: WorkflowExecution;
|
|
777
|
+
invoke: (frame: RunFrame) => Promise<Output>;
|
|
778
|
+
name: string;
|
|
779
|
+
runId: string;
|
|
780
|
+
runner: StepRunner;
|
|
781
|
+
runtime: WorkflowRuntime;
|
|
782
|
+
/**
|
|
783
|
+
* What the run's trail said before this invocation began. Empty for an inline run, which cannot
|
|
784
|
+
* be re-invoked; read from the journal by a durable executor.
|
|
785
|
+
*/
|
|
786
|
+
completedSteps?: ReadonlySet<string>;
|
|
787
|
+
refused?: ReadonlySet<string>;
|
|
788
|
+
unwindingBegan?: boolean;
|
|
789
|
+
};
|
|
790
|
+
/**
|
|
791
|
+
* One run, from the first step to the write that closes it.
|
|
792
|
+
*
|
|
793
|
+
* Ordering, the step trail, the reverse-start unwinding and the closing batch are ONE implementation
|
|
794
|
+
* whatever executes the steps: the inline executor calls them, a durable platform checkpoints them,
|
|
795
|
+
* and the only difference between the two is the `runner` handed in.
|
|
796
|
+
*/
|
|
797
|
+
declare const executeRun: <Output>(execution: RunExecution<Output>) => Promise<Output>;
|
|
798
|
+
|
|
799
|
+
type DurableExecution<Input, Output, Scope extends object> = {
|
|
800
|
+
input: Input;
|
|
801
|
+
/** The run record, opened before the instance existed. This invocation only ever writes to it. */
|
|
802
|
+
runId: string;
|
|
803
|
+
runtime: WorkflowRuntime<Scope>;
|
|
804
|
+
/** The platform's step primitives, which checkpoint and retry each unit of work. */
|
|
805
|
+
step: StepPrimitive;
|
|
806
|
+
workflow: Workflow<Input, Output, Scope>;
|
|
807
|
+
};
|
|
808
|
+
/**
|
|
809
|
+
* The same body an inline run would carry out, driven through a durable platform instead.
|
|
810
|
+
*
|
|
811
|
+
* The two reads at the top are what make every ordering claim in CONTRACT.md hold across a
|
|
812
|
+
* re-invocation, and they are REQUIRED rather than feature-detected (D1): the run record says
|
|
813
|
+
* whether there is anything left to do, and the trail says how far the last invocation got.
|
|
814
|
+
*/
|
|
815
|
+
declare const executeDurable: <Input, Output, Scope extends object>(execution: DurableExecution<Input, Output, Scope>) => Promise<Output>;
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* A budget in the platform's words. The plural is not a typo and not a preference: Cloudflare reads
|
|
819
|
+
* `retries`, and `delay` is required there while the engine's is optional, so a budget that named a
|
|
820
|
+
* limit and no delay would otherwise reach the platform as a config it refuses.
|
|
821
|
+
*/
|
|
822
|
+
type PlatformStepConfig = {
|
|
823
|
+
retries?: {
|
|
824
|
+
backoff?: StepBackoff;
|
|
825
|
+
delay: number | string;
|
|
826
|
+
limit: number;
|
|
827
|
+
};
|
|
828
|
+
timeout?: number | string;
|
|
829
|
+
};
|
|
830
|
+
/**
|
|
831
|
+
* What a durable instance is handed to checkpoint its work with — Cloudflare's `WorkflowStep`,
|
|
832
|
+
* described structurally and taken as an argument.
|
|
833
|
+
*
|
|
834
|
+
* Declared as METHODS so TypeScript checks their parameters bivariantly: the real one spells a
|
|
835
|
+
* duration as a template literal and constrains its outputs to what it can serialise, and a
|
|
836
|
+
* structural type that insisted on neither would refuse the very object it is written for.
|
|
837
|
+
*/
|
|
838
|
+
type PlatformStep = {
|
|
839
|
+
do<Output>(name: string, config: PlatformStepConfig, run: (context: {
|
|
840
|
+
attempt: number;
|
|
841
|
+
}) => Promise<Output>): Promise<Output>;
|
|
842
|
+
sleep(name: string, duration: number | string): Promise<void>;
|
|
843
|
+
waitForEvent<Payload>(name: string, options: {
|
|
844
|
+
timeout?: number | string;
|
|
845
|
+
type: string;
|
|
846
|
+
}): Promise<{
|
|
847
|
+
payload: Payload;
|
|
848
|
+
}>;
|
|
849
|
+
};
|
|
850
|
+
/**
|
|
851
|
+
* The `StepPrimitive` seam over a real workflow instance's step object. This file is the only place
|
|
852
|
+
* in the package that knows the durable executor runs on Cloudflare Workflows — which is what lets
|
|
853
|
+
* that executor be tested without a worker runtime, and what makes an adapter for the next platform
|
|
854
|
+
* about twenty lines.
|
|
855
|
+
*/
|
|
856
|
+
declare const createStepPrimitive: (step: PlatformStep) => StepPrimitive;
|
|
857
|
+
|
|
858
|
+
/** What a durable instance is started with: which workflow, for which run, on whose behalf. */
|
|
859
|
+
type DurableRunParams = {
|
|
860
|
+
actor?: string | null;
|
|
861
|
+
input: unknown;
|
|
862
|
+
name: string;
|
|
863
|
+
runId: string;
|
|
864
|
+
tenantId: string;
|
|
865
|
+
};
|
|
866
|
+
type DurableRunEvent = {
|
|
867
|
+
payload: DurableRunParams;
|
|
868
|
+
};
|
|
869
|
+
/**
|
|
870
|
+
* `WorkflowEntrypoint` from `cloudflare:workers`, handed in rather than imported.
|
|
871
|
+
*
|
|
872
|
+
* That module exists only inside a worker, so a package that imported it could not be loaded by a
|
|
873
|
+
* test, by a Node process, or by any other runtime — and this one is a foundation every host has to
|
|
874
|
+
* be able to read. The class is the caller's; what it does with a run is this file's.
|
|
875
|
+
*
|
|
876
|
+
* `abstract` because the real `WorkflowEntrypoint` is, and a non-abstract constructor type refuses
|
|
877
|
+
* it. The execution context is `never` because nothing here reads it and the platform's own type for
|
|
878
|
+
* it is a class this package cannot name: a parameter is checked contravariantly, so `never` is the
|
|
879
|
+
* one spelling that accepts whatever the real base declares.
|
|
880
|
+
*/
|
|
881
|
+
type EntrypointBase<Env> = abstract new (context: never, env: Env) => object;
|
|
882
|
+
/** A value, or how to make one out of a worker's env. */
|
|
883
|
+
type FromEnv<Env, Value> = Value | ((env: Env) => Value);
|
|
884
|
+
type DurableEntrypointOptions<Env, Scope extends object> = {
|
|
885
|
+
base: EntrypointBase<Env>;
|
|
886
|
+
/**
|
|
887
|
+
* Called for EVERY run. What a factory opens — a database client above all — belongs to the
|
|
888
|
+
* invocation that opened it: an instance hibernates between steps and the platform refuses a
|
|
889
|
+
* handle from an earlier invocation at the first query.
|
|
890
|
+
*/
|
|
891
|
+
runtime: (env: Env, params: DurableRunParams) => Runtime<Scope>;
|
|
892
|
+
workflows: FromEnv<Env, readonly Workflow<unknown, unknown, Scope>[]>;
|
|
893
|
+
};
|
|
894
|
+
/**
|
|
895
|
+
* One entrypoint class for every durable definition a worker has.
|
|
896
|
+
*
|
|
897
|
+
* Cloudflare binds a CLASS, not a function, so a binding per workflow would mean a config edit and a
|
|
898
|
+
* deploy for every new one — and once per named environment, because those inherit nothing. This
|
|
899
|
+
* dispatches by the name the instance was started with, so adding a workflow is adding it to the
|
|
900
|
+
* list and nothing else.
|
|
901
|
+
*/
|
|
902
|
+
declare const entrypointFor: <Env, Scope extends object = Record<string, unknown>>(options: DurableEntrypointOptions<Env, Scope>) => {
|
|
903
|
+
new (context: never, env: Env): {
|
|
904
|
+
run(event: DurableRunEvent, step: PlatformStep): Promise<unknown>;
|
|
905
|
+
};
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
/** One delivered message, as a queue consumer is handed it. */
|
|
909
|
+
type QueueMessage = {
|
|
910
|
+
ack: () => void;
|
|
911
|
+
body: unknown;
|
|
912
|
+
retry: () => void;
|
|
913
|
+
};
|
|
914
|
+
type QueueBatch = {
|
|
915
|
+
messages: readonly QueueMessage[];
|
|
916
|
+
};
|
|
917
|
+
type QueueOptions = {
|
|
918
|
+
/** What to do with each event. Without one, a delivered message is only acknowledged. */
|
|
919
|
+
onEvent?: (envelope: EventEnvelope) => Promise<void> | void;
|
|
920
|
+
/**
|
|
921
|
+
* Whether this envelope has been seen before — `@geonosis/events`' half of the outbox. Delivery is
|
|
922
|
+
* at-least-once, so a consumer that does anything a repeat would do twice needs one of these,
|
|
923
|
+
* usually a unique index on the envelope id in whatever table the consumer writes.
|
|
924
|
+
*/
|
|
925
|
+
seen?: SeenBefore;
|
|
926
|
+
};
|
|
927
|
+
/**
|
|
928
|
+
* The queue consumer, for the events the outbox delivers.
|
|
929
|
+
*
|
|
930
|
+
* Each message is acknowledged or retried on its own: one message this consumer cannot handle does
|
|
931
|
+
* not send the other nine round again. A dedup check that could not ANSWER is a retry rather than a
|
|
932
|
+
* delivery, because "not known to have been seen" and "not seen" are the same value and not the same
|
|
933
|
+
* fact.
|
|
934
|
+
*/
|
|
935
|
+
declare const handleQueue: (options?: QueueOptions) => (batch: QueueBatch) => Promise<void>;
|
|
936
|
+
type SweepOptions = {
|
|
937
|
+
/** How long an inline run may be open before it is declared abandoned. Defaults to 15 minutes. */
|
|
938
|
+
abandonedAfterMs?: number;
|
|
939
|
+
/** How long an outbox row is left for the run that made it. Defaults to a minute. */
|
|
940
|
+
outboxOlderThanMs?: number;
|
|
941
|
+
};
|
|
942
|
+
type Swept = {
|
|
943
|
+
abandoned: number;
|
|
944
|
+
delivered: number;
|
|
945
|
+
};
|
|
946
|
+
/**
|
|
947
|
+
* Both sweeps, on whatever cron you point at this.
|
|
948
|
+
*
|
|
949
|
+
* They are separate concerns with the same shape — one carries the events a drain could not deliver,
|
|
950
|
+
* the other closes inline runs whose process died — and there is no reason to make anybody wire two
|
|
951
|
+
* handlers for them. Both windows are measured from the moment the cron was SCHEDULED for rather
|
|
952
|
+
* than from the clock the sweep happens to read, which is also what lets a test ask for tomorrow's
|
|
953
|
+
* cron instead of waiting for it.
|
|
954
|
+
*
|
|
955
|
+
* A durable run is never swept at any age: one may be asleep for a week or waiting on a human. That
|
|
956
|
+
* is also the boundary this handler stops at — a sweep that could close a run another invocation is
|
|
957
|
+
* driving needs the claim interlock the journaled executor brings (plan 026, W6), and until then the
|
|
958
|
+
* two sweeps only ever touch work nothing is carrying.
|
|
959
|
+
*/
|
|
960
|
+
declare const handleScheduled: <Env, Scope extends object>(source: FromEnv<Env, Runtime<Scope>>, options?: SweepOptions) => (controller?: unknown, env?: Env) => Promise<Swept>;
|
|
961
|
+
|
|
962
|
+
type MemoryPlatform = {
|
|
963
|
+
/** The budget each step was handed. A durable platform retries by this; the engine does not. */
|
|
964
|
+
budgets: Map<string, StepBudget>;
|
|
965
|
+
/** Every step this platform was asked to carry out, in order, the engine's own included. */
|
|
966
|
+
calls: string[];
|
|
967
|
+
/** What it has checkpointed, by step name. It outlives an invocation, as a real checkpoint does. */
|
|
968
|
+
checkpoints: Map<string, unknown>;
|
|
969
|
+
/**
|
|
970
|
+
* Stop answering at this step, as an evicted instance does — the invocation ends and the run does
|
|
971
|
+
* not. It is the one thing a step cannot express: a step that throws is a failure the engine
|
|
972
|
+
* unwinds, and an eviction is the engine never hearing another word.
|
|
973
|
+
*/
|
|
974
|
+
evictAt: (stepName: string | null) => void;
|
|
975
|
+
primitive: StepPrimitive;
|
|
976
|
+
};
|
|
977
|
+
/**
|
|
978
|
+
* A durable platform in memory: the worked reference for the `StepPrimitive` seam, and what a suite
|
|
979
|
+
* drives `executeDurable` through when it has no worker runtime.
|
|
980
|
+
*
|
|
981
|
+
* It does the three things a real one does — memoise a completed step by NAME, retry a step with the
|
|
982
|
+
* budget it was handed, and hand each attempt its number — so a body that walks a different path on
|
|
983
|
+
* a replay fails here exactly as it would on the platform.
|
|
984
|
+
*/
|
|
985
|
+
declare const createMemoryPlatform: (options?: {
|
|
986
|
+
checkpoints?: Map<string, unknown>;
|
|
987
|
+
}) => MemoryPlatform;
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* A journal under test, plus the two readings a caller has to supply because the contract itself
|
|
991
|
+
* has no way to make them: what status a run is in, and how many step rows a run has. Both are one
|
|
992
|
+
* query in any store.
|
|
993
|
+
*/
|
|
994
|
+
type JournalSubject = {
|
|
995
|
+
/**
|
|
996
|
+
* Make outbox writes fail from now on. Required, because the case it enables is the strongest
|
|
997
|
+
* promise in the contract and an adapter that cannot be broken on purpose cannot prove it.
|
|
998
|
+
* Dropping the table, revoking the permission or setting a flag are all fine.
|
|
999
|
+
*/
|
|
1000
|
+
breakOutboxWrites: () => Promise<void> | void;
|
|
1001
|
+
countSteps: (params: {
|
|
1002
|
+
runId: string;
|
|
1003
|
+
tenantId: string;
|
|
1004
|
+
}) => Promise<number>;
|
|
1005
|
+
journal: RunJournal;
|
|
1006
|
+
runStatus: (params: {
|
|
1007
|
+
runId: string;
|
|
1008
|
+
tenantId: string;
|
|
1009
|
+
}) => Promise<RunStatus | null>;
|
|
1010
|
+
};
|
|
1011
|
+
type ConformanceCase = {
|
|
1012
|
+
name: string;
|
|
1013
|
+
run: () => Promise<void>;
|
|
1014
|
+
};
|
|
1015
|
+
/**
|
|
1016
|
+
* The RunJournal contract, as an executable suite.
|
|
1017
|
+
*
|
|
1018
|
+
* The contract is not a shape — TypeScript already checks the shape — it is a set of promises about
|
|
1019
|
+
* BEHAVIOUR that the engine relies on absolutely: that a held key is refused, that a finish is one
|
|
1020
|
+
* write, that a step row is written once however often it is retried. An adapter that type-checks
|
|
1021
|
+
* and breaks one of those breaks the engine in ways that only show up in production.
|
|
1022
|
+
*
|
|
1023
|
+
* Runner-agnostic on purpose. Wire it into whatever you use:
|
|
1024
|
+
*
|
|
1025
|
+
* ```ts
|
|
1026
|
+
* for (const one of journalConformance(createSubject)) it(one.name, one.run)
|
|
1027
|
+
* ```
|
|
1028
|
+
*/
|
|
1029
|
+
declare const journalConformance: (createSubject: () => JournalSubject | Promise<JournalSubject>) => ConformanceCase[];
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* What a step says about itself beyond its name.
|
|
1033
|
+
*
|
|
1034
|
+
* `noCompensation` is the DECLARED absence of an undo, and it exists because a deliberate
|
|
1035
|
+
* non-compensation and a forgotten one are otherwise the same source. It is `true` or it is absent:
|
|
1036
|
+
* `false` would be the inference written out, which says nothing the missing third argument did not
|
|
1037
|
+
* already say.
|
|
1038
|
+
*/
|
|
1039
|
+
type StepConfig = StepBudget & {
|
|
1040
|
+
name: string;
|
|
1041
|
+
noCompensation?: true;
|
|
1042
|
+
};
|
|
1043
|
+
type StepInvoke<Scope, Input, Output> = (input: Input, ctx: StepContext<Scope>) => MaybePromise<Output>;
|
|
1044
|
+
type StepCompensate<Scope, Output> = (output: Output, ctx: StepContext<Scope>, reason: CompensationReason) => MaybePromise<void>;
|
|
1045
|
+
/**
|
|
1046
|
+
* A step, as a body calls it: `const { saved } = await save(doc)`. The value it answers with is the
|
|
1047
|
+
* value `invoke` returned — a real value in a real async body, not a placeholder in a graph.
|
|
1048
|
+
*/
|
|
1049
|
+
type StepCall<Input, Output> = {
|
|
1050
|
+
(input: Input): Promise<Output>;
|
|
1051
|
+
readonly stepName: string;
|
|
1052
|
+
};
|
|
1053
|
+
/**
|
|
1054
|
+
* The step the engine runs, behind the callable a body sees. Its scope is `never` because the
|
|
1055
|
+
* engine holds steps from every scope in one run: `invoke` and `compensate` are declared as methods,
|
|
1056
|
+
* so TypeScript checks their parameters bivariantly and a heterogeneous list needs no cast.
|
|
1057
|
+
*/
|
|
1058
|
+
declare const stepDefinitionOf: <Input, Output>(call: StepCall<Input, Output>) => Step<never, Input, Output>;
|
|
1059
|
+
/**
|
|
1060
|
+
* Declare a step: a named unit of work whose undo travels with it.
|
|
1061
|
+
*
|
|
1062
|
+
* Positional and in Medusa's spelling — `createStep(nameOrConfig, invoke, compensate?)` — so one set
|
|
1063
|
+
* of architecture rules reads both engines. The undo is handed exactly what `invoke` RETURNED, and
|
|
1064
|
+
* is registered from that returned value rather than from a closure taken during the step, because
|
|
1065
|
+
* a closure does not survive a replay: on the second invocation the step's body does not run, and
|
|
1066
|
+
* anything living inside it is gone.
|
|
1067
|
+
*/
|
|
1068
|
+
declare function createStep<Input, Output, Scope = Record<string, unknown>>(config: StepConfig | string, invoke: StepInvoke<Scope, Input, Output>, compensate?: StepCompensate<Scope, Output>): StepCall<Input, Output>;
|
|
1069
|
+
|
|
1070
|
+
type MemoryRunRow = {
|
|
1071
|
+
cancelRequested: boolean;
|
|
1072
|
+
error?: string | null;
|
|
1073
|
+
execution: WorkflowExecution;
|
|
1074
|
+
finishedAt?: number | null;
|
|
1075
|
+
id: string;
|
|
1076
|
+
idempotencyKey: string | null;
|
|
1077
|
+
input: unknown;
|
|
1078
|
+
name: string;
|
|
1079
|
+
output?: unknown;
|
|
1080
|
+
parentRunId: string | null;
|
|
1081
|
+
replayOf?: string | null;
|
|
1082
|
+
startedAt: number;
|
|
1083
|
+
status: RunStatus;
|
|
1084
|
+
tenantId: string;
|
|
1085
|
+
};
|
|
1086
|
+
type MemoryStepRow = {
|
|
1087
|
+
attempt: number;
|
|
1088
|
+
error?: string | null;
|
|
1089
|
+
name: string;
|
|
1090
|
+
output?: unknown;
|
|
1091
|
+
runId: string;
|
|
1092
|
+
seq: number;
|
|
1093
|
+
status: string;
|
|
1094
|
+
tenantId: string;
|
|
1095
|
+
};
|
|
1096
|
+
type MemoryFinishRow = {
|
|
1097
|
+
error?: string | null;
|
|
1098
|
+
events: EventEnvelope[];
|
|
1099
|
+
output?: unknown;
|
|
1100
|
+
runId: string;
|
|
1101
|
+
status: RunOutcome;
|
|
1102
|
+
};
|
|
1103
|
+
/**
|
|
1104
|
+
* An outbox row. The tenant is a COLUMN and not a field of the envelope: the envelope is the kit's
|
|
1105
|
+
* (D-057), where a tenant is a flat extension the consumer names — so the store that has to sweep
|
|
1106
|
+
* per tenant keeps its own copy of the one fact it queries by.
|
|
1107
|
+
*/
|
|
1108
|
+
type MemoryOutboxRow = {
|
|
1109
|
+
envelope: EventEnvelope;
|
|
1110
|
+
tenantId: string;
|
|
1111
|
+
};
|
|
1112
|
+
/** The journal, plus the rows it is holding, so a test asserts on what was written. */
|
|
1113
|
+
type MemoryJournal = {
|
|
1114
|
+
/**
|
|
1115
|
+
* Make every outbox write fail from here on, so a suite can prove that a finish which cannot
|
|
1116
|
+
* queue its events does not close the run either.
|
|
1117
|
+
*/
|
|
1118
|
+
breakOutboxWrites: () => void;
|
|
1119
|
+
dispatched: string[];
|
|
1120
|
+
finishes: MemoryFinishRow[];
|
|
1121
|
+
journal: RunJournal;
|
|
1122
|
+
outbox: MemoryOutboxRow[];
|
|
1123
|
+
runs: MemoryRunRow[];
|
|
1124
|
+
steps: MemoryStepRow[];
|
|
1125
|
+
};
|
|
1126
|
+
/**
|
|
1127
|
+
* The journal a test reaches for, and the worked reference for writing your own: rows in arrays,
|
|
1128
|
+
* and the one invariant a real table has to enforce — a single claim per (tenant, idempotency key)
|
|
1129
|
+
* — enforced here too, so a suite exercises the engine's behaviour rather than a fixture's
|
|
1130
|
+
* convenience.
|
|
1131
|
+
*/
|
|
1132
|
+
declare const createMemoryJournal: (options?: {
|
|
1133
|
+
now?: () => number;
|
|
1134
|
+
}) => MemoryJournal;
|
|
1135
|
+
type MemorySink = {
|
|
1136
|
+
/** One entry per `sendBatch` call, so a suite can ask how many calls a drain made. */
|
|
1137
|
+
batches: EventEnvelope[][];
|
|
1138
|
+
/** Every message that travelled, flattened. */
|
|
1139
|
+
sent: EventEnvelope[];
|
|
1140
|
+
sink: EventSink;
|
|
1141
|
+
};
|
|
1142
|
+
/** A sink that remembers what travelled, and in how many calls. */
|
|
1143
|
+
declare const createMemorySink: (options?: {
|
|
1144
|
+
refuses?: boolean;
|
|
1145
|
+
}) => MemorySink;
|
|
1146
|
+
/**
|
|
1147
|
+
* A sink that hands each envelope straight to a function, in the same process. Delivery is still
|
|
1148
|
+
* at-least-once from the engine's point of view — the drain can fail and the sweeper carries the
|
|
1149
|
+
* rows — so a handler here should be as tolerant of a repeat as a queue consumer would be.
|
|
1150
|
+
*/
|
|
1151
|
+
declare const createInProcessSink: (deliver: (envelope: EventEnvelope) => Promise<void> | void) => EventSink;
|
|
1152
|
+
|
|
1153
|
+
export { ABANDONED_SWEEP_LIMIT, type Announcement, type Bound, type BoundRunOptions, type BoundWorkflow, COMPENSATION_PREFIX, type CompensationOutcome, type CompensationReason, type ConformanceCase, DEFAULT_STEP_BUDGET, type Definitions, type DurableEntrypointOptions, type DurableExecution, type DurableRunEvent, type DurableRunParams, EVENT_BATCH_LIMIT, EVENT_SWEEP_GRACE_MS, EVENT_SWEEP_LIMIT, type EmitConfig, type EntrypointBase, type EventSink, type FromEnv, IdempotencyKeyHeldError, type JournalSubject, LIFECYCLE_EVENTS, type LifecycleEventType, type MaybePromise, type MemoryFinishRow, type MemoryJournal, type MemoryOutboxRow, type MemoryPlatform, type MemoryRunRow, type MemorySink, type MemoryStepRow, type PlatformStep, type PlatformStepConfig, type QueueBatch, type QueueMessage, type QueueOptions, RESERVED_STEP_NAMES, RUN_OUTCOMES, RUN_STATUSES, RunCancelledError, type RunClosure, type RunEmitter, type RunExecution, type RunFacts, RunFailedError, type RunJournal, type RunObserver, type RunOptions, type RunOutcome, type RunResult, type RunScope, type RunStatus, type Runtime, type RuntimeConfig, STEP_STATUSES, type Step, type StepBackoff, type StepBudget, type StepCall, type StepCompensate, type StepConfig, type StepContext, type StepInvoke, type StepPrimitive, type StepRetry, type StepRunner, type StepScope, type StepStatus, type SweepOptions, type Swept, type Workflow, type WorkflowBody, type WorkflowDefinition, type WorkflowExecution, type WorkflowOptions, type WorkflowRuntime, WorkflowsError, compensationIdempotencyKey, compensationStepName, createInProcessSink, createInlineRunner, createMemoryJournal, createMemoryPlatform, createMemorySink, createRunEmitter, createRuntime, createStep, createStepPrimitive, createWorkflow, dispatchEvents, entrypointFor, envelopeId, executeDurable, executeRun, handleQueue, handleScheduled, journalConformance, lifecycleEnvelopeId, messageOf, parallelize, stepDefinitionOf, stepIdempotencyKey, sweepAbandonedRuns, sweepEventOutbox, workflowDefinitionOf };
|