yieldstar 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +410 -0
- package/dist/exports/workflow.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +242 -38
- package/dist/internal/serialise.d.ts.map +1 -1
- package/dist/internal/step-runner.d.ts +25 -4
- package/dist/internal/step-runner.d.ts.map +1 -1
- package/dist/internal/utils.d.ts +11 -0
- package/dist/internal/utils.d.ts.map +1 -1
- package/package.json +10 -3
- package/src/exports/workflow.ts +7 -3
- package/src/index.ts +18 -0
- package/src/internal/serialise.ts +3 -0
- package/src/internal/step-runner.ts +406 -35
- package/src/internal/utils.ts +24 -10
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/exports/scheduler.d.ts +0 -49
- package/dist/exports/scheduler.d.ts.map +0 -1
- package/dist/exports/simplified-types.d.ts +0 -31
- package/dist/exports/simplified-types.d.ts.map +0 -1
- package/dist/exports/types.d.ts +0 -6
- package/dist/exports/types.d.ts.map +0 -1
- package/dist/internal/types.d.ts +0 -9
- package/dist/internal/types.d.ts.map +0 -1
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import {
|
|
2
|
+
cloneStoreState,
|
|
3
|
+
defineStore,
|
|
4
|
+
type Draft,
|
|
5
|
+
type StandardSchemaV1,
|
|
2
6
|
StepResponse,
|
|
3
7
|
StepKey,
|
|
4
8
|
StepError,
|
|
5
9
|
StepResult,
|
|
6
10
|
StepDelay,
|
|
7
11
|
StepCacheCheck,
|
|
12
|
+
StepStoreWait,
|
|
13
|
+
type StoreClient,
|
|
14
|
+
type StoreDefinition,
|
|
15
|
+
type StoreDeleteFromResult,
|
|
16
|
+
type StoreKey,
|
|
17
|
+
type StoreSelector,
|
|
18
|
+
type StoreSnapshot,
|
|
19
|
+
type StoreState,
|
|
20
|
+
type StoreTakeResult,
|
|
21
|
+
type StoreUpdateFromResult,
|
|
22
|
+
type StoreUpdateResult,
|
|
23
|
+
trackStoreSelector,
|
|
8
24
|
} from "@yieldstar/core";
|
|
9
25
|
import { RetryableError } from "../exports/errors";
|
|
10
26
|
import { getCallSiteHash } from "./utils";
|
|
27
|
+
import type { WorkflowEvent } from "@yieldstar/core";
|
|
11
28
|
|
|
12
29
|
/**
|
|
13
30
|
* @description A library of step generators, each of which:
|
|
@@ -17,9 +34,228 @@ import { getCallSiteHash } from "./utils";
|
|
|
17
34
|
* @throws any error caught when running the user-defined function,
|
|
18
35
|
* once retries have been exhausted, or if there are no retry semantics
|
|
19
36
|
*/
|
|
20
|
-
export
|
|
37
|
+
export { defineStore };
|
|
21
38
|
|
|
22
|
-
export type
|
|
39
|
+
export type WorkflowStore<T> = {
|
|
40
|
+
readonly definition: StoreDefinition<any>;
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly key: StoreKey;
|
|
43
|
+
get(key?: string): AsyncGenerator<StepResponse, StoreSnapshot<T>>;
|
|
44
|
+
select<R>(
|
|
45
|
+
key: string,
|
|
46
|
+
selector: StoreSelector<T, R>
|
|
47
|
+
): AsyncGenerator<StepResponse, R>;
|
|
48
|
+
update(
|
|
49
|
+
key: string,
|
|
50
|
+
updater: (draft: Draft<T>) => void | T
|
|
51
|
+
): AsyncGenerator<StepResponse, StoreUpdateResult<T>>;
|
|
52
|
+
updateFrom(
|
|
53
|
+
key: string,
|
|
54
|
+
snapshot: StoreSnapshot<T>,
|
|
55
|
+
updater: (draft: Draft<T>) => void | T
|
|
56
|
+
): AsyncGenerator<StepResponse, StoreUpdateFromResult<T>>;
|
|
57
|
+
deleteFrom(
|
|
58
|
+
key: string,
|
|
59
|
+
snapshot: StoreSnapshot<T>
|
|
60
|
+
): AsyncGenerator<StepResponse, StoreDeleteFromResult>;
|
|
61
|
+
when<R>(
|
|
62
|
+
selector: StoreSelector<T, R | undefined | null | false>
|
|
63
|
+
): AsyncGenerator<StepResponse, NonNullable<R>>;
|
|
64
|
+
when<R>(
|
|
65
|
+
key: string,
|
|
66
|
+
selector: StoreSelector<T, R | undefined | null | false>
|
|
67
|
+
): AsyncGenerator<StepResponse, NonNullable<R>>;
|
|
68
|
+
take<R>(
|
|
69
|
+
key: string,
|
|
70
|
+
selector: StoreSelector<T, R | undefined | null | false>,
|
|
71
|
+
claim: (draft: Draft<T>, selected: NonNullable<R>) => void
|
|
72
|
+
): AsyncGenerator<StepResponse, NonNullable<R>>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type StepRunner = ReturnType<typeof createStepRunner>;
|
|
76
|
+
|
|
77
|
+
export function createStepRunner(params: {
|
|
78
|
+
event: WorkflowEvent;
|
|
79
|
+
storeClient: StoreClient;
|
|
80
|
+
}) {
|
|
81
|
+
const { event, storeClient } = params;
|
|
82
|
+
|
|
83
|
+
function store<Schema extends StandardSchemaV1>(
|
|
84
|
+
definition: StoreDefinition<Schema>,
|
|
85
|
+
params?: {
|
|
86
|
+
id?: string;
|
|
87
|
+
initial?:
|
|
88
|
+
| StoreState<Schema>
|
|
89
|
+
| (() => StoreState<Schema> | Promise<StoreState<Schema>>);
|
|
90
|
+
}
|
|
91
|
+
): AsyncGenerator<StepResponse, WorkflowStore<StoreState<Schema>>> {
|
|
92
|
+
const id = params?.id ?? event.executionId;
|
|
93
|
+
const key = `store:${definition.name}:${id}`;
|
|
94
|
+
|
|
95
|
+
return storeStep({
|
|
96
|
+
key,
|
|
97
|
+
definition,
|
|
98
|
+
id,
|
|
99
|
+
event,
|
|
100
|
+
storeClient,
|
|
101
|
+
initial: params?.initial,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { run, delay, poll, store };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function* storeStep<Schema extends StandardSchemaV1>(params: {
|
|
109
|
+
key: string;
|
|
110
|
+
definition: StoreDefinition<Schema>;
|
|
111
|
+
id: string;
|
|
112
|
+
event: WorkflowEvent;
|
|
113
|
+
storeClient: StoreClient;
|
|
114
|
+
initial?:
|
|
115
|
+
| StoreState<Schema>
|
|
116
|
+
| (() => StoreState<Schema> | Promise<StoreState<Schema>>);
|
|
117
|
+
}): AsyncGenerator<
|
|
118
|
+
StepResponse,
|
|
119
|
+
WorkflowStore<StoreState<Schema>>,
|
|
120
|
+
StepResult | StepError
|
|
121
|
+
> {
|
|
122
|
+
const { key, definition, id, event, storeClient, initial } = params;
|
|
123
|
+
|
|
124
|
+
yield new StepKey(key);
|
|
125
|
+
|
|
126
|
+
const cached = yield new StepCacheCheck();
|
|
127
|
+
|
|
128
|
+
if (cached) {
|
|
129
|
+
yield cached;
|
|
130
|
+
if (cached instanceof StepError) {
|
|
131
|
+
throw cached.err;
|
|
132
|
+
}
|
|
133
|
+
return createWorkflowStore({
|
|
134
|
+
definition,
|
|
135
|
+
id,
|
|
136
|
+
event,
|
|
137
|
+
storeClient,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
await storeClient.getOrCreateStore({
|
|
143
|
+
definition,
|
|
144
|
+
id,
|
|
145
|
+
initial,
|
|
146
|
+
});
|
|
147
|
+
yield new StepResult({ storeName: definition.name, storeId: id });
|
|
148
|
+
return createWorkflowStore({
|
|
149
|
+
definition,
|
|
150
|
+
id,
|
|
151
|
+
event,
|
|
152
|
+
storeClient,
|
|
153
|
+
});
|
|
154
|
+
} catch (err: unknown) {
|
|
155
|
+
yield new StepError(err);
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function createWorkflowStore<T>(params: {
|
|
161
|
+
definition: StoreDefinition<any>;
|
|
162
|
+
id: string;
|
|
163
|
+
event: WorkflowEvent;
|
|
164
|
+
storeClient: StoreClient;
|
|
165
|
+
}): WorkflowStore<T> {
|
|
166
|
+
const { definition, id, event, storeClient } = params;
|
|
167
|
+
const key = { storeName: definition.name, storeId: id };
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
definition,
|
|
171
|
+
id,
|
|
172
|
+
key,
|
|
173
|
+
get(stepKey?: string) {
|
|
174
|
+
return durableStep<StoreSnapshot<T>>(stepKey ?? getCallSiteHash(this.get), async () =>
|
|
175
|
+
(await storeClient.getStore({ definition, id })) as StoreSnapshot<T>
|
|
176
|
+
);
|
|
177
|
+
},
|
|
178
|
+
select<R>(stepKey: string, selector: StoreSelector<T, R>) {
|
|
179
|
+
return durableStep(stepKey, async () => {
|
|
180
|
+
const snapshot = await storeClient.getStore({ definition, id });
|
|
181
|
+
// Run the selector against a clone so accidental mutation cannot
|
|
182
|
+
// corrupt shared state (consistent with when, which throws via
|
|
183
|
+
// its tracking proxy).
|
|
184
|
+
return selector(cloneStoreState(snapshot.state as T));
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
update(stepKey, updater) {
|
|
188
|
+
return durableStep<StoreUpdateResult<T>>(stepKey, async () =>
|
|
189
|
+
(await storeClient.updateStore({
|
|
190
|
+
definition,
|
|
191
|
+
id,
|
|
192
|
+
updater: updater as any,
|
|
193
|
+
// Exactly-once across the store-commit/heap-write gap: on replay
|
|
194
|
+
// after a crash, the store's applied-steps ledger returns the
|
|
195
|
+
// recorded result instead of re-running the updater.
|
|
196
|
+
stepId: { executionId: event.executionId, stepKey },
|
|
197
|
+
})) as StoreUpdateResult<T>
|
|
198
|
+
);
|
|
199
|
+
},
|
|
200
|
+
updateFrom(stepKey, snapshot, updater) {
|
|
201
|
+
return durableStep<StoreUpdateFromResult<T>>(stepKey, async () =>
|
|
202
|
+
(await storeClient.updateStoreFrom({
|
|
203
|
+
definition,
|
|
204
|
+
id,
|
|
205
|
+
snapshot,
|
|
206
|
+
updater: updater as any,
|
|
207
|
+
stepId: { executionId: event.executionId, stepKey },
|
|
208
|
+
})) as StoreUpdateFromResult<T>
|
|
209
|
+
);
|
|
210
|
+
},
|
|
211
|
+
deleteFrom(stepKey, snapshot) {
|
|
212
|
+
return durableStep<StoreDeleteFromResult>(stepKey, () =>
|
|
213
|
+
storeClient.deleteStoreFrom({
|
|
214
|
+
definition,
|
|
215
|
+
id,
|
|
216
|
+
snapshot,
|
|
217
|
+
stepId: { executionId: event.executionId, stepKey },
|
|
218
|
+
})
|
|
219
|
+
);
|
|
220
|
+
},
|
|
221
|
+
when<R>(
|
|
222
|
+
arg1:
|
|
223
|
+
| string
|
|
224
|
+
| StoreSelector<T, R | undefined | null | false>,
|
|
225
|
+
arg2?: StoreSelector<T, R | undefined | null | false>
|
|
226
|
+
) {
|
|
227
|
+
const stepKey =
|
|
228
|
+
typeof arg1 === "string" ? arg1 : getCallSiteHash(this.when);
|
|
229
|
+
const selector = (
|
|
230
|
+
typeof arg1 === "string" ? arg2 : arg1
|
|
231
|
+
) as StoreSelector<T, R | undefined | null | false>;
|
|
232
|
+
|
|
233
|
+
return whenStep({
|
|
234
|
+
definition,
|
|
235
|
+
id,
|
|
236
|
+
event,
|
|
237
|
+
storeClient,
|
|
238
|
+
stepKey,
|
|
239
|
+
selector,
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
take<R>(
|
|
243
|
+
stepKey: string,
|
|
244
|
+
selector: StoreSelector<T, R | undefined | null | false>,
|
|
245
|
+
claim: (draft: Draft<T>, selected: NonNullable<R>) => void
|
|
246
|
+
) {
|
|
247
|
+
return takeStep({
|
|
248
|
+
definition,
|
|
249
|
+
id,
|
|
250
|
+
event,
|
|
251
|
+
storeClient,
|
|
252
|
+
stepKey,
|
|
253
|
+
selector,
|
|
254
|
+
claim,
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
23
259
|
|
|
24
260
|
function run<T extends any>(
|
|
25
261
|
fn: () => T | Promise<T>,
|
|
@@ -30,7 +266,13 @@ function run<T extends any>(
|
|
|
30
266
|
fn: () => T | Promise<T>,
|
|
31
267
|
): AsyncGenerator<StepResponse, T, StepResult | StepError>;
|
|
32
268
|
|
|
33
|
-
|
|
269
|
+
// NOTE: run/delay/poll are plain (synchronous) functions that delegate to
|
|
270
|
+
// async generators. This matters for keyless steps: the call-site hash must be
|
|
271
|
+
// captured while the user's call site is still on the stack. An async
|
|
272
|
+
// generator body doesn't execute until its first .next(), which happens inside
|
|
273
|
+
// the workflow driver, so hashing there would give every keyless step the same
|
|
274
|
+
// key.
|
|
275
|
+
function run<T extends any>(
|
|
34
276
|
arg1: string | (() => T | Promise<T>),
|
|
35
277
|
arg2?: (() => T | Promise<T>) | string,
|
|
36
278
|
): AsyncGenerator<StepResponse, T, StepResult | StepError> {
|
|
@@ -46,41 +288,13 @@ async function* run<T extends any>(
|
|
|
46
288
|
|
|
47
289
|
if (!key) key = getCallSiteHash(run);
|
|
48
290
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const cached = yield new StepCacheCheck();
|
|
52
|
-
|
|
53
|
-
if (cached) {
|
|
54
|
-
yield cached;
|
|
55
|
-
if (cached instanceof StepError) {
|
|
56
|
-
// unreachable – consumer calls throw() on the generator first
|
|
57
|
-
throw cached.err;
|
|
58
|
-
}
|
|
59
|
-
return cached.result;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
const result = await fn();
|
|
64
|
-
yield new StepResult(result);
|
|
65
|
-
return result;
|
|
66
|
-
} catch (err: unknown) {
|
|
67
|
-
if (err instanceof RetryableError) {
|
|
68
|
-
yield new StepError(err, {
|
|
69
|
-
maxAttempts: err.maxAttempts,
|
|
70
|
-
retryInterval: err.retryInterval,
|
|
71
|
-
});
|
|
72
|
-
} else {
|
|
73
|
-
yield new StepError(err);
|
|
74
|
-
}
|
|
75
|
-
// unreachable – consumer calls throw() on the generator first
|
|
76
|
-
throw err;
|
|
77
|
-
}
|
|
291
|
+
return durableStep(key, fn);
|
|
78
292
|
}
|
|
79
293
|
|
|
80
294
|
function delay(retryInterval: number): any;
|
|
81
295
|
function delay(key: string, retryInterval: number): any;
|
|
82
296
|
|
|
83
|
-
|
|
297
|
+
function delay(
|
|
84
298
|
arg1: string | number,
|
|
85
299
|
arg2?: number
|
|
86
300
|
): AsyncGenerator<any, void, StepDelay> {
|
|
@@ -96,6 +310,13 @@ async function* delay(
|
|
|
96
310
|
|
|
97
311
|
if (!key) key = getCallSiteHash(delay);
|
|
98
312
|
|
|
313
|
+
return delayStep(key, retryInterval);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function* delayStep(
|
|
317
|
+
key: string,
|
|
318
|
+
retryInterval: number
|
|
319
|
+
): AsyncGenerator<any, void, StepDelay> {
|
|
99
320
|
yield new StepKey(key);
|
|
100
321
|
|
|
101
322
|
const cached = yield new StepCacheCheck();
|
|
@@ -113,7 +334,7 @@ type PollPredicate = () => boolean | Promise<boolean>;
|
|
|
113
334
|
function poll(opts: PollOpts, predicate: PollPredicate): any;
|
|
114
335
|
function poll(key: string, opts: PollOpts, predicate: PollPredicate): any;
|
|
115
336
|
|
|
116
|
-
|
|
337
|
+
function poll(
|
|
117
338
|
arg1: string | PollOpts,
|
|
118
339
|
arg2: PollOpts | PollPredicate,
|
|
119
340
|
arg3?: PollPredicate
|
|
@@ -142,5 +363,155 @@ async function* poll(
|
|
|
142
363
|
}
|
|
143
364
|
};
|
|
144
365
|
|
|
145
|
-
|
|
366
|
+
return run(key, task);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function* durableStep<T extends any>(
|
|
370
|
+
key: string,
|
|
371
|
+
fn: () => T | Promise<T>
|
|
372
|
+
): AsyncGenerator<StepResponse, T, StepResult | StepError> {
|
|
373
|
+
yield new StepKey(key);
|
|
374
|
+
|
|
375
|
+
const cached = yield new StepCacheCheck();
|
|
376
|
+
|
|
377
|
+
if (cached) {
|
|
378
|
+
yield cached;
|
|
379
|
+
if (cached instanceof StepError) {
|
|
380
|
+
// unreachable – consumer calls throw() on the generator first
|
|
381
|
+
throw cached.err;
|
|
382
|
+
}
|
|
383
|
+
return cached.result;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
const result = await fn();
|
|
388
|
+
yield new StepResult(result);
|
|
389
|
+
return result;
|
|
390
|
+
} catch (err: unknown) {
|
|
391
|
+
if (err instanceof RetryableError) {
|
|
392
|
+
yield new StepError(err, {
|
|
393
|
+
maxAttempts: err.maxAttempts,
|
|
394
|
+
retryInterval: err.retryInterval,
|
|
395
|
+
});
|
|
396
|
+
} else {
|
|
397
|
+
yield new StepError(err);
|
|
398
|
+
}
|
|
399
|
+
// unreachable – consumer calls throw() on the generator first
|
|
400
|
+
throw err;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function* whenStep<T, R>(params: {
|
|
405
|
+
definition: StoreDefinition<any>;
|
|
406
|
+
id: string;
|
|
407
|
+
event: WorkflowEvent;
|
|
408
|
+
storeClient: StoreClient;
|
|
409
|
+
stepKey: string;
|
|
410
|
+
selector: StoreSelector<T, R | undefined | null | false>;
|
|
411
|
+
}): AsyncGenerator<StepResponse, NonNullable<R>, StepResult | StepStoreWait> {
|
|
412
|
+
const { definition, id, event, storeClient, stepKey, selector } = params;
|
|
413
|
+
|
|
414
|
+
yield new StepKey(stepKey);
|
|
415
|
+
|
|
416
|
+
const cached = yield new StepCacheCheck();
|
|
417
|
+
|
|
418
|
+
if (cached) {
|
|
419
|
+
yield cached;
|
|
420
|
+
if (!(cached instanceof StepResult)) {
|
|
421
|
+
throw new Error("Store wait step cache must resolve to a step result");
|
|
422
|
+
}
|
|
423
|
+
return cached.result;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const snapshot = await storeClient.getStore({ definition, id });
|
|
427
|
+
const { result, readPaths } = trackStoreSelector(
|
|
428
|
+
cloneStoreState(snapshot.state as T),
|
|
429
|
+
selector
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
if (result !== undefined && result !== null && result !== false) {
|
|
433
|
+
const clonedResult = cloneStoreState(result);
|
|
434
|
+
yield new StepResult(clonedResult);
|
|
435
|
+
return clonedResult as NonNullable<R>;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
await storeClient.registerWaiter({
|
|
439
|
+
workflowId: event.workflowId,
|
|
440
|
+
executionId: event.executionId,
|
|
441
|
+
stepKey,
|
|
442
|
+
event,
|
|
443
|
+
storeName: definition.name,
|
|
444
|
+
storeId: id,
|
|
445
|
+
instanceId: snapshot.instanceId,
|
|
446
|
+
sinceVersion: snapshot.version,
|
|
447
|
+
readPaths,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
yield new StepStoreWait();
|
|
451
|
+
|
|
452
|
+
throw new Error("Store wait yielded control unexpectedly");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function* takeStep<T, R>(params: {
|
|
456
|
+
definition: StoreDefinition<any>;
|
|
457
|
+
id: string;
|
|
458
|
+
event: WorkflowEvent;
|
|
459
|
+
storeClient: StoreClient;
|
|
460
|
+
stepKey: string;
|
|
461
|
+
selector: StoreSelector<T, R | undefined | null | false>;
|
|
462
|
+
claim: (draft: Draft<T>, selected: NonNullable<R>) => void;
|
|
463
|
+
}): AsyncGenerator<StepResponse, NonNullable<R>, StepResult | StepStoreWait> {
|
|
464
|
+
const { definition, id, event, storeClient, stepKey, selector, claim } =
|
|
465
|
+
params;
|
|
466
|
+
|
|
467
|
+
yield new StepKey(stepKey);
|
|
468
|
+
|
|
469
|
+
const cached = yield new StepCacheCheck();
|
|
470
|
+
|
|
471
|
+
if (cached) {
|
|
472
|
+
yield cached;
|
|
473
|
+
if (!(cached instanceof StepResult)) {
|
|
474
|
+
throw new Error("Store take step cache must resolve to a step result");
|
|
475
|
+
}
|
|
476
|
+
return cached.result;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
let outcome: StoreTakeResult<R>;
|
|
480
|
+
try {
|
|
481
|
+
outcome = await storeClient.takeFromStore({
|
|
482
|
+
definition,
|
|
483
|
+
id,
|
|
484
|
+
selector: selector as any,
|
|
485
|
+
claim: claim as any,
|
|
486
|
+
// Exactly-once across the store-commit/heap-write gap: on replay
|
|
487
|
+
// after a crash, the store's applied-steps ledger returns the
|
|
488
|
+
// recorded (matched) outcome instead of re-running selector/claim.
|
|
489
|
+
stepId: { executionId: event.executionId, stepKey },
|
|
490
|
+
});
|
|
491
|
+
} catch (err: unknown) {
|
|
492
|
+
yield new StepError(err);
|
|
493
|
+
// unreachable – consumer calls throw() on the generator first
|
|
494
|
+
throw err;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (outcome.matched) {
|
|
498
|
+
yield new StepResult(outcome.selected);
|
|
499
|
+
return outcome.selected as NonNullable<R>;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
await storeClient.registerWaiter({
|
|
503
|
+
workflowId: event.workflowId,
|
|
504
|
+
executionId: event.executionId,
|
|
505
|
+
stepKey,
|
|
506
|
+
event,
|
|
507
|
+
storeName: definition.name,
|
|
508
|
+
storeId: id,
|
|
509
|
+
instanceId: outcome.instanceId,
|
|
510
|
+
sinceVersion: outcome.version,
|
|
511
|
+
readPaths: outcome.readPaths,
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
yield new StepStoreWait();
|
|
515
|
+
|
|
516
|
+
throw new Error("Store take yielded control unexpectedly");
|
|
146
517
|
}
|
package/src/internal/utils.ts
CHANGED
|
@@ -7,19 +7,33 @@ export const isIterable = <T>(i: T | Iterable<T>): i is Iterable<T> => {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Derives a replay-stable cache key from the call site of a keyless step.
|
|
12
|
+
*
|
|
13
|
+
* IMPORTANT: `stepFn` must be the synchronously-executing function that the
|
|
14
|
+
* user called directly (e.g. the `step.run` wrapper) – NOT an async generator
|
|
15
|
+
* body. Async generator bodies don't run until the first `.next()`, by which
|
|
16
|
+
* point the user's frame is no longer on the stack. `Error.captureStackTrace`
|
|
17
|
+
* omits every frame above (and including) `stepFn`, so the first captured
|
|
18
|
+
* frame is the user's call site (file:line:column), which is deterministic
|
|
19
|
+
* across executions of the same workflow code.
|
|
20
|
+
*/
|
|
21
|
+
export function getCallSiteHash(stepFn: Function): string {
|
|
22
|
+
const holder: { stack?: string } = {};
|
|
12
23
|
|
|
13
|
-
Error.captureStackTrace(
|
|
24
|
+
Error.captureStackTrace(holder, stepFn);
|
|
14
25
|
|
|
15
|
-
const
|
|
16
|
-
|
|
26
|
+
const frames = (holder.stack ?? "")
|
|
27
|
+
.split("\n")
|
|
28
|
+
.filter((line) => /^\s*at\s/.test(line));
|
|
17
29
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
const callSite = frames[0]?.trim();
|
|
31
|
+
|
|
32
|
+
if (!callSite) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"Could not derive a cache key from the call site. Pass an explicit key to this step."
|
|
35
|
+
);
|
|
22
36
|
}
|
|
23
37
|
|
|
24
|
-
return createHash("sha1").update(
|
|
38
|
+
return createHash("sha1").update(callSite).digest("hex");
|
|
25
39
|
}
|