state-scenarios 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,10 +3,15 @@ import {
3
3
  } from "./chunk-U3I7ORTB.js";
4
4
  import {
5
5
  captureScenario,
6
+ redactQuery,
7
+ redactSecrets,
8
+ unsignedJwt
9
+ } from "./chunk-3VHH36HY.js";
10
+ import {
6
11
  checkReadySelector,
7
12
  selectorForElement,
8
13
  suggestReadySelectors
9
- } from "./chunk-FTWYRB6F.js";
14
+ } from "./chunk-XLNYICOI.js";
10
15
  import {
11
16
  DATA_KEY,
12
17
  PARAM,
@@ -21,7 +26,7 @@ import {
21
26
  deepMerge,
22
27
  defineScenarioRules,
23
28
  diffScenario
24
- } from "./chunk-KYU7RUWI.js";
29
+ } from "./chunk-MS3OSAWQ.js";
25
30
  import {
26
31
  JsonSchema,
27
32
  MethodSchema,
@@ -29,7 +34,7 @@ import {
29
34
  parseJson,
30
35
  parseScenario,
31
36
  scenarioJsonSchema
32
- } from "./chunk-DCURPYW5.js";
37
+ } from "./chunk-BK4EEQXV.js";
33
38
 
34
39
  // src/record.ts
35
40
  function createRecorder() {
@@ -59,14 +64,26 @@ function createRecorder() {
59
64
  return Promise.resolve();
60
65
  }
61
66
  const read = response.clone().text().then((text) => {
67
+ const { query, dropped } = redactQuery(Object.fromEntries(url.searchParams));
62
68
  const call = {
63
69
  order,
64
70
  method: method.data,
65
71
  path: url.origin === location.origin ? url.pathname : url.origin + url.pathname,
66
- query: Object.fromEntries(url.searchParams),
72
+ query,
67
73
  status: response.status
68
74
  };
69
- if (text) call.body = parseJson(text);
75
+ if (dropped.length) {
76
+ console.info(
77
+ `[state-scenarios] left out query ${dropped.join(", ")} in ${method.data} ${call.path}`
78
+ );
79
+ }
80
+ if (text) {
81
+ const { value, redacted } = redactSecrets(parseJson(text));
82
+ call.body = value;
83
+ if (redacted.length) {
84
+ console.info(`[state-scenarios] redacted ${redacted.join(", ")} in ${method.data} ${call.path}`);
85
+ }
86
+ }
70
87
  calls.push(call);
71
88
  }).catch((err) => console.warn("[state-scenarios] skipped a recorded response", err)).finally(() => pending.delete(read));
72
89
  pending.add(read);
@@ -161,6 +178,16 @@ async function startScenarios(options) {
161
178
  stopListening = null;
162
179
  };
163
180
  };
181
+ const named = (resolved2) => (resolved2.handlers ?? []).flatMap((name) => {
182
+ const found = options.handlers?.[name];
183
+ if (!found) {
184
+ const known = Object.keys(options.handlers ?? {}).join(", ") || "none";
185
+ throw new Error(
186
+ `Scenario "${resolved2.name}" uses unknown handler "${name}". Known handlers: ${known}`
187
+ );
188
+ }
189
+ return found;
190
+ });
164
191
  let worker = options.worker ?? null;
165
192
  let ownWorker = null;
166
193
  const appHandlers = options.worker ? [...options.worker.listHandlers()] : [];
@@ -183,19 +210,18 @@ async function startScenarios(options) {
183
210
  };
184
211
  const boundAdapters = () => [...connected.values()].map((entry) => entry.adapter);
