executable-stories-showcase 0.0.0-stage → 0.0.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.
Files changed (67) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +17 -2
  3. package/dist/adapters/cookies.d.ts +11 -0
  4. package/dist/adapters/cookies.js +27 -0
  5. package/dist/adapters/cookies.js.map +1 -0
  6. package/dist/adapters/redux.d.ts +23 -0
  7. package/dist/adapters/redux.js +28 -0
  8. package/dist/adapters/redux.js.map +1 -0
  9. package/dist/adapters/tanstack-query.d.ts +17 -0
  10. package/dist/adapters/tanstack-query.js +43 -0
  11. package/dist/adapters/tanstack-query.js.map +1 -0
  12. package/dist/adapters/xstate.d.ts +33 -0
  13. package/dist/adapters/xstate.js +36 -0
  14. package/dist/adapters/xstate.js.map +1 -0
  15. package/dist/adapters/zustand.d.ts +17 -0
  16. package/dist/adapters/zustand.js +16 -0
  17. package/dist/adapters/zustand.js.map +1 -0
  18. package/dist/capture-GCESXDQD.js +10 -0
  19. package/dist/capture-GCESXDQD.js.map +1 -0
  20. package/dist/capture.d.ts +26 -0
  21. package/dist/catalog.d.ts +27 -0
  22. package/dist/chunk-7VEOUHJP.js +40 -0
  23. package/dist/chunk-7VEOUHJP.js.map +1 -0
  24. package/dist/chunk-BK4EEQXV.js +98 -0
  25. package/dist/chunk-BK4EEQXV.js.map +1 -0
  26. package/dist/chunk-MCC3CAWQ.js +208 -0
  27. package/dist/chunk-MCC3CAWQ.js.map +1 -0
  28. package/dist/chunk-PKGBYTBX.js +46 -0
  29. package/dist/chunk-PKGBYTBX.js.map +1 -0
  30. package/dist/chunk-QDLRQPTA.js +125 -0
  31. package/dist/chunk-QDLRQPTA.js.map +1 -0
  32. package/dist/chunk-XLNYICOI.js +234 -0
  33. package/dist/chunk-XLNYICOI.js.map +1 -0
  34. package/dist/chunk-Z664X6AQ.js +148 -0
  35. package/dist/chunk-Z664X6AQ.js.map +1 -0
  36. package/dist/index.d.ts +9 -0
  37. package/dist/index.js +531 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/jwt.d.ts +7 -0
  40. package/dist/link.d.ts +32 -0
  41. package/dist/network-4UFPO74C.js +7 -0
  42. package/dist/network-4UFPO74C.js.map +1 -0
  43. package/dist/network.d.ts +3 -0
  44. package/dist/next.d.ts +21 -0
  45. package/dist/next.js +30 -0
  46. package/dist/next.js.map +1 -0
  47. package/dist/no-msw-browser.d.ts +2 -0
  48. package/dist/no-msw-browser.js +8 -0
  49. package/dist/no-msw-browser.js.map +1 -0
  50. package/dist/panel.d.ts +30 -0
  51. package/dist/panel.js +608 -0
  52. package/dist/panel.js.map +1 -0
  53. package/dist/playwright.d.ts +31 -0
  54. package/dist/playwright.js +92 -0
  55. package/dist/playwright.js.map +1 -0
  56. package/dist/ready-4MENAMNI.js +13 -0
  57. package/dist/ready-4MENAMNI.js.map +1 -0
  58. package/dist/ready.d.ts +39 -0
  59. package/dist/record.d.ts +49 -0
  60. package/dist/redact.d.ts +19 -0
  61. package/dist/runtime.d.ts +191 -0
  62. package/dist/save.d.ts +27 -0
  63. package/dist/schema.d.ts +161 -0
  64. package/dist/vite.d.ts +11 -0
  65. package/dist/vite.js +26 -0
  66. package/dist/vite.js.map +1 -0
  67. package/package.json +118 -3
