@mnci/az-durable 0.1.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.
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Minimal ESLint rule shapes, declared locally.
3
+ *
4
+ * @remarks
5
+ * Declared here rather than imported from `@types/eslint` or
6
+ * `@typescript-eslint/utils` because this package ships **zero runtime
7
+ * dependencies**, and a type-only dependency is still a dependency a consumer
8
+ * must be able to resolve. These cover exactly what the three rules use.
9
+ */
10
+ /**
11
+ * An ESTree-ish node, narrowed only where a rule actually reads it.
12
+ *
13
+ * @remarks
14
+ * The index signature is what keeps this honest: a rule reads whatever
15
+ * property it needs and casts at the read site, rather than this file growing
16
+ * a partial mirror of ESTree that would drift against the real one.
17
+ *
18
+ * @typeParam None - this interface has no generic type parameters.
19
+ */
20
+ export interface Node {
21
+ type: string;
22
+ [key: string]: unknown;
23
+ }
24
+ /**
25
+ * The subset of ESLint's rule context these rules touch.
26
+ *
27
+ * @remarks
28
+ * Only `report` is declared, because only `report` is used. Widening this to
29
+ * the real context type would pull in a dependency for no gain.
30
+ *
31
+ * @typeParam None - this interface has no generic type parameters.
32
+ */
33
+ export interface RuleContext {
34
+ report: (descriptor: {
35
+ node: Node;
36
+ messageId: string;
37
+ data?: Record<string, string>;
38
+ }) => void;
39
+ }
40
+ /**
41
+ * An ESLint rule module, as the plugin exports it.
42
+ *
43
+ * @remarks
44
+ * `schema: []` is deliberate rather than omitted - every rule here is
45
+ * option-free, and an empty schema makes ESLint reject an options object
46
+ * instead of silently ignoring it.
47
+ *
48
+ * @typeParam None - this interface has no generic type parameters.
49
+ */
50
+ export interface Rule {
51
+ meta: {
52
+ type: 'problem' | 'suggestion';
53
+ docs: {
54
+ description: string;
55
+ };
56
+ schema: [];
57
+ messages: Record<string, string>;
58
+ };
59
+ create: (context: RuleContext) => Record<string, (node: Node) => void>;
60
+ }
61
+ /**
62
+ * Whether a call expression registers an orchestration.
63
+ *
64
+ * @remarks
65
+ * Matches both `defineOrchestration(...)` and the raw
66
+ * `df.app.orchestration(...)`, since an orchestration written either way has
67
+ * the same determinism constraints.
68
+ *
69
+ * **Heuristic, by construction.** These rules match on call-site SHAPE, so an
70
+ * orchestration body extracted into a helper function is not caught. That is a
71
+ * documented limit rather than a defect: following the value would require type
72
+ * information the rule deliberately does not depend on.
73
+ *
74
+ * @param node - A `CallExpression` node.
75
+ * @returns `true` when the call registers an orchestration.
76
+ * @throws Never - pure inspection.
77
+ * @typeParam None - this function has no generic type parameters.
78
+ */
79
+ export declare function isOrchestrationRegistration(node: Node): boolean;
80
+ /**
81
+ * The name a callee refers to, if it is a plain identifier or member access.
82
+ *
83
+ * @remarks
84
+ * For a member expression this returns the TRAILING property, so `df.now()`
85
+ * and `context.df.now()` both read as `now`. Callers that must distinguish a
86
+ * bare call from a member call check `node.type` themselves - `require-yield-star`
87
+ * does exactly that, because `yield c.df.callActivity(...)` is the correct raw
88
+ * SDK call and must not be flagged.
89
+ *
90
+ * @param node - A callee node.
91
+ * @returns The identifier or property name, or `undefined`.
92
+ * @throws Never - pure inspection.
93
+ * @typeParam None - this function has no generic type parameters.
94
+ */
95
+ export declare function calleeName(node: Node | undefined): string | undefined;
96
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1,32 @@
1
+ import type { OrchestrationContext } from 'durable-functions';
2
+ /**
3
+ * Declares the custom statuses an orchestration can report.
4
+ *
5
+ * @remarks
6
+ * `const` on the type parameter preserves the literal types, so `setStatus`
7
+ * can check the key against the actual set rather than against `string`. The
8
+ * object is returned unchanged — this is a typing device, not a transform.
9
+ *
10
+ * @param statuses - The status map.
11
+ * @returns The same object, with its literal types preserved.
12
+ * @throws Never - returns its argument.
13
+ * @typeParam T - The status map's literal type.
14
+ */
15
+ export declare function defineStatuses<const T extends Record<string, string>>(statuses: T): T;
16
+ /**
17
+ * Sets the orchestration's custom status from a declared set.
18
+ *
19
+ * @remarks
20
+ * `setCustomStatus` accepts `unknown`, so a typo in a status string is invisible
21
+ * until someone reads the instance's status and finds a value nothing produces.
22
+ * Constraining `key` to the declared map is the whole point.
23
+ *
24
+ * @param context - The orchestration context.
25
+ * @param statuses - The declared status map.
26
+ * @param key - Which status to set; checked against the map.
27
+ * @returns Nothing.
28
+ * @throws Never - delegates to the SDK.
29
+ * @typeParam T - The status map's literal type.
30
+ */
31
+ export declare function setStatus<T extends Record<string, string>>(context: OrchestrationContext, statuses: T, key: keyof T): void;
32
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1,122 @@
1
+ import type { TypedOrchestration } from './types';
2
+ /**
3
+ * A recorded activity or sub-orchestration call.
4
+ *
5
+ * @remarks
6
+ * The ORDER of these is what replay compatibility depends on, which is why the
7
+ * harness returns them as a list rather than a set: reordering two activity
8
+ * calls is a breaking change to an orchestration, and this is what makes that
9
+ * directly assertable.
10
+ *
11
+ * @typeParam None - this interface has no generic type parameters.
12
+ */
13
+ export interface RecordedCall {
14
+ /** The activity or orchestration name, as scheduled. */
15
+ readonly name: string;
16
+ /** The input it was scheduled with. */
17
+ readonly input: unknown;
18
+ }
19
+ /**
20
+ * What a stubbed activity returns, or an `Error` to make it throw.
21
+ *
22
+ * @remarks
23
+ * Deliberately `unknown` rather than generic: a stub map covers many activities
24
+ * with different outputs, so a single parameter could only be their union,
25
+ * which is less useful than an explicit cast at the one place it matters.
26
+ *
27
+ * @typeParam None - this type has no generic type parameters.
28
+ */
29
+ export type StubResult = unknown;
30
+ /**
31
+ * The fakes a workflow run is driven against.
32
+ *
33
+ * @remarks
34
+ * `activities` is keyed by the same name literal `defineActivity` registered —
35
+ * deliberately, since that string is the contract the task hub stores.
36
+ *
37
+ * @typeParam None - this interface has no generic type parameters.
38
+ */
39
+ export interface WorkflowStub {
40
+ /**
41
+ * Result per activity name. Returning an `Error` instance makes that call
42
+ * THROW inside the orchestration, which is how failure branches and
43
+ * retry-exhaustion paths become testable.
44
+ */
45
+ readonly activities: Record<string, (input: unknown) => StubResult>;
46
+ /** Fixed clock, so time-dependent output is deterministic. Defaults to the epoch. */
47
+ readonly now?: Date;
48
+ /** Instance id the orchestration sees. Defaults to `test-instance`. */
49
+ readonly instanceId?: string;
50
+ /**
51
+ * Picks the winner of a `Task.any` race, by scheduled name.
52
+ *
53
+ * @remarks
54
+ * Defaults to the first candidate, and returning `undefined` accepts that
55
+ * default — so a selector only has to name the cases it cares about, rather
56
+ * than inventing a fallback for a list it knows is non-empty. Worth setting
57
+ * whenever a race decides something important: the approval-versus-timeout
58
+ * pattern has a branch per outcome, and with a fixed winner only one of them
59
+ * is ever reachable. A timer candidate is named `__timer`.
60
+ */
61
+ readonly raceWinner?: (candidates: string[]) => string | undefined;
62
+ }
63
+ /**
64
+ * What a completed {@link runWorkflow} reports.
65
+ *
66
+ * @remarks
67
+ * `calls` and `statuses` are ordered lists rather than sets because the order
68
+ * is the property worth asserting: it is what replay compatibility depends on.
69
+ *
70
+ * @typeParam TInput - The orchestration's input type.
71
+ * @typeParam TOutput - The orchestration's return type.
72
+ */
73
+ export interface WorkflowRun<TInput, TOutput> {
74
+ /** The orchestration's return value. */
75
+ readonly result: TOutput;
76
+ /** Every activity and sub-orchestration call, in order. */
77
+ readonly calls: RecordedCall[];
78
+ /** Every `setCustomStatus` transition, in order. */
79
+ readonly statuses: string[];
80
+ /**
81
+ * The input the orchestration asked to restart with, if it called
82
+ * `self.continueAsNew`.
83
+ *
84
+ * @remarks
85
+ * The harness records the request and lets the run finish rather than
86
+ * looping: an eternal orchestration restarts forever by design, so a harness
87
+ * that honoured it would never return. What is worth asserting is that the
88
+ * restart was requested and with what — the next generation is then a
89
+ * separate `runWorkflow` call with that input.
90
+ */
91
+ readonly continuedAsNew?: TInput;
92
+ }
93
+ /**
94
+ * Runs an orchestration against stubbed activities, with no Azure running.
95
+ *
96
+ * @remarks
97
+ * Drives the generator synchronously, feeding each yielded task the stubbed
98
+ * result for that activity's name. There is no host, no emulator and no
99
+ * storage — a three-step workflow with a short-circuit branch is testable in
100
+ * about fifteen lines.
101
+ *
102
+ * **How it intercepts.** The orchestration is driven with a fake
103
+ * `OrchestrationContext`, which works only because every scheduling call in
104
+ * this package goes through `context.df` rather than through the registered
105
+ * callable. The alternative — reading the name off `task.action.functionName` —
106
+ * is an undocumented SDK internal the package's non-goals forbid.
107
+ *
108
+ * **Limits, stated rather than discovered later.** Retry policies are not
109
+ * simulated: a stub returning an `Error` throws once, it does not exhaust
110
+ * attempts. `Task.any` resolves to the FIRST task in the list, since there is
111
+ * no real concurrency to race. Timers complete immediately.
112
+ *
113
+ * @param orchestration - The orchestration to run.
114
+ * @param input - The input, checked against its declared type.
115
+ * @param stub - The activity fakes, clock and instance id.
116
+ * @returns The result, the ordered calls, and the status transitions.
117
+ * @throws Error when an activity is called with no stub registered for it.
118
+ * @typeParam TInput - The orchestration's input type.
119
+ * @typeParam TOutput - The orchestration's output type.
120
+ */
121
+ export declare function runWorkflow<TInput, TOutput>(orchestration: TypedOrchestration<TInput, TOutput>, input: TInput, stub: WorkflowStub): WorkflowRun<TInput, TOutput>;
122
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1,79 @@
1
+ import type { OrchestrationContext, Task } from 'durable-functions';
2
+ import type { TypedTimerTask } from './types';
3
+ /**
4
+ * The current time, safely for replay.
5
+ *
6
+ * @remarks
7
+ * `new Date()` and `Date.now()` return a different value on every replay, which
8
+ * silently corrupts orchestration output rather than failing. `currentUtcDateTime`
9
+ * is derived from orchestration history and returns the same value at the same
10
+ * point every time. This is the replacement the lint rule suggests.
11
+ *
12
+ * @param context - The orchestration context.
13
+ * @returns The replay-safe current time.
14
+ * @throws Never - reads a property.
15
+ * @typeParam None - this function has no generic type parameters.
16
+ */
17
+ export declare function now(context: OrchestrationContext): Date;
18
+ /**
19
+ * Sleeps until an absolute time.
20
+ *
21
+ * @remarks
22
+ * **Must be invoked with `yield *`.**
23
+ *
24
+ * @param context - The orchestration context.
25
+ * @param when - The absolute time to wake at.
26
+ * @returns A generator that completes when the timer fires.
27
+ * @throws Never - the timer either fires or the instance ends.
28
+ * @typeParam None - this function has no generic type parameters.
29
+ */
30
+ export declare function sleepUntil(context: OrchestrationContext, when: Date): Generator<Task, void, unknown>;
31
+ /**
32
+ * Sleeps for a duration.
33
+ *
34
+ * @remarks
35
+ * The deadline is computed from {@link now}, **never** `Date.now()`. Using wall
36
+ * clock here would make the deadline move on every replay, so a timer could fire
37
+ * early, late, or repeatedly. This is the single most common determinism bug in
38
+ * hand-written orchestrations.
39
+ *
40
+ * **Must be invoked with `yield *`.**
41
+ *
42
+ * @param context - The orchestration context.
43
+ * @param ms - How long to sleep, in milliseconds.
44
+ * @returns A generator that completes when the timer fires.
45
+ * @throws Never - the timer either fires or the instance ends.
46
+ * @typeParam None - this function has no generic type parameters.
47
+ */
48
+ export declare function sleepFor(context: OrchestrationContext, ms: number): Generator<Task, void, unknown>;
49
+ /**
50
+ * Schedules a durable timer for an absolute instant, without yielding it.
51
+ *
52
+ * @remarks
53
+ * The task form of {@link sleepUntil}, so a timer can race an event or an
54
+ * activity through `any`. See {@link TypedTimerTask} for why the returned
55
+ * value carries `cancel` — **a pending timer keeps the instance alive**, so the
56
+ * loser of a race must be cancelled.
57
+ *
58
+ * @param context - The orchestration context.
59
+ * @param when - The instant to fire at.
60
+ * @returns A cancellable timer task.
61
+ * @throws Never - scheduling only.
62
+ * @typeParam None - this function has no generic type parameters.
63
+ */
64
+ export declare function timerTaskUntil(context: OrchestrationContext, when: Date): TypedTimerTask;
65
+ /**
66
+ * Schedules a durable timer a fixed duration ahead, without yielding it.
67
+ *
68
+ * @remarks
69
+ * Computes the deadline from `context.df.currentUtcDateTime`, never
70
+ * `Date.now()` — the same replay-safety reason {@link sleepFor} does.
71
+ *
72
+ * @param context - The orchestration context.
73
+ * @param ms - How far ahead to fire, in milliseconds.
74
+ * @returns A cancellable timer task.
75
+ * @throws Never - scheduling only.
76
+ * @typeParam None - this function has no generic type parameters.
77
+ */
78
+ export declare function timerTask(context: OrchestrationContext, ms: number): TypedTimerTask;
79
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1,96 @@
1
+ import type { OrchestrationContext, RegisteredActivity, RegisteredOrchestration, Task, TimerTask } from 'durable-functions';
2
+ /**
3
+ * An activity with its input and output types attached.
4
+ *
5
+ * @remarks
6
+ * `__input` and `__output` are **phantom**: never assigned, never read, and
7
+ * erased at build time. They exist only to carry `TInput`/`TOutput` through the
8
+ * type system, since `RegisteredActivity` is `(input?: unknown) => …` and
9
+ * therefore forgets both.
10
+ *
11
+ * `__input` is written as a function *parameter* rather than a bare property so
12
+ * that `TInput` is **contravariant**. That is not decoration: with a covariant
13
+ * property, an activity accepting a wider input would be assignable where a
14
+ * narrower one is expected, and the wrapper would accept calls the handler
15
+ * cannot serve.
16
+ *
17
+ * @typeParam TInput - The JSON-serialisable input the activity accepts.
18
+ * @typeParam TOutput - The awaited output the activity produces.
19
+ */
20
+ export interface TypedActivity<TInput, TOutput> {
21
+ /** The activity name as registered in the Function App, verbatim. */
22
+ readonly name: string;
23
+ /** The value `durable-functions` returned from `app.activity`. */
24
+ readonly registered: RegisteredActivity;
25
+ /** Phantom. Never assigned. Makes `TInput` contravariant. */
26
+ readonly __input?: (input: TInput) => void;
27
+ /** Phantom. Never assigned. Carries `TOutput`. */
28
+ readonly __output?: () => TOutput;
29
+ }
30
+ /**
31
+ * An orchestration with its input and output types attached.
32
+ *
33
+ * @remarks
34
+ * Same phantom-member design as {@link TypedActivity}; see there for why
35
+ * `__input` is a function parameter.
36
+ *
37
+ * @typeParam TInput - The JSON-serialisable input the orchestration accepts.
38
+ * @typeParam TOutput - The value the orchestration returns.
39
+ */
40
+ export interface TypedOrchestration<TInput, TOutput> {
41
+ /** The orchestration name as registered in the Function App, verbatim. */
42
+ readonly name: string;
43
+ /** The value `durable-functions` returned from `app.orchestration`. */
44
+ readonly registered: RegisteredOrchestration;
45
+ /**
46
+ * The handler, retained so `runWorkflow` can drive it directly.
47
+ *
48
+ * @remarks
49
+ * The SDK keeps no accessible reference to the generator once registered, so
50
+ * without this the testing harness would have to go through the Functions
51
+ * host. Reading it outside `@mnci/az-durable/testing` is not supported.
52
+ */
53
+ readonly handler: (context: OrchestrationContext, input: TInput) => Generator<Task, TOutput, unknown>;
54
+ /** Phantom. Never assigned. Makes `TInput` contravariant. */
55
+ readonly __input?: (input: TInput) => void;
56
+ /** Phantom. Never assigned. Carries `TOutput`. */
57
+ readonly __output?: () => TOutput;
58
+ }
59
+ /**
60
+ * A task that has been scheduled but not yet yielded, for fan-out.
61
+ *
62
+ * @remarks
63
+ * Produced by `activityTask` and consumed by `all`/`any`. Holding the SDK
64
+ * `Task` rather than yielding it immediately is what lets several activities
65
+ * run concurrently.
66
+ *
67
+ * @typeParam TOutput - The output the task will produce.
68
+ */
69
+ export interface TypedTask<TOutput> {
70
+ /** The underlying SDK task. Yield it, or hand it to `all`/`any`. */
71
+ readonly task: Task;
72
+ /** Phantom. Never assigned. Carries `TOutput`. */
73
+ readonly __output?: () => TOutput;
74
+ }
75
+ /**
76
+ * A durable timer, scheduled but not yet yielded.
77
+ *
78
+ * @remarks
79
+ * Separate from {@link TypedTask} because a timer carries `cancel`, and losing
80
+ * it is a real bug rather than a missing convenience: **an orchestration does
81
+ * not complete until every scheduled timer has fired or been cancelled**, so a
82
+ * timeout timer left pending after its race is won keeps the instance alive
83
+ * until it expires. The SDK documents this on `TimerTask`; surfacing `cancel`
84
+ * here is what lets a caller obey it without reaching for the raw task.
85
+ *
86
+ * @typeParam None - this interface has no generic type parameters.
87
+ */
88
+ export interface TypedTimerTask extends TypedTask<void> {
89
+ /** The underlying SDK timer. */
90
+ readonly task: TimerTask;
91
+ /** Requests cancellation, applied on the next `yield` or `return`. */
92
+ readonly cancel: () => void;
93
+ /** Whether the timer has fired. */
94
+ readonly isCompleted: () => boolean;
95
+ }
96
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ export * from "./src/testing";
@@ -0,0 +1,225 @@
1
+ function _instanceof(left, right) {
2
+ "@swc/helpers - instanceof";
3
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
4
+ return !!right[Symbol.hasInstance](left);
5
+ } else return left instanceof right;
6
+ }
7
+ function _type_of(obj) {
8
+ "@swc/helpers - typeof";
9
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
10
+ }
11
+ /**
12
+ * Runs an orchestration against stubbed activities, with no Azure running.
13
+ *
14
+ * @remarks
15
+ * Drives the generator synchronously, feeding each yielded task the stubbed
16
+ * result for that activity's name. There is no host, no emulator and no
17
+ * storage — a three-step workflow with a short-circuit branch is testable in
18
+ * about fifteen lines.
19
+ *
20
+ * **How it intercepts.** The orchestration is driven with a fake
21
+ * `OrchestrationContext`, which works only because every scheduling call in
22
+ * this package goes through `context.df` rather than through the registered
23
+ * callable. The alternative — reading the name off `task.action.functionName` —
24
+ * is an undocumented SDK internal the package's non-goals forbid.
25
+ *
26
+ * **Limits, stated rather than discovered later.** Retry policies are not
27
+ * simulated: a stub returning an `Error` throws once, it does not exhaust
28
+ * attempts. `Task.any` resolves to the FIRST task in the list, since there is
29
+ * no real concurrency to race. Timers complete immediately.
30
+ *
31
+ * @param orchestration - The orchestration to run.
32
+ * @param input - The input, checked against its declared type.
33
+ * @param stub - The activity fakes, clock and instance id.
34
+ * @returns The result, the ordered calls, and the status transitions.
35
+ * @throws Error when an activity is called with no stub registered for it.
36
+ * @typeParam TInput - The orchestration's input type.
37
+ * @typeParam TOutput - The orchestration's output type.
38
+ */ function runWorkflow(orchestration, input, stub) {
39
+ var _stub_now, _stub_instanceId;
40
+ var calls = [];
41
+ var statuses = [];
42
+ var continuedAsNew;
43
+ var clock = (_stub_now = stub.now) !== null && _stub_now !== void 0 ? _stub_now : new Date(0);
44
+ var schedule = function schedule(name, scheduledInput) {
45
+ calls.push({
46
+ name: name,
47
+ input: scheduledInput
48
+ });
49
+ return {
50
+ isCompleted: false,
51
+ isFaulted: false,
52
+ __name: name,
53
+ __input: scheduledInput
54
+ };
55
+ };
56
+ var context = {
57
+ df: {
58
+ instanceId: (_stub_instanceId = stub.instanceId) !== null && _stub_instanceId !== void 0 ? _stub_instanceId : 'test-instance',
59
+ isReplaying: false,
60
+ currentUtcDateTime: clock,
61
+ callActivity: schedule,
62
+ callActivityWithRetry: function callActivityWithRetry(name, _retry, i) {
63
+ return schedule(name, i);
64
+ },
65
+ callSubOrchestrator: function callSubOrchestrator(name, i) {
66
+ return schedule(name, i);
67
+ },
68
+ callSubOrchestratorWithRetry: function callSubOrchestratorWithRetry(name, _retry, i) {
69
+ return schedule(name, i);
70
+ },
71
+ waitForExternalEvent: function waitForExternalEvent(name) {
72
+ return schedule(name, undefined);
73
+ },
74
+ // Timers complete immediately: there is no real time to wait for, and a
75
+ // harness that blocked on one would be useless. `cancel` is real,
76
+ // because an orchestration that correctly cancels its losing timer must
77
+ // not crash in a test for doing the right thing.
78
+ createTimer: function createTimer(fireAt) {
79
+ var timer = schedule('__timer', fireAt.toISOString());
80
+ timer.isCanceled = false;
81
+ timer.cancel = function() {
82
+ timer.isCanceled = true;
83
+ };
84
+ return timer;
85
+ },
86
+ setCustomStatus: function setCustomStatus(value) {
87
+ statuses.push(String(value));
88
+ },
89
+ continueAsNew: function continueAsNew(next) {
90
+ continuedAsNew = next;
91
+ },
92
+ Task: {
93
+ all: function all(tasks) {
94
+ return {
95
+ isCompleted: false,
96
+ isFaulted: false,
97
+ __all: tasks
98
+ };
99
+ },
100
+ // A marker, not a winner. `Task.any` resolves to the winning TASK, not
101
+ // to its value, so choosing here would hand the orchestration the
102
+ // wrong kind of thing — which is exactly the bug the reconstructed
103
+ // workflows found.
104
+ any: function any(tasks) {
105
+ return {
106
+ isCompleted: false,
107
+ isFaulted: false,
108
+ __any: tasks
109
+ };
110
+ }
111
+ }
112
+ }
113
+ };
114
+ var generator = orchestration.handler(context, input);
115
+ var step = generator.next();
116
+ while(!step.done){
117
+ var resumed = resolve(step.value, stub);
118
+ // INTO the generator, not out of the driver. Throwing here instead is the
119
+ // bug the reconstructed workflows found: every compensation branch was
120
+ // unreachable, while the docstring promised the opposite.
121
+ step = isThrowRequest(resumed) ? generator.throw(resumed.__throw) : generator.next(resumed);
122
+ }
123
+ return continuedAsNew === undefined ? {
124
+ result: step.value,
125
+ calls: calls,
126
+ statuses: statuses
127
+ } : {
128
+ result: step.value,
129
+ calls: calls,
130
+ statuses: statuses,
131
+ continuedAsNew: continuedAsNew
132
+ };
133
+ }
134
+ /**
135
+ * Produces the value the driver resumes a yielded task with.
136
+ *
137
+ * @remarks
138
+ * Separated so the `Task.all` fan-out case — where one yielded task stands for
139
+ * several — is handled in one place rather than inline in the drive loop.
140
+ *
141
+ * @param task - The task the orchestration yielded.
142
+ * @param stub - The stubs to resolve against.
143
+ * @returns The value to resume with, or a {@link ThrowRequest} for the driver to inject.
144
+ * @throws Error naming an activity with no stub registered.
145
+ * @typeParam None - this function has no generic type parameters.
146
+ */ function resolve(task, stub) {
147
+ var fanOut = task.__all;
148
+ if (fanOut !== undefined) {
149
+ return fanOut.map(function(t) {
150
+ return resolve(t, stub);
151
+ });
152
+ }
153
+ var race = task.__any;
154
+ if (race !== undefined) {
155
+ return resolveRace(race, stub);
156
+ }
157
+ var name = task.__name, input = task.__input;
158
+ if (name === '__timer') {
159
+ return undefined;
160
+ }
161
+ var activity = stub.activities[name];
162
+ if (activity === undefined) {
163
+ // Naming the activity matters: the alternative is `undefined` flowing into
164
+ // the orchestration and failing somewhere unrelated.
165
+ throw new Error("No stub registered for '".concat(name, "'. Add it to stub.activities to run this workflow."));
166
+ }
167
+ var result = activity(input);
168
+ if (_instanceof(result, Error)) {
169
+ // A returned Error becomes a THROWN error inside the orchestration, which
170
+ // is what makes failure branches testable at all. Handed back as a request
171
+ // so the DRIVER injects it; see {@link ThrowRequest}.
172
+ return {
173
+ __throw: result
174
+ };
175
+ }
176
+ return result;
177
+ }
178
+ /**
179
+ * Settles a `Task.any` race and returns the winning TASK.
180
+ *
181
+ * @remarks
182
+ * The distinction that matters: the SDK's `Task.any` resolves to the winning
183
+ * task object, not to its value, and callers read the value afterwards with
184
+ * `resultOf`. An earlier harness returned the resolved value instead, which
185
+ * made every orchestration using a race fail with "Task.any returned a task
186
+ * that was not one of the inputs" — correct code, rejected by the fake.
187
+ *
188
+ * The winner's `result` is populated and `isCompleted` set, so `resultOf` and
189
+ * an `isCompleted` check on the loser both behave as they do in production.
190
+ *
191
+ * @param candidates - The racing tasks.
192
+ * @param stub - The stubs to resolve the winner against.
193
+ * @returns The winning task, completed and carrying its result.
194
+ * @throws Error when `raceWinner` names a task that is not racing.
195
+ * @typeParam None - this function has no generic type parameters.
196
+ */ function resolveRace(candidates, stub) {
197
+ var _ref;
198
+ var _stub_raceWinner;
199
+ var names = candidates.map(function(c) {
200
+ return c.__name;
201
+ });
202
+ var chosen = (_ref = (_stub_raceWinner = stub.raceWinner) === null || _stub_raceWinner === void 0 ? void 0 : _stub_raceWinner.call(stub, names)) !== null && _ref !== void 0 ? _ref : names[0];
203
+ var winner = candidates.find(function(c) {
204
+ return c.__name === chosen;
205
+ });
206
+ if (winner === undefined) {
207
+ throw new Error("raceWinner chose '".concat(String(chosen), "', which is not racing. Candidates: ").concat(names.join(', '), "."));
208
+ }
209
+ var mutable = winner;
210
+ mutable.result = winner.__name === '__timer' ? undefined : resolve(winner, stub);
211
+ mutable.isCompleted = true;
212
+ return winner;
213
+ }
214
+ /**
215
+ * Whether a resolved value is a request to throw inside the orchestration.
216
+ *
217
+ * @param value - Whatever `resolve` produced.
218
+ * @returns `true` when the driver should call `generator.throw`.
219
+ * @throws Never - a type guard.
220
+ * @typeParam None - this function has no generic type parameters.
221
+ */ function isThrowRequest(value) {
222
+ return (typeof value === "undefined" ? "undefined" : _type_of(value)) === 'object' && value !== null && '__throw' in value;
223
+ }
224
+
225
+ export { runWorkflow };