185
212
  const setNetwork = async (resolved2) => {
186
- if (!resolved2.network?.length && !session.strict && !session.recording && !worker) return;
213
+ if (!mocks(resolved2) && !session.strict && !session.recording && !worker) return;
187
214
  const [{ toHandlers: toHandlers2 }, msw] = await Promise.all([import("./network-MYTTJ2HL.js"), import("msw")]);
188
215
  if (destroyed) return;
189
- const allow = options.allow ?? [];
190
216
  const fallback = msw.http.all("*", ({ request }) => {
191
217
  const { pathname } = new URL(request.url);
192
218
  const isPage = request.mode === "navigate" || !!request.headers.get("accept")?.includes("text/html");
193
- const allowed = allow.some(
194
- (rule) => rule instanceof RegExp ? rule.test(request.url) : request.url.includes(rule)
195
- );
196
- if (isPage || isAsset(request.url) || pathname.startsWith("/@") || pathname.startsWith(SAVE_ENDPOINT) || allowed) {
219
+ if (isPage || isAsset(request.url) || pathname.startsWith("/@") || pathname.startsWith(SAVE_ENDPOINT) || matches(options.allow, request.url)) {
197
220
  return msw.passthrough();
198
221
  }
222
+ if (matches(options.ignore, request.url)) {
223
+ return session.strict ? new msw.HttpResponse(null, { status: 204 }) : msw.passthrough();
224
+ }
199
225
  const label = `${request.method} ${request.url}`;
200
226
  unhandled.push(label);
201
227
  emit();
@@ -203,7 +229,7 @@ async function startScenarios(options) {
203
229
  console.error(`[state-scenarios] "${resolved2.name}" has no handler for ${label}`);
204
230
  return msw.HttpResponse.json({ message: `No scenario handler for ${label}` }, { status: 501 });
205
231
  });
206
- const handlers = [...toHandlers2(resolved2.network), ...appHandlers, fallback];
232
+ const handlers = [...toHandlers2(resolved2.network), ...named(resolved2), ...appHandlers, fallback];
207
233
  if (worker) {
208
234
  worker.resetHandlers(...handlers);
209
235
  listen(worker);
@@ -236,7 +262,8 @@ async function startScenarios(options) {
236
262
  const prepared2 = prepareStates(resolved2, boundAdapters(), inline);
237
263
  const prev = session.resolved;
238
264
  const nextKeys = Object.keys(resolved2.state ?? {});
239
- const mocksNetwork = worker !== null || !!resolved2.network?.length || session.strict;
265
+ named(resolved2);
266
+ const mocksNetwork = worker !== null || mocks(resolved2) || session.strict;
240
267
  const href = scenarioHref(resolved2, { inline, strict: session.strict });
241
268
  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);
242
269
  if (!inPlace) {
@@ -338,6 +365,7 @@ async function startScenarios(options) {
338
365
  worker = ownWorker = null;
339
366
  if (current === session) current = null;
340
367
  if (globalThis.stateScenariosUnhandled === unhandled) globalThis.stateScenariosUnhandled = void 0;
368
+ if (globalThis.stateScenariosPage === hooks) globalThis.stateScenariosPage = void 0;
341
369
  if (markerOwner === session) {
342
370
  markerOwner = null;
343
371
  delete document.documentElement.dataset.scenario;
@@ -346,6 +374,21 @@ async function startScenarios(options) {
346
374
  }
347
375
  };
348
376
  current = session;
377
+ const hooks = {
378
+ scenario: () => session.resolved?.name ?? null,
379
+ async checkReady(selector) {
380
+ const { checkReadySelector: checkReadySelector2, suggestReadySelectors: suggestReadySelectors2 } = await import("./ready-4MENAMNI.js");
381
+ const check = checkReadySelector2(selector);
382
+ const suggestions = suggestReadySelectors2(null, { limit: 5 });
383
+ return check.status === "match" ? { status: "match", count: check.count, visible: check.visible, suggestions } : { ...check, suggestions };
384
+ },
385
+ async capture({ extends: base, ...options2 }) {
386
+ const { captureScenario: captureScenario2 } = await import("./capture-BKVROWKV.js");
387
+ await session.settleRecording();
388
+ return captureScenario2(session, { ...options2, base: base ? lookup(catalog, base) : void 0 }).forCatalog;
389
+ }
390
+ };
391
+ globalThis.stateScenariosPage = hooks;
349
392
  const initial = (options.adapters ?? []).map(bind);
350
393
  for (const adapter of initial) attach(adapter);
351
394
  if (!selection) {
@@ -356,6 +399,7 @@ async function startScenarios(options) {
356
399
  try {
357
400
  const active2 = selection.data ? parseScenario(JSON.parse(selection.data), "inline scenario") : lookup(catalog, selection.name ?? "");
358
401
  const resolved2 = catalog.resolve(active2);
402
+ named(resolved2);
359
403
  return { active: active2, resolved: resolved2, prepared: prepareStates(resolved2, initial, !!selection.data) };
360
404
  } catch (err) {
361
405
  store(null);
@@ -375,6 +419,8 @@ async function startScenarios(options) {
375
419
  emit();
376
420
  return session;
377
421
  }
422
+ var mocks = (s) => !!s.network?.length || !!s.handlers?.length;
423
+ var matches = (rules = [], url) => rules.some((rule) => rule instanceof RegExp ? rule.test(url) : url.includes(rule));
378
424
  var isAsset = (href) => {
379
425
  const url = new URL(href);
380
426
  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);
@@ -459,11 +505,13 @@ export {
459
505
  parseJson,
460
506
  parseScenario,
461
507
  readInline,
508
+ redactSecrets,
462
509
  scenarioHref,
463
510
  scenarioJsonSchema,
464
511
  selectorForElement,
465
512
  startScenarios,
466
513
  suggestReadySelectors,
467
- toHandlers
514
+ toHandlers,
515
+ unsignedJwt
468
516
  };
469
517
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/record.ts","../src/runtime.ts"],"sourcesContent":["import { SAVE_ENDPOINT } from './link.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(`[state-scenarios] 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: Object.fromEntries(url.searchParams),\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) call.body = parseJson(text);\n calls.push(call);\n }).catch((err) => console.warn('[state-scenarios] 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 { SetupWorker } from 'msw/browser';\nimport type { z } from 'zod';\nimport { type Catalog, createCatalog, type ScenarioRule } from './catalog.js';\nimport {\n DATA_KEY,\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 = 'state-scenarios: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 * 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 /** Requests that may reach the network in strict mode (URL substring or RegExp). */\n allow?: (string | RegExp)[];\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 state-scenarios starts its own worker. */\n workerOptions?: Parameters<SetupWorker['start']>[0];\n}\n\n/** The parts of an MSW worker or server state-scenarios 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\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);\n const unhandled: string[] = [];\n globalThis.stateScenariosUnhandled = 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('[state-scenarios] 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('[state-scenarios] 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 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(`[state-scenarios] 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 (!resolved.network?.length && !session.strict && !session.recording && !worker) return;\n const [{ toHandlers }, msw] = await Promise.all([import('./network.js'), import('msw')]);\n\n if (destroyed) return;\n const allow = options.allow ?? [];\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 const allowed = allow.some((rule) =>\n rule instanceof RegExp ? rule.test(request.url) : request.url.includes(rule)\n );\n\n if (\n isPage || isAsset(request.url) || pathname.startsWith('/@') || pathname.startsWith(SAVE_ENDPOINT)\n || allowed\n ) {\n return 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(`[state-scenarios] \"${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 the app's own handlers, then the catch-all.\n const handlers = [...toHandlers(resolved.network), ...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 // Opening a scenario always restarts its network: fresh handler sequences, refetched data.\n const mocksNetwork = worker !== null || !!resolved.network?.length || 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('[state-scenarios]', 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 strict: options.strict ?? !!selection?.strict,\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('[state-scenarios]', 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.stateScenariosUnhandled === unhandled) globalThis.stateScenariosUnhandled = 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 // 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 if (session.recording) 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\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\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/** Go to a scenario link, reloading even when only the hash changes. */\nexport function navigate(href: string) {\n const next = new URL(href, location.href);\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): Selection | null {\n const data = readInline(url.hash);\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 const stored = load();\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\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}\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"]}
1
+ {"version":3,"sources":["../src/record.ts","../src/runtime.ts"],"sourcesContent":["import { SAVE_ENDPOINT } from './link.js';\nimport { redactQuery, redactSecrets } 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(`[state-scenarios] 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 { query, dropped } = redactQuery(Object.fromEntries(url.searchParams));\n\n const call: RecordedCall = {\n order,\n method: method.data,\n path: url.origin === location.origin ? url.pathname : url.origin + url.pathname,\n query,\n status: response.status,\n };\n\n if (dropped.length) {\n console.info(\n `[state-scenarios] left out query ${dropped.join(', ')} in ${method.data} ${call.path}`,\n );\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(`[state-scenarios] redacted ${redacted.join(', ')} in ${method.data} ${call.path}`);\n }\n }\n\n calls.push(call);\n }).catch((err) => console.warn('[state-scenarios] 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 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 = 'state-scenarios: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 * 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 state-scenarios starts its own worker. */\n workerOptions?: Parameters<SetupWorker['start']>[0];\n}\n\n/** The parts of an MSW worker or server state-scenarios 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 stateScenariosPage: 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);\n const unhandled: string[] = [];\n globalThis.stateScenariosUnhandled = 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('[state-scenarios] 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('[state-scenarios] 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(`[state-scenarios] 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(`[state-scenarios] \"${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('[state-scenarios]', 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 strict: options.strict ?? !!selection?.strict,\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('[state-scenarios]', 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.stateScenariosUnhandled === unhandled) globalThis.stateScenariosUnhandled = undefined;\n\n if (globalThis.stateScenariosPage === hooks) globalThis.stateScenariosPage = 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.stateScenariosPage = 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 if (session.recording) 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/** Go to a scenario link, reloading even when only the hash changes. */\nexport function navigate(href: string) {\n const next = new URL(href, location.href);\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): Selection | null {\n const data = readInline(url.hash);\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 const stored = load();\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\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}\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;
package/dist/next.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  startSaveServer
3
- } from "./chunk-HEVJQ47T.js";
3
+ } from "./chunk-XAH3YMWR.js";
4
4
  import "./chunk-3M6SD4TA.js";
5
- import "./chunk-DCURPYW5.js";
5
+ import "./chunk-BK4EEQXV.js";
6
6
 
7
7
  // src/next.ts
8
8
  import { relative, resolve } from "path";
package/dist/panel.js CHANGED
@@ -1,19 +1,21 @@
1
1
  import {
2
- captureScenario,
2
+ captureScenario
3
+ } from "./chunk-3VHH36HY.js";
4
+ import {
3
5
  checkReadySelector,
4
6
  closestReadySelector,
5
7
  suggestReadySelectors
6
- } from "./chunk-FTWYRB6F.js";
8
+ } from "./chunk-XLNYICOI.js";
7
9
  import {
8
10
  PARAM,
9
11
  SAVE_ENDPOINT,
10
12
  readInline,
11
13
  scenarioHref
12
14
  } from "./chunk-3M6SD4TA.js";
13
- import "./chunk-KYU7RUWI.js";
15
+ import "./chunk-MS3OSAWQ.js";
14
16
  import {
15
17
  parseScenario
16
- } from "./chunk-DCURPYW5.js";
18
+ } from "./chunk-BK4EEQXV.js";
17
19
 
18
20
  // src/panel.ts
19
21
  import { z } from "zod";