package/dist/index.js ADDED
@@ -0,0 +1,531 @@
1
+ import {
2
+ toHandlers
3
+ } from "./chunk-7VEOUHJP.js";
4
+ import {
5
+ captureScenario,
6
+ redactSecrets,
7
+ unsignedJwt,
8
+ withoutSecretParams
9
+ } from "./chunk-QDLRQPTA.js";
10
+ import {
11
+ checkReadySelector,
12
+ selectorForElement,
13
+ suggestReadySelectors
14
+ } from "./chunk-XLNYICOI.js";
15
+ import {
16
+ DATA_KEY,
17
+ LOOPBACK,
18
+ PARAM,
19
+ RECORD_PARAM,
20
+ SAVE_ENDPOINT,
21
+ STRICT_PARAM,
22
+ readInline,
23
+ scenarioHref
24
+ } from "./chunk-PKGBYTBX.js";
25
+ import {
26
+ createCatalog,
27
+ deepMerge,
28
+ defineScenarioRules,
29
+ diffScenario
30
+ } from "./chunk-MCC3CAWQ.js";
31
+ import {
32
+ JsonSchema,
33
+ MethodSchema,
34
+ ScenarioSchema,
35
+ parseJson,
36
+ parseScenario,
37
+ scenarioJsonSchema
38
+ } from "./chunk-BK4EEQXV.js";
39
+
40
+ // src/record.ts
41
+ function createRecorder() {
42
+ const started = /* @__PURE__ */ new Map();
43
+ const calls = [];
44
+ const pending = /* @__PURE__ */ new Set();
45
+ let next = 0;
46
+ return {
47
+ start(requestId) {
48
+ started.set(requestId, next++);
49
+ },
50
+ /** A mocked response: nothing to record. */
51
+ drop(requestId) {
52
+ started.delete(requestId);
53
+ },
54
+ bypass(requestId, request, response) {
55
+ const order = started.get(requestId) ?? next++;
56
+ started.delete(requestId);
57
+ const method = MethodSchema.safeParse(request.method);
58
+ const own = new URL(request.url).pathname.startsWith(SAVE_ENDPOINT);
59
+ if (own || !method.success || !response.headers.get("content-type")?.includes("json")) {
60
+ return Promise.resolve();
61
+ }
62
+ const url = new URL(request.url);
63
+ if (new Set(url.searchParams.keys()).size < [...url.searchParams.keys()].length) {
64
+ console.warn(`[executable-stories-showcase] not recorded: ${request.method} ${url} repeats a query key`);
65
+ return Promise.resolve();
66
+ }
67
+ const read = response.clone().text().then((text) => {
68
+ const call = {
69
+ order,
70
+ method: method.data,
71
+ path: url.origin === location.origin ? url.pathname : url.origin + url.pathname,
72
+ query: withoutSecretParams(Object.fromEntries(url.searchParams), `${method.data} ${url.pathname}`),
73
+ status: response.status
74
+ };
75
+ if (text) {
76
+ const { value, redacted } = redactSecrets(parseJson(text));
77
+ call.body = value;
78
+ if (redacted.length) {
79
+ console.info(`[executable-stories-showcase] redacted ${redacted.join(", ")} in ${method.data} ${call.path}`);
80
+ }
81
+ }
82
+ calls.push(call);
83
+ }).catch((err) => console.warn("[executable-stories-showcase] skipped a recorded response", err)).finally(() => pending.delete(read));
84
+ pending.add(read);
85
+ return read;
86
+ },
87
+ /** Responses whose bodies have been read, as network entries. */
88
+ entries: () => toNetwork(calls),
89
+ /** Wait for bodies still being read. */
90
+ settled: async () => {
91
+ await Promise.all(pending);
92
+ }
93
+ };
94
+ }
95
+ function toNetwork(calls) {
96
+ const groups = /* @__PURE__ */ new Map();
97
+ for (const call of calls.toSorted((a, b) => a.order - b.order)) {
98
+ const query = Object.fromEntries(Object.entries(call.query).toSorted(([a], [b]) => a.localeCompare(b)));
99
+ const key = JSON.stringify([call.method, call.path, query]);
100
+ groups.set(key, [...groups.get(key) ?? [], { ...call, query }]);
101
+ }
102
+ return [...groups.values()].map(([first, ...rest]) => {
103
+ const responses = [first, ...rest].map(({ status, body }) => ({ status, body }));
104
+ const same = responses.every((r) => JSON.stringify(r) === JSON.stringify(responses[0]));
105
+ const entry = { method: first.method, path: first.path };
106
+ if (Object.keys(first.query).length) entry.query = first.query;
107
+ if (same) entry.response = responses[0];
108
+ else entry.sequence = responses;
109
+ return entry;
110
+ }).toSorted((a, b) => Object.keys(b.query ?? {}).length - Object.keys(a.query ?? {}).length);
111
+ }
112
+
113
+ // src/runtime.ts
114
+ var STORAGE_KEY = "executable-stories-showcase:active";
115
+ function bind(adapter) {
116
+ return {
117
+ key: adapter.key,
118
+ read: () => adapter.read?.(),
119
+ subscribe: (onChange) => adapter.subscribe?.(onChange),
120
+ inPlace: adapter.inPlace !== false,
121
+ fromLinks: adapter.fromLinks !== false,
122
+ decode(state, scenario) {
123
+ if (adapter.schema === void 0) return () => adapter.apply(state);
124
+ const parsed = adapter.schema.safeParse(state);
125
+ if (!parsed.success) {
126
+ throw new Error(`Scenario "${scenario}": invalid state["${adapter.key}"]: ${parsed.error.message}`);
127
+ }
128
+ return () => adapter.apply(parsed.data);
129
+ }
130
+ };
131
+ }
132
+ var current = null;
133
+ var markerOwner = null;
134
+ var currentSession = () => current;
135
+ async function startScenarios(options) {
136
+ const catalog = createCatalog(options.scenarios, { rules: options.rules });
137
+ const url = new URL(location.href);
138
+ const selection = select(url, options.defaultScenario, options.allowLinks ?? LOOPBACK.has(url.hostname));
139
+ const unhandled = [];
140
+ globalThis.executableStoriesShowcaseUnhandled = unhandled;
141
+ const connected = /* @__PURE__ */ new Map();
142
+ const listeners = /* @__PURE__ */ new Set();
143
+ const emit = () => {
144
+ for (const listener of listeners) {
145
+ try {
146
+ listener();
147
+ } catch (err) {
148
+ console.error("[executable-stories-showcase] onChange listener failed", err);
149
+ }
150
+ }
151
+ };
152
+ const recorder = createRecorder();
153
+ let stopListening = null;
154
+ const listen = (target) => {
155
+ if (!session.recording || stopListening) return;
156
+ if (!target.events) {
157
+ console.warn("[executable-stories-showcase] recording needs a worker with `events`");
158
+ return;
159
+ }
160
+ const { events } = target;
161
+ const start = ({ requestId }) => recorder.start(requestId);
162
+ const mocked = ({ requestId }) => recorder.drop(requestId);
163
+ const bypass = ({ requestId, request, response }) => {
164
+ void recorder.bypass(requestId, request, response).then(emit);
165
+ };
166
+ events.on("request:start", start);
167
+ events.on("response:mocked", mocked);
168
+ events.on("response:bypass", bypass);
169
+ stopListening = () => {
170
+ events.removeListener("request:start", start);
171
+ events.removeListener("response:mocked", mocked);
172
+ events.removeListener("response:bypass", bypass);
173
+ stopListening = null;
174
+ };
175
+ };
176
+ const named = (resolved2) => (resolved2.handlers ?? []).flatMap((name) => {
177
+ const found = options.handlers?.[name];
178
+ if (!found) {
179
+ const known = Object.keys(options.handlers ?? {}).join(", ") || "none";
180
+ throw new Error(
181
+ `Scenario "${resolved2.name}" uses unknown handler "${name}". Known handlers: ${known}`
182
+ );
183
+ }
184
+ return found;
185
+ });
186
+ let worker = options.worker ?? null;
187
+ let ownWorker = null;
188
+ const appHandlers = options.worker ? [...options.worker.listHandlers()] : [];
189
+ let destroyed = false;
190
+ let fromLink = false;
191
+ const prepareStates = (resolved2, adapters, link = fromLink) => adapters.flatMap((adapter) => {
192
+ const state = resolved2.state?.[adapter.key];
193
+ if (state === void 0) return [];
194
+ if (link && !adapter.fromLinks) {
195
+ console.warn(`[executable-stories-showcase] ignored state.${adapter.key}: a link can't set it`);
196
+ return [];
197
+ }
198
+ return [adapter.decode(state, resolved2.name)];
199
+ });
200
+ const applyPrepared = async (prepared2) => {
201
+ for (const apply of prepared2) {
202
+ if (destroyed) return;
203
+ await apply();
204
+ }
205
+ };
206
+ const boundAdapters = () => [...connected.values()].map((entry) => entry.adapter);
207
+ const setNetwork = async (resolved2) => {
208
+ if (!mocks(resolved2) && !session.strict && !session.recording && !worker) return;
209
+ const [{ toHandlers: toHandlers2 }, msw] = await Promise.all([import("./network-4UFPO74C.js"), import("msw")]);
210
+ if (destroyed) return;
211
+ const fallback = msw.http.all("*", ({ request }) => {
212
+ const { pathname } = new URL(request.url);
213
+ const isPage = request.mode === "navigate" || !!request.headers.get("accept")?.includes("text/html");
214
+ if (isPage || isAsset(request.url) || pathname.startsWith("/@") || pathname.startsWith(SAVE_ENDPOINT) || matches(options.allow, request.url)) {
215
+ return msw.passthrough();
216
+ }
217
+ if (matches(options.ignore, request.url)) {
218
+ return session.strict ? new msw.HttpResponse(null, { status: 204 }) : msw.passthrough();
219
+ }
220
+ const label = `${request.method} ${request.url}`;
221
+ unhandled.push(label);
222
+ emit();
223
+ if (!session.strict) return msw.passthrough();
224
+ console.error(`[executable-stories-showcase] "${resolved2.name}" has no handler for ${label}`);
225
+ return msw.HttpResponse.json({ message: `No scenario handler for ${label}` }, { status: 501 });
226
+ });
227
+ const handlers = [...toHandlers2(resolved2.network), ...named(resolved2), ...appHandlers, fallback];
228
+ if (worker) {
229
+ worker.resetHandlers(...handlers);
230
+ listen(worker);
231
+ return;
232
+ }
233
+ const { setupWorker } = await import("#msw-browser");
234
+ const started = setupWorker(...handlers);
235
+ await started.start({ quiet: true, ...options.workerOptions });
236
+ if (destroyed) {
237
+ void started.stop();
238
+ return;
239
+ }
240
+ worker = ownWorker = started;
241
+ listen(started);
242
+ };
243
+ const activate = (active2, resolved2, link) => {
244
+ fromLink = link;
245
+ session.active = active2;
246
+ session.resolved = resolved2;
247
+ const html = document.documentElement;
248
+ markerOwner = session;
249
+ html.dataset.scenario = resolved2.name;
250
+ if (resolved2.ready) html.dataset.scenarioReady = resolved2.ready;
251
+ else delete html.dataset.scenarioReady;
252
+ };
253
+ let transition = 0;
254
+ let queue = Promise.resolve();
255
+ const switchTo = async (next, inline) => {
256
+ const resolved2 = catalog.resolve(next);
257
+ const prepared2 = prepareStates(resolved2, boundAdapters(), inline);
258
+ const prev = session.resolved;
259
+ const nextKeys = Object.keys(resolved2.state ?? {});
260
+ named(resolved2);
261
+ const mocksNetwork = worker !== null || mocks(resolved2) || session.strict;
262
+ const href = scenarioHref(resolved2, { inline, strict: session.strict });
263
+ const inPlace = prev !== null && new URL(href).pathname === location.pathname && JSON.stringify(appParams(location.search)) === JSON.stringify(sortedParams(resolved2.url)) && (!mocksNetwork || !!options.refresh) && Object.keys(prev.state ?? {}).every((k) => nextKeys.includes(k)) && nextKeys.every((k) => connected.get(k)?.adapter.inPlace === true);
264
+ if (!inPlace) {
265
+ navigate(href);
266
+ return;
267
+ }
268
+ if (mocksNetwork) {
269
+ await setNetwork(resolved2);
270
+ if (destroyed) return;
271
+ Promise.resolve(options.refresh?.()).catch((err) => console.error("[executable-stories-showcase]", err));
272
+ }
273
+ await applyPrepared(prepared2);
274
+ if (destroyed) return;
275
+ unhandled.splice(0);
276
+ activate(next, resolved2, inline);
277
+ const at = new URL(href);
278
+ replaceUrl(at);
279
+ const data = readInline(at.hash);
280
+ store(data ? { data, strict: session.strict } : { name: next.name, strict: session.strict });
281
+ emit();
282
+ };
283
+ const enqueue = (next, inline) => {
284
+ if (destroyed) return Promise.reject(new Error("This scenario session was destroyed"));
285
+ const id = ++transition;
286
+ const run = queue.then(() => id === transition ? switchTo(next(), inline) : void 0);
287
+ queue = run.catch(() => {
288
+ });
289
+ return run;
290
+ };
291
+ const attach = (adapter) => {
292
+ const entry = { adapter };
293
+ connected.set(adapter.key, entry);
294
+ entry.unsubscribe = adapter.subscribe?.(emit);
295
+ emit();
296
+ return {
297
+ update(state) {
298
+ entry.value = state;
299
+ emit();
300
+ },
301
+ disconnect() {
302
+ entry.unsubscribe?.();
303
+ if (connected.get(adapter.key) === entry) connected.delete(adapter.key);
304
+ emit();
305
+ }
306
+ };
307
+ };
308
+ const session = {
309
+ catalog,
310
+ active: null,
311
+ resolved: null,
312
+ // Read apart from the selection: `?scenario-strict` alone still blocks the real network.
313
+ strict: options.strict ?? (!!selection?.strict || url.searchParams.has(STRICT_PARAM)),
314
+ unhandled,
315
+ // Held in memory: a full page load starts a new recording.
316
+ recording: options.record ?? url.searchParams.has(RECORD_PARAM),
317
+ recorded: recorder.entries,
318
+ settleRecording: recorder.settled,
319
+ keys: () => [...connected.keys()],
320
+ read(key) {
321
+ const entry = connected.get(key);
322
+ const read = entry?.adapter.read();
323
+ if (read !== void 0) return read;
324
+ return entry?.value === void 0 ? void 0 : parseJson(JSON.stringify(entry.value));
325
+ },
326
+ async applyState(key, state) {
327
+ const entry = connected.get(key);
328
+ if (!entry) throw new Error(`No adapter connected for "${key}"`);
329
+ await entry.adapter.decode(state, session.active?.name ?? "live")();
330
+ },
331
+ connect(adapter) {
332
+ const bound = bind(adapter);
333
+ const noop = { update() {
334
+ }, disconnect() {
335
+ } };
336
+ if (destroyed) return noop;
337
+ const connection = attach(bound);
338
+ if (session.resolved) {
339
+ applyPrepared(prepareStates(session.resolved, [bound])).catch(
340
+ (err) => console.error("[executable-stories-showcase]", err)
341
+ );
342
+ }
343
+ return connection;
344
+ },
345
+ open: (names) => enqueue(() => lookup(catalog, names), false),
346
+ openInline: (scenario) => enqueue(() => scenario, true),
347
+ onChange(listener) {
348
+ listeners.add(listener);
349
+ return () => listeners.delete(listener);
350
+ },
351
+ destroy() {
352
+ if (destroyed) return;
353
+ destroyed = true;
354
+ transition++;
355
+ for (const entry of connected.values()) entry.unsubscribe?.();
356
+ connected.clear();
357
+ listeners.clear();
358
+ stopListening?.();
359
+ if (ownWorker) void ownWorker.stop();
360
+ else worker?.resetHandlers(...appHandlers);
361
+ worker = ownWorker = null;
362
+ if (current === session) current = null;
363
+ if (globalThis.executableStoriesShowcaseUnhandled === unhandled) globalThis.executableStoriesShowcaseUnhandled = void 0;
364
+ if (globalThis.executableStoriesShowcasePage === hooks) globalThis.executableStoriesShowcasePage = void 0;
365
+ if (markerOwner === session) {
366
+ markerOwner = null;
367
+ delete document.documentElement.dataset.scenario;
368
+ delete document.documentElement.dataset.scenarioReady;
369
+ }
370
+ }
371
+ };
372
+ current = session;
373
+ const hooks = {
374
+ scenario: () => session.resolved?.name ?? null,
375
+ async checkReady(selector) {
376
+ const { checkReadySelector: checkReadySelector2, suggestReadySelectors: suggestReadySelectors2 } = await import("./ready-4MENAMNI.js");
377
+ const check = checkReadySelector2(selector);
378
+ const suggestions = suggestReadySelectors2(null, { limit: 5 });
379
+ return check.status === "match" ? { status: "match", count: check.count, visible: check.visible, suggestions } : { ...check, suggestions };
380
+ },
381
+ async capture({ extends: base, ...options2 }) {
382
+ const { captureScenario: captureScenario2 } = await import("./capture-GCESXDQD.js");
383
+ await session.settleRecording();
384
+ return captureScenario2(session, { ...options2, base: base ? lookup(catalog, base) : void 0 }).forCatalog;
385
+ }
386
+ };
387
+ globalThis.executableStoriesShowcasePage = hooks;
388
+ const initial = (options.adapters ?? []).map(bind);
389
+ for (const adapter of initial) attach(adapter);
390
+ if (!selection) {
391
+ if (session.recording || session.strict) await setNetwork({ name: "live" });
392
+ return session;
393
+ }
394
+ const loaded = (() => {
395
+ try {
396
+ const active2 = selection.data ? parseScenario(JSON.parse(selection.data), "inline scenario") : lookup(catalog, selection.name ?? "");
397
+ const resolved2 = catalog.resolve(active2);
398
+ named(resolved2);
399
+ return { active: active2, resolved: resolved2, prepared: prepareStates(resolved2, initial, !!selection.data) };
400
+ } catch (err) {
401
+ store(null);
402
+ throw err;
403
+ }
404
+ })();
405
+ const { active, resolved, prepared } = loaded;
406
+ await setNetwork(resolved);
407
+ if (destroyed) return session;
408
+ for (const [k, v] of Object.entries(resolved.url ?? {})) {
409
+ if (v !== null && !url.searchParams.has(k)) url.searchParams.set(k, v);
410
+ }
411
+ replaceUrl(url);
412
+ await applyPrepared(prepared);
413
+ if (destroyed) return session;
414
+ activate(active, resolved, !!selection.data);
415
+ emit();
416
+ return session;
417
+ }
418
+ var mocks = (s) => !!s.network?.length || !!s.handlers?.length;
419
+ var matches = (rules = [], url) => rules.some((rule) => rule instanceof RegExp ? rule.test(url) : url.includes(rule));
420
+ var isAsset = (href) => {
421
+ const url = new URL(href);
422
+ return url.protocol === "file:" || url.hostname === "fonts.googleapis.com" || url.pathname.includes("node_modules") || url.pathname.includes("@vite") || /\.(s?css|less|m?jsx?|m?tsx?|html|ttf|otf|woff2?|eot|gif|jpe?g|png|avif|webp|svg|mp4|webm|ogg|mov|mp3|wav|flac|aac|pdf|txt|csv|json|xml|md|zip|tar|gz|rar|7z)$/i.test(url.pathname);
423
+ };
424
+ var replaceUrl = (url) => {
425
+ const state = history.state;
426
+ const { __NA, ...rest } = Object(state);
427
+ history.replaceState(__NA === void 0 ? state : rest, "", url);
428
+ };
429
+ var byKey = ([a], [b]) => a.localeCompare(b);
430
+ var appParams = (search) => [...new URLSearchParams(search)].filter(([k]) => k !== PARAM && k !== STRICT_PARAM).toSorted(byKey);
431
+ var sortedParams = (url) => Object.entries(url ?? {}).filter(([, v]) => v !== null).toSorted(byKey);
432
+ function navigate(href) {
433
+ const next = new URL(href, location.href);
434
+ if (next.origin !== location.origin) {
435
+ throw new Error(`[executable-stories-showcase] navigate: ${next.origin} is not this app (${location.origin})`);
436
+ }
437
+ if (next.pathname + next.search === location.pathname + location.search) {
438
+ replaceUrl(next);
439
+ location.reload();
440
+ } else {
441
+ location.href = next.toString();
442
+ }
443
+ }
444
+ function select(url, fallback, allowLinks) {
445
+ let data = readInline(url.hash);
446
+ if (data && !allowLinks) {
447
+ refuseLink(url);
448
+ data = null;
449
+ }
450
+ const name = url.searchParams.get(PARAM);
451
+ const strict = url.searchParams.has(STRICT_PARAM);
452
+ let selection = null;
453
+ if (data) selection = { data, strict };
454
+ else if (name) selection = { name, strict };
455
+ else if (name === "") {
456
+ url.searchParams.delete(PARAM);
457
+ store(null);
458
+ } else {
459
+ let stored = load();
460
+ if (stored?.data && !allowLinks) {
461
+ refuseLink(url);
462
+ store(null);
463
+ stored = null;
464
+ }
465
+ selection = stored && { ...stored, strict: stored.strict || strict };
466
+ if (selection?.name) url.searchParams.set(PARAM, selection.name);
467
+ if (selection?.data) url.hash = new URLSearchParams({ [DATA_KEY]: selection.data }).toString();
468
+ if (selection?.strict) url.searchParams.set(STRICT_PARAM, "");
469
+ }
470
+ if (selection) store(selection);
471
+ return selection ?? (fallback ? { name: fallback, strict } : null);
472
+ }
473
+ function refuseLink(url) {
474
+ url.hash = "";
475
+ console.warn(
476
+ `[executable-stories-showcase] ignored an inline scenario link: links only open on localhost unless startScenarios({ allowLinks: true })`
477
+ );
478
+ }
479
+ function lookup(catalog, names) {
480
+ const list = names.split(",").map((n) => n.trim());
481
+ for (const name of list) {
482
+ if (catalog.get(name)) continue;
483
+ const known = catalog.list.map((s) => s.name).join(", ");
484
+ throw new Error(`Unknown scenario "${name}". Known scenarios: ${known}`);
485
+ }
486
+ const [only] = list;
487
+ const single = list.length === 1 && only !== void 0 ? catalog.get(only) : void 0;
488
+ return single ?? { name: list.join(","), extends: list };
489
+ }
490
+ function store(selection) {
491
+ try {
492
+ if (selection) sessionStorage.setItem(STORAGE_KEY, JSON.stringify(selection));
493
+ else sessionStorage.removeItem(STORAGE_KEY);
494
+ } catch {
495
+ }
496
+ }
497
+ function load() {
498
+ try {
499
+ return JSON.parse(sessionStorage.getItem(STORAGE_KEY) ?? "null");
500
+ } catch {
501
+ return null;
502
+ }
503
+ }
504
+ export {
505
+ DATA_KEY,
506
+ JsonSchema,
507
+ PARAM,
508
+ RECORD_PARAM,
509
+ STRICT_PARAM,
510
+ ScenarioSchema,
511
+ captureScenario,
512
+ checkReadySelector,
513
+ createCatalog,
514
+ currentSession,
515
+ deepMerge,
516
+ defineScenarioRules,
517
+ diffScenario,
518
+ navigate,
519
+ parseJson,
520
+ parseScenario,
521
+ readInline,
522
+ redactSecrets,
523
+ scenarioHref,
524
+ scenarioJsonSchema,
525
+ selectorForElement,
526
+ startScenarios,
527
+ suggestReadySelectors,
528
+ toHandlers,
529
+ unsignedJwt
530
+ };
531
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/record.ts","../src/runtime.ts"],"sourcesContent":["import { SAVE_ENDPOINT } from './link.js';\nimport { redactSecrets, withoutSecretParams } from './redact.js';\nimport { type Json, MethodSchema, type NetworkEntry, parseJson } from './schema.js';\n\n/** One real response, placed by when its request started. */\nexport interface RecordedCall {\n /** Request start order: responses can arrive in any order. */\n order: number;\n method: NetworkEntry['method'];\n path: string;\n query: Record<string, string>;\n status: number;\n body?: Json;\n}\n\n/**\n * Records responses that reached the real network, from MSW's lifecycle events. Only JSON is\n * kept: pages, scripts and other assets aren't app state.\n */\nexport function createRecorder() {\n const started = new Map<string, number>();\n const calls: RecordedCall[] = [];\n const pending = new Set<Promise<void>>();\n let next = 0;\n\n return {\n start(requestId: string) {\n started.set(requestId, next++);\n },\n /** A mocked response: nothing to record. */\n drop(requestId: string) {\n started.delete(requestId);\n },\n bypass(requestId: string, request: Request, response: Response): Promise<void> {\n const order = started.get(requestId) ?? next++;\n started.delete(requestId);\n const method = MethodSchema.safeParse(request.method);\n // The panel's own save calls aren't part of the app's traffic.\n const own = new URL(request.url).pathname.startsWith(SAVE_ENDPOINT);\n\n if (own || !method.success || !response.headers.get('content-type')?.includes('json')) {\n return Promise.resolve();\n }\n\n const url = new URL(request.url);\n\n // Scenario queries hold one value per key.\n if (new Set(url.searchParams.keys()).size < [...url.searchParams.keys()].length) {\n console.warn(`[executable-stories-showcase] not recorded: ${request.method} ${url} repeats a query key`);\n\n return Promise.resolve();\n }\n\n const read = response.clone().text().then((text) => {\n const call: RecordedCall = {\n order,\n method: method.data,\n path: url.origin === location.origin ? url.pathname : url.origin + url.pathname,\n query: withoutSecretParams(Object.fromEntries(url.searchParams), `${method.data} ${url.pathname}`),\n status: response.status,\n };\n\n // Parsed before the push, so a body that fails to parse leaves no half-recorded call.\n if (text) {\n // Recorded scenarios get committed, linked and pasted into chats: keep secrets out.\n const { value, redacted } = redactSecrets(parseJson(text));\n call.body = value;\n\n if (redacted.length) {\n console.info(`[executable-stories-showcase] redacted ${redacted.join(', ')} in ${method.data} ${call.path}`);\n }\n }\n\n calls.push(call);\n }).catch((err) => console.warn('[executable-stories-showcase] skipped a recorded response', err))\n .finally(() => pending.delete(read));\n\n pending.add(read);\n\n return read;\n },\n /** Responses whose bodies have been read, as network entries. */\n entries: () => toNetwork(calls),\n /** Wait for bodies still being read. */\n settled: async () => {\n await Promise.all(pending);\n },\n };\n}\n\n/**\n * Recorded calls as scenario network entries: repeated calls to one endpoint become a\n * `sequence` in request order, or a single `response` when every call answered the same.\n */\nexport function toNetwork(calls: RecordedCall[]): NetworkEntry[] {\n const groups = new Map<string, RecordedCall[]>();\n\n for (const call of calls.toSorted((a, b) => a.order - b.order)) {\n // Sorted, because replay matches query params in any order.\n const query = Object.fromEntries(Object.entries(call.query).toSorted(([a], [b]) => a.localeCompare(b)));\n const key = JSON.stringify([call.method, call.path, query]);\n groups.set(key, [...(groups.get(key) ?? []), { ...call, query }]);\n }\n\n return [...groups.values()]\n .map(([first, ...rest]) => {\n const responses = [first, ...rest].map(({ status, body }) => ({ status, body }));\n const same = responses.every((r) => JSON.stringify(r) === JSON.stringify(responses[0]));\n const entry: NetworkEntry = { method: first.method, path: first.path };\n\n if (Object.keys(first.query).length) entry.query = first.query;\n\n if (same) entry.response = responses[0];\n else entry.sequence = responses;\n\n return entry;\n })\n // An entry without `query` matches any query string: try the more specific entries first.\n .toSorted((a, b) => Object.keys(b.query ?? {}).length - Object.keys(a.query ?? {}).length);\n}\n","import type { RequestHandler } from 'msw';\nimport type { SetupWorker } from 'msw/browser';\nimport type { z } from 'zod';\nimport { type Catalog, createCatalog, type ScenarioRule } from './catalog.js';\nimport {\n DATA_KEY,\n LOOPBACK,\n PARAM,\n readInline,\n RECORD_PARAM,\n SAVE_ENDPOINT,\n scenarioHref,\n STRICT_PARAM,\n} from './link.js';\nimport { createRecorder } from './record.js';\nimport {\n type Json,\n type NetworkEntry,\n parseJson,\n parseScenario,\n type Scenario,\n type ScenarioInput,\n type Serializable,\n} from './schema.js';\n\nconst STORAGE_KEY = 'executable-stories-showcase:active';\n\ninterface AdapterBase {\n /** Key under `scenario.state` this adapter reads. */\n key: string;\n /** Current state as JSON, for the panel's live editor and \"Save as scenario\". */\n read?(): Json;\n /** Call `onChange` when the state changes; return an unsubscribe. */\n subscribe?(onChange: () => void): () => void;\n /** False when new state only takes effect on a reload, e.g. a snapshot an actor starts from. */\n inPlace?: boolean;\n /**\n * False to apply this state from catalog scenarios only, and skip it for inline links.\n * For state that outlives the scenario, such as a cookie the real backend reads.\n */\n fromLinks?: boolean;\n}\n\n/** Decodes scenario JSON with `schema`, so `apply` receives typed state. */\nexport interface TypedAdapter<T> extends AdapterBase {\n /** Also feeds `scenarioJsonSchema`. */\n schema: z.ZodType<T>;\n apply(state: T): void | Promise<void>;\n}\n\n/** Receives the scenario's JSON as it is. */\nexport interface JsonAdapter extends AdapterBase {\n schema?: undefined;\n apply(state: Json): void | Promise<void>;\n}\n\n/** Connects one slice of app state (a store, a cache, a reducer) to scenarios and the panel. */\nexport type Adapter<T = Json> = TypedAdapter<T> | JsonAdapter;\n\nexport interface Connection<T extends Serializable> {\n /** Push the app's current state (for state the library can't `read`, e.g. useReducer). */\n update(state: T): void;\n disconnect(): void;\n}\n\nexport interface StartOptions {\n /** Scenario objects, e.g. `import.meta.glob<ScenarioInput>('./scenarios/*.json')`, or built in code. */\n scenarios: ScenarioInput[];\n /** Scenario to use when the URL names none. Omit to leave the app untouched. */\n defaultScenario?: string;\n /** Adapters available before render. Components can `connect` more later. */\n adapters?: Adapter<unknown>[];\n rules?: ScenarioRule[];\n /**\n * Open inline share links (`#scenario-data=...`). Anyone can write one, and it fakes API\n * responses for whoever opens it, so by default they only open on localhost.\n * Pass `true` on a staging or preview deploy that should open them; `false` refuses them everywhere.\n */\n allowLinks?: boolean;\n /**\n * Answer unmatched requests with 501 instead of passing them to the real network.\n * Assets and page loads always pass. Also switched on by `?scenario-strict`.\n */\n strict?: boolean;\n /**\n * Let unmatched requests reach the real network and record their JSON responses, so\n * \"Save as scenario\" can replay them offline. Also switched on by `?scenario-record`.\n */\n record?: boolean;\n /**\n * Requests that may reach the real network in strict mode (URL substring or RegExp),\n * e.g. images from a CDN or a maps API the screen needs.\n */\n allow?: (string | RegExp)[];\n /**\n * Requests the scenario doesn't care about, such as analytics, error reporting and captcha\n * checks (URL substring or RegExp). They are never reported as unhandled, and in strict mode\n * they get an empty 204 instead of a 501, so SDKs don't log errors or retry.\n */\n ignore?: (string | RegExp)[];\n /**\n * MSW handlers a scenario can use by name with `\"handlers\": [\"name\"]`, e.g. the ones your\n * unit tests already use. They answer after the scenario's `network` entries.\n */\n handlers?: Record<string, RequestHandler | RequestHandler[]>;\n /**\n * Called after an in-place switch changes network fixtures, so the app refetches,\n * e.g. `() => queryClient.resetQueries()`. Without it, such switches reload the page.\n */\n refresh?: () => void | Promise<void>;\n /**\n * The app's own MSW worker (or a `setupServer()` in tests), instead of starting one.\n * Its handlers stay active beneath the scenario's, and are restored by `destroy()`.\n * The app owns its lifecycle: start it before `startScenarios`.\n */\n worker?: MswWorker;\n /** Passed to `worker.start()` when executable-stories-showcase starts its own worker. */\n workerOptions?: Parameters<SetupWorker['start']>[0];\n}\n\n/** The parts of an MSW worker or server executable-stories-showcase uses. `events` is needed to record. */\nexport type MswWorker = Pick<SetupWorker, 'resetHandlers' | 'listHandlers'> & {\n events?: Pick<RecordingEvents, 'on' | 'removeListener'>;\n};\n\ntype StartListener = (e: { requestId: string; }) => void;\n\ntype ResponseListener = (e: { requestId: string; request: Request; response: Response; }) => void;\n\n/** MSW's lifecycle events that recording listens to, as both MSW 2 and 3 type them. */\ninterface RecordingEvents {\n on(event: 'request:start', listener: StartListener): void;\n on(event: 'response:mocked' | 'response:bypass', listener: ResponseListener): void;\n removeListener(event: 'request:start', listener: StartListener): void;\n removeListener(event: 'response:mocked' | 'response:bypass', listener: ResponseListener): void;\n}\n\nexport interface ScenarioSession {\n catalog: Catalog;\n /** The scenario as authored (with `extends`), or null when none is active. */\n active: Scenario | null;\n /** The active scenario with `extends` flattened. */\n resolved: Scenario | null;\n strict: boolean;\n /** Non-asset requests no scenario entry matched (live list). */\n unhandled: string[];\n /** Whether real responses are being recorded (`record` or `?scenario-record`). */\n recording: boolean;\n /** Real responses recorded so far, as network entries. Await `settleRecording()` first for all of them. */\n recorded(): NetworkEntry[];\n /** Resolves once every recorded response body has been read. */\n settleRecording(): Promise<void>;\n /** Keys of connected adapters. */\n keys(): string[];\n /** The connected adapter's current state, as JSON. */\n read(key: string): Json | undefined;\n /** Validate and apply state to a connected adapter now, without changing scenario. */\n applyState(key: string, state: Json): Promise<void>;\n /** Connect an adapter; the active scenario's state for its key is applied at once. */\n connect<T extends Serializable>(adapter: Adapter<T>): Connection<T>;\n /**\n * Switch to a catalog scenario: in place when only connected state (or network, with\n * `refresh`) changes, otherwise by reloading. Names combine: \"signed-in,server-error\".\n */\n open(names: string): Promise<void>;\n /** Switch to a scenario that isn't in the catalog, e.g. one edited in the panel. */\n openInline(scenario: Scenario): Promise<void>;\n onChange(listener: () => void): () => void;\n /**\n * Tear down: unsubscribe adapters, drop listeners and queued switches, and stop the\n * worker this session started (or restore the app's own worker's handlers).\n * For HMR and unmounting. The page keeps whatever state it shows.\n */\n destroy(): void;\n}\n\n/** An adapter with its type erased behind decode-then-apply. */\ninterface Bound {\n key: string;\n /** Validate now; the returned function applies. */\n decode(state: Json, scenario: string): () => void | Promise<void>;\n /** Undefined when the adapter can't read (state is pushed through `connection.update`). */\n read(): Json | undefined;\n subscribe(onChange: () => void): (() => void) | undefined;\n inPlace: boolean;\n fromLinks: boolean;\n}\n\nfunction bind<T>(adapter: Adapter<T>): Bound {\n return {\n key: adapter.key,\n read: () => adapter.read?.(),\n subscribe: (onChange) => adapter.subscribe?.(onChange),\n inPlace: adapter.inPlace !== false,\n fromLinks: adapter.fromLinks !== false,\n decode(state, scenario) {\n if (adapter.schema === undefined) return () => adapter.apply(state);\n const parsed = adapter.schema.safeParse(state);\n\n if (!parsed.success) {\n throw new Error(`Scenario \"${scenario}\": invalid state[\"${adapter.key}\"]: ${parsed.error.message}`);\n }\n\n return () => adapter.apply(parsed.data);\n },\n };\n}\n\ninterface Connected {\n adapter: Bound;\n /** Last state pushed through `connection.update`; converted to JSON when read. */\n value?: Serializable;\n unsubscribe?: () => void;\n}\n\n/**\n * What the CLI's `shoot`, `doctor` and `mcp` call inside the page, as JSON in and out.\n * The modules behind it load on first call, so apps that never use it don't pay for it.\n */\nexport interface PageHooks {\n /** The active scenario's name, or null. */\n scenario(): string | null;\n /** Whether a ready selector matches now, plus selectors that would. */\n checkReady(selector: string): Promise<ReadyReport>;\n /** The app's current state as a catalog file, after recorded responses settle. */\n capture(\n options: { name: string; description?: string; ready?: string; extends?: string; },\n ): Promise<ScenarioInput>;\n}\n\nexport interface ReadyReport {\n status: 'match' | 'none' | 'unchecked' | 'invalid';\n count?: number;\n visible?: boolean;\n error?: string;\n /** Distinctive selectors on the page now, best first. */\n suggestions: string[];\n}\n\ndeclare global {\n /** Set by `startScenarios` for the CLI and agents; see `PageHooks`. */\n var executableStoriesShowcasePage: PageHooks | undefined;\n}\n\nlet current: ScenarioSession | null = null;\n\n/** The session whose scenario the <html> markers describe; only it may clear them. */\nlet markerOwner: ScenarioSession | null = null;\n\n/** The session started by `startScenarios`, if any (null in production builds that skip it). */\nexport const currentSession = () => current;\n\ninterface Selection {\n name?: string;\n data?: string;\n strict?: boolean;\n}\n\n/**\n * Read the scenario from the URL (or the tab's last one), mock the network if the\n * scenario needs it, apply URL and adapter state. Await this before rendering the app.\n */\nexport async function startScenarios(options: StartOptions): Promise<ScenarioSession> {\n const catalog = createCatalog(options.scenarios, { rules: options.rules });\n const url = new URL(location.href);\n const selection = select(url, options.defaultScenario, options.allowLinks ?? LOOPBACK.has(url.hostname));\n const unhandled: string[] = [];\n globalThis.executableStoriesShowcaseUnhandled = unhandled;\n\n const connected = new Map<string, Connected>();\n const listeners = new Set<() => void>();\n\n // One failing listener (a panel, an app hook) must not stop the others or fail a switch.\n const emit = () => {\n for (const listener of listeners) {\n try {\n listener();\n } catch (err) {\n console.error('[executable-stories-showcase] onChange listener failed', err);\n }\n }\n };\n\n const recorder = createRecorder();\n // Set while recording listeners are attached; removes them.\n let stopListening: (() => void) | null = null;\n\n const listen = (target: MswWorker) => {\n if (!session.recording || stopListening) return;\n\n if (!target.events) {\n console.warn('[executable-stories-showcase] recording needs a worker with `events`');\n\n return;\n }\n\n const { events } = target;\n // Positions are taken when requests start, so a slow response keeps its place.\n const start: StartListener = ({ requestId }) => recorder.start(requestId);\n const mocked: ResponseListener = ({ requestId }) => recorder.drop(requestId);\n\n const bypass: ResponseListener = ({ requestId, request, response }) => {\n void recorder.bypass(requestId, request, response).then(emit);\n };\n\n events.on('request:start', start);\n events.on('response:mocked', mocked);\n events.on('response:bypass', bypass);\n\n stopListening = () => {\n events.removeListener('request:start', start);\n events.removeListener('response:mocked', mocked);\n events.removeListener('response:bypass', bypass);\n stopListening = null;\n };\n };\n\n /** The scenario's named handlers, in order. */\n const named = (resolved: Scenario) =>\n (resolved.handlers ?? []).flatMap((name) => {\n const found = options.handlers?.[name];\n\n if (!found) {\n const known = Object.keys(options.handlers ?? {}).join(', ') || 'none';\n throw new Error(\n `Scenario \"${resolved.name}\" uses unknown handler \"${name}\". Known handlers: ${known}`,\n );\n }\n\n return found;\n });\n\n // The app's worker, if given, else one this session starts on first need.\n let worker: MswWorker | null = options.worker ?? null;\n let ownWorker: SetupWorker | null = null;\n const appHandlers = options.worker ? [...options.worker.listHandlers()] : [];\n let destroyed = false;\n\n // Whether the active scenario came from an inline link.\n let fromLink = false;\n\n /** Validate every adapter's state up front, so nothing changes if any of it is invalid. */\n const prepareStates = (resolved: Scenario, adapters: Bound[], link = fromLink) =>\n adapters.flatMap((adapter) => {\n const state = resolved.state?.[adapter.key];\n\n if (state === undefined) return [];\n\n if (link && !adapter.fromLinks) {\n console.warn(`[executable-stories-showcase] ignored state.${adapter.key}: a link can't set it`);\n\n return [];\n }\n\n return [adapter.decode(state, resolved.name)];\n });\n\n const applyPrepared = async (prepared: ReturnType<typeof prepareStates>) => {\n for (const apply of prepared) {\n // Destroyed while an earlier adapter was applying: leave the rest untouched.\n if (destroyed) return;\n await apply();\n }\n };\n\n const boundAdapters = () => [...connected.values()].map((entry) => entry.adapter);\n\n // MSW loads only when a scenario mocks the network or strict mode needs a catch-all.\n const setNetwork = async (resolved: Scenario) => {\n if (!mocks(resolved) && !session.strict && !session.recording && !worker) return;\n const [{ toHandlers }, msw] = await Promise.all([import('./network.js'), import('msw')]);\n\n if (destroyed) return;\n\n const fallback = msw.http.all('*', ({ request }) => {\n const { pathname } = new URL(request.url);\n const isPage = request.mode === 'navigate' || !!request.headers.get('accept')?.includes('text/html');\n\n if (\n isPage || isAsset(request.url) || pathname.startsWith('/@') || pathname.startsWith(SAVE_ENDPOINT)\n || matches(options.allow, request.url)\n ) {\n return msw.passthrough();\n }\n\n if (matches(options.ignore, request.url)) {\n return session.strict ? new msw.HttpResponse(null, { status: 204 }) : msw.passthrough();\n }\n\n const label = `${request.method} ${request.url}`;\n unhandled.push(label);\n emit();\n\n if (!session.strict) return msw.passthrough();\n console.error(`[executable-stories-showcase] \"${resolved.name}\" has no handler for ${label}`);\n\n return msw.HttpResponse.json({ message: `No scenario handler for ${label}` }, { status: 501 });\n });\n\n // Scenario first, then its named handlers, then the app's own, then the catch-all.\n const handlers = [...toHandlers(resolved.network), ...named(resolved), ...appHandlers, fallback];\n\n if (worker) {\n worker.resetHandlers(...handlers);\n listen(worker);\n\n return;\n }\n\n const { setupWorker } = await import('#msw-browser');\n const started = setupWorker(...handlers);\n await started.start({ quiet: true, ...options.workerOptions });\n\n // Destroyed while starting: don't leave a worker running.\n if (destroyed) {\n void started.stop();\n\n return;\n }\n\n worker = ownWorker = started;\n listen(started);\n };\n\n const activate = (active: Scenario, resolved: Scenario, link: boolean) => {\n fromLink = link;\n session.active = active;\n session.resolved = resolved;\n // Tests and agents wait on these: html[data-scenario=\"name\"], then the ready selector.\n const html = document.documentElement;\n markerOwner = session;\n html.dataset.scenario = resolved.name;\n\n if (resolved.ready) html.dataset.scenarioReady = resolved.ready;\n else delete html.dataset.scenarioReady;\n };\n\n let transition = 0;\n let queue: Promise<void> = Promise.resolve();\n\n const switchTo = async (next: Scenario, inline: boolean) => {\n const resolved = catalog.resolve(next);\n // Validate before choosing reload or in place: a rejected switch must leave the app as it was.\n const prepared = prepareStates(resolved, boundAdapters(), inline);\n\n const prev = session.resolved;\n const nextKeys = Object.keys(resolved.state ?? {});\n named(resolved); // throws on an unknown handler name, before anything changes\n // Opening a scenario always restarts its network: fresh handler sequences, refetched data.\n const mocksNetwork = worker !== null || mocks(resolved) || session.strict;\n\n const href = scenarioHref(resolved, { inline, strict: session.strict });\n\n const inPlace = prev !== null\n // Compare with the app's actual URL: URL-driven UI only resets on a reload.\n && new URL(href).pathname === location.pathname\n && JSON.stringify(appParams(location.search)) === JSON.stringify(sortedParams(resolved.url))\n && (!mocksNetwork || !!options.refresh)\n // Can't un-apply state, and can't apply state nobody is connected to receive.\n && Object.keys(prev.state ?? {}).every((k) => nextKeys.includes(k))\n && nextKeys.every((k) => connected.get(k)?.adapter.inPlace === true);\n\n if (!inPlace) {\n navigate(href);\n\n return;\n }\n\n // After every await: a destroyed session must not touch the app, URL or storage.\n if (mocksNetwork) {\n await setNetwork(resolved);\n\n if (destroyed) return;\n // Kick off the refetch but don't wait: a scenario may hold requests open forever.\n Promise.resolve(options.refresh?.()).catch((err) => console.error('[executable-stories-showcase]', err));\n }\n\n await applyPrepared(prepared);\n\n if (destroyed) return;\n unhandled.splice(0);\n activate(next, resolved, inline);\n const at = new URL(href);\n replaceUrl(at);\n const data = readInline(at.hash);\n store(data ? { data, strict: session.strict } : { name: next.name, strict: session.strict });\n emit();\n };\n\n // Switches run one at a time, and one superseded while queued is skipped,\n // so a slow earlier switch can never land on top of a later one.\n const enqueue = (next: () => Scenario, inline: boolean) => {\n if (destroyed) return Promise.reject(new Error('This scenario session was destroyed'));\n const id = ++transition;\n const run = queue.then(() => (id === transition ? switchTo(next(), inline) : undefined));\n queue = run.catch(() => {});\n\n return run;\n };\n\n const attach = (adapter: Bound): Connection<Serializable> => {\n const entry: Connected = { adapter };\n connected.set(adapter.key, entry);\n entry.unsubscribe = adapter.subscribe?.(emit);\n emit();\n\n return {\n update(state) {\n entry.value = state;\n emit();\n },\n disconnect() {\n entry.unsubscribe?.();\n\n if (connected.get(adapter.key) === entry) connected.delete(adapter.key);\n emit();\n },\n };\n };\n\n const session: ScenarioSession = {\n catalog,\n active: null,\n resolved: null,\n // Read apart from the selection: `?scenario-strict` alone still blocks the real network.\n strict: options.strict ?? (!!selection?.strict || url.searchParams.has(STRICT_PARAM)),\n unhandled,\n // Held in memory: a full page load starts a new recording.\n recording: options.record ?? url.searchParams.has(RECORD_PARAM),\n recorded: recorder.entries,\n settleRecording: recorder.settled,\n keys: () => [...connected.keys()],\n read(key) {\n const entry = connected.get(key);\n const read = entry?.adapter.read();\n\n if (read !== undefined) return read;\n\n return entry?.value === undefined ? undefined : parseJson(JSON.stringify(entry.value));\n },\n async applyState(key, state) {\n const entry = connected.get(key);\n\n if (!entry) throw new Error(`No adapter connected for \"${key}\"`);\n await entry.adapter.decode(state, session.active?.name ?? 'live')();\n },\n connect(adapter) {\n const bound = bind(adapter);\n const noop = { update() {}, disconnect() {} };\n\n if (destroyed) return noop;\n const connection = attach(bound);\n\n if (session.resolved) {\n applyPrepared(prepareStates(session.resolved, [bound])).catch((err) =>\n console.error('[executable-stories-showcase]', err)\n );\n }\n\n return connection;\n },\n open: (names) => enqueue(() => lookup(catalog, names), false),\n openInline: (scenario) => enqueue(() => scenario, true),\n onChange(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n transition++; // drop queued switches\n\n for (const entry of connected.values()) entry.unsubscribe?.();\n connected.clear();\n listeners.clear();\n\n // An app-owned worker outlives the session: detach from it.\n stopListening?.();\n\n if (ownWorker) void ownWorker.stop();\n else worker?.resetHandlers(...appHandlers);\n worker = ownWorker = null;\n\n if (current === session) current = null;\n\n if (globalThis.executableStoriesShowcaseUnhandled === unhandled) globalThis.executableStoriesShowcaseUnhandled = undefined;\n\n if (globalThis.executableStoriesShowcasePage === hooks) globalThis.executableStoriesShowcasePage = undefined;\n\n if (markerOwner === session) {\n markerOwner = null;\n delete document.documentElement.dataset.scenario;\n delete document.documentElement.dataset.scenarioReady;\n }\n },\n };\n\n current = session;\n\n const hooks: PageHooks = {\n scenario: () => session.resolved?.name ?? null,\n async checkReady(selector) {\n const { checkReadySelector, suggestReadySelectors } = await import('./ready.js');\n const check = checkReadySelector(selector);\n const suggestions = suggestReadySelectors(null, { limit: 5 });\n\n return check.status === 'match'\n ? { status: 'match', count: check.count, visible: check.visible, suggestions }\n : { ...check, suggestions };\n },\n async capture({ extends: base, ...options }) {\n const { captureScenario } = await import('./capture.js');\n await session.settleRecording();\n\n return captureScenario(session, { ...options, base: base ? lookup(catalog, base) : undefined })\n .forCatalog;\n },\n };\n\n globalThis.executableStoriesShowcasePage = hooks;\n\n // Adapters connect whether or not a scenario is selected, so the plain app can be\n // inspected and captured too. Only applying scenario state is conditional.\n const initial = (options.adapters ?? []).map(bind);\n\n for (const adapter of initial) attach(adapter);\n\n if (!selection) {\n // Nothing to mock, but recording needs the worker to see the real responses,\n // and strict mode needs its catch-all.\n if (session.recording || session.strict) await setNetwork({ name: 'live' });\n\n return session;\n }\n\n // A selection that doesn't load must not stick to the tab and break every later page load.\n const loaded = (() => {\n try {\n const active = selection.data\n ? parseScenario(JSON.parse(selection.data), 'inline scenario')\n : lookup(catalog, selection.name ?? '');\n\n const resolved = catalog.resolve(active);\n named(resolved);\n\n return { active, resolved, prepared: prepareStates(resolved, initial, !!selection.data) };\n } catch (err) {\n store(null);\n throw err;\n }\n })();\n\n const { active, resolved, prepared } = loaded;\n await setNetwork(resolved);\n\n if (destroyed) return session;\n\n // Only fill params that are missing, so in-app navigation survives a reload.\n for (const [k, v] of Object.entries(resolved.url ?? {})) {\n if (v !== null && !url.searchParams.has(k)) url.searchParams.set(k, v);\n }\n\n replaceUrl(url);\n // Apply (awaiting async adapters) before the app renders.\n await applyPrepared(prepared);\n\n if (destroyed) return session;\n activate(active, resolved, !!selection.data);\n emit();\n\n return session;\n}\n\nconst mocks = (s: Scenario) => !!s.network?.length || !!s.handlers?.length;\n\nconst matches = (rules: (string | RegExp)[] = [], url: string) =>\n rules.some((rule) => (rule instanceof RegExp ? rule.test(url) : url.includes(rule)));\n\n/** msw's `isCommonAssetRequest`, inlined so msw 2.4 and later all work. */\nconst isAsset = (href: string) => {\n const url = new URL(href);\n\n return url.protocol === 'file:' || url.hostname === 'fonts.googleapis.com'\n || url.pathname.includes('node_modules') || url.pathname.includes('@vite')\n || /\\.(s?css|less|m?jsx?|m?tsx?|html|ttf|otf|woff2?|eot|gif|jpe?g|png|avif|webp|svg|mp4|webm|ogg|mov|mp3|wav|flac|aac|pdf|txt|csv|json|xml|md|zip|tar|gz|rar|7z)$/i\n .test(url.pathname);\n};\n\n// Keeps the router's history state. Next.js ignores replaceState calls whose state carries its\n// own marker (__NA), so drop that marker: Next then re-adds its state and updates useSearchParams.\nconst replaceUrl = (url: URL) => {\n const state: unknown = history.state;\n const { __NA, ...rest }: { __NA?: unknown; } = Object(state);\n\n history.replaceState(__NA === undefined ? state : rest, '', url);\n};\n\nconst byKey = ([a]: [string, string | null], [b]: [string, string | null]) => a.localeCompare(b);\n\n/** The app's own query params (scenario params excluded), sorted for comparison. */\nconst appParams = (search: string) =>\n [...new URLSearchParams(search)].filter(([k]) => k !== PARAM && k !== STRICT_PARAM).toSorted(byKey);\n\nconst sortedParams = (url: Scenario['url']) =>\n Object.entries(url ?? {}).filter(([, v]) => v !== null).toSorted(byKey);\n\n/**\n * Go to a scenario link, reloading even when only the hash changes. Scenario links stay on the\n * app, so another origin (or a `javascript:` URL, whose origin is `null`) is refused.\n */\nexport function navigate(href: string) {\n const next = new URL(href, location.href);\n\n if (next.origin !== location.origin) {\n throw new Error(`[executable-stories-showcase] navigate: ${next.origin} is not this app (${location.origin})`);\n }\n\n if (next.pathname + next.search === location.pathname + location.search) {\n replaceUrl(next);\n location.reload();\n } else {\n location.href = next.toString();\n }\n}\n\n/**\n * URL first (`?scenario=name` or `#scenario-data=...`), then the tab's last\n * selection, so navigation that drops the query keeps the scenario.\n * `?scenario=` (empty) resets to the default.\n */\nfunction select(url: URL, fallback: string | undefined, allowLinks: boolean): Selection | null {\n let data = readInline(url.hash);\n\n if (data && !allowLinks) {\n refuseLink(url);\n data = null;\n }\n const name = url.searchParams.get(PARAM);\n const strict = url.searchParams.has(STRICT_PARAM);\n\n let selection: Selection | null = null;\n\n if (data) selection = { data, strict };\n else if (name) selection = { name, strict };\n else if (name === '') {\n url.searchParams.delete(PARAM);\n store(null);\n } else {\n let stored = load();\n\n if (stored?.data && !allowLinks) {\n refuseLink(url);\n store(null);\n stored = null;\n }\n\n // An explicit ?scenario-strict in the current URL wins over the stored flag.\n selection = stored && { ...stored, strict: stored.strict || strict };\n\n // Restore the selection into the URL so it stays shareable.\n if (selection?.name) url.searchParams.set(PARAM, selection.name);\n\n if (selection?.data) url.hash = new URLSearchParams({ [DATA_KEY]: selection.data }).toString();\n\n if (selection?.strict) url.searchParams.set(STRICT_PARAM, '');\n }\n\n if (selection) store(selection);\n\n return selection ?? (fallback ? { name: fallback, strict } : null);\n}\n\nfunction refuseLink(url: URL) {\n url.hash = '';\n console.warn(\n `[executable-stories-showcase] ignored an inline scenario link: links only open on localhost unless startScenarios({ allowLinks: true })`,\n );\n}\n\n/** One name, or several joined with commas: later ones win, like `extends`. */\nfunction lookup(catalog: Catalog, names: string): Scenario {\n const list = names.split(',').map((n) => n.trim());\n\n for (const name of list) {\n if (catalog.get(name)) continue;\n const known = catalog.list.map((s) => s.name).join(', ');\n throw new Error(`Unknown scenario \"${name}\". Known scenarios: ${known}`);\n }\n\n const [only] = list;\n const single = list.length === 1 && only !== undefined ? catalog.get(only) : undefined;\n\n return single ?? { name: list.join(','), extends: list };\n}\n\nfunction store(selection: Selection | null) {\n try {\n if (selection) sessionStorage.setItem(STORAGE_KEY, JSON.stringify(selection));\n else sessionStorage.removeItem(STORAGE_KEY);\n } catch {\n // sessionStorage throws when storage is blocked; the selection just is not kept\n }\n}\n\nfunction load(): Selection | null {\n try {\n return JSON.parse(sessionStorage.getItem(STORAGE_KEY) ?? 'null');\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["resolved","prepared","toHandlers","active","checkReadySelector","suggestReadySelectors","options","captureScenario"]}
package/dist/jwt.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { JsonObject } from './schema.js';
2
+ /**
3
+ * An unsigned JWT (`alg: "none"`) carrying `claims`, for mocked responses in "signed-in" scenarios
4
+ * when the app only decodes the token it gets back. Anything that verifies signatures rejects it,
5
+ * which keeps it out of real backends. Set `exp` in the future if the app checks expiry.
6
+ */
7
+ export declare const unsignedJwt: (claims: JsonObject) => string;