@astrale-os/sdk 0.6.0-beta.36 → 0.6.0-beta.37
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/dist/application/action/context.d.ts +14 -11
- package/dist/application/action/index.d.ts +1 -1
- package/dist/application/dependency/invoker.d.ts +10 -4
- package/dist/application/integration/define.d.ts +8 -1
- package/dist/application/integration/index.d.ts +2 -2
- package/dist/application/integration/integration.d.ts +13 -1
- package/dist/application/integration/operation.d.ts +14 -1
- package/dist/application/mutation/execution/execute.js +1 -1
- package/dist/application/mutation/failure.d.ts +6 -0
- package/dist/application/mutation/failure.js +24 -18
- package/dist/application/query/collection/union.js +11 -4
- package/dist/application/query/conformance.d.ts +15 -0
- package/dist/application/query/conformance.js +46 -0
- package/dist/application/query/define.js +4 -3
- package/dist/application/query/execution/execute.d.ts +0 -1
- package/dist/application/query/execution/execute.js +7 -1
- package/dist/application/query/failure.d.ts +8 -0
- package/dist/application/query/failure.js +8 -0
- package/dist/application/query/index.d.ts +1 -1
- package/dist/application/query/index.js +1 -1
- package/dist/application/workflow/authoring.d.ts +1 -1
- package/dist/application/workflow/context.d.ts +1 -1
- package/dist/application/workflow/durable.d.ts +55 -20
- package/dist/application/workflow/index.d.ts +1 -1
- package/dist/execution/identity/authentication.js +12 -6
- package/dist/execution/identity/callback.d.ts +7 -8
- package/dist/execution/identity/callback.js +17 -14
- package/dist/execution/invocation/capabilities/bind.d.ts +11 -0
- package/dist/execution/invocation/capabilities/bind.js +25 -0
- package/dist/execution/invocation/capabilities/index.d.ts +1 -0
- package/dist/execution/invocation/capabilities/index.js +1 -0
- package/dist/execution/invocation/dispatch/context.js +2 -21
- package/dist/execution/invocation/integration/authentication.d.ts +3 -3
- package/dist/execution/invocation/integration/authentication.js +7 -7
- package/dist/execution/invocation/integration/call.js +2 -2
- package/dist/execution/invocation/integration/index.d.ts +1 -0
- package/dist/execution/invocation/integration/index.js +1 -0
- package/dist/execution/invocation/invocation.js +2 -2
- package/dist/execution/invocation/lifecycle/active.d.ts +8 -2
- package/dist/execution/invocation/lifecycle/active.js +3 -3
- package/dist/execution/invocation/lifecycle/index.d.ts +1 -1
- package/dist/execution/service/create.js +1 -0
- package/dist/execution/workflows/engine.d.ts +12 -2
- package/dist/execution/workflows/index.d.ts +1 -1
- package/dist/execution/workflows/integrations.d.ts +13 -0
- package/dist/execution/workflows/integrations.js +25 -0
- package/dist/execution/workflows/options.js +3 -1
- package/dist/execution/workflows/receiver.d.ts +3 -0
- package/dist/execution/workflows/receiver.js +7 -2
- package/dist/execution/workflows/run.d.ts +3 -4
- package/dist/execution/workflows/run.js +32 -60
- package/dist/execution/workflows/steps.d.ts +42 -0
- package/dist/execution/workflows/steps.js +67 -0
- package/dist/testing/durable/engine.d.ts +111 -0
- package/dist/testing/durable/engine.js +540 -0
- package/dist/testing/durable/index.d.ts +3 -0
- package/dist/testing/durable/index.js +1 -0
- package/dist/testing/durable/steps.d.ts +30 -0
- package/dist/testing/durable/steps.js +40 -0
- package/dist/testing/index.d.ts +6 -1
- package/dist/testing/index.js +5 -1
- package/dist/tooling/cli/dotenv.d.ts +3 -3
- package/dist/tooling/cli/dotenv.js +3 -3
- package/dist/tooling/cli/log.d.ts +0 -2
- package/dist/tooling/cli/log.js +0 -2
- package/dist/tooling/linter/adapters/typescript/imports.d.ts +0 -1
- package/dist/tooling/linter/adapters/typescript/imports.js +1 -5
- package/dist/tooling/linter/adapters/typescript/index.d.ts +1 -1
- package/dist/tooling/linter/adapters/typescript/index.js +1 -1
- package/dist/tooling/linter/adapters/typescript/syntax.d.ts +3 -3
- package/dist/tooling/linter/adapters/typescript/syntax.js +13 -25
- package/dist/tooling/linter/implementations/source/callable-requirements.d.ts +0 -1
- package/dist/tooling/linter/implementations/source/callable-requirements.js +1 -6
- package/dist/tooling/linter/implementations/source/dependencies.js +12 -1
- package/dist/tooling/linter/implementations/source/mutations.js +1 -12
- package/dist/tooling/linter/implementations/source/queries.js +1 -17
- package/dist/tooling/linter/implementations/source/shared.d.ts +0 -4
- package/dist/tooling/linter/implementations/source/shared.js +1 -33
- package/dist/tooling/linter/implementations/source/workflows.js +5 -15
- package/dist/tooling/linter/policy/generated.js +2 -2
- package/package.json +6 -6
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { declaredErrorFactory } from '../../application/error/index.js';
|
|
2
|
-
import { bindMutation } from '../../application/mutation/execution/executor.js';
|
|
3
|
-
import { bindQuery } from '../../application/query/execution/executor.js';
|
|
4
2
|
import { acceptStepId } from '../../application/workflow/step/index.js';
|
|
5
3
|
import { acceptStepOutput } from '../../application/workflow/step/output.js';
|
|
6
4
|
import { ResponseError } from '../../platform/client/index.js';
|
|
7
|
-
import { bindSession } from '../../platform/client/session/index.js';
|
|
8
5
|
import { validateOutput } from '../invocation/result/index.js';
|
|
9
6
|
import { decodeEnvelope } from './envelope.js';
|
|
10
7
|
import { replayedFailure, terminalFailure } from './failure.js';
|
|
8
|
+
import { keySequence } from './steps.js';
|
|
11
9
|
/** Token of a Workflow that declares none. */
|
|
12
10
|
const DEFAULT_COMPAT = '0';
|
|
13
11
|
const MAXIMUM_SLEEP_MS = 365 * 24 * 60 * 60_000;
|
|
14
|
-
const
|
|
12
|
+
const MAXIMUM_SIGNAL_NAME = 128;
|
|
15
13
|
/** Bounds of one `result` wait and of the durable pause between two of them. */
|
|
16
14
|
const AWAIT_WAIT_MS = 10_000;
|
|
17
15
|
const AWAIT_MAXIMUM_PAUSE_MS = 5 * 60_000;
|
|
@@ -81,17 +79,16 @@ function durableContext(input, context, envelope) {
|
|
|
81
79
|
const execution = envelope.execution;
|
|
82
80
|
const signal = context.request().attemptCompletedSignal;
|
|
83
81
|
const steps = new Set();
|
|
84
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* One journaled unit of work holding the capabilities of an Action as the Domain alone. Its
|
|
84
|
+
* Mutations, durable calls and Integration calls share one key sequence derived from its name.
|
|
85
|
+
*/
|
|
85
86
|
const journal = (name, execute) => Promise.resolve(context.run(name, async () => {
|
|
86
|
-
const
|
|
87
|
-
const
|
|
87
|
+
const lifetime = { deadline: Date.now() + serving.stepTimeoutMs, signal };
|
|
88
|
+
const next = await keySequence(execution.id, name);
|
|
89
|
+
const step = input.steps({ name, execution, next, lifetime });
|
|
88
90
|
try {
|
|
89
|
-
const
|
|
90
|
-
const output = await execute(Object.freeze({
|
|
91
|
-
kernel,
|
|
92
|
-
query: bindQuery(kernel, domain),
|
|
93
|
-
mutate: bindMutation(kernel, domain),
|
|
94
|
-
}));
|
|
91
|
+
const output = await execute(step.authority);
|
|
95
92
|
try {
|
|
96
93
|
return acceptStepOutput(output);
|
|
97
94
|
}
|
|
@@ -103,7 +100,7 @@ function durableContext(input, context, envelope) {
|
|
|
103
100
|
throw terminalFailure(engine, cause, definition) ?? cause;
|
|
104
101
|
}
|
|
105
102
|
finally {
|
|
106
|
-
|
|
103
|
+
step.close();
|
|
107
104
|
}
|
|
108
105
|
})).catch((cause) => {
|
|
109
106
|
throw replayedFailure(engine, cause, definition) ?? cause;
|
|
@@ -128,6 +125,7 @@ function durableContext(input, context, envelope) {
|
|
|
128
125
|
error: declaredErrorFactory(definition),
|
|
129
126
|
step: Object.freeze({
|
|
130
127
|
run: step,
|
|
128
|
+
query: (id, query, value) => step(id, (authority) => authority.query(query, value)),
|
|
131
129
|
mutate: (id, mutation, value) => step(id, (authority) => authority.mutate(mutation, value)),
|
|
132
130
|
}),
|
|
133
131
|
async sleep(durationMs) {
|
|
@@ -136,11 +134,26 @@ function durableContext(input, context, envelope) {
|
|
|
136
134
|
}
|
|
137
135
|
await context.sleep(durationMs);
|
|
138
136
|
},
|
|
139
|
-
async
|
|
140
|
-
if (typeof name !== 'string' || name.length === 0 || name.length >
|
|
141
|
-
throw new TypeError('Durable
|
|
137
|
+
async awaitSignal(name, options) {
|
|
138
|
+
if (typeof name !== 'string' || name.length === 0 || name.length > MAXIMUM_SIGNAL_NAME) {
|
|
139
|
+
throw new TypeError('Durable signal name must have 1 to 128 characters.');
|
|
140
|
+
}
|
|
141
|
+
if (options === undefined)
|
|
142
|
+
return (await context.promise(name).get());
|
|
143
|
+
const timeoutMs = options?.timeoutMs;
|
|
144
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0 || timeoutMs > MAXIMUM_SLEEP_MS) {
|
|
145
|
+
throw new TypeError(`Durable signal timeout must be an integer between 0 and ${MAXIMUM_SLEEP_MS} ms.`);
|
|
146
|
+
}
|
|
147
|
+
// The engine journals the race between the signal and the timer: a replay settles it alike.
|
|
148
|
+
try {
|
|
149
|
+
const value = await context.promise(name).get().orTimeout(timeoutMs);
|
|
150
|
+
return Object.freeze({ kind: 'signal', value });
|
|
151
|
+
}
|
|
152
|
+
catch (cause) {
|
|
153
|
+
if (cause instanceof engine.TimeoutError)
|
|
154
|
+
return Object.freeze({ kind: 'timeout' });
|
|
155
|
+
throw cause;
|
|
142
156
|
}
|
|
143
|
-
return (await context.promise(name).get());
|
|
144
157
|
},
|
|
145
158
|
cancelled: (error) => error instanceof engine.CancelledError,
|
|
146
159
|
async awaitExecution(target) {
|
|
@@ -167,47 +180,6 @@ function durableContext(input, context, envelope) {
|
|
|
167
180
|
...(execution.receiver === undefined ? {} : { self: execution.receiver }),
|
|
168
181
|
});
|
|
169
182
|
}
|
|
170
|
-
function bindSessionOf(session, deadline, signal) {
|
|
171
|
-
return bindSession(session, { deadline, signal });
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Every Mutation and every `invoke` of a durable callable of one Step without its own key carries
|
|
175
|
-
* the next key of the Step's sequence. The sequence restarts with every attempt, so a retried Step
|
|
176
|
-
* replays the same keys: the Kernel answers a Mutation with its receipt and a durable call with the
|
|
177
|
-
* Execution it started, or refuses a request that changed under its key. Any other `invoke` carries
|
|
178
|
-
* no key, because the Kernel accepts one only for a durable Function, and runs again with the Step.
|
|
179
|
-
*/
|
|
180
|
-
function keyed(session, domain, next) {
|
|
181
|
-
const invokeSource = session.invoke;
|
|
182
|
-
const mutateSource = session.mutate;
|
|
183
|
-
const invoke = ((reference, value, options) => invokeSource(reference, value, declaredDurable(domain, reference) ? withKey(options, next) : options));
|
|
184
|
-
const mutate = ((author, options) => mutateSource(author, withKey(options, next)));
|
|
185
|
-
return Object.freeze({ ...session, invoke, mutate });
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Whether the Domain's Schema closure declares the referenced callable durable. An untyped Path
|
|
189
|
-
* names no declaration and a callable outside the closure has none here: both need their own key.
|
|
190
|
-
*/
|
|
191
|
-
function declaredDurable(domain, reference) {
|
|
192
|
-
if (reference === null || typeof reference !== 'object')
|
|
193
|
-
return false;
|
|
194
|
-
const key = reference.callable;
|
|
195
|
-
return (typeof key === 'string' && domain.callable(key)?.execution?.durable === true);
|
|
196
|
-
}
|
|
197
|
-
function withKey(options, next) {
|
|
198
|
-
if (options?.idempotencyKey !== undefined || options?.body !== undefined)
|
|
199
|
-
return options;
|
|
200
|
-
return { ...options, idempotencyKey: next() };
|
|
201
|
-
}
|
|
202
|
-
async function keySequence(execution, name) {
|
|
203
|
-
const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(name)));
|
|
204
|
-
const prefix = `${execution}.${Array.from(digest.subarray(0, 12), hex).join('')}`;
|
|
205
|
-
let position = 0;
|
|
206
|
-
return () => `${prefix}.${++position}`;
|
|
207
|
-
}
|
|
208
|
-
function hex(byte) {
|
|
209
|
-
return byte.toString(16).padStart(2, '0');
|
|
210
|
-
}
|
|
211
183
|
function signalRequest(input) {
|
|
212
184
|
if (input === null ||
|
|
213
185
|
typeof input !== 'object' ||
|
|
@@ -216,7 +188,7 @@ function signalRequest(input) {
|
|
|
216
188
|
throw new TypeError('Durable signal request is invalid.');
|
|
217
189
|
}
|
|
218
190
|
const { name, value } = input;
|
|
219
|
-
if (name.length === 0 || name.length >
|
|
191
|
+
if (name.length === 0 || name.length > MAXIMUM_SIGNAL_NAME) {
|
|
220
192
|
throw new TypeError('Durable signal name must have 1 to 128 characters.');
|
|
221
193
|
}
|
|
222
194
|
return { name, value: value ?? null };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { IntegrationClientFactory, Integrations } from '../../application/integration/index.js';
|
|
2
|
+
import type { DurableExecution, DurableStepAuthority } from '../../application/workflow/index.js';
|
|
3
|
+
import type { BoundClientSession } from '../../platform/client/session/index.js';
|
|
4
|
+
import type { Domain } from '../../platform/schema/index.js';
|
|
5
|
+
import type { Identity } from '../identity/index.js';
|
|
6
|
+
import type { Lifetime } from '../invocation/lifecycle/index.js';
|
|
7
|
+
type Executor = NonNullable<Identity['executor']>;
|
|
8
|
+
/** One attempt of one Step: its journal name, its Execution, its key sequence and its bounds. */
|
|
9
|
+
export interface StepAttempt {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly execution: DurableExecution;
|
|
12
|
+
/** The next key of the Step's sequence; the sequence restarts with every attempt. */
|
|
13
|
+
readonly next: () => string;
|
|
14
|
+
readonly lifetime: Lifetime;
|
|
15
|
+
}
|
|
16
|
+
/** What one Step attempt holds, released once the attempt ends. */
|
|
17
|
+
export interface StepAuthorityBinding {
|
|
18
|
+
readonly authority: DurableStepAuthority;
|
|
19
|
+
close(): void;
|
|
20
|
+
}
|
|
21
|
+
/** Bind the capabilities one Step attempt holds. */
|
|
22
|
+
export type StepAuthorities = (attempt: StepAttempt) => StepAuthorityBinding;
|
|
23
|
+
/**
|
|
24
|
+
* Steps acting as the Domain through a Session of the Execution they belong to: the capabilities of
|
|
25
|
+
* an Action over the keyed Session, and the Runtime's Integration clients over the same keys.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sessionSteps(input: {
|
|
28
|
+
readonly executor: Executor;
|
|
29
|
+
readonly domain: Domain;
|
|
30
|
+
readonly integrations: IntegrationClientFactory<Integrations>;
|
|
31
|
+
}): StepAuthorities;
|
|
32
|
+
/**
|
|
33
|
+
* Every Mutation and every `invoke` of a durable callable of one Step without its own key carries
|
|
34
|
+
* the next key of the Step's sequence. The sequence restarts with every attempt, so a retried Step
|
|
35
|
+
* replays the same keys: the Kernel answers a Mutation with its receipt and a durable call with the
|
|
36
|
+
* Execution it started, or refuses a request that changed under its key. Any other `invoke` carries
|
|
37
|
+
* no key, because the Kernel accepts one only for a durable Function, and runs again with the Step.
|
|
38
|
+
*/
|
|
39
|
+
export declare function keyed<Session extends Pick<BoundClientSession, 'invoke' | 'mutate'>>(session: Session, domain: Domain, next: () => string): Session;
|
|
40
|
+
/** The key sequence of one Step of one Execution: `<execution>.<digest of the name>.<position>`. */
|
|
41
|
+
export declare function keySequence(execution: DurableExecution['id'], name: string): Promise<() => string>;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { bindSession } from '../../platform/client/session/index.js';
|
|
2
|
+
import { bindCapabilities } from '../invocation/capabilities/index.js';
|
|
3
|
+
import { bindIntegrationAuthentication } from '../invocation/integration/index.js';
|
|
4
|
+
import { stepCalls } from './integrations.js';
|
|
5
|
+
/**
|
|
6
|
+
* Steps acting as the Domain through a Session of the Execution they belong to: the capabilities of
|
|
7
|
+
* an Action over the keyed Session, and the Runtime's Integration clients over the same keys.
|
|
8
|
+
*/
|
|
9
|
+
export function sessionSteps(input) {
|
|
10
|
+
const { executor, domain, integrations } = input;
|
|
11
|
+
return ({ execution, next, lifetime }) => {
|
|
12
|
+
const session = executor(execution.kernel, execution.id, lifetime);
|
|
13
|
+
try {
|
|
14
|
+
const bound = bindSession(session, lifetime);
|
|
15
|
+
const self = keyed(bound, domain, next);
|
|
16
|
+
const auth = bindIntegrationAuthentication(lifetime, bound);
|
|
17
|
+
const authority = Object.freeze({
|
|
18
|
+
...bindCapabilities(domain, { self }),
|
|
19
|
+
integrations: integrations(stepCalls({ next, lifetime, auth })),
|
|
20
|
+
});
|
|
21
|
+
return Object.freeze({ authority, close: () => session.close() });
|
|
22
|
+
}
|
|
23
|
+
catch (cause) {
|
|
24
|
+
session.close();
|
|
25
|
+
throw cause;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Every Mutation and every `invoke` of a durable callable of one Step without its own key carries
|
|
31
|
+
* the next key of the Step's sequence. The sequence restarts with every attempt, so a retried Step
|
|
32
|
+
* replays the same keys: the Kernel answers a Mutation with its receipt and a durable call with the
|
|
33
|
+
* Execution it started, or refuses a request that changed under its key. Any other `invoke` carries
|
|
34
|
+
* no key, because the Kernel accepts one only for a durable Function, and runs again with the Step.
|
|
35
|
+
*/
|
|
36
|
+
export function keyed(session, domain, next) {
|
|
37
|
+
const invokeSource = session.invoke;
|
|
38
|
+
const mutateSource = session.mutate;
|
|
39
|
+
const invoke = ((reference, value, options) => invokeSource(reference, value, declaredDurable(domain, reference) ? withKey(options, next) : options));
|
|
40
|
+
const mutate = ((author, options) => mutateSource(author, withKey(options, next)));
|
|
41
|
+
return Object.freeze({ ...session, invoke, mutate });
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether the Domain's Schema closure declares the referenced callable durable. An untyped Path
|
|
45
|
+
* names no declaration and a callable outside the closure has none here: both need their own key.
|
|
46
|
+
*/
|
|
47
|
+
function declaredDurable(domain, reference) {
|
|
48
|
+
if (reference === null || typeof reference !== 'object')
|
|
49
|
+
return false;
|
|
50
|
+
const key = reference.callable;
|
|
51
|
+
return (typeof key === 'string' && domain.callable(key)?.execution?.durable === true);
|
|
52
|
+
}
|
|
53
|
+
function withKey(options, next) {
|
|
54
|
+
if (options?.idempotencyKey !== undefined || options?.body !== undefined)
|
|
55
|
+
return options;
|
|
56
|
+
return { ...options, idempotencyKey: next() };
|
|
57
|
+
}
|
|
58
|
+
/** The key sequence of one Step of one Execution: `<execution>.<digest of the name>.<position>`. */
|
|
59
|
+
export async function keySequence(execution, name) {
|
|
60
|
+
const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(name)));
|
|
61
|
+
const prefix = `${execution}.${Array.from(digest.subarray(0, 12), hex).join('')}`;
|
|
62
|
+
let position = 0;
|
|
63
|
+
return () => `${prefix}.${++position}`;
|
|
64
|
+
}
|
|
65
|
+
function hex(byte) {
|
|
66
|
+
return byte.toString(16).padStart(2, '0');
|
|
67
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ActionOutputOf, ResolvedDomainOf } from '../../application/action/index.js';
|
|
2
|
+
import type { DeclaredErrors } from '../../application/error/index.js';
|
|
3
|
+
import type { Integrations, Providers } from '../../application/integration/index.js';
|
|
4
|
+
import type { CallableAtWorkflowAddress, WorkflowAddress, WorkflowDefinition } from '../../application/workflow/index.js';
|
|
5
|
+
import type { ExecutionOutcome, ExecutionState } from '../../platform/client/session/index.js';
|
|
6
|
+
import type { NodeId } from '../../platform/graph/node/index.js';
|
|
7
|
+
import type { CallableInputOf, ResolvedMethod, schema } from '../../platform/schema/index.js';
|
|
8
|
+
import type { Value } from '../../platform/value/index.js';
|
|
9
|
+
import type { TestStepCapabilities } from './steps.js';
|
|
10
|
+
export interface TestEngineOptions {
|
|
11
|
+
/** Start of the virtual clock, in Unix milliseconds; the current time by default. */
|
|
12
|
+
readonly now?: number;
|
|
13
|
+
/** Attempts of a run before it pauses; 20 by default, as on a Host. */
|
|
14
|
+
readonly maxAttempts?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* An in-memory workflow engine that runs durable Workflows with the receiver a Host serves: it
|
|
18
|
+
* journals each Step, replays the journal on every attempt, retries failed attempts, keeps timers
|
|
19
|
+
* on a virtual clock, buffers signals, pauses after the last attempt and cancels cooperatively.
|
|
20
|
+
*/
|
|
21
|
+
export interface TestEngine {
|
|
22
|
+
/** The virtual clock, in Unix milliseconds. */
|
|
23
|
+
readonly now: number;
|
|
24
|
+
/** Start one durable Execution and settle it: it ends, waits, or pauses. */
|
|
25
|
+
start<Schema extends schema.DomainSchema, Address extends WorkflowAddress<Schema>, Definitions extends Integrations, Errors extends DeclaredErrors>(workflow: WorkflowDefinition<Schema, Address, Definitions, Errors>, options: TestStartOptions<CallableAtWorkflowAddress<Schema, Address>, ResolvedDomainOf<Schema>, Definitions>): Promise<TestExecution<ActionOutputOf<CallableAtWorkflowAddress<Schema, Address>>>>;
|
|
26
|
+
/** Move the virtual clock forward and settle every waiting Execution. */
|
|
27
|
+
advance(durationMs: number): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export type TestStartOptions<Callable, DomainValue, Definitions extends Integrations> = {
|
|
30
|
+
/** The resolved Schema the Workflow belongs to. */
|
|
31
|
+
readonly schema: DomainValue;
|
|
32
|
+
readonly input: CallableInputOf<Callable>;
|
|
33
|
+
/** The `K.Execution` id; a fresh one by default. */
|
|
34
|
+
readonly id?: NodeId;
|
|
35
|
+
/** Who started the Execution; test Identities by default. */
|
|
36
|
+
readonly initiator?: {
|
|
37
|
+
readonly principal: NodeId;
|
|
38
|
+
readonly caller: NodeId;
|
|
39
|
+
};
|
|
40
|
+
readonly capabilities?: TestStepCapabilities;
|
|
41
|
+
/** Steps whose first completion is lost before the journal records it; see `interruptAfter`. */
|
|
42
|
+
readonly interruptAfter?: readonly string[];
|
|
43
|
+
} & ReceiverOf<Callable> & ProvidersOf<Definitions>;
|
|
44
|
+
type ReceiverOf<Callable> = Callable extends ResolvedMethod<unknown, unknown, false> ? {
|
|
45
|
+
readonly self: NodeId;
|
|
46
|
+
} : {
|
|
47
|
+
readonly self?: never;
|
|
48
|
+
};
|
|
49
|
+
type ProvidersOf<Definitions extends Integrations> = keyof Definitions extends never ? {
|
|
50
|
+
readonly providers?: never;
|
|
51
|
+
} : {
|
|
52
|
+
readonly providers: Providers<Definitions>;
|
|
53
|
+
};
|
|
54
|
+
/** One durable Execution on a test engine. */
|
|
55
|
+
export interface TestExecution<Output> {
|
|
56
|
+
readonly id: NodeId;
|
|
57
|
+
readonly state: Extract<ExecutionState, 'running' | 'waiting' | 'paused' | 'completed' | 'failed' | 'cancelled'>;
|
|
58
|
+
/** Present once the Execution ended, as `Execution.result` answers it. */
|
|
59
|
+
readonly outcome: ExecutionOutcome<Output> | undefined;
|
|
60
|
+
/** What the last failed attempt threw; it is why a paused Execution paused. */
|
|
61
|
+
readonly failure: unknown;
|
|
62
|
+
/** Attempts made so far, replays included. */
|
|
63
|
+
readonly attempts: number;
|
|
64
|
+
/** The run's journal, in order. */
|
|
65
|
+
readonly journal: readonly TestJournalEntry[];
|
|
66
|
+
/**
|
|
67
|
+
* Deliver `Execution.signal({ name, value })`, then settle. False when a signal already resolved
|
|
68
|
+
* that name.
|
|
69
|
+
*/
|
|
70
|
+
signal(name: string, value?: Value): Promise<boolean>;
|
|
71
|
+
/** Request cooperative cancellation, as `Execution.cancel()`, then settle. */
|
|
72
|
+
cancel(): Promise<void>;
|
|
73
|
+
/** Attempt a paused Execution again, as `Execution.resume()`, then settle. */
|
|
74
|
+
resume(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Run the Workflow once more from its journal alone, as a restart does, and fail if it runs a Step
|
|
77
|
+
* again, diverges from the journal, or reaches another outcome.
|
|
78
|
+
*/
|
|
79
|
+
replay(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* The next time Step `id` completes, lose its result before the journal records it, as a crash
|
|
82
|
+
* between its effect and the journal does. The engine then retries the Step.
|
|
83
|
+
*/
|
|
84
|
+
interruptAfter(id: string): void;
|
|
85
|
+
}
|
|
86
|
+
/** One entry of a run's journal. */
|
|
87
|
+
export type TestJournalEntry = {
|
|
88
|
+
readonly kind: 'run';
|
|
89
|
+
readonly name: string;
|
|
90
|
+
readonly settled: boolean;
|
|
91
|
+
readonly value?: unknown;
|
|
92
|
+
readonly error?: unknown;
|
|
93
|
+
} | {
|
|
94
|
+
readonly kind: 'sleep';
|
|
95
|
+
readonly until: number;
|
|
96
|
+
readonly settled: boolean;
|
|
97
|
+
} | {
|
|
98
|
+
readonly kind: 'signal';
|
|
99
|
+
readonly name: string;
|
|
100
|
+
readonly settled: boolean;
|
|
101
|
+
readonly value?: unknown;
|
|
102
|
+
} | {
|
|
103
|
+
readonly kind: 'race';
|
|
104
|
+
readonly name: string;
|
|
105
|
+
readonly until: number;
|
|
106
|
+
readonly settled: boolean;
|
|
107
|
+
readonly outcome?: 'signal' | 'timeout' | 'cancelled';
|
|
108
|
+
};
|
|
109
|
+
/** Create an in-memory workflow engine for tests. */
|
|
110
|
+
export declare function createTestEngine(options?: TestEngineOptions): TestEngine;
|
|
111
|
+
export {};
|