@usejourney/core 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.
- package/LICENSE +21 -0
- package/dist/index.d.ts +696 -0
- package/dist/index.js +1217 -0
- package/package.json +36 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
import { z, ZodType } from 'zod';
|
|
2
|
+
export { infer as ZodInfer, ZodType, ZodTypeAny, z } from 'zod';
|
|
3
|
+
|
|
4
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
|
|
5
|
+
declare const EndpointResponseBrand: unique symbol;
|
|
6
|
+
interface EndpointRef<TResponse> {
|
|
7
|
+
readonly method: HttpMethod;
|
|
8
|
+
readonly path: string;
|
|
9
|
+
readonly operationId?: string;
|
|
10
|
+
readonly [EndpointResponseBrand]?: TResponse;
|
|
11
|
+
}
|
|
12
|
+
interface EndpointDescriptor {
|
|
13
|
+
readonly method: HttpMethod;
|
|
14
|
+
readonly path: string;
|
|
15
|
+
readonly baseUrl?: string;
|
|
16
|
+
}
|
|
17
|
+
type Endpoint<TResponse = unknown> = EndpointRef<TResponse> | EndpointDescriptor;
|
|
18
|
+
type ResponseOf<E> = E extends EndpointRef<infer R> ? R : unknown;
|
|
19
|
+
declare function isEndpointRef(e: Endpoint): e is EndpointRef<unknown>;
|
|
20
|
+
|
|
21
|
+
declare const JourneyConfigSchema: z.ZodObject<{
|
|
22
|
+
name: z.ZodOptional<z.ZodString>;
|
|
23
|
+
spec: z.ZodDefault<z.ZodString>;
|
|
24
|
+
generatedDir: z.ZodDefault<z.ZodString>;
|
|
25
|
+
journeysDir: z.ZodDefault<z.ZodString>;
|
|
26
|
+
environmentsDir: z.ZodDefault<z.ZodString>;
|
|
27
|
+
defaultEnvironment: z.ZodOptional<z.ZodString>;
|
|
28
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
29
|
+
runHistoryKeepCount: z.ZodDefault<z.ZodNumber>;
|
|
30
|
+
tlsRejectUnauthorized: z.ZodDefault<z.ZodBoolean>;
|
|
31
|
+
}, "strict", z.ZodTypeAny, {
|
|
32
|
+
spec: string;
|
|
33
|
+
generatedDir: string;
|
|
34
|
+
journeysDir: string;
|
|
35
|
+
environmentsDir: string;
|
|
36
|
+
runHistoryKeepCount: number;
|
|
37
|
+
tlsRejectUnauthorized: boolean;
|
|
38
|
+
name?: string | undefined;
|
|
39
|
+
defaultEnvironment?: string | undefined;
|
|
40
|
+
baseUrl?: string | undefined;
|
|
41
|
+
}, {
|
|
42
|
+
name?: string | undefined;
|
|
43
|
+
spec?: string | undefined;
|
|
44
|
+
generatedDir?: string | undefined;
|
|
45
|
+
journeysDir?: string | undefined;
|
|
46
|
+
environmentsDir?: string | undefined;
|
|
47
|
+
defaultEnvironment?: string | undefined;
|
|
48
|
+
baseUrl?: string | undefined;
|
|
49
|
+
runHistoryKeepCount?: number | undefined;
|
|
50
|
+
tlsRejectUnauthorized?: boolean | undefined;
|
|
51
|
+
}>;
|
|
52
|
+
type JourneyConfig = z.infer<typeof JourneyConfigSchema>;
|
|
53
|
+
interface LoadedConfig {
|
|
54
|
+
readonly config: JourneyConfig;
|
|
55
|
+
readonly projectDir: string;
|
|
56
|
+
readonly configPath: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Effective base URL for runtime requests. `config.baseUrl` wins when set;
|
|
60
|
+
* otherwise the active environment's `BASE_URL` is used. Returns `undefined`
|
|
61
|
+
* if neither source supplies one — descriptor endpoints can still carry their
|
|
62
|
+
* own per-step baseUrl in that case.
|
|
63
|
+
*/
|
|
64
|
+
declare function resolveBaseUrl(config: JourneyConfig): string | undefined;
|
|
65
|
+
declare function resolveConfigPaths(loaded: LoadedConfig): {
|
|
66
|
+
specPath: string;
|
|
67
|
+
generatedDir: string;
|
|
68
|
+
journeysDir: string;
|
|
69
|
+
environmentsDir: string;
|
|
70
|
+
};
|
|
71
|
+
declare function loadConfig(projectDir: string): Promise<LoadedConfig>;
|
|
72
|
+
|
|
73
|
+
type EnvValues = Record<string, string>;
|
|
74
|
+
declare function setActiveEnvironment(name: string, values: EnvValues): void;
|
|
75
|
+
declare function clearActiveEnvironment(): void;
|
|
76
|
+
declare function env(key: string): string;
|
|
77
|
+
declare function tryEnv(key: string): string | undefined;
|
|
78
|
+
declare function loadEnvironment(environmentsDir: string, name: string): Promise<EnvValues>;
|
|
79
|
+
declare function listEnvironments(environmentsDir: string): Promise<string[]>;
|
|
80
|
+
|
|
81
|
+
declare class AssertionError extends Error {
|
|
82
|
+
constructor(message: string);
|
|
83
|
+
}
|
|
84
|
+
interface Expectation<T> {
|
|
85
|
+
toBe(expected: T): void;
|
|
86
|
+
toEqual(expected: unknown): void;
|
|
87
|
+
toBeDefined(): void;
|
|
88
|
+
toContain(expected: unknown): void;
|
|
89
|
+
toMatch(expected: RegExp | string): void;
|
|
90
|
+
toBeGreaterThan(n: number): void;
|
|
91
|
+
toBeGreaterThanOrEqual(n: number): void;
|
|
92
|
+
toBeLessThan(n: number): void;
|
|
93
|
+
toBeLessThanOrEqual(n: number): void;
|
|
94
|
+
toHaveLength(n: number): void;
|
|
95
|
+
}
|
|
96
|
+
declare function expect<T>(value: T): Expectation<T>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Output cache for sub-journey invocations. When an `invokeJourney(...)` call
|
|
100
|
+
* supplies a `cacheKey`, the resolved key + child journey name identify a
|
|
101
|
+
* cache slot; a hit short-circuits the child run and replays the stored
|
|
102
|
+
* output. See the M7 RFC (#86) for the lifetime model.
|
|
103
|
+
*/
|
|
104
|
+
/** Cache lifetime selected via `--cache`. */
|
|
105
|
+
type CacheMode = "off" | "run" | "process" | "disk";
|
|
106
|
+
interface CacheEntry {
|
|
107
|
+
value: unknown;
|
|
108
|
+
/** Epoch ms after which the entry is stale; absent → no expiry. */
|
|
109
|
+
expiresAt?: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Storage backend for sub-journey outputs. `get`/`set` may be sync (memory) or
|
|
113
|
+
* async (disk); callers `await` the result either way.
|
|
114
|
+
*/
|
|
115
|
+
interface SubJourneyCache {
|
|
116
|
+
get(key: string): CacheEntry | undefined | Promise<CacheEntry | undefined>;
|
|
117
|
+
set(key: string, value: unknown, ttlMs?: number): void | Promise<void>;
|
|
118
|
+
}
|
|
119
|
+
/** Composite cache key — `${childJourneyName}:${resolvedKey}`. */
|
|
120
|
+
declare function subJourneyCacheKey(journeyName: string, resolvedKey: string): string;
|
|
121
|
+
/**
|
|
122
|
+
* In-memory cache. Lives as long as the holding process keeps the instance —
|
|
123
|
+
* the CLI reuses one instance for `--cache=process` (persists across runs of
|
|
124
|
+
* `journey serve`) and a fresh one per run for `--cache=run`.
|
|
125
|
+
*/
|
|
126
|
+
declare class MemorySubJourneyCache implements SubJourneyCache {
|
|
127
|
+
private readonly store;
|
|
128
|
+
get(key: string): CacheEntry | undefined;
|
|
129
|
+
set(key: string, value: unknown, ttlMs?: number): void;
|
|
130
|
+
clear(): void;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Disk-backed cache: one JSON file per key under `dir`, named by the SHA-256
|
|
134
|
+
* of the composite key. Survives process restarts (`--cache=disk`).
|
|
135
|
+
*
|
|
136
|
+
* Values must be JSON-serializable — a sub-journey `output(...)` is normally a
|
|
137
|
+
* plain object, so this holds in practice. Non-JSON values (Date, undefined,
|
|
138
|
+
* functions) are silently lost in the round-trip.
|
|
139
|
+
*/
|
|
140
|
+
declare class DiskSubJourneyCache implements SubJourneyCache {
|
|
141
|
+
private readonly dir;
|
|
142
|
+
constructor(dir: string);
|
|
143
|
+
private pathFor;
|
|
144
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
145
|
+
set(key: string, value: unknown, ttlMs?: number): Promise<void>;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Builds the cache backend for a `CacheMode`. `off` → no cache. `run` and
|
|
149
|
+
* `process` → a fresh `MemorySubJourneyCache` (the caller controls reuse, and
|
|
150
|
+
* so the lifetime). `disk` → a `DiskSubJourneyCache` under `diskDir`.
|
|
151
|
+
*/
|
|
152
|
+
declare function createSubJourneyCache(mode: CacheMode, opts: {
|
|
153
|
+
diskDir: string;
|
|
154
|
+
}): SubJourneyCache | undefined;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Pluggable request/response logger. The runtime calls into a logger attached
|
|
158
|
+
* to the HttpContext (if any); CLI/GUI surfaces wire one up when --debug or
|
|
159
|
+
* DEBUG=journey is set.
|
|
160
|
+
*/
|
|
161
|
+
interface RequestLog {
|
|
162
|
+
method: string;
|
|
163
|
+
url: string;
|
|
164
|
+
headers: Record<string, string>;
|
|
165
|
+
body?: unknown;
|
|
166
|
+
}
|
|
167
|
+
interface ResponseLog {
|
|
168
|
+
status: number;
|
|
169
|
+
headers: Record<string, string>;
|
|
170
|
+
body: unknown;
|
|
171
|
+
durationMs: number;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Fired at the top of a run before any step executes. The runtime allocates a
|
|
175
|
+
* runId if the caller didn't pass one in, so subscribers can correlate later
|
|
176
|
+
* events back to the run.
|
|
177
|
+
*/
|
|
178
|
+
interface RunStartEvent {
|
|
179
|
+
runId: string;
|
|
180
|
+
journeyNames: string[];
|
|
181
|
+
}
|
|
182
|
+
/** Fired once per run after every journey has either completed or halted. */
|
|
183
|
+
interface RunEndEvent {
|
|
184
|
+
runId: string;
|
|
185
|
+
ok: boolean;
|
|
186
|
+
durationMs: number;
|
|
187
|
+
results: ReadonlyArray<{
|
|
188
|
+
name: string;
|
|
189
|
+
ok: boolean;
|
|
190
|
+
}>;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Fired once per journey, right after `runJourney` has finished collecting the
|
|
194
|
+
* step list (a single execution of `def.body()`) and before any `onStepStart`.
|
|
195
|
+
* The full ordered step list is known at this point — subscribers can use it to
|
|
196
|
+
* pre-render a timeline without waiting for each `step:start` to arrive.
|
|
197
|
+
*
|
|
198
|
+
* Particularly useful for journeys whose bodies inject steps via helpers (e.g.
|
|
199
|
+
* `registerAuthStep()` calling `step()` from inside the body): a static parse
|
|
200
|
+
* of the source can't see those, but `onPlanned` does.
|
|
201
|
+
*
|
|
202
|
+
* `stepIdxOffset` is the absolute index of the first step in this journey
|
|
203
|
+
* within the surrounding `runAllRegistered` call; consumers can use it to map
|
|
204
|
+
* positions in `steps` back to the monotonic `stepIdx` values that subsequent
|
|
205
|
+
* `step:start` / `step:end` events will carry.
|
|
206
|
+
*/
|
|
207
|
+
/**
|
|
208
|
+
* One entry in a planned pipeline. `kind` is `"step"` for an HTTP step
|
|
209
|
+
* (carries method/path from the endpoint) and `"sub"` for an
|
|
210
|
+
* `invokeJourney(...)` node (carries the child journey's display name;
|
|
211
|
+
* method/path are unset).
|
|
212
|
+
*
|
|
213
|
+
* A `"sub"` node carries `children` — the best-effort plan of the child
|
|
214
|
+
* pipeline, discovered by evaluating the reusable journey body at plan time
|
|
215
|
+
* (recursively, so nested sub-journeys are included). `incomplete` is set
|
|
216
|
+
* when that discovery could not run — e.g. the body threw, or the recursion
|
|
217
|
+
* cap was hit — in which case `children` is absent and subscribers fall back
|
|
218
|
+
* to the live `onGroupStart` / step events once the sub-journey executes.
|
|
219
|
+
*
|
|
220
|
+
* Plan-time discovery is best-effort: conditional `step()` calls and a
|
|
221
|
+
* sub-journey that turns out to be a cache hit can make the planned tree
|
|
222
|
+
* differ from what actually runs. The live group/step events are always
|
|
223
|
+
* authoritative.
|
|
224
|
+
*/
|
|
225
|
+
interface PlannedNode {
|
|
226
|
+
kind?: "step" | "sub";
|
|
227
|
+
name: string;
|
|
228
|
+
method?: string;
|
|
229
|
+
path?: string;
|
|
230
|
+
/** Sub-journey only — best-effort plan of the child pipeline. */
|
|
231
|
+
children?: PlannedNode[];
|
|
232
|
+
/** Sub-journey only — true when the child pipeline could not be discovered. */
|
|
233
|
+
incomplete?: boolean;
|
|
234
|
+
}
|
|
235
|
+
interface RunPlannedEvent {
|
|
236
|
+
runId: string;
|
|
237
|
+
journeyIdx: number;
|
|
238
|
+
journeyName: string;
|
|
239
|
+
stepIdxOffset: number;
|
|
240
|
+
/**
|
|
241
|
+
* Resolved pipeline entries, in order. A `"sub"` entry nests its child
|
|
242
|
+
* pipeline under `children` (see `PlannedNode`) so subscribers can
|
|
243
|
+
* pre-render the full tree — including nested sub-journeys — without
|
|
244
|
+
* waiting for the run to enter each group.
|
|
245
|
+
*/
|
|
246
|
+
steps: ReadonlyArray<PlannedNode>;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Fired at the moment a sub-journey node (an `invokeJourney(handle, ...)`
|
|
250
|
+
* call) begins executing — before any of the child's steps run. The `stepIdx`
|
|
251
|
+
* is the slot consumed by the sub-journey itself in the parent run's
|
|
252
|
+
* monotonic counter; child step events that follow carry `firstChildStepIdx`
|
|
253
|
+
* and beyond.
|
|
254
|
+
*/
|
|
255
|
+
interface GroupStartEvent {
|
|
256
|
+
runId: string;
|
|
257
|
+
journeyIdx: number;
|
|
258
|
+
/** Display name for the timeline (override on the call site, else handle.name). */
|
|
259
|
+
name: string;
|
|
260
|
+
/** Child journey's authored name (i.e. handle.name). Distinct from `name` only when overridden. */
|
|
261
|
+
childJourneyName: string;
|
|
262
|
+
/** Slot consumed by the sub-journey node itself. */
|
|
263
|
+
stepIdx: number;
|
|
264
|
+
/** First child stepIdx that will fire inside this group (== `stepIdx + 1`). */
|
|
265
|
+
firstChildStepIdx: number;
|
|
266
|
+
/** "miss" / "hit". Until #90 lands the cache store, this is always "miss". */
|
|
267
|
+
cacheStatus: "miss" | "hit";
|
|
268
|
+
/** Resolved cache key if the caller supplied a `cacheKey` opt. Omitted otherwise. */
|
|
269
|
+
resolvedKey?: string;
|
|
270
|
+
}
|
|
271
|
+
/** Fired once the sub-journey node finishes (success, failure, or cache hit short-circuit). */
|
|
272
|
+
interface GroupEndEvent {
|
|
273
|
+
runId: string;
|
|
274
|
+
journeyIdx: number;
|
|
275
|
+
name: string;
|
|
276
|
+
childJourneyName: string;
|
|
277
|
+
stepIdx: number;
|
|
278
|
+
/** Last child stepIdx that fired inside this group. Equal to `stepIdx` when the child had no steps (or hit cache). */
|
|
279
|
+
lastChildStepIdx: number;
|
|
280
|
+
ok: boolean;
|
|
281
|
+
durationMs: number;
|
|
282
|
+
error?: string;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Fired when a step starts executing, before any lazy-resolved headers/query/
|
|
286
|
+
* body are materialized and before `onRequest`. `stepIdx` is monotonic across
|
|
287
|
+
* the whole run, not just within a journey — this way consumers don't need to
|
|
288
|
+
* track journey boundaries to correlate events.
|
|
289
|
+
*/
|
|
290
|
+
interface StepStartEvent {
|
|
291
|
+
runId: string;
|
|
292
|
+
journeyIdx: number;
|
|
293
|
+
journeyName: string;
|
|
294
|
+
stepIdx: number;
|
|
295
|
+
name: string;
|
|
296
|
+
}
|
|
297
|
+
/** Fired when a step finishes, after `onResponse`/`onError`. */
|
|
298
|
+
interface StepEndEvent {
|
|
299
|
+
runId: string;
|
|
300
|
+
journeyIdx: number;
|
|
301
|
+
stepIdx: number;
|
|
302
|
+
ok: boolean;
|
|
303
|
+
durationMs: number;
|
|
304
|
+
error?: string;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Fired for `console.log` / `console.warn` / `console.error` calls made from
|
|
308
|
+
* inside step hooks (e.g. `after(res) { console.log(res.body.id); }`). The
|
|
309
|
+
* subscriber attaches its own `stepIdx`; core carries only the raw level + text.
|
|
310
|
+
*/
|
|
311
|
+
interface LogEvent {
|
|
312
|
+
level: "info" | "warn" | "error";
|
|
313
|
+
text: string;
|
|
314
|
+
}
|
|
315
|
+
interface JourneyLogger {
|
|
316
|
+
onRunStart?(event: RunStartEvent): void;
|
|
317
|
+
onRunEnd?(event: RunEndEvent): void;
|
|
318
|
+
onPlanned?(event: RunPlannedEvent): void;
|
|
319
|
+
onStepStart?(event: StepStartEvent): void;
|
|
320
|
+
onStepEnd?(event: StepEndEvent): void;
|
|
321
|
+
onRequest?(req: RequestLog): void;
|
|
322
|
+
onResponse?(req: RequestLog, res: ResponseLog): void;
|
|
323
|
+
onError?(req: RequestLog, error: unknown, durationMs: number): void;
|
|
324
|
+
/** Fired when a sub-journey node begins executing, before any child step events. */
|
|
325
|
+
onGroupStart?(event: GroupStartEvent): void;
|
|
326
|
+
/** Fired when a sub-journey node finishes; carries the child step range and the group outcome. */
|
|
327
|
+
onGroupEnd?(event: GroupEndEvent): void;
|
|
328
|
+
/**
|
|
329
|
+
* Fired for user-code `console.*` calls captured during a run. The runner
|
|
330
|
+
* installs a shim that forwards each call here and to the original console
|
|
331
|
+
* so terminal output stays intact.
|
|
332
|
+
*/
|
|
333
|
+
onLog?(event: LogEvent): void;
|
|
334
|
+
info?(message: string): void;
|
|
335
|
+
}
|
|
336
|
+
/** Header names that get redacted before logging by default. */
|
|
337
|
+
declare const SECRET_HEADERS: ReadonlyArray<string>;
|
|
338
|
+
/**
|
|
339
|
+
* Walks `err.cause` up to `depth` links and joins each message with ` ← `.
|
|
340
|
+
* Surfaces the real reason behind `fetch failed` (Node's fetch wraps undici
|
|
341
|
+
* errors, e.g. `ECONNREFUSED`, `ENOTFOUND`, OpenSSL cert errors). Includes
|
|
342
|
+
* the error `code` in parentheses when present.
|
|
343
|
+
*/
|
|
344
|
+
declare function describeError(err: unknown, depth?: number): string;
|
|
345
|
+
declare function maskHeaders(headers: Record<string, string>, masks?: ReadonlyArray<string>): Record<string, string>;
|
|
346
|
+
interface ConsoleLoggerOptions {
|
|
347
|
+
/** Where to write each line. Defaults to console.error so stdout stays clean. */
|
|
348
|
+
write?: (line: string) => void;
|
|
349
|
+
/** Mask secret-looking headers (default true). */
|
|
350
|
+
mask?: boolean;
|
|
351
|
+
/** Truncate logged response/request bodies past this many chars (default 1024). */
|
|
352
|
+
maxBodyChars?: number;
|
|
353
|
+
}
|
|
354
|
+
declare function createConsoleLogger(opts?: ConsoleLoggerOptions): JourneyLogger;
|
|
355
|
+
/**
|
|
356
|
+
* Returns a console logger when DEBUG env var includes "journey" (or "*"),
|
|
357
|
+
* otherwise undefined. Useful for CLI bins that want to opt in via env var.
|
|
358
|
+
*/
|
|
359
|
+
declare function loggerFromEnv(env?: NodeJS.ProcessEnv): JourneyLogger | undefined;
|
|
360
|
+
|
|
361
|
+
interface HttpResponse<T = unknown> {
|
|
362
|
+
status: number;
|
|
363
|
+
headers: Record<string, string>;
|
|
364
|
+
body: T;
|
|
365
|
+
}
|
|
366
|
+
interface HttpContext {
|
|
367
|
+
/** Fallback base URL when the endpoint doesn't carry its own. */
|
|
368
|
+
baseUrl?: string;
|
|
369
|
+
/** Global default headers applied before per-step headers. */
|
|
370
|
+
defaultHeaders?: Record<string, string>;
|
|
371
|
+
/** Injectable fetch — default is global fetch. Tests override this. */
|
|
372
|
+
fetchImpl?: typeof fetch;
|
|
373
|
+
/** Optional logger called before/after each request. */
|
|
374
|
+
logger?: JourneyLogger;
|
|
375
|
+
/**
|
|
376
|
+
* Optional undici `Dispatcher` (typed as `unknown` so core stays
|
|
377
|
+
* dependency-free). When set, it is forwarded to `fetch` as `init.dispatcher`
|
|
378
|
+
* so callers can disable TLS verification, route through a proxy, or pin a
|
|
379
|
+
* client cert. The CLI's `--insecure` flag uses this.
|
|
380
|
+
*/
|
|
381
|
+
dispatcher?: unknown;
|
|
382
|
+
/**
|
|
383
|
+
* Optional run-scoped AbortSignal. When set, every `fetch` issued through
|
|
384
|
+
* `execute` (and through the instrumented `@usejourney/core` `fetch` helper)
|
|
385
|
+
* receives it, and `runJourney` stops iterating steps as soon as it fires.
|
|
386
|
+
* Used by the dev server's `POST /api/runs/:id/abort` route to cancel an
|
|
387
|
+
* in-flight run.
|
|
388
|
+
*/
|
|
389
|
+
signal?: AbortSignal;
|
|
390
|
+
/**
|
|
391
|
+
* Optional sub-journey output cache. When set, an `invokeJourney(...)` call
|
|
392
|
+
* that supplies a `cacheKey` (and isn't `cache: "off"`) replays a stored
|
|
393
|
+
* output instead of re-running the child. Absent → caching disabled
|
|
394
|
+
* (`--cache=off`). Wired by the CLI from the `--cache` flag.
|
|
395
|
+
*/
|
|
396
|
+
subJourneyCache?: SubJourneyCache;
|
|
397
|
+
/** Default TTL (ms) for sub-journey cache writes; per-call `cacheTtlMs` overrides. */
|
|
398
|
+
subJourneyCacheTtlMs?: number;
|
|
399
|
+
}
|
|
400
|
+
interface RequestSpec {
|
|
401
|
+
method: HttpMethod;
|
|
402
|
+
url: string;
|
|
403
|
+
headers: Record<string, string>;
|
|
404
|
+
body?: unknown;
|
|
405
|
+
timeoutMs?: number;
|
|
406
|
+
}
|
|
407
|
+
interface BuildRequestOptions {
|
|
408
|
+
endpoint: Endpoint;
|
|
409
|
+
params?: Record<string, string | number>;
|
|
410
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
411
|
+
headers?: Record<string, string>;
|
|
412
|
+
body?: unknown;
|
|
413
|
+
timeoutMs?: number;
|
|
414
|
+
}
|
|
415
|
+
declare function resolveUrl(endpoint: Endpoint, ctx: HttpContext, params: Record<string, string | number> | undefined, query: Record<string, string | number | boolean | undefined> | undefined): string;
|
|
416
|
+
declare function buildRequest(opts: BuildRequestOptions, ctx: HttpContext): RequestSpec;
|
|
417
|
+
declare function execute(req: RequestSpec, ctx: HttpContext): Promise<HttpResponse>;
|
|
418
|
+
|
|
419
|
+
/** Caller-supplied run metadata. runId is forwarded to every lifecycle event. */
|
|
420
|
+
interface RunMeta {
|
|
421
|
+
runId?: string;
|
|
422
|
+
/**
|
|
423
|
+
* Absolute (monotonic across journey boundaries) stepIdx to stop after. The
|
|
424
|
+
* run still emits `run:end` cleanly; journeys/steps past this index are not
|
|
425
|
+
* collected or executed. Use for "run only up to this step" in the GUI.
|
|
426
|
+
*/
|
|
427
|
+
upToStepIdx?: number;
|
|
428
|
+
}
|
|
429
|
+
type Lazy<T> = T | (() => T | Promise<T>);
|
|
430
|
+
interface StepOptions<E extends Endpoint> {
|
|
431
|
+
endpoint: E;
|
|
432
|
+
params?: Lazy<Record<string, string | number>>;
|
|
433
|
+
query?: Lazy<Record<string, string | number | boolean | undefined>>;
|
|
434
|
+
headers?: Lazy<Record<string, string>>;
|
|
435
|
+
body?: Lazy<unknown>;
|
|
436
|
+
timeoutMs?: number;
|
|
437
|
+
assert?: (res: HttpResponse<ResponseOf<E>>) => void | Promise<void>;
|
|
438
|
+
after?: (res: HttpResponse<ResponseOf<E>>) => void | Promise<void>;
|
|
439
|
+
}
|
|
440
|
+
interface StepDef {
|
|
441
|
+
name: string;
|
|
442
|
+
options: StepOptions<Endpoint>;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Recorded call to a reusable journey, captured in the parent's pipeline at
|
|
446
|
+
* registration time. The `handle` carries the child def and its input/output
|
|
447
|
+
* schemas; everything else is per-call configuration. See `invokeJourney`.
|
|
448
|
+
*/
|
|
449
|
+
interface SubJourneyCallDef {
|
|
450
|
+
handle: JourneyHandle<unknown, unknown>;
|
|
451
|
+
/** Display label for the timeline; defaults to `handle.name`. */
|
|
452
|
+
name?: string;
|
|
453
|
+
/** Lazy or eager input. Validated against `handle.inputs` (if set) before child body runs. */
|
|
454
|
+
inputs?: Lazy<unknown>;
|
|
455
|
+
/** Cache opts — plumbed through `onGroupStart`. Store lookup lands in #90. */
|
|
456
|
+
cacheKey?: string | ((input: unknown) => string);
|
|
457
|
+
cacheTtlMs?: number;
|
|
458
|
+
cache?: "off" | "inherit";
|
|
459
|
+
assert?: (out: unknown) => void | Promise<void>;
|
|
460
|
+
after?: (out: unknown) => void | Promise<void>;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* One ordered entry in the resolved pipeline of a journey. Both kinds consume
|
|
464
|
+
* exactly one `stepIdx` slot in the run's monotonic counter; child steps
|
|
465
|
+
* inside a `sub` node consume their own slots in the same counter.
|
|
466
|
+
*/
|
|
467
|
+
type PipelineNode = {
|
|
468
|
+
kind: "step";
|
|
469
|
+
def: StepDef;
|
|
470
|
+
} | {
|
|
471
|
+
kind: "sub";
|
|
472
|
+
def: SubJourneyCallDef;
|
|
473
|
+
};
|
|
474
|
+
/**
|
|
475
|
+
* Typed reference to a reusable journey. Returned by `journey(name, { reusable: true, ... }, body)`.
|
|
476
|
+
* Pass it to `invokeJourney(handle, ...)`; the phantom `I`/`O` parameters drive
|
|
477
|
+
* type inference at the call site.
|
|
478
|
+
*/
|
|
479
|
+
interface JourneyHandle<I = unknown, O = unknown> {
|
|
480
|
+
readonly name: string;
|
|
481
|
+
readonly inputs?: ZodType<I>;
|
|
482
|
+
readonly outputs?: ZodType<O>;
|
|
483
|
+
/** Internal: the def the runtime invokes. Treat as opaque. */
|
|
484
|
+
readonly __def: JourneyDef;
|
|
485
|
+
/** Phantom — never assigned at runtime, only present in the type. */
|
|
486
|
+
readonly __input?: I;
|
|
487
|
+
readonly __output?: O;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* k6 `export const options` shape for an exported journey. Strict named fields
|
|
491
|
+
* cover the common load profiles (vus + duration, iterations, stages); the
|
|
492
|
+
* index signature passes everything else through so adding a k6 knob (e.g.
|
|
493
|
+
* thresholds, scenarios, ext) does not require a core release.
|
|
494
|
+
*/
|
|
495
|
+
interface K6JourneyOptions {
|
|
496
|
+
vus?: number;
|
|
497
|
+
duration?: string;
|
|
498
|
+
iterations?: number;
|
|
499
|
+
stages?: Array<{
|
|
500
|
+
duration: string;
|
|
501
|
+
target: number;
|
|
502
|
+
}>;
|
|
503
|
+
[extra: string]: unknown;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Per-call configuration accepted by the 3-arg `journey()` overloads. The
|
|
507
|
+
* field set is split by mode:
|
|
508
|
+
*
|
|
509
|
+
* - Entry journeys (default — registered for `runAllRegistered`): `tags`, `k6`.
|
|
510
|
+
* - Reusable journeys (`reusable: true`, no auto-run, returns a handle):
|
|
511
|
+
* `inputs`, `outputs`.
|
|
512
|
+
*
|
|
513
|
+
* The two sets are disjoint in the public overloads. Mixing is an `as any`
|
|
514
|
+
* escape from the type-level split; the footgun guard in `runAllRegistered`
|
|
515
|
+
* catches it at run start.
|
|
516
|
+
*/
|
|
517
|
+
interface JourneyOptions {
|
|
518
|
+
/** Entry-only — drives `journey export k6 --tag` filtering. */
|
|
519
|
+
tags?: string[];
|
|
520
|
+
/** Entry-only — baked into the emitted k6 script's `export const options`. */
|
|
521
|
+
k6?: K6JourneyOptions;
|
|
522
|
+
/** Switches to reusable mode when true. */
|
|
523
|
+
reusable?: boolean;
|
|
524
|
+
/** Reusable-only — child input schema. Validated before the child body runs. */
|
|
525
|
+
inputs?: ZodType;
|
|
526
|
+
/** Reusable-only — child output schema. Validated when the child completes. */
|
|
527
|
+
outputs?: ZodType;
|
|
528
|
+
}
|
|
529
|
+
interface JourneyDef {
|
|
530
|
+
name: string;
|
|
531
|
+
/** Bodies for entries take no argument; reusable bodies take the validated input. */
|
|
532
|
+
body: (input?: unknown) => void | Promise<void>;
|
|
533
|
+
options?: JourneyOptions;
|
|
534
|
+
}
|
|
535
|
+
interface StepResult {
|
|
536
|
+
name: string;
|
|
537
|
+
ok: boolean;
|
|
538
|
+
request?: {
|
|
539
|
+
method: string;
|
|
540
|
+
url: string;
|
|
541
|
+
};
|
|
542
|
+
response?: HttpResponse;
|
|
543
|
+
error?: string;
|
|
544
|
+
durationMs: number;
|
|
545
|
+
/** Set on sub-journey nodes; the flat list of child step results in execution order. */
|
|
546
|
+
children?: StepResult[];
|
|
547
|
+
/** Set on sub-journey nodes — distinguishes a group entry from a regular step. */
|
|
548
|
+
kind?: "step" | "sub";
|
|
549
|
+
/** Set on sub-journey nodes — whether the output cache was hit or missed. */
|
|
550
|
+
cacheStatus?: "hit" | "miss";
|
|
551
|
+
}
|
|
552
|
+
interface JourneyResult {
|
|
553
|
+
name: string;
|
|
554
|
+
ok: boolean;
|
|
555
|
+
steps: StepResult[];
|
|
556
|
+
durationMs: number;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Returns the HttpContext bound to the currently-executing journey, or
|
|
560
|
+
* undefined when called outside a run. Consumed by the instrumented `fetch`
|
|
561
|
+
* helper so that ad-hoc HTTP calls made from inside step hooks land on the
|
|
562
|
+
* same logger as the steps themselves.
|
|
563
|
+
*/
|
|
564
|
+
declare function getCurrentCtx(): HttpContext | undefined;
|
|
565
|
+
/** Type for the journey body when no `inputs` schema is set. */
|
|
566
|
+
type EntryBody = () => void | Promise<void>;
|
|
567
|
+
/** Type for a reusable journey body — receives the validated input. */
|
|
568
|
+
type ReusableBody<I> = (input: I) => void | Promise<void>;
|
|
569
|
+
/** Strict reusable options — discriminated by literal `reusable: true`. */
|
|
570
|
+
interface ReusableJourneyOptions<I, O> {
|
|
571
|
+
reusable: true;
|
|
572
|
+
inputs?: ZodType<I>;
|
|
573
|
+
outputs?: ZodType<O>;
|
|
574
|
+
}
|
|
575
|
+
/** Strict entry options — `reusable` must be absent or false. */
|
|
576
|
+
interface EntryJourneyOptions {
|
|
577
|
+
reusable?: false;
|
|
578
|
+
tags?: string[];
|
|
579
|
+
k6?: K6JourneyOptions;
|
|
580
|
+
}
|
|
581
|
+
declare function journey<I, O>(name: string, options: ReusableJourneyOptions<I, O>, body: ReusableBody<I>): JourneyHandle<I, O>;
|
|
582
|
+
declare function journey(name: string, options: EntryJourneyOptions, body: EntryBody): void;
|
|
583
|
+
declare function journey(name: string, body: EntryBody): void;
|
|
584
|
+
declare function step<E extends Endpoint>(name: string, options: StepOptions<E>): void;
|
|
585
|
+
/** Options accepted at an `invokeJourney(handle, opts)` call site. */
|
|
586
|
+
interface InvokeJourneyOptions<I, O> {
|
|
587
|
+
inputs?: I | (() => I | Promise<I>);
|
|
588
|
+
/** Override the timeline display label; defaults to `handle.name`. */
|
|
589
|
+
name?: string;
|
|
590
|
+
cacheKey?: string | ((input: I) => string);
|
|
591
|
+
cacheTtlMs?: number;
|
|
592
|
+
cache?: "off" | "inherit";
|
|
593
|
+
assert?: (out: O) => void | Promise<void>;
|
|
594
|
+
after?: (out: O) => void | Promise<void>;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Pipeline-level primitive: registers a sub-journey call as a node in the
|
|
598
|
+
* surrounding journey's pipeline. Like `step()`, this is called during the
|
|
599
|
+
* registration phase (the journey body), not at execution time — the child
|
|
600
|
+
* runs inline when the runtime reaches the node.
|
|
601
|
+
*/
|
|
602
|
+
declare function invokeJourney<I, O>(handle: JourneyHandle<I, O>, opts?: InvokeJourneyOptions<I, O>): void;
|
|
603
|
+
/**
|
|
604
|
+
* Terminal helper for reusable journeys: records the value returned to the
|
|
605
|
+
* parent's `invokeJourney({ after })`. Called from inside any step's `after`
|
|
606
|
+
* hook within the child body. Multiple calls: last wins (with a warn log).
|
|
607
|
+
* Called outside a reusable journey context: warn and no-op.
|
|
608
|
+
*/
|
|
609
|
+
declare function output(value: unknown): void;
|
|
610
|
+
declare function getRegisteredJourneys(): ReadonlyArray<JourneyDef>;
|
|
611
|
+
declare function clearRegistry(): void;
|
|
612
|
+
/** Walks a journey body and returns every pipeline node (step + sub) in order. */
|
|
613
|
+
declare function collectPipeline(def: JourneyDef): Promise<ReadonlyArray<PipelineNode>>;
|
|
614
|
+
/**
|
|
615
|
+
* Collects the child pipeline of a sub-journey call, resolving the call's
|
|
616
|
+
* `inputs` and passing them to the reusable body so `input.*` references in
|
|
617
|
+
* the child resolve. Lets an exporter (#92) walk a journey into a nested
|
|
618
|
+
* tree without running it. Best-effort: input resolution failures fall back
|
|
619
|
+
* to `undefined`, mirroring `discoverChildNodes`.
|
|
620
|
+
*/
|
|
621
|
+
declare function collectSubPipeline(call: SubJourneyCallDef): Promise<ReadonlyArray<PipelineNode>>;
|
|
622
|
+
/**
|
|
623
|
+
* Resolves a journey's plan tree without executing it — the same nested
|
|
624
|
+
* `PlannedNode[]` that `onPlanned` broadcasts at run start. Evaluates the
|
|
625
|
+
* journey body (and, best-effort, each sub-journey body) to discover steps;
|
|
626
|
+
* performs no HTTP. Lets a surface pre-render a timeline before the run.
|
|
627
|
+
*
|
|
628
|
+
* `env()` references in the body are resolved against the active environment,
|
|
629
|
+
* so callers should `setActiveEnvironment(...)` first, exactly as they would
|
|
630
|
+
* before a run.
|
|
631
|
+
*/
|
|
632
|
+
declare function planJourney(def: JourneyDef): Promise<PlannedNode[]>;
|
|
633
|
+
/**
|
|
634
|
+
* Runs a single entry journey. Emits `onStepStart` / `onStepEnd` (and
|
|
635
|
+
* `onGroupStart` / `onGroupEnd` for any sub-journey nodes) through
|
|
636
|
+
* `ctx.logger`; run-level lifecycle events (`onRunStart` / `onRunEnd`) are
|
|
637
|
+
* the caller's responsibility — use `runAllRegistered` to get the full set.
|
|
638
|
+
*
|
|
639
|
+
* Pass `opts.journeyIdx` (and `opts.runId`) so step events carry the same
|
|
640
|
+
* correlation identifiers as surrounding `runAllRegistered` invocations would
|
|
641
|
+
* use; otherwise a fresh runId is minted and journeyIdx defaults to 0.
|
|
642
|
+
*/
|
|
643
|
+
declare function runJourney(def: JourneyDef, ctx: HttpContext, opts?: {
|
|
644
|
+
runId?: string;
|
|
645
|
+
journeyIdx?: number;
|
|
646
|
+
stepIdxOffset?: number;
|
|
647
|
+
upToStepIdx?: number;
|
|
648
|
+
}): Promise<JourneyResult>;
|
|
649
|
+
/**
|
|
650
|
+
* Runs every currently-registered entry journey, clearing the registry first.
|
|
651
|
+
* Emits `onRunStart` / `onRunEnd` bookends around the set, with each journey's
|
|
652
|
+
* steps in between sharing the same runId. stepIdx is monotonic across the
|
|
653
|
+
* whole run so a subscriber can key network/log streams by stepIdx without
|
|
654
|
+
* caring about journey boundaries.
|
|
655
|
+
*/
|
|
656
|
+
declare function runAllRegistered(ctx: HttpContext, opts?: RunMeta): Promise<JourneyResult[]>;
|
|
657
|
+
|
|
658
|
+
interface RunRecord {
|
|
659
|
+
id: string;
|
|
660
|
+
timestamp: string;
|
|
661
|
+
results: JourneyResult[];
|
|
662
|
+
}
|
|
663
|
+
interface RunSummary {
|
|
664
|
+
id: string;
|
|
665
|
+
timestamp: string;
|
|
666
|
+
journeyNames: string[];
|
|
667
|
+
ok: boolean;
|
|
668
|
+
/** Sum of durationMs across all journeys in the run. */
|
|
669
|
+
durationMs: number;
|
|
670
|
+
/** Total step count across all journeys. */
|
|
671
|
+
stepCount: number;
|
|
672
|
+
}
|
|
673
|
+
declare function writeRun(cacheDir: string, results: JourneyResult[]): Promise<RunRecord>;
|
|
674
|
+
declare function listRuns(cacheDir: string): Promise<RunSummary[]>;
|
|
675
|
+
declare function readRun(cacheDir: string, id: string): Promise<RunRecord | undefined>;
|
|
676
|
+
declare function pruneRuns(cacheDir: string, keep: number): Promise<number>;
|
|
677
|
+
|
|
678
|
+
type FetchInput = Parameters<typeof globalThis.fetch>[0];
|
|
679
|
+
type FetchInit = Parameters<typeof globalThis.fetch>[1];
|
|
680
|
+
type FetchResponse = Awaited<ReturnType<typeof globalThis.fetch>>;
|
|
681
|
+
/**
|
|
682
|
+
* Drop-in replacement for `globalThis.fetch` that routes through the
|
|
683
|
+
* currently-executing journey's logger when called from inside a step hook.
|
|
684
|
+
*
|
|
685
|
+
* Outside a run context (no active `runJourney`) it delegates to the platform
|
|
686
|
+
* `fetch` with no behaviour change — the same helper module is therefore
|
|
687
|
+
* safe to import from ad-hoc scripts or top-level setup code.
|
|
688
|
+
*
|
|
689
|
+
* Intended use: auth-helper steps that mint a token from a separate service
|
|
690
|
+
* inside an `after` hook. Raw `globalThis.fetch` works but is invisible to
|
|
691
|
+
* the Debug Console and run history; this wrapper makes those calls
|
|
692
|
+
* observable without forcing helpers to thread the HttpContext through.
|
|
693
|
+
*/
|
|
694
|
+
declare function fetch$1(input: FetchInput, init?: FetchInit): Promise<FetchResponse>;
|
|
695
|
+
|
|
696
|
+
export { AssertionError, type BuildRequestOptions, type CacheEntry, type CacheMode, type ConsoleLoggerOptions, DiskSubJourneyCache, type Endpoint, type EndpointDescriptor, type EndpointRef, type EntryJourneyOptions, type EnvValues, type Expectation, type GroupEndEvent, type GroupStartEvent, type HttpContext, type HttpMethod, type HttpResponse, type InvokeJourneyOptions, type JourneyConfig, JourneyConfigSchema, type JourneyDef, type JourneyHandle, type JourneyLogger, type JourneyOptions, type JourneyResult, type K6JourneyOptions, type LoadedConfig, type LogEvent, MemorySubJourneyCache, type PipelineNode, type PlannedNode, type RequestLog, type RequestSpec, type ResponseLog, type ResponseOf, type ReusableJourneyOptions, type RunEndEvent, type RunMeta, type RunPlannedEvent, type RunRecord, type RunStartEvent, type RunSummary, SECRET_HEADERS, type StepDef, type StepEndEvent, type StepOptions, type StepResult, type StepStartEvent, type SubJourneyCache, type SubJourneyCallDef, buildRequest, clearActiveEnvironment, clearRegistry, collectPipeline, collectSubPipeline, createConsoleLogger, createSubJourneyCache, describeError, env, execute, expect, fetch$1 as fetch, getCurrentCtx, getRegisteredJourneys, invokeJourney, isEndpointRef, journey, listEnvironments, listRuns, loadConfig, loadEnvironment, loggerFromEnv, maskHeaders, output, planJourney, pruneRuns, readRun, resolveBaseUrl, resolveConfigPaths, resolveUrl, runAllRegistered, runJourney, setActiveEnvironment, step, subJourneyCacheKey, tryEnv, writeRun };
|