waygraph 0.15.24 → 0.15.25

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,289 @@
1
+ import type { Block, Checkpoint } from "../types.js";
2
+ import type { EngineConfig } from "./config.js";
3
+ import type { EndMarker, StartMarker } from "./run-graph.js";
4
+ import type { Flow } from "./flow.js";
5
+ import type { NavBlock } from "./blocks/nav.js";
6
+ import type { PageBlock } from "./blocks/page.js";
7
+ import type { AssertBlock, MethodBlock } from "./blocks/method.js";
8
+ import type { EffectBlock } from "./blocks/effect.js";
9
+ type S = Checkpoint<"__start__">;
10
+ /**
11
+ * Holds engine-level configuration shared across every flow defined from it -
12
+ * for now, just how to launch a browser when a Flow owns its own
13
+ * (`headless`/`browserName`/`slowMo`); traits/interstitials/watchers land here
14
+ * in a later change.
15
+ * @example const engine = new Engine({ headless: false, slowMo: 250 });
16
+ */
17
+ export declare class Engine {
18
+ private readonly config;
19
+ constructor(config?: EngineConfig);
20
+ /**
21
+ * Builds a runnable {@link Flow} from `[start, ...Blocks, end]`, typechecked
22
+ * so each Block's `In` must match the previous Block's `Out` - the same
23
+ * check `connect()` does, just declared as a flat array instead of hand-nested
24
+ * calls. Overloaded for 2-9 array slots (1-8 real Blocks between `start`/`end`)
25
+ * rather than one fully-generic recursive tuple type, so each arity is as
26
+ * reliably checked as `connect<A,B,C>` itself.
27
+ * @example engine.defineFlow([start, LoginBlock, AddToCartBlock, end])
28
+ */
29
+ defineFlow<B extends Checkpoint<string>>(blocks: readonly [StartMarker, Block<S, B>, EndMarker]): Flow<B>;
30
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>>(blocks: readonly [StartMarker, Block<S, B>, Block<B, C>, EndMarker]): Flow<C>;
31
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>>(blocks: readonly [StartMarker, Block<S, B>, Block<B, C>, Block<C, D>, EndMarker]): Flow<D>;
32
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>>(blocks: readonly [StartMarker, Block<S, B>, Block<B, C>, Block<C, D>, Block<D, E>, EndMarker]): Flow<E>;
33
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>>(blocks: readonly [
34
+ StartMarker,
35
+ Block<S, B>,
36
+ Block<B, C>,
37
+ Block<C, D>,
38
+ Block<D, E>,
39
+ Block<E, F>,
40
+ EndMarker
41
+ ]): Flow<F>;
42
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>>(blocks: readonly [
43
+ StartMarker,
44
+ Block<S, B>,
45
+ Block<B, C>,
46
+ Block<C, D>,
47
+ Block<D, E>,
48
+ Block<E, F>,
49
+ Block<F, G>,
50
+ EndMarker
51
+ ]): Flow<G>;
52
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>>(blocks: readonly [
53
+ StartMarker,
54
+ Block<S, B>,
55
+ Block<B, C>,
56
+ Block<C, D>,
57
+ Block<D, E>,
58
+ Block<E, F>,
59
+ Block<F, G>,
60
+ Block<G, H>,
61
+ EndMarker
62
+ ]): Flow<H>;
63
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>>(blocks: readonly [
64
+ StartMarker,
65
+ Block<S, B>,
66
+ Block<B, C>,
67
+ Block<C, D>,
68
+ Block<D, E>,
69
+ Block<E, F>,
70
+ Block<F, G>,
71
+ Block<G, H>,
72
+ Block<H, I>,
73
+ EndMarker
74
+ ]): Flow<I>;
75
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>>(blocks: readonly [
76
+ StartMarker,
77
+ Block<S, B>,
78
+ Block<B, C>,
79
+ Block<C, D>,
80
+ Block<D, E>,
81
+ Block<E, F>,
82
+ Block<F, G>,
83
+ Block<G, H>,
84
+ Block<H, I>,
85
+ Block<I, J>,
86
+ EndMarker
87
+ ]): Flow<J>;
88
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>, K extends Checkpoint<string>>(blocks: readonly [
89
+ StartMarker,
90
+ Block<S, B>,
91
+ Block<B, C>,
92
+ Block<C, D>,
93
+ Block<D, E>,
94
+ Block<E, F>,
95
+ Block<F, G>,
96
+ Block<G, H>,
97
+ Block<H, I>,
98
+ Block<I, J>,
99
+ Block<J, K>,
100
+ EndMarker
101
+ ]): Flow<K>;
102
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>, K extends Checkpoint<string>, L extends Checkpoint<string>>(blocks: readonly [
103
+ StartMarker,
104
+ Block<S, B>,
105
+ Block<B, C>,
106
+ Block<C, D>,
107
+ Block<D, E>,
108
+ Block<E, F>,
109
+ Block<F, G>,
110
+ Block<G, H>,
111
+ Block<H, I>,
112
+ Block<I, J>,
113
+ Block<J, K>,
114
+ Block<K, L>,
115
+ EndMarker
116
+ ]): Flow<L>;
117
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>, K extends Checkpoint<string>, L extends Checkpoint<string>, M extends Checkpoint<string>>(blocks: readonly [
118
+ StartMarker,
119
+ Block<S, B>,
120
+ Block<B, C>,
121
+ Block<C, D>,
122
+ Block<D, E>,
123
+ Block<E, F>,
124
+ Block<F, G>,
125
+ Block<G, H>,
126
+ Block<H, I>,
127
+ Block<I, J>,
128
+ Block<J, K>,
129
+ Block<K, L>,
130
+ Block<L, M>,
131
+ EndMarker
132
+ ]): Flow<M>;
133
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>, K extends Checkpoint<string>, L extends Checkpoint<string>, M extends Checkpoint<string>, N extends Checkpoint<string>>(blocks: readonly [
134
+ StartMarker,
135
+ Block<S, B>,
136
+ Block<B, C>,
137
+ Block<C, D>,
138
+ Block<D, E>,
139
+ Block<E, F>,
140
+ Block<F, G>,
141
+ Block<G, H>,
142
+ Block<H, I>,
143
+ Block<I, J>,
144
+ Block<J, K>,
145
+ Block<K, L>,
146
+ Block<L, M>,
147
+ Block<M, N>,
148
+ EndMarker
149
+ ]): Flow<N>;
150
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>, K extends Checkpoint<string>, L extends Checkpoint<string>, M extends Checkpoint<string>, N extends Checkpoint<string>, O extends Checkpoint<string>>(blocks: readonly [
151
+ StartMarker,
152
+ Block<S, B>,
153
+ Block<B, C>,
154
+ Block<C, D>,
155
+ Block<D, E>,
156
+ Block<E, F>,
157
+ Block<F, G>,
158
+ Block<G, H>,
159
+ Block<H, I>,
160
+ Block<I, J>,
161
+ Block<J, K>,
162
+ Block<K, L>,
163
+ Block<L, M>,
164
+ Block<M, N>,
165
+ Block<N, O>,
166
+ EndMarker
167
+ ]): Flow<O>;
168
+ /**
169
+ * Fluent, kind-checked alternative to `defineFlow([start, ...blocks, end])` -
170
+ * see {@link MapBuilder}. Real, direct request this responds to: pia/zsign's
171
+ * own agents kept hand-editing/hand-composing Blocks into ad hoc shapes
172
+ * ("locks" convention tried, still got broken) - `map()` forces every step
173
+ * through this Engine's own `define*Block` factories (checked by the same
174
+ * `__waygraphKind`/`__waygraphSalt` runtime markers `graph.ts`/`map-check.ts`
175
+ * already trust), so a hand-rolled plain-object Block can never silently
176
+ * pass as a real navigation/assertion/method step. `homeOrigin`, if given,
177
+ * also gates `.gotoPage()`/`.gotoExternal()` against each Nav/Page Block's
178
+ * own static `url` (skipped, honestly, for click-based/dynamic nav - not
179
+ * statically checkable, same limitation `map-check.ts` already documents).
180
+ * @example
181
+ * const flow = engine.map({ homeOrigin: "https://app.example.com" })
182
+ * .start()
183
+ * .gotoPage(NavHomeBlock)
184
+ * .assert(AssertHelloBlock)
185
+ * .gotoExternal(NavMailpitBlock)
186
+ * .end();
187
+ */
188
+ map(options?: MapBuilderOptions): MapBuilder<S>;
189
+ }
190
+ export interface MapBuilderOptions {
191
+ /**
192
+ * This project's own origin (e.g. `"https://app.example.com"`) - enables
193
+ * `.gotoPage()`/`.gotoExternal()`'s origin check against a Block's static
194
+ * `url`. Omit to skip that check entirely (still fully kind-checked either
195
+ * way; only the internal/external origin distinction is opt-in).
196
+ */
197
+ homeOrigin?: string;
198
+ }
199
+ /**
200
+ * Fluent builder over {@link Engine.defineFlow} - see `map()`'s own doc
201
+ * comment for why this exists. Each step method is scoped to exactly the
202
+ * Block kind its name promises, checked at RUNTIME against the
203
+ * `__waygraphKind`/`__waygraphSalt` markers `defineNavBlock`/`definePageBlock`/
204
+ * `defineAssertBlock`/`defineMethodBlock`/`defineEffectBlock` already set
205
+ * (the same markers `graph.ts` and `map-check.ts` trust) - not just a type
206
+ * hint, since a hand-rolled object can satisfy the TypeScript `Block<In,Out>`
207
+ * shape without ever going through a real factory. Each call also
208
+ * typechecks the accumulated Checkpoint chain exactly like `defineFlow`'s
209
+ * own tuple overloads do (a step's `In` must equal the previous step's
210
+ * `Out`) - this is what "no teleporting" means: there is no method on this
211
+ * builder that can skip from one Checkpoint to an unrelated one without a
212
+ * real, kind-correct Block in between.
213
+ */
214
+ export declare class MapBuilder<Out extends Checkpoint<string>> {
215
+ private readonly engine;
216
+ private readonly steps;
217
+ private readonly homeOrigin;
218
+ private readonly ff;
219
+ private constructor();
220
+ /** @internal - use `engine.map()` or the standalone `map()` export. */
221
+ static begin(engine: Engine, homeOrigin: string | undefined): MapBuilder<S>;
222
+ /** Readable chain-opener - mirrors `defineFlow`'s leading `start` sentinel. Returns `this` unchanged; entirely optional. */
223
+ start(): MapBuilder<Out>;
224
+ /**
225
+ * Open a fast-forward window - steps until {@link ffEnd} collapse into one
226
+ * opaque `fastForwardComposeBlock` (blitz theater). Kind/salt checks still
227
+ * run on each inner step as it is added.
228
+ */
229
+ ffStart(name?: string): MapBuilder<Out>;
230
+ /**
231
+ * Close the current fast-forward window and append one FFCompose step.
232
+ * Throws if empty or if {@link ffStart} was never opened.
233
+ */
234
+ ffEnd(): MapBuilder<Out>;
235
+ private appendStep;
236
+ /**
237
+ * Appends an internal navigation/page-arrival step - requires a Block from
238
+ * `defineNavBlock`/`defineMemNavBlock`/`definePageBlock`. Throws if given
239
+ * anything else, including a `homeOrigin`-mismatched static `url`.
240
+ * Compile-time: {@link NavBlock} or {@link PageBlock} only (not Assert/Method).
241
+ */
242
+ gotoPage<NextOut extends Checkpoint<string>>(block: (NavBlock<NextOut> | PageBlock<NextOut>) & {
243
+ name: string;
244
+ }): MapBuilder<NextOut>;
245
+ /**
246
+ * Appends a genuinely cross-origin navigation step (e.g. `(external)/`
247
+ * Blocks in the Waygraph Map convention: mailpit, maildrop.cc) - same
248
+ * kind requirement as {@link gotoPage}, plus the inverse `homeOrigin` check.
249
+ * Compile-time: {@link NavBlock} or {@link PageBlock} only.
250
+ */
251
+ gotoExternal<NextOut extends Checkpoint<string>>(block: (NavBlock<NextOut> | PageBlock<NextOut>) & {
252
+ name: string;
253
+ }): MapBuilder<NextOut>;
254
+ /**
255
+ * Appends a self-loop verification step - requires a Block from
256
+ * `defineAssertBlock`. Compile-time: {@link AssertBlock} only (not `.method()`).
257
+ */
258
+ assert(block: AssertBlock<Out> & {
259
+ name: string;
260
+ }): MapBuilder<Out>;
261
+ /**
262
+ * Appends a non-navigating page action (submit, upload, add/remove an
263
+ * instance) - requires a Block from `defineMethodBlock`/`defineActionBlock`/
264
+ * `defineEffectBlock`/`defineMemEffectBlock`.
265
+ * Compile-time: {@link MethodBlock} or {@link EffectBlock} only - Assert
266
+ * Blocks must use `.assert()`. Runtime also rejects `__waygraphKind =
267
+ * "assert"` (those still carry method salt from the factory internals).
268
+ */
269
+ method<NextOut extends Checkpoint<string>>(block: (MethodBlock<Out, NextOut> | EffectBlock<Out, NextOut>) & {
270
+ name: string;
271
+ }): MapBuilder<NextOut>;
272
+ /**
273
+ * Finalizes this chain into a real, runnable {@link Flow} - same object
274
+ * `defineFlow` returns, so `withBlockVerify`/`modBlockVerify` and every
275
+ * `withTitle`/`withHighlightFixtures`/etc. decorator still apply exactly
276
+ * as they do today; this builder only changes how the chain is assembled,
277
+ * never what it produces. Throws if no step was ever added - an empty map
278
+ * isn't a flow.
279
+ */
280
+ end(): Flow<Out>;
281
+ }
282
+ /**
283
+ * Standalone convenience for `new Engine(config).map(options)` - use this
284
+ * when a call site doesn't otherwise need its own `Engine` instance (no
285
+ * shared `headless`/`slowMo`/`layouts` across several flows).
286
+ * @example const flow = map({ homeOrigin: "https://app.example.com" }).gotoPage(NavHomeBlock).end();
287
+ */
288
+ export declare function map(options?: MapBuilderOptions & EngineConfig): MapBuilder<S>;
289
+ export {};
@@ -0,0 +1,257 @@
1
+ import { end, start } from "./run-graph.js";
2
+ import { buildFlow } from "./flow.js";
3
+ import { fastForwardComposeBlock } from "./compose.js";
4
+ /**
5
+ * Holds engine-level configuration shared across every flow defined from it -
6
+ * for now, just how to launch a browser when a Flow owns its own
7
+ * (`headless`/`browserName`/`slowMo`); traits/interstitials/watchers land here
8
+ * in a later change.
9
+ * @example const engine = new Engine({ headless: false, slowMo: 250 });
10
+ */
11
+ export class Engine {
12
+ config;
13
+ constructor(config = {}) {
14
+ this.config = config;
15
+ }
16
+ defineFlow(blocks) {
17
+ return buildFlow(blocks.slice(1, -1), this.config);
18
+ }
19
+ /**
20
+ * Fluent, kind-checked alternative to `defineFlow([start, ...blocks, end])` -
21
+ * see {@link MapBuilder}. Real, direct request this responds to: pia/zsign's
22
+ * own agents kept hand-editing/hand-composing Blocks into ad hoc shapes
23
+ * ("locks" convention tried, still got broken) - `map()` forces every step
24
+ * through this Engine's own `define*Block` factories (checked by the same
25
+ * `__waygraphKind`/`__waygraphSalt` runtime markers `graph.ts`/`map-check.ts`
26
+ * already trust), so a hand-rolled plain-object Block can never silently
27
+ * pass as a real navigation/assertion/method step. `homeOrigin`, if given,
28
+ * also gates `.gotoPage()`/`.gotoExternal()` against each Nav/Page Block's
29
+ * own static `url` (skipped, honestly, for click-based/dynamic nav - not
30
+ * statically checkable, same limitation `map-check.ts` already documents).
31
+ * @example
32
+ * const flow = engine.map({ homeOrigin: "https://app.example.com" })
33
+ * .start()
34
+ * .gotoPage(NavHomeBlock)
35
+ * .assert(AssertHelloBlock)
36
+ * .gotoExternal(NavMailpitBlock)
37
+ * .end();
38
+ */
39
+ map(options) {
40
+ return MapBuilder.begin(this, options?.homeOrigin);
41
+ }
42
+ }
43
+ function mapKindOf(block) {
44
+ return block?.__waygraphKind;
45
+ }
46
+ function mapSaltOf(block) {
47
+ return block?.__waygraphSalt;
48
+ }
49
+ function mapNavUrlOf(block) {
50
+ const url = block?.__waygraphNavUrl;
51
+ return typeof url === "string" ? url : undefined;
52
+ }
53
+ const MAP_KIND_FACTORY_HINT = {
54
+ nav: "defineNavBlock/defineMemNavBlock",
55
+ page: "definePageBlock",
56
+ assert: "defineAssertBlock",
57
+ };
58
+ const MAP_SALT_FACTORY_HINT = {
59
+ method: "defineMethodBlock",
60
+ effect: "defineEffectBlock",
61
+ };
62
+ function assertMapKind(block, allowed, method) {
63
+ const kind = mapKindOf(block);
64
+ if (kind !== undefined && allowed.includes(kind))
65
+ return;
66
+ const wanted = allowed.map((k) => MAP_KIND_FACTORY_HINT[k] ?? k).join(" or ");
67
+ const found = kind ? `a Block of kind "${kind}"` : "an object with no waygraph kind marker at all";
68
+ throw new Error(`Waygraph map: .${method}("${block?.name ?? "?"}") requires a Block built with ${wanted} ` +
69
+ `(found ${found} - a hand-built plain object doesn't count). This check is the whole point ` +
70
+ "of the map() builder: only real Blocks from waygraph's own factories can enter a chain.");
71
+ }
72
+ function assertMapSalt(block, allowed, method) {
73
+ const salt = mapSaltOf(block);
74
+ if (salt !== undefined && allowed.includes(salt))
75
+ return;
76
+ const wanted = allowed.map((s) => MAP_SALT_FACTORY_HINT[s] ?? s).join(" or ");
77
+ const found = salt ? `a Block salted "${salt}"` : "an object with no waygraph salt marker at all";
78
+ throw new Error(`Waygraph map: .${method}("${block?.name ?? "?"}") requires a Block built with ${wanted} ` +
79
+ `(found ${found} - a hand-built plain object doesn't count). This check is the whole point ` +
80
+ "of the map() builder: only real Blocks from waygraph's own factories can enter a chain.");
81
+ }
82
+ function assertMapOrigin(block, homeOrigin, expect, method) {
83
+ if (!homeOrigin)
84
+ return; // not configured - can't validate, honest no-op
85
+ const navUrl = mapNavUrlOf(block);
86
+ if (navUrl === undefined)
87
+ return; // click-based/dynamic nav - not statically checkable
88
+ let targetOrigin;
89
+ let wantOrigin;
90
+ try {
91
+ targetOrigin = new URL(navUrl, homeOrigin).origin;
92
+ wantOrigin = new URL(homeOrigin).origin;
93
+ }
94
+ catch {
95
+ return; // malformed URL - not this check's job to validate that
96
+ }
97
+ const isExternal = targetOrigin !== wantOrigin;
98
+ if (expect === "internal" && isExternal) {
99
+ throw new Error(`Waygraph map: .gotoPage("${block.name}") targets ${targetOrigin}, which is NOT this map's ` +
100
+ `home origin (${wantOrigin}) - use .gotoExternal() for a genuinely cross-origin destination.`);
101
+ }
102
+ if (expect === "external" && !isExternal) {
103
+ throw new Error(`Waygraph map: .gotoExternal("${block.name}") targets ${targetOrigin}, which IS this map's ` +
104
+ `home origin (${wantOrigin}) - use .gotoPage() for an internal destination.`);
105
+ }
106
+ }
107
+ /**
108
+ * Fluent builder over {@link Engine.defineFlow} - see `map()`'s own doc
109
+ * comment for why this exists. Each step method is scoped to exactly the
110
+ * Block kind its name promises, checked at RUNTIME against the
111
+ * `__waygraphKind`/`__waygraphSalt` markers `defineNavBlock`/`definePageBlock`/
112
+ * `defineAssertBlock`/`defineMethodBlock`/`defineEffectBlock` already set
113
+ * (the same markers `graph.ts` and `map-check.ts` trust) - not just a type
114
+ * hint, since a hand-rolled object can satisfy the TypeScript `Block<In,Out>`
115
+ * shape without ever going through a real factory. Each call also
116
+ * typechecks the accumulated Checkpoint chain exactly like `defineFlow`'s
117
+ * own tuple overloads do (a step's `In` must equal the previous step's
118
+ * `Out`) - this is what "no teleporting" means: there is no method on this
119
+ * builder that can skip from one Checkpoint to an unrelated one without a
120
+ * real, kind-correct Block in between.
121
+ */
122
+ export class MapBuilder {
123
+ engine;
124
+ steps;
125
+ homeOrigin;
126
+ ff;
127
+ constructor(engine, steps, homeOrigin, ff = null) {
128
+ this.engine = engine;
129
+ this.steps = steps;
130
+ this.homeOrigin = homeOrigin;
131
+ this.ff = ff;
132
+ }
133
+ /** @internal - use `engine.map()` or the standalone `map()` export. */
134
+ static begin(engine, homeOrigin) {
135
+ return new MapBuilder(engine, [], homeOrigin, null);
136
+ }
137
+ /** Readable chain-opener - mirrors `defineFlow`'s leading `start` sentinel. Returns `this` unchanged; entirely optional. */
138
+ start() {
139
+ return this;
140
+ }
141
+ /**
142
+ * Open a fast-forward window - steps until {@link ffEnd} collapse into one
143
+ * opaque `fastForwardComposeBlock` (blitz theater). Kind/salt checks still
144
+ * run on each inner step as it is added.
145
+ */
146
+ ffStart(name) {
147
+ if (this.ff) {
148
+ throw new Error(`Waygraph map: .ffStart() while already inside "${this.ff.name}" - call .ffEnd() first`);
149
+ }
150
+ const ffName = typeof name === "string" && name.trim()
151
+ ? name.trim()
152
+ : `ff-${this.steps.length + 1}`;
153
+ return new MapBuilder(this.engine, this.steps, this.homeOrigin, {
154
+ name: ffName,
155
+ buffer: [],
156
+ });
157
+ }
158
+ /**
159
+ * Close the current fast-forward window and append one FFCompose step.
160
+ * Throws if empty or if {@link ffStart} was never opened.
161
+ */
162
+ ffEnd() {
163
+ if (!this.ff) {
164
+ throw new Error("Waygraph map: .ffEnd() with no open .ffStart()");
165
+ }
166
+ if (this.ff.buffer.length === 0) {
167
+ throw new Error(`Waygraph map: .ffEnd() for "${this.ff.name}" has no steps - add .gotoPage/.method/… inside the window`);
168
+ }
169
+ const composed = fastForwardComposeBlock(this.ff.name,
170
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
+ this.ff.buffer);
172
+ return new MapBuilder(this.engine, [...this.steps, composed], this.homeOrigin, null);
173
+ }
174
+ appendStep(block) {
175
+ if (this.ff) {
176
+ return new MapBuilder(this.engine, this.steps, this.homeOrigin, {
177
+ name: this.ff.name,
178
+ buffer: [...this.ff.buffer, block],
179
+ });
180
+ }
181
+ return new MapBuilder(this.engine, [...this.steps, block], this.homeOrigin, null);
182
+ }
183
+ /**
184
+ * Appends an internal navigation/page-arrival step - requires a Block from
185
+ * `defineNavBlock`/`defineMemNavBlock`/`definePageBlock`. Throws if given
186
+ * anything else, including a `homeOrigin`-mismatched static `url`.
187
+ * Compile-time: {@link NavBlock} or {@link PageBlock} only (not Assert/Method).
188
+ */
189
+ gotoPage(block) {
190
+ assertMapKind(block, ["nav", "page"], "gotoPage");
191
+ assertMapOrigin(block, this.homeOrigin, "internal", "gotoPage");
192
+ return this.appendStep(block);
193
+ }
194
+ /**
195
+ * Appends a genuinely cross-origin navigation step (e.g. `(external)/`
196
+ * Blocks in the Waygraph Map convention: mailpit, maildrop.cc) - same
197
+ * kind requirement as {@link gotoPage}, plus the inverse `homeOrigin` check.
198
+ * Compile-time: {@link NavBlock} or {@link PageBlock} only.
199
+ */
200
+ gotoExternal(block) {
201
+ assertMapKind(block, ["nav", "page"], "gotoExternal");
202
+ assertMapOrigin(block, this.homeOrigin, "external", "gotoExternal");
203
+ return this.appendStep(block);
204
+ }
205
+ /**
206
+ * Appends a self-loop verification step - requires a Block from
207
+ * `defineAssertBlock`. Compile-time: {@link AssertBlock} only (not `.method()`).
208
+ */
209
+ assert(block) {
210
+ assertMapKind(block, ["assert"], "assert");
211
+ return this.appendStep(block);
212
+ }
213
+ /**
214
+ * Appends a non-navigating page action (submit, upload, add/remove an
215
+ * instance) - requires a Block from `defineMethodBlock`/`defineActionBlock`/
216
+ * `defineEffectBlock`/`defineMemEffectBlock`.
217
+ * Compile-time: {@link MethodBlock} or {@link EffectBlock} only - Assert
218
+ * Blocks must use `.assert()`. Runtime also rejects `__waygraphKind =
219
+ * "assert"` (those still carry method salt from the factory internals).
220
+ */
221
+ method(block) {
222
+ if (mapKindOf(block) === "assert") {
223
+ throw new Error(`Waygraph map: .method("${block.name}") got a defineAssertBlock - use .assert() for ` +
224
+ "self-loop verify steps (Assert Blocks share method salt internally but are not methods).");
225
+ }
226
+ assertMapSalt(block, ["method", "effect"], "method");
227
+ return this.appendStep(block);
228
+ }
229
+ /**
230
+ * Finalizes this chain into a real, runnable {@link Flow} - same object
231
+ * `defineFlow` returns, so `withBlockVerify`/`modBlockVerify` and every
232
+ * `withTitle`/`withHighlightFixtures`/etc. decorator still apply exactly
233
+ * as they do today; this builder only changes how the chain is assembled,
234
+ * never what it produces. Throws if no step was ever added - an empty map
235
+ * isn't a flow.
236
+ */
237
+ end() {
238
+ if (this.ff) {
239
+ throw new Error(`Waygraph map: .end() while .ffStart("${this.ff.name}") is still open - call .ffEnd() first`);
240
+ }
241
+ if (this.steps.length === 0) {
242
+ throw new Error("Waygraph map: .end() called with zero steps - add at least one .gotoPage()/.gotoExternal()/" +
243
+ ".assert()/.method() before .end()");
244
+ }
245
+ const chain = [start, ...this.steps, end];
246
+ return this.engine.defineFlow(chain);
247
+ }
248
+ }
249
+ /**
250
+ * Standalone convenience for `new Engine(config).map(options)` - use this
251
+ * when a call site doesn't otherwise need its own `Engine` instance (no
252
+ * shared `headless`/`slowMo`/`layouts` across several flows).
253
+ * @example const flow = map({ homeOrigin: "https://app.example.com" }).gotoPage(NavHomeBlock).end();
254
+ */
255
+ export function map(options) {
256
+ return new Engine(options).map(options);
257
+ }