state-scenarios 0.2.0 → 0.3.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/adapters/redux.js +2 -2
- package/dist/adapters/tanstack-query.js +1 -1
- package/dist/adapters/xstate.js +2 -2
- package/dist/adapters/zustand.js +1 -1
- package/dist/capture.d.ts +26 -0
- package/dist/catalog.d.ts +6 -0
- package/dist/{chunk-KRCYO64S.js → chunk-3M6SD4TA.js} +5 -1
- package/dist/chunk-3M6SD4TA.js.map +1 -0
- package/dist/{chunk-6L55WKFS.js → chunk-DCURPYW5.js} +4 -2
- package/dist/chunk-DCURPYW5.js.map +1 -0
- package/dist/chunk-FTWYRB6F.js +277 -0
- package/dist/chunk-FTWYRB6F.js.map +1 -0
- package/dist/chunk-HEVJQ47T.js +148 -0
- package/dist/chunk-HEVJQ47T.js.map +1 -0
- package/dist/chunk-KYU7RUWI.js +179 -0
- package/dist/chunk-KYU7RUWI.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +133 -11
- package/dist/index.js.map +1 -1
- package/dist/link.d.ts +4 -0
- package/dist/next.d.ts +21 -0
- package/dist/next.js +30 -0
- package/dist/next.js.map +1 -0
- package/dist/no-msw-browser.d.ts +2 -0
- package/dist/no-msw-browser.js +8 -0
- package/dist/no-msw-browser.js.map +1 -0
- package/dist/panel.d.ts +10 -8
- package/dist/panel.js +284 -40
- package/dist/panel.js.map +1 -1
- package/dist/playwright.js +1 -1
- package/dist/ready.d.ts +39 -0
- package/dist/record.d.ts +49 -0
- package/dist/runtime.d.ts +31 -3
- package/dist/save.d.ts +27 -0
- package/dist/schema.d.ts +9 -0
- package/dist/vite.d.ts +11 -0
- package/dist/vite.js +26 -0
- package/dist/vite.js.map +1 -0
- package/package.json +27 -3
- package/dist/chunk-6L55WKFS.js.map +0 -1
- package/dist/chunk-KDH572NI.js +0 -88
- package/dist/chunk-KDH572NI.js.map +0 -1
- package/dist/chunk-KRCYO64S.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["import type { SetupWorker } from 'msw/browser';\nimport type { z } from 'zod';\nimport { type Catalog, createCatalog, type ScenarioRule } from './catalog.js';\nimport { DATA_KEY, PARAM, readInline, scenarioHref, STRICT_PARAM } from './link.js';\nimport {\n type Json,\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 /** 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. */\nexport type MswWorker = Pick<SetupWorker, 'resetHandlers' | 'listHandlers'>;\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 /** 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 // 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 && !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 (isPage || isAsset(request.url) || pathname.startsWith('/@') || allowed) {\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\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 };\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 history.replaceState(history.state, '', 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 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 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) return session;\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 history.replaceState(history.state, '', 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\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 history.replaceState(history.state, '', 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 { 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"]}
|
package/dist/link.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ declare global {
|
|
|
6
6
|
export declare const PARAM = "scenario";
|
|
7
7
|
/** Fail unmatched requests instead of letting them reach the network. */
|
|
8
8
|
export declare const STRICT_PARAM = "scenario-strict";
|
|
9
|
+
/** Record real responses so they can be saved as a scenario. */
|
|
10
|
+
export declare const RECORD_PARAM = "scenario-record";
|
|
11
|
+
/** Dev-server route the `state-scenarios/vite` plugin serves for the panel's "Save to project". */
|
|
12
|
+
export declare const SAVE_ENDPOINT = "/__state-scenarios/save";
|
|
9
13
|
/** Inline scenario JSON, kept in the URL hash so it never reaches a server. */
|
|
10
14
|
export declare const DATA_KEY = "scenario-data";
|
|
11
15
|
/**
|
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface SaveRouteOptions {
|
|
2
|
+
/** Folder the panel's "Save to project" writes into, relative to the project root. Default "src/scenarios". */
|
|
3
|
+
dir?: string;
|
|
4
|
+
}
|
|
5
|
+
declare global {
|
|
6
|
+
/** One save server per folder for the life of the dev process, across route reloads. */
|
|
7
|
+
var stateScenariosSaveServers: Map<string, Promise<string>> | undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The route handler behind the panel's "Save to project" in Next.js (App Router). Put it at
|
|
11
|
+
* `app/%5F%5Fstate-scenarios/save/route.ts` (`%5F` is an underscore; a plain `_` folder is
|
|
12
|
+
* private and never routes):
|
|
13
|
+
*
|
|
14
|
+
* export const { GET } = createSaveRoute({ dir: 'scenarios' });
|
|
15
|
+
*
|
|
16
|
+
* `next dev` listens on the network, so this route only tells the panel where to save: a
|
|
17
|
+
* server on 127.0.0.1 that other machines can't reach. Outside `next dev` it answers 404.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createSaveRoute({ dir }?: SaveRouteOptions): {
|
|
20
|
+
GET: (request: Request) => Promise<Response>;
|
|
21
|
+
};
|
package/dist/next.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
startSaveServer
|
|
3
|
+
} from "./chunk-HEVJQ47T.js";
|
|
4
|
+
import "./chunk-3M6SD4TA.js";
|
|
5
|
+
import "./chunk-DCURPYW5.js";
|
|
6
|
+
|
|
7
|
+
// src/next.ts
|
|
8
|
+
import { relative, resolve } from "path";
|
|
9
|
+
function createSaveRoute({ dir = "src/scenarios" } = {}) {
|
|
10
|
+
const GET = async (request) => {
|
|
11
|
+
if (process.env.NODE_ENV !== "development") return new Response(null, { status: 404 });
|
|
12
|
+
const root = process.cwd();
|
|
13
|
+
const target = resolve(root, dir);
|
|
14
|
+
const port = process.env.PORT ?? new URL(request.url).port;
|
|
15
|
+
const origins = ["localhost", "127.0.0.1", "[::1]"].map((host) => `http://${host}:${port}`);
|
|
16
|
+
const servers = globalThis.stateScenariosSaveServers ??= /* @__PURE__ */ new Map();
|
|
17
|
+
let url = servers.get(target);
|
|
18
|
+
if (!url) {
|
|
19
|
+
url = startSaveServer({ dir: target, root, origins });
|
|
20
|
+
servers.set(target, url);
|
|
21
|
+
url.catch(() => servers.delete(target));
|
|
22
|
+
}
|
|
23
|
+
return Response.json({ dir: relative(root, target).replaceAll("\\", "/"), url: await url });
|
|
24
|
+
};
|
|
25
|
+
return { GET };
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
createSaveRoute
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=next.js.map
|
package/dist/next.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/next.ts"],"sourcesContent":["import { relative, resolve } from 'node:path';\nimport { startSaveServer } from './save.js';\n\nexport interface SaveRouteOptions {\n /** Folder the panel's \"Save to project\" writes into, relative to the project root. Default \"src/scenarios\". */\n dir?: string;\n}\n\ndeclare global {\n /** One save server per folder for the life of the dev process, across route reloads. */\n var stateScenariosSaveServers: Map<string, Promise<string>> | undefined;\n}\n\n/**\n * The route handler behind the panel's \"Save to project\" in Next.js (App Router). Put it at\n * `app/%5F%5Fstate-scenarios/save/route.ts` (`%5F` is an underscore; a plain `_` folder is\n * private and never routes):\n *\n * export const { GET } = createSaveRoute({ dir: 'scenarios' });\n *\n * `next dev` listens on the network, so this route only tells the panel where to save: a\n * server on 127.0.0.1 that other machines can't reach. Outside `next dev` it answers 404.\n */\nexport function createSaveRoute({ dir = 'src/scenarios' }: SaveRouteOptions = {}) {\n const GET = async (request: Request): Promise<Response> => {\n if (process.env.NODE_ENV !== 'development') return new Response(null, { status: 404 });\n\n const root = process.cwd();\n const target = resolve(root, dir);\n const port = process.env.PORT ?? new URL(request.url).port;\n const origins = ['localhost', '127.0.0.1', '[::1]'].map((host) => `http://${host}:${port}`);\n\n const servers = (globalThis.stateScenariosSaveServers ??= new Map());\n let url = servers.get(target);\n\n if (!url) {\n url = startSaveServer({ dir: target, root, origins });\n servers.set(target, url);\n // A failed start (port exhaustion, say) retries on the next request.\n url.catch(() => servers.delete(target));\n }\n\n return Response.json({ dir: relative(root, target).replaceAll('\\\\', '/'), url: await url });\n };\n\n return { GET };\n}\n"],"mappings":";;;;;;;AAAA,SAAS,UAAU,eAAe;AAuB3B,SAAS,gBAAgB,EAAE,MAAM,gBAAgB,IAAsB,CAAC,GAAG;AAChF,QAAM,MAAM,OAAO,YAAwC;AACzD,QAAI,QAAQ,IAAI,aAAa,cAAe,QAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAErF,UAAM,OAAO,QAAQ,IAAI;AACzB,UAAM,SAAS,QAAQ,MAAM,GAAG;AAChC,UAAM,OAAO,QAAQ,IAAI,QAAQ,IAAI,IAAI,QAAQ,GAAG,EAAE;AACtD,UAAM,UAAU,CAAC,aAAa,aAAa,OAAO,EAAE,IAAI,CAAC,SAAS,UAAU,IAAI,IAAI,IAAI,EAAE;AAE1F,UAAM,UAAW,WAAW,8BAA8B,oBAAI,IAAI;AAClE,QAAI,MAAM,QAAQ,IAAI,MAAM;AAE5B,QAAI,CAAC,KAAK;AACR,YAAM,gBAAgB,EAAE,KAAK,QAAQ,MAAM,QAAQ,CAAC;AACpD,cAAQ,IAAI,QAAQ,GAAG;AAEvB,UAAI,MAAM,MAAM,QAAQ,OAAO,MAAM,CAAC;AAAA,IACxC;AAEA,WAAO,SAAS,KAAK,EAAE,KAAK,SAAS,MAAM,MAAM,EAAE,WAAW,MAAM,GAAG,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,EAC5F;AAEA,SAAO,EAAE,IAAI;AACf;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/no-msw-browser.ts"],"sourcesContent":["import type { setupWorker as SetupWorker } from 'msw/browser';\n\n// What `#msw-browser` resolves to on the server: msw/browser has no Node build, and SSR\n// bundlers (Next.js) compile client components for the server too. The worker only starts in a browser.\nexport const setupWorker: typeof SetupWorker = () => {\n throw new Error('state-scenarios: the MSW worker only runs in a browser');\n};\n"],"mappings":";AAIO,IAAM,cAAkC,MAAM;AACnD,QAAM,IAAI,MAAM,wDAAwD;AAC1E;","names":[]}
|
package/dist/panel.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ScenarioSession } from './runtime.js';
|
|
2
|
-
import { type Scenario } from './schema.js';
|
|
3
2
|
export interface PanelOptions {
|
|
4
3
|
/** Corner to dock in, e.g. to avoid a chat widget. Default "bottom-right". */
|
|
5
4
|
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
@@ -7,22 +6,25 @@ export interface PanelOptions {
|
|
|
7
6
|
zIndex?: number;
|
|
8
7
|
/** "auto" follows the OS colour scheme. Default "auto". */
|
|
9
8
|
theme?: 'auto' | 'light' | 'dark';
|
|
9
|
+
/**
|
|
10
|
+
* Relative `$schema` path stamped on Download JSON, e.g. `../scenario.schema.json`.
|
|
11
|
+
* Copy link stays schema-free.
|
|
12
|
+
*/
|
|
13
|
+
schemaPath?: string;
|
|
10
14
|
}
|
|
11
15
|
/**
|
|
12
16
|
* Add the floating scenario panel to the page. Framework-agnostic.
|
|
13
17
|
* Remove it with `panel.remove()`; it unsubscribes from the session.
|
|
14
18
|
*/
|
|
15
19
|
export declare function mountPanel(session: ScenarioSession, options?: PanelOptions): ScenarioPanel;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
export declare function captureScenario(session: ScenarioSession, name: string, description?: string): Scenario;
|
|
21
|
-
declare class ScenarioPanel extends HTMLElement {
|
|
20
|
+
export { type CapturedScenario, type CaptureOptions, captureScenario } from './capture.js';
|
|
21
|
+
declare const Base: typeof HTMLElement;
|
|
22
|
+
declare class ScenarioPanel extends Base {
|
|
22
23
|
session: ScenarioSession;
|
|
24
|
+
schemaPath?: string;
|
|
23
25
|
private render?;
|
|
24
26
|
private unsubscribe?;
|
|
27
|
+
private stopPick?;
|
|
25
28
|
disconnectedCallback(): void;
|
|
26
29
|
connectedCallback(): void;
|
|
27
30
|
}
|
|
28
|
-
export {};
|