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.
- package/dist/engine/blocks/effect.d.ts +41 -0
- package/dist/engine/blocks/effect.js +23 -0
- package/dist/engine/blocks/method.d.ts +90 -0
- package/dist/engine/blocks/method.js +70 -0
- package/dist/engine/blocks/nav.d.ts +138 -0
- package/dist/engine/blocks/nav.js +110 -0
- package/dist/engine/blocks/page.d.ts +59 -0
- package/dist/engine/blocks/page.js +91 -0
- package/dist/engine/compose.d.ts +75 -0
- package/dist/engine/compose.js +76 -0
- package/dist/engine/config.d.ts +55 -0
- package/dist/engine/config.js +17 -0
- package/dist/engine/core.d.ts +92 -0
- package/dist/engine/core.js +256 -0
- package/dist/engine/engine-class.d.ts +289 -0
- package/dist/engine/engine-class.js +257 -0
- package/dist/engine/flow.d.ts +266 -0
- package/dist/engine/flow.js +387 -0
- package/dist/engine/locate.d.ts +26 -0
- package/dist/engine/locate.js +62 -0
- package/dist/engine/run-graph.d.ts +147 -0
- package/dist/engine/run-graph.js +223 -0
- package/dist/engine.d.ts +11 -1224
- package/dist/engine.js +12 -1550
- package/package.json +1 -1
|
@@ -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
|
+
}
|