state-scenarios 0.0.1 → 0.1.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/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/adapters/redux.d.ts +23 -0
- package/dist/adapters/redux.js +28 -0
- package/dist/adapters/redux.js.map +1 -0
- package/dist/adapters/tanstack-query.d.ts +17 -0
- package/dist/adapters/tanstack-query.js +43 -0
- package/dist/adapters/tanstack-query.js.map +1 -0
- package/dist/adapters/xstate.d.ts +33 -0
- package/dist/adapters/xstate.js +36 -0
- package/dist/adapters/xstate.js.map +1 -0
- package/dist/adapters/zustand.d.ts +17 -0
- package/dist/adapters/zustand.js +16 -0
- package/dist/adapters/zustand.js.map +1 -0
- package/dist/catalog.d.ts +21 -0
- package/dist/chunk-JIHDABWO.js +88 -0
- package/dist/chunk-JIHDABWO.js.map +1 -0
- package/dist/chunk-M5Q4I3KT.js +30 -0
- package/dist/chunk-M5Q4I3KT.js.map +1 -0
- package/dist/chunk-MLXYB7GV.js +87 -0
- package/dist/chunk-MLXYB7GV.js.map +1 -0
- package/dist/chunk-U3I7ORTB.js +35 -0
- package/dist/chunk-U3I7ORTB.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +335 -0
- package/dist/index.js.map +1 -0
- package/dist/link.d.ts +21 -0
- package/dist/network-MYTTJ2HL.js +7 -0
- package/dist/network-MYTTJ2HL.js.map +1 -0
- package/dist/network.d.ts +3 -0
- package/dist/panel.d.ts +28 -0
- package/dist/panel.js +360 -0
- package/dist/panel.js.map +1 -0
- package/dist/playwright.d.ts +17 -0
- package/dist/playwright.js +30 -0
- package/dist/playwright.js.map +1 -0
- package/dist/runtime.d.ts +105 -0
- package/dist/schema.d.ts +140 -0
- package/package.json +83 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jag Reehal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# state-scenarios
|
|
2
|
+
|
|
3
|
+
Put a running app into any state from a URL. Scenarios are JSON files that set network fixtures, URL params and app state. A dev panel lets you switch between them, inspect and edit live state, and save it as a shareable link. Playwright helpers open scenarios in tests, and adapters connect TanStack Query, Zustand, Redux and XState.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { type ScenarioInput, startScenarios } from 'state-scenarios';
|
|
7
|
+
import { mountPanel } from 'state-scenarios/panel';
|
|
8
|
+
|
|
9
|
+
const session = await startScenarios({
|
|
10
|
+
scenarios: Object.values(
|
|
11
|
+
import.meta.glob<ScenarioInput>('./scenarios/*.json', { eager: true, import: 'default' }),
|
|
12
|
+
),
|
|
13
|
+
defaultScenario: 'default',
|
|
14
|
+
});
|
|
15
|
+
mountPanel(session);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Documentation: https://github.com/jagreehal/state-scenarios
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { JsonAdapter } from '../runtime.js';
|
|
3
|
+
import { type Json, type Serializable } from '../schema.js';
|
|
4
|
+
export declare const SCENARIO_HYDRATE = "@@scenarios/hydrate";
|
|
5
|
+
interface Action {
|
|
6
|
+
type: string;
|
|
7
|
+
payload?: Json;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Wrap your root reducer so scenario state can be merged in. Objects deep-merge into the
|
|
11
|
+
* current state and `null` removes a key; `schema` checks the result is still valid state.
|
|
12
|
+
*/
|
|
13
|
+
export declare function withScenarioState<S extends Serializable, A extends Action>(reducer: (state: S | undefined, action: A) => S, schema: z.ZodType<S>): (state: S | undefined, action: A) => S;
|
|
14
|
+
interface Store {
|
|
15
|
+
dispatch(action: Action): void;
|
|
16
|
+
getState(): Serializable;
|
|
17
|
+
subscribe(listener: () => void): () => void;
|
|
18
|
+
}
|
|
19
|
+
/** Connects a Redux store as `state.redux` (or your key). Needs `withScenarioState`. */
|
|
20
|
+
export declare const reduxAdapter: (store: Store, { key }?: {
|
|
21
|
+
key?: string;
|
|
22
|
+
}) => JsonAdapter;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deepMerge
|
|
3
|
+
} from "../chunk-JIHDABWO.js";
|
|
4
|
+
import {
|
|
5
|
+
parseJson
|
|
6
|
+
} from "../chunk-MLXYB7GV.js";
|
|
7
|
+
|
|
8
|
+
// src/adapters/redux.ts
|
|
9
|
+
var SCENARIO_HYDRATE = "@@scenarios/hydrate";
|
|
10
|
+
function withScenarioState(reducer, schema) {
|
|
11
|
+
return (state, action) => {
|
|
12
|
+
if (action.type !== SCENARIO_HYDRATE || action.payload === void 0) return reducer(state, action);
|
|
13
|
+
const base = state ?? reducer(void 0, action);
|
|
14
|
+
return schema.parse(deepMerge(parseJson(JSON.stringify(base)), action.payload));
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
var reduxAdapter = (store, { key = "redux" } = {}) => ({
|
|
18
|
+
key,
|
|
19
|
+
apply: (payload) => store.dispatch({ type: SCENARIO_HYDRATE, payload }),
|
|
20
|
+
read: () => parseJson(JSON.stringify(store.getState())),
|
|
21
|
+
subscribe: (onChange) => store.subscribe(onChange)
|
|
22
|
+
});
|
|
23
|
+
export {
|
|
24
|
+
SCENARIO_HYDRATE,
|
|
25
|
+
reduxAdapter,
|
|
26
|
+
withScenarioState
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=redux.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/redux.ts"],"sourcesContent":["import type { z } from 'zod';\nimport { deepMerge } from '../catalog.js';\nimport type { JsonAdapter } from '../runtime.js';\nimport { type Json, parseJson, type Serializable } from '../schema.js';\n\nexport const SCENARIO_HYDRATE = '@@scenarios/hydrate';\n\ninterface Action {\n type: string;\n payload?: Json;\n}\n\n/**\n * Wrap your root reducer so scenario state can be merged in. Objects deep-merge into the\n * current state and `null` removes a key; `schema` checks the result is still valid state.\n */\nexport function withScenarioState<S extends Serializable, A extends Action>(\n reducer: (state: S | undefined, action: A) => S,\n schema: z.ZodType<S>,\n) {\n return (state: S | undefined, action: A): S => {\n if (action.type !== SCENARIO_HYDRATE || action.payload === undefined) return reducer(state, action);\n // Reducers return their initial state for an action they don't handle.\n const base = state ?? reducer(undefined, action);\n\n return schema.parse(deepMerge(parseJson(JSON.stringify(base)), action.payload));\n };\n}\n\ninterface Store {\n dispatch(action: Action): void;\n getState(): Serializable;\n subscribe(listener: () => void): () => void;\n}\n\n/** Connects a Redux store as `state.redux` (or your key). Needs `withScenarioState`. */\nexport const reduxAdapter = (store: Store, { key = 'redux' }: { key?: string; } = {}): JsonAdapter => ({\n key,\n apply: (payload) => store.dispatch({ type: SCENARIO_HYDRATE, payload }),\n read: () => parseJson(JSON.stringify(store.getState())),\n subscribe: (onChange) => store.subscribe(onChange),\n});\n"],"mappings":";;;;;;;;AAKO,IAAM,mBAAmB;AAWzB,SAAS,kBACd,SACA,QACA;AACA,SAAO,CAAC,OAAsB,WAAiB;AAC7C,QAAI,OAAO,SAAS,oBAAoB,OAAO,YAAY,OAAW,QAAO,QAAQ,OAAO,MAAM;AAElG,UAAM,OAAO,SAAS,QAAQ,QAAW,MAAM;AAE/C,WAAO,OAAO,MAAM,UAAU,UAAU,KAAK,UAAU,IAAI,CAAC,GAAG,OAAO,OAAO,CAAC;AAAA,EAChF;AACF;AASO,IAAM,eAAe,CAAC,OAAc,EAAE,MAAM,QAAQ,IAAuB,CAAC,OAAoB;AAAA,EACrG;AAAA,EACA,OAAO,CAAC,YAAY,MAAM,SAAS,EAAE,MAAM,kBAAkB,QAAQ,CAAC;AAAA,EACtE,MAAM,MAAM,UAAU,KAAK,UAAU,MAAM,SAAS,CAAC,CAAC;AAAA,EACtD,WAAW,CAAC,aAAa,MAAM,UAAU,QAAQ;AACnD;","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { TypedAdapter } from '../runtime.js';
|
|
4
|
+
export declare const tanstackQueryState: z.ZodArray<z.ZodObject<{
|
|
5
|
+
queryKey: z.ZodArray<z.ZodJSONSchema>;
|
|
6
|
+
data: z.ZodJSONSchema;
|
|
7
|
+
stale: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
}, z.core.$strip>>;
|
|
9
|
+
type CacheState = z.output<typeof tanstackQueryState>;
|
|
10
|
+
/**
|
|
11
|
+
* Connects the TanStack Query cache as `state["tanstack-query"]`:
|
|
12
|
+
* [{ "queryKey": ["countries"], "data": [...] }]
|
|
13
|
+
* Seeds states the network alone can't produce (cached data while a refetch hangs),
|
|
14
|
+
* and lets the panel inspect, edit and capture the live cache.
|
|
15
|
+
*/
|
|
16
|
+
export declare const tanstackQuery: (client: QueryClient) => TypedAdapter<CacheState>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JsonSchema,
|
|
3
|
+
parseJson
|
|
4
|
+
} from "../chunk-MLXYB7GV.js";
|
|
5
|
+
|
|
6
|
+
// src/adapters/tanstack-query.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
var tanstackQueryState = z.array(
|
|
9
|
+
z.object({
|
|
10
|
+
queryKey: z.array(JsonSchema).min(1),
|
|
11
|
+
data: JsonSchema,
|
|
12
|
+
/** Refetch on mount (e.g. to show a background refresh). Default: fresh, so the data stays as given. */
|
|
13
|
+
stale: z.boolean().optional()
|
|
14
|
+
})
|
|
15
|
+
);
|
|
16
|
+
var tanstackQuery = (client) => ({
|
|
17
|
+
key: "tanstack-query",
|
|
18
|
+
schema: tanstackQueryState,
|
|
19
|
+
async apply(entries) {
|
|
20
|
+
for (const { queryKey, data, stale } of entries) {
|
|
21
|
+
if (!stale) await client.cancelQueries({ queryKey, exact: true });
|
|
22
|
+
client.setQueryData(queryKey, data);
|
|
23
|
+
if (stale) void client.invalidateQueries({ queryKey, exact: true, refetchType: "none" });
|
|
24
|
+
else client.setQueryDefaults(queryKey, { staleTime: Infinity });
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
read: () => parseJson(JSON.stringify(
|
|
28
|
+
client.getQueryCache().getAll().flatMap(
|
|
29
|
+
(query) => query.state.data === void 0 ? [] : [{
|
|
30
|
+
queryKey: query.queryKey,
|
|
31
|
+
data: query.state.data,
|
|
32
|
+
// A refresh in flight is part of what's on screen ("Refreshing…"), so capture it.
|
|
33
|
+
stale: query.state.fetchStatus === "fetching" || void 0
|
|
34
|
+
}]
|
|
35
|
+
)
|
|
36
|
+
)),
|
|
37
|
+
subscribe: (onChange) => client.getQueryCache().subscribe(onChange)
|
|
38
|
+
});
|
|
39
|
+
export {
|
|
40
|
+
tanstackQuery,
|
|
41
|
+
tanstackQueryState
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=tanstack-query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/tanstack-query.ts"],"sourcesContent":["import type { QueryClient } from '@tanstack/react-query';\nimport { z } from 'zod';\nimport type { TypedAdapter } from '../runtime.js';\nimport { JsonSchema, parseJson } from '../schema.js';\n\nexport const tanstackQueryState = z.array(\n z.object({\n queryKey: z.array(JsonSchema).min(1),\n data: JsonSchema,\n /** Refetch on mount (e.g. to show a background refresh). Default: fresh, so the data stays as given. */\n stale: z.boolean().optional(),\n }),\n);\n\ntype CacheState = z.output<typeof tanstackQueryState>;\n\n/**\n * Connects the TanStack Query cache as `state[\"tanstack-query\"]`:\n * [{ \"queryKey\": [\"countries\"], \"data\": [...] }]\n * Seeds states the network alone can't produce (cached data while a refetch hangs),\n * and lets the panel inspect, edit and capture the live cache.\n */\nexport const tanstackQuery = (client: QueryClient): TypedAdapter<CacheState> => ({\n key: 'tanstack-query',\n schema: tanstackQueryState,\n async apply(entries) {\n for (const { queryKey, data, stale } of entries) {\n // A refetch started by a scenario switch must not land on top of fresh scenario data.\n if (!stale) await client.cancelQueries({ queryKey, exact: true });\n client.setQueryData(queryKey, data);\n\n // Fresh by default, so a saved scenario reproduces the screen instead of refetching over it.\n if (stale) void client.invalidateQueries({ queryKey, exact: true, refetchType: 'none' });\n else client.setQueryDefaults(queryKey, { staleTime: Infinity });\n }\n },\n read: () =>\n parseJson(JSON.stringify(\n client.getQueryCache().getAll().flatMap((query) =>\n query.state.data === undefined ? [] : [{\n queryKey: query.queryKey,\n data: query.state.data,\n // A refresh in flight is part of what's on screen (\"Refreshing…\"), so capture it.\n stale: query.state.fetchStatus === 'fetching' || undefined,\n }]\n ),\n )),\n subscribe: (onChange) => client.getQueryCache().subscribe(onChange),\n});\n"],"mappings":";;;;;;AACA,SAAS,SAAS;AAIX,IAAM,qBAAqB,EAAE;AAAA,EAClC,EAAE,OAAO;AAAA,IACP,UAAU,EAAE,MAAM,UAAU,EAAE,IAAI,CAAC;AAAA,IACnC,MAAM;AAAA;AAAA,IAEN,OAAO,EAAE,QAAQ,EAAE,SAAS;AAAA,EAC9B,CAAC;AACH;AAUO,IAAM,gBAAgB,CAAC,YAAmD;AAAA,EAC/E,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM,MAAM,SAAS;AACnB,eAAW,EAAE,UAAU,MAAM,MAAM,KAAK,SAAS;AAE/C,UAAI,CAAC,MAAO,OAAM,OAAO,cAAc,EAAE,UAAU,OAAO,KAAK,CAAC;AAChE,aAAO,aAAa,UAAU,IAAI;AAGlC,UAAI,MAAO,MAAK,OAAO,kBAAkB,EAAE,UAAU,OAAO,MAAM,aAAa,OAAO,CAAC;AAAA,UAClF,QAAO,iBAAiB,UAAU,EAAE,WAAW,SAAS,CAAC;AAAA,IAChE;AAAA,EACF;AAAA,EACA,MAAM,MACJ,UAAU,KAAK;AAAA,IACb,OAAO,cAAc,EAAE,OAAO,EAAE;AAAA,MAAQ,CAAC,UACvC,MAAM,MAAM,SAAS,SAAY,CAAC,IAAI,CAAC;AAAA,QACrC,UAAU,MAAM;AAAA,QAChB,MAAM,MAAM,MAAM;AAAA;AAAA,QAElB,OAAO,MAAM,MAAM,gBAAgB,cAAc;AAAA,MACnD,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAAA,EACH,WAAW,CAAC,aAAa,OAAO,cAAc,EAAE,UAAU,QAAQ;AACpE;","names":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { TypedAdapter } from '../runtime.js';
|
|
3
|
+
/** XState's state value: "loading", or { "checkout": "payment" } for nested states. */
|
|
4
|
+
type StateValue = string | {
|
|
5
|
+
[state: string]: StateValue;
|
|
6
|
+
};
|
|
7
|
+
export declare const xstateState: z.ZodObject<{
|
|
8
|
+
value: z.ZodType<StateValue, unknown, z.core.$ZodTypeInternals<StateValue, unknown>>;
|
|
9
|
+
context: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
interface Machine<Context, Snapshot> {
|
|
12
|
+
config: {
|
|
13
|
+
context?: unknown;
|
|
14
|
+
};
|
|
15
|
+
resolveState(config: {
|
|
16
|
+
value: StateValue;
|
|
17
|
+
context: Context;
|
|
18
|
+
}): Snapshot;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolves `state.xstate` into a snapshot. Start your actor from it:
|
|
22
|
+
* `createActor(machine, { snapshot: adapter.snapshot })`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function xstateAdapter<Context, Snapshot>(machine: Machine<Context, Snapshot>, { key, context: contextSchema }: {
|
|
25
|
+
key?: string;
|
|
26
|
+
context: z.ZodType<Context>;
|
|
27
|
+
}): TypedAdapter<{
|
|
28
|
+
value: StateValue;
|
|
29
|
+
context?: Record<string, z.JSONType> | undefined;
|
|
30
|
+
}> & {
|
|
31
|
+
snapshot?: Snapshot;
|
|
32
|
+
};
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deepMerge
|
|
3
|
+
} from "../chunk-JIHDABWO.js";
|
|
4
|
+
import {
|
|
5
|
+
JsonSchema
|
|
6
|
+
} from "../chunk-MLXYB7GV.js";
|
|
7
|
+
|
|
8
|
+
// src/adapters/xstate.ts
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
var StateValueSchema = z.lazy(
|
|
11
|
+
() => z.union([z.string(), z.record(z.string(), StateValueSchema)])
|
|
12
|
+
);
|
|
13
|
+
var xstateState = z.object({
|
|
14
|
+
value: StateValueSchema,
|
|
15
|
+
/** Merged over the machine's initial context, then decoded with the context schema. */
|
|
16
|
+
context: z.record(z.string(), JsonSchema).optional()
|
|
17
|
+
});
|
|
18
|
+
function xstateAdapter(machine, { key = "xstate", context: contextSchema }) {
|
|
19
|
+
const adapter = {
|
|
20
|
+
key,
|
|
21
|
+
schema: xstateState,
|
|
22
|
+
// The actor starts from the snapshot once, so a new one needs a reload.
|
|
23
|
+
inPlace: false,
|
|
24
|
+
apply({ value, context }) {
|
|
25
|
+
const initial = JsonSchema.safeParse(machine.config.context ?? {});
|
|
26
|
+
const merged = deepMerge(initial.success ? initial.data : {}, context ?? {});
|
|
27
|
+
adapter.snapshot = machine.resolveState({ value, context: contextSchema.parse(merged) });
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
return adapter;
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
xstateAdapter,
|
|
34
|
+
xstateState
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=xstate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/xstate.ts"],"sourcesContent":["import { z } from 'zod';\nimport { deepMerge } from '../catalog.js';\nimport type { TypedAdapter } from '../runtime.js';\nimport { JsonSchema } from '../schema.js';\n\n/** XState's state value: \"loading\", or { \"checkout\": \"payment\" } for nested states. */\ntype StateValue = string | { [state: string]: StateValue; };\n\nconst StateValueSchema: z.ZodType<StateValue> = z.lazy(() =>\n z.union([z.string(), z.record(z.string(), StateValueSchema)])\n);\n\nexport const xstateState = z.object({\n value: StateValueSchema,\n /** Merged over the machine's initial context, then decoded with the context schema. */\n context: z.record(z.string(), JsonSchema).optional(),\n});\n\ntype MachineState = z.output<typeof xstateState>;\n\ninterface Machine<Context, Snapshot> {\n config: { context?: unknown; };\n resolveState(config: { value: StateValue; context: Context; }): Snapshot;\n}\n\n/**\n * Resolves `state.xstate` into a snapshot. Start your actor from it:\n * `createActor(machine, { snapshot: adapter.snapshot })`.\n */\nexport function xstateAdapter<Context, Snapshot>(\n machine: Machine<Context, Snapshot>,\n { key = 'xstate', context: contextSchema }: { key?: string; context: z.ZodType<Context>; },\n) {\n const adapter: TypedAdapter<MachineState> & { snapshot?: Snapshot; } = {\n key,\n schema: xstateState,\n // The actor starts from the snapshot once, so a new one needs a reload.\n inPlace: false,\n apply({ value, context }) {\n // Context factories can't be merged into; only a plain JSON context is.\n const initial = JsonSchema.safeParse(machine.config.context ?? {});\n const merged = deepMerge(initial.success ? initial.data : {}, context ?? {});\n adapter.snapshot = machine.resolveState({ value, context: contextSchema.parse(merged) });\n },\n };\n\n return adapter;\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,SAAS;AAQlB,IAAM,mBAA0C,EAAE;AAAA,EAAK,MACrD,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAAC,CAAC;AAC9D;AAEO,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,OAAO;AAAA;AAAA,EAEP,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,UAAU,EAAE,SAAS;AACrD,CAAC;AAaM,SAAS,cACd,SACA,EAAE,MAAM,UAAU,SAAS,cAAc,GACzC;AACA,QAAM,UAAiE;AAAA,IACrE;AAAA,IACA,QAAQ;AAAA;AAAA,IAER,SAAS;AAAA,IACT,MAAM,EAAE,OAAO,QAAQ,GAAG;AAExB,YAAM,UAAU,WAAW,UAAU,QAAQ,OAAO,WAAW,CAAC,CAAC;AACjE,YAAM,SAAS,UAAU,QAAQ,UAAU,QAAQ,OAAO,CAAC,GAAG,WAAW,CAAC,CAAC;AAC3E,cAAQ,WAAW,QAAQ,aAAa,EAAE,OAAO,SAAS,cAAc,MAAM,MAAM,EAAE,CAAC;AAAA,IACzF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { TypedAdapter } from '../runtime.js';
|
|
3
|
+
import { type Serializable } from '../schema.js';
|
|
4
|
+
interface Store<T> {
|
|
5
|
+
setState(partial: Partial<T>): void;
|
|
6
|
+
getState(): T;
|
|
7
|
+
subscribe(listener: () => void): () => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Connects a Zustand store as `state.zustand` (or your key). The schema decodes scenario
|
|
11
|
+
* JSON into the slice of state to merge, e.g. `StoreSchema.partial()`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const zustandAdapter: <T extends Serializable>(store: Store<T>, { key, schema }: {
|
|
14
|
+
key?: string;
|
|
15
|
+
schema: z.ZodType<Partial<T>>;
|
|
16
|
+
}) => TypedAdapter<Partial<T>>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseJson
|
|
3
|
+
} from "../chunk-MLXYB7GV.js";
|
|
4
|
+
|
|
5
|
+
// src/adapters/zustand.ts
|
|
6
|
+
var zustandAdapter = (store, { key = "zustand", schema }) => ({
|
|
7
|
+
key,
|
|
8
|
+
schema,
|
|
9
|
+
apply: (state) => store.setState(state),
|
|
10
|
+
read: () => parseJson(JSON.stringify(store.getState())),
|
|
11
|
+
subscribe: (onChange) => store.subscribe(onChange)
|
|
12
|
+
});
|
|
13
|
+
export {
|
|
14
|
+
zustandAdapter
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=zustand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/zustand.ts"],"sourcesContent":["import type { z } from 'zod';\nimport type { TypedAdapter } from '../runtime.js';\nimport { parseJson, type Serializable } from '../schema.js';\n\ninterface Store<T> {\n setState(partial: Partial<T>): void;\n getState(): T;\n subscribe(listener: () => void): () => void;\n}\n\n/**\n * Connects a Zustand store as `state.zustand` (or your key). The schema decodes scenario\n * JSON into the slice of state to merge, e.g. `StoreSchema.partial()`.\n */\nexport const zustandAdapter = <T extends Serializable>(\n store: Store<T>,\n { key = 'zustand', schema }: { key?: string; schema: z.ZodType<Partial<T>>; },\n): TypedAdapter<Partial<T>> => ({\n key,\n schema,\n apply: (state) => store.setState(state),\n read: () => parseJson(JSON.stringify(store.getState())),\n subscribe: (onChange) => store.subscribe(onChange),\n});\n"],"mappings":";;;;;AAcO,IAAM,iBAAiB,CAC5B,OACA,EAAE,MAAM,WAAW,OAAO,OACI;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,OAAO,CAAC,UAAU,MAAM,SAAS,KAAK;AAAA,EACtC,MAAM,MAAM,UAAU,KAAK,UAAU,MAAM,SAAS,CAAC,CAAC;AAAA,EACtD,WAAW,CAAC,aAAa,MAAM,UAAU,QAAQ;AACnD;","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Json, type Scenario, type ScenarioInput } from './schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* A business rule a resolved scenario must satisfy, beyond schema shape.
|
|
4
|
+
* Return a message (or several) for each violation.
|
|
5
|
+
*/
|
|
6
|
+
export type ScenarioRule = (scenario: Scenario) => string | string[] | void | undefined;
|
|
7
|
+
/** Identity helper for typing rule lists. */
|
|
8
|
+
export declare const defineScenarioRules: (rules: ScenarioRule[]) => ScenarioRule[];
|
|
9
|
+
export interface Catalog {
|
|
10
|
+
list: Scenario[];
|
|
11
|
+
get(name: string): Scenario | undefined;
|
|
12
|
+
/** Flatten `extends` into one self-contained scenario, then check the rules. */
|
|
13
|
+
resolve(scenario: Scenario): Scenario;
|
|
14
|
+
/** Resolve every scenario; returns one message per problem. */
|
|
15
|
+
check(): string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function createCatalog(raw: ScenarioInput[], { rules }?: {
|
|
18
|
+
rules?: ScenarioRule[];
|
|
19
|
+
}): Catalog;
|
|
20
|
+
/** Objects merge recursively, `null` deletes a key, anything else replaces. */
|
|
21
|
+
export declare function deepMerge(base: Json | undefined, over: Json): Json;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseScenario
|
|
3
|
+
} from "./chunk-MLXYB7GV.js";
|
|
4
|
+
|
|
5
|
+
// src/catalog.ts
|
|
6
|
+
var defineScenarioRules = (rules) => rules;
|
|
7
|
+
function createCatalog(raw, { rules = [] } = {}) {
|
|
8
|
+
const byName = /* @__PURE__ */ new Map();
|
|
9
|
+
for (const [i, data] of raw.entries()) {
|
|
10
|
+
const s = parseScenario(data, `#${i}`);
|
|
11
|
+
if (s.name.includes(",")) throw new Error(`Scenario name "${s.name}" can't contain a comma`);
|
|
12
|
+
if (byName.has(s.name)) throw new Error(`Duplicate scenario name "${s.name}"`);
|
|
13
|
+
byName.set(s.name, s);
|
|
14
|
+
}
|
|
15
|
+
const flatten = (s, seen) => {
|
|
16
|
+
if (seen.includes(s.name)) {
|
|
17
|
+
throw new Error(`Circular extends: ${[...seen, s.name].join(" -> ")}`);
|
|
18
|
+
}
|
|
19
|
+
const parents = [s.extends ?? []].flat().map((name) => {
|
|
20
|
+
const parent = byName.get(name);
|
|
21
|
+
if (!parent) throw new Error(`Scenario "${s.name}" extends unknown "${name}"`);
|
|
22
|
+
return flatten(parent, [...seen, s.name]);
|
|
23
|
+
});
|
|
24
|
+
const { extends: _extends, $schema: _schema, ...own } = s;
|
|
25
|
+
return [...parents, own].reduce(merge, { name: own.name });
|
|
26
|
+
};
|
|
27
|
+
const resolve = (s) => {
|
|
28
|
+
const flat = flatten(s, []);
|
|
29
|
+
const violations = rules.flatMap((rule) => rule(flat) ?? []);
|
|
30
|
+
if (violations.length) {
|
|
31
|
+
throw new Error(`Scenario "${s.name}" breaks rules:
|
|
32
|
+
${violations.map((m) => `- ${m}`).join("\n")}`);
|
|
33
|
+
}
|
|
34
|
+
return flat;
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
list: [...byName.values()],
|
|
38
|
+
get: (name) => byName.get(name),
|
|
39
|
+
resolve,
|
|
40
|
+
check: () => [...byName.values()].flatMap((s) => {
|
|
41
|
+
try {
|
|
42
|
+
resolve(s);
|
|
43
|
+
return [];
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return [err instanceof Error ? err.message : String(err)];
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
var byKey = ([a], [b]) => a.localeCompare(b);
|
|
51
|
+
var routeKey = (e) => `${e.method} ${e.path} ${JSON.stringify(Object.entries(e.query ?? {}).toSorted(byKey))}`;
|
|
52
|
+
function merge(base, over) {
|
|
53
|
+
const overKeys = new Set((over.network ?? []).map(routeKey));
|
|
54
|
+
return {
|
|
55
|
+
...base,
|
|
56
|
+
...over,
|
|
57
|
+
// Own-only fields: a child describes and finishes rendering differently.
|
|
58
|
+
description: over.description,
|
|
59
|
+
tags: over.tags,
|
|
60
|
+
ready: over.ready,
|
|
61
|
+
// A null param removes the inherited one.
|
|
62
|
+
url: Object.fromEntries(Object.entries({ ...base.url, ...over.url }).filter(([, v]) => v !== null)),
|
|
63
|
+
network: [
|
|
64
|
+
...(over.network ?? []).filter((e) => !e.remove),
|
|
65
|
+
...(base.network ?? []).filter((e) => !overKeys.has(routeKey(e)))
|
|
66
|
+
],
|
|
67
|
+
state: mergeObjects(base.state ?? {}, over.state ?? {})
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
var isJsonObject = (value) => value instanceof Object && !Array.isArray(value);
|
|
71
|
+
function mergeObjects(base, over) {
|
|
72
|
+
const out = { ...base };
|
|
73
|
+
for (const [k, v] of Object.entries(over)) {
|
|
74
|
+
if (v === null) delete out[k];
|
|
75
|
+
else out[k] = deepMerge(base[k], v);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
function deepMerge(base, over) {
|
|
80
|
+
return isJsonObject(base) && isJsonObject(over) ? mergeObjects(base, over) : over;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
defineScenarioRules,
|
|
85
|
+
createCatalog,
|
|
86
|
+
deepMerge
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=chunk-JIHDABWO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["import {\n type Json,\n type JsonObject,\n type NetworkEntry,\n parseScenario,\n type Scenario,\n type ScenarioInput,\n} from './schema.js';\n\n/**\n * A business rule a resolved scenario must satisfy, beyond schema shape.\n * Return a message (or several) for each violation.\n */\nexport type ScenarioRule = (scenario: Scenario) => string | string[] | void | undefined;\n\n/** Identity helper for typing rule lists. */\nexport const defineScenarioRules = (rules: ScenarioRule[]) => rules;\n\nexport interface Catalog {\n list: Scenario[];\n get(name: string): Scenario | undefined;\n /** Flatten `extends` into one self-contained scenario, then check the rules. */\n resolve(scenario: Scenario): Scenario;\n /** Resolve every scenario; returns one message per problem. */\n check(): string[];\n}\n\nexport function createCatalog(\n raw: ScenarioInput[],\n { rules = [] }: { rules?: ScenarioRule[]; } = {},\n): Catalog {\n const byName = new Map<string, Scenario>();\n\n for (const [i, data] of raw.entries()) {\n const s = parseScenario(data, `#${i}`);\n\n // Commas join names in ?scenario=a,b, so a catalog name can't contain one.\n if (s.name.includes(',')) throw new Error(`Scenario name \"${s.name}\" can't contain a comma`);\n\n if (byName.has(s.name)) throw new Error(`Duplicate scenario name \"${s.name}\"`);\n byName.set(s.name, s);\n }\n\n const flatten = (s: Scenario, seen: string[]): Scenario => {\n if (seen.includes(s.name)) {\n throw new Error(`Circular extends: ${[...seen, s.name].join(' -> ')}`);\n }\n\n const parents = [s.extends ?? []].flat().map((name) => {\n const parent = byName.get(name);\n\n if (!parent) throw new Error(`Scenario \"${s.name}\" extends unknown \"${name}\"`);\n\n return flatten(parent, [...seen, s.name]);\n });\n\n const { extends: _extends, $schema: _schema, ...own } = s;\n\n // Start from an empty base so standalone scenarios are normalised too (remove entries, nulls).\n return [...parents, own].reduce(merge, { name: own.name });\n };\n\n const resolve = (s: Scenario) => {\n const flat = flatten(s, []);\n const violations = rules.flatMap((rule) => rule(flat) ?? []);\n\n if (violations.length) {\n throw new Error(`Scenario \"${s.name}\" breaks rules:\\n${violations.map((m) => `- ${m}`).join('\\n')}`);\n }\n\n return flat;\n };\n\n return {\n list: [...byName.values()],\n get: (name) => byName.get(name),\n resolve,\n check: () =>\n [...byName.values()].flatMap((s) => {\n try {\n resolve(s);\n\n return [];\n } catch (err) {\n return [err instanceof Error ? err.message : String(err)];\n }\n }),\n };\n}\n\nconst byKey = ([a]: [string, unknown], [b]: [string, unknown]) => a.localeCompare(b);\n\nconst routeKey = (e: NetworkEntry) =>\n `${e.method} ${e.path} ${JSON.stringify(Object.entries(e.query ?? {}).toSorted(byKey))}`;\n\nfunction merge(base: Scenario, over: Scenario): Scenario {\n const overKeys = new Set((over.network ?? []).map(routeKey));\n\n return {\n ...base,\n ...over,\n // Own-only fields: a child describes and finishes rendering differently.\n description: over.description,\n tags: over.tags,\n ready: over.ready,\n // A null param removes the inherited one.\n url: Object.fromEntries(Object.entries({ ...base.url, ...over.url }).filter(([, v]) => v !== null)),\n network: [\n ...(over.network ?? []).filter((e) => !e.remove),\n ...(base.network ?? []).filter((e) => !overKeys.has(routeKey(e))),\n ],\n state: mergeObjects(base.state ?? {}, over.state ?? {}),\n };\n}\n\nconst isJsonObject = (value: Json | undefined): value is JsonObject =>\n value instanceof Object && !Array.isArray(value);\n\nfunction mergeObjects(base: JsonObject, over: JsonObject): JsonObject {\n const out = { ...base };\n\n for (const [k, v] of Object.entries(over)) {\n if (v === null) delete out[k];\n else out[k] = deepMerge(base[k], v);\n }\n\n return out;\n}\n\n/** Objects merge recursively, `null` deletes a key, anything else replaces. */\nexport function deepMerge(base: Json | undefined, over: Json): Json {\n return isJsonObject(base) && isJsonObject(over) ? mergeObjects(base, over) : over;\n}\n"],"mappings":";;;;;AAgBO,IAAM,sBAAsB,CAAC,UAA0B;AAWvD,SAAS,cACd,KACA,EAAE,QAAQ,CAAC,EAAE,IAAiC,CAAC,GACtC;AACT,QAAM,SAAS,oBAAI,IAAsB;AAEzC,aAAW,CAAC,GAAG,IAAI,KAAK,IAAI,QAAQ,GAAG;AACrC,UAAM,IAAI,cAAc,MAAM,IAAI,CAAC,EAAE;AAGrC,QAAI,EAAE,KAAK,SAAS,GAAG,EAAG,OAAM,IAAI,MAAM,kBAAkB,EAAE,IAAI,yBAAyB;AAE3F,QAAI,OAAO,IAAI,EAAE,IAAI,EAAG,OAAM,IAAI,MAAM,4BAA4B,EAAE,IAAI,GAAG;AAC7E,WAAO,IAAI,EAAE,MAAM,CAAC;AAAA,EACtB;AAEA,QAAM,UAAU,CAAC,GAAa,SAA6B;AACzD,QAAI,KAAK,SAAS,EAAE,IAAI,GAAG;AACzB,YAAM,IAAI,MAAM,qBAAqB,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,KAAK,MAAM,CAAC,EAAE;AAAA,IACvE;AAEA,UAAM,UAAU,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS;AACrD,YAAM,SAAS,OAAO,IAAI,IAAI;AAE9B,UAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,aAAa,EAAE,IAAI,sBAAsB,IAAI,GAAG;AAE7E,aAAO,QAAQ,QAAQ,CAAC,GAAG,MAAM,EAAE,IAAI,CAAC;AAAA,IAC1C,CAAC;AAED,UAAM,EAAE,SAAS,UAAU,SAAS,SAAS,GAAG,IAAI,IAAI;AAGxD,WAAO,CAAC,GAAG,SAAS,GAAG,EAAE,OAAO,OAAO,EAAE,MAAM,IAAI,KAAK,CAAC;AAAA,EAC3D;AAEA,QAAM,UAAU,CAAC,MAAgB;AAC/B,UAAM,OAAO,QAAQ,GAAG,CAAC,CAAC;AAC1B,UAAM,aAAa,MAAM,QAAQ,CAAC,SAAS,KAAK,IAAI,KAAK,CAAC,CAAC;AAE3D,QAAI,WAAW,QAAQ;AACrB,YAAM,IAAI,MAAM,aAAa,EAAE,IAAI;AAAA,EAAoB,WAAW,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,IACrG;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM,CAAC,GAAG,OAAO,OAAO,CAAC;AAAA,IACzB,KAAK,CAAC,SAAS,OAAO,IAAI,IAAI;AAAA,IAC9B;AAAA,IACA,OAAO,MACL,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM;AAClC,UAAI;AACF,gBAAQ,CAAC;AAET,eAAO,CAAC;AAAA,MACV,SAAS,KAAK;AACZ,eAAO,CAAC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MAC1D;AAAA,IACF,CAAC;AAAA,EACL;AACF;AAEA,IAAM,QAAQ,CAAC,CAAC,CAAC,GAAsB,CAAC,CAAC,MAAyB,EAAE,cAAc,CAAC;AAEnF,IAAM,WAAW,CAAC,MAChB,GAAG,EAAE,MAAM,IAAI,EAAE,IAAI,IAAI,KAAK,UAAU,OAAO,QAAQ,EAAE,SAAS,CAAC,CAAC,EAAE,SAAS,KAAK,CAAC,CAAC;AAExF,SAAS,MAAM,MAAgB,MAA0B;AACvD,QAAM,WAAW,IAAI,KAAK,KAAK,WAAW,CAAC,GAAG,IAAI,QAAQ,CAAC;AAE3D,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA;AAAA,IAEH,aAAa,KAAK;AAAA,IAClB,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA;AAAA,IAEZ,KAAK,OAAO,YAAY,OAAO,QAAQ,EAAE,GAAG,KAAK,KAAK,GAAG,KAAK,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,MAAM,IAAI,CAAC;AAAA,IAClG,SAAS;AAAA,MACP,IAAI,KAAK,WAAW,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM;AAAA,MAC/C,IAAI,KAAK,WAAW,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC;AAAA,IAClE;AAAA,IACA,OAAO,aAAa,KAAK,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC;AAAA,EACxD;AACF;AAEA,IAAM,eAAe,CAAC,UACpB,iBAAiB,UAAU,CAAC,MAAM,QAAQ,KAAK;AAEjD,SAAS,aAAa,MAAkB,MAA8B;AACpE,QAAM,MAAM,EAAE,GAAG,KAAK;AAEtB,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI,GAAG;AACzC,QAAI,MAAM,KAAM,QAAO,IAAI,CAAC;AAAA,QACvB,KAAI,CAAC,IAAI,UAAU,KAAK,CAAC,GAAG,CAAC;AAAA,EACpC;AAEA,SAAO;AACT;AAGO,SAAS,UAAU,MAAwB,MAAkB;AAClE,SAAO,aAAa,IAAI,KAAK,aAAa,IAAI,IAAI,aAAa,MAAM,IAAI,IAAI;AAC/E;","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/link.ts
|
|
2
|
+
var PARAM = "scenario";
|
|
3
|
+
var STRICT_PARAM = "scenario-strict";
|
|
4
|
+
var DATA_KEY = "scenario-data";
|
|
5
|
+
function scenarioHref(resolved, { base = location.href, inline = false, strict = false } = {}) {
|
|
6
|
+
const url = new URL(base);
|
|
7
|
+
url.search = "";
|
|
8
|
+
url.hash = "";
|
|
9
|
+
for (const [k, v] of Object.entries(resolved.url ?? {})) {
|
|
10
|
+
if (v !== null) url.searchParams.set(k, v);
|
|
11
|
+
}
|
|
12
|
+
if (inline) {
|
|
13
|
+
const { $schema: _schema, extends: _extends, ...snapshot } = resolved;
|
|
14
|
+
url.hash = new URLSearchParams({ [DATA_KEY]: JSON.stringify(snapshot) }).toString();
|
|
15
|
+
} else {
|
|
16
|
+
url.searchParams.set(PARAM, resolved.name);
|
|
17
|
+
}
|
|
18
|
+
if (strict) url.searchParams.set(STRICT_PARAM, "");
|
|
19
|
+
return url.toString();
|
|
20
|
+
}
|
|
21
|
+
var readInline = (hash) => new URLSearchParams(hash.slice(1)).get(DATA_KEY);
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
PARAM,
|
|
25
|
+
STRICT_PARAM,
|
|
26
|
+
DATA_KEY,
|
|
27
|
+
scenarioHref,
|
|
28
|
+
readInline
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=chunk-M5Q4I3KT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/link.ts"],"sourcesContent":["import type { Scenario } from './schema.js';\n\ndeclare global {\n /** Requests the active scenario didn't cover, for tests and agents (see `unhandledRequests`). */\n var stateScenariosUnhandled: string[] | undefined;\n}\n\nexport const PARAM = 'scenario';\n\n/** Fail unmatched requests instead of letting them reach the network. */\nexport const STRICT_PARAM = 'scenario-strict';\n\n/** Inline scenario JSON, kept in the URL hash so it never reaches a server. */\nexport const DATA_KEY = 'scenario-data';\n\n/**\n * A shareable URL that opens the app in a resolved scenario.\n * `inline` embeds the flattened scenario, so the link works without the catalog.\n */\nexport function scenarioHref(\n resolved: Scenario,\n { base = location.href, inline = false, strict = false } = {},\n): string {\n const url = new URL(base);\n url.search = '';\n url.hash = '';\n\n for (const [k, v] of Object.entries(resolved.url ?? {})) {\n if (v !== null) url.searchParams.set(k, v);\n }\n\n if (inline) {\n const { $schema: _schema, extends: _extends, ...snapshot } = resolved;\n url.hash = new URLSearchParams({ [DATA_KEY]: JSON.stringify(snapshot) }).toString();\n } else {\n url.searchParams.set(PARAM, resolved.name);\n }\n\n if (strict) url.searchParams.set(STRICT_PARAM, '');\n\n return url.toString();\n}\n\n/** Inline scenario JSON from a URL hash, if present. */\nexport const readInline = (hash: string) => new URLSearchParams(hash.slice(1)).get(DATA_KEY);\n"],"mappings":";AAOO,IAAM,QAAQ;AAGd,IAAM,eAAe;AAGrB,IAAM,WAAW;AAMjB,SAAS,aACd,UACA,EAAE,OAAO,SAAS,MAAM,SAAS,OAAO,SAAS,MAAM,IAAI,CAAC,GACpD;AACR,QAAM,MAAM,IAAI,IAAI,IAAI;AACxB,MAAI,SAAS;AACb,MAAI,OAAO;AAEX,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,SAAS,OAAO,CAAC,CAAC,GAAG;AACvD,QAAI,MAAM,KAAM,KAAI,aAAa,IAAI,GAAG,CAAC;AAAA,EAC3C;AAEA,MAAI,QAAQ;AACV,UAAM,EAAE,SAAS,SAAS,SAAS,UAAU,GAAG,SAAS,IAAI;AAC7D,QAAI,OAAO,IAAI,gBAAgB,EAAE,CAAC,QAAQ,GAAG,KAAK,UAAU,QAAQ,EAAE,CAAC,EAAE,SAAS;AAAA,EACpF,OAAO;AACL,QAAI,aAAa,IAAI,OAAO,SAAS,IAAI;AAAA,EAC3C;AAEA,MAAI,OAAQ,KAAI,aAAa,IAAI,cAAc,EAAE;AAEjD,SAAO,IAAI,SAAS;AACtB;AAGO,IAAM,aAAa,CAAC,SAAiB,IAAI,gBAAgB,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,QAAQ;","names":[]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// src/schema.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var JsonSchema = z.json();
|
|
4
|
+
var parseJson = (text) => JsonSchema.parse(JSON.parse(text));
|
|
5
|
+
var ResponseSchema = z.object({
|
|
6
|
+
status: z.number().int().default(200),
|
|
7
|
+
/** Sent as JSON. */
|
|
8
|
+
body: JsonSchema.optional(),
|
|
9
|
+
/** Sent as text, e.g. an HTML error page. */
|
|
10
|
+
text: z.string().optional(),
|
|
11
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
12
|
+
/** Milliseconds, or "infinite" to hold the request open (loading states). */
|
|
13
|
+
delay: z.union([z.number().nonnegative(), z.literal("infinite")]).optional()
|
|
14
|
+
}).refine((r) => r.body === void 0 || r.text === void 0, {
|
|
15
|
+
message: 'Provide "body" (JSON) or "text", not both'
|
|
16
|
+
});
|
|
17
|
+
var NetworkEntrySchema = z.object({
|
|
18
|
+
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]).default("GET"),
|
|
19
|
+
/** MSW path syntax: "/api/users/:id", absolute URLs and wildcards work. */
|
|
20
|
+
path: z.string().min(1),
|
|
21
|
+
/** Only match requests whose query string contains these values. */
|
|
22
|
+
query: z.record(z.string(), z.string()).optional(),
|
|
23
|
+
response: ResponseSchema.optional(),
|
|
24
|
+
/** Successive responses for successive calls; the last one repeats. */
|
|
25
|
+
sequence: z.array(ResponseSchema).min(1).optional(),
|
|
26
|
+
/** Drop the inherited entry with the same method, path and query. */
|
|
27
|
+
remove: z.literal(true).optional()
|
|
28
|
+
}).refine((e) => [e.response, e.sequence, e.remove].filter(Boolean).length === 1, {
|
|
29
|
+
message: 'Provide exactly one of "response", "sequence" or "remove"'
|
|
30
|
+
});
|
|
31
|
+
var ScenarioSchema = z.object({
|
|
32
|
+
$schema: z.string().optional(),
|
|
33
|
+
// Kebab-case; a combined scenario is named by its parts, e.g. "signed-in,server-error".
|
|
34
|
+
name: z.string().regex(/^[a-z0-9][a-z0-9-]*(,[a-z0-9][a-z0-9-]*)*$/, 'Use kebab-case, e.g. "empty-search"'),
|
|
35
|
+
/** Not inherited. */
|
|
36
|
+
description: z.string().optional(),
|
|
37
|
+
/** Not inherited. */
|
|
38
|
+
tags: z.array(z.string()).optional(),
|
|
39
|
+
/** Scenario name(s) to build on. Later parents override earlier ones. */
|
|
40
|
+
extends: z.union([z.string(), z.array(z.string())]).optional(),
|
|
41
|
+
/** Query params the app should start with. null removes an inherited param. */
|
|
42
|
+
url: z.record(z.string(), z.string().nullable()).optional(),
|
|
43
|
+
/**
|
|
44
|
+
* A child entry replaces the inherited entry with the same method, path and query.
|
|
45
|
+
* Otherwise earlier entries win, and a scenario's own entries come before its parents'.
|
|
46
|
+
*/
|
|
47
|
+
network: z.array(NetworkEntrySchema).optional(),
|
|
48
|
+
/** Per-adapter state, keyed by adapter key. Objects deep-merge; null removes a key. */
|
|
49
|
+
state: z.record(z.string(), JsonSchema).optional(),
|
|
50
|
+
/**
|
|
51
|
+
* Playwright selector that is visible once the UI shows this state,
|
|
52
|
+
* e.g. "text=Loading countries…". Not inherited: each state looks different.
|
|
53
|
+
*/
|
|
54
|
+
ready: z.string().optional()
|
|
55
|
+
});
|
|
56
|
+
function scenarioJsonSchema({ state = {} } = {}) {
|
|
57
|
+
return z.toJSONSchema(
|
|
58
|
+
ScenarioSchema.extend({ state: z.object(state).partial().catchall(JsonSchema).optional() }),
|
|
59
|
+
{
|
|
60
|
+
io: "input",
|
|
61
|
+
// Refinements aren't representable in JSON Schema; spell the exclusivity rules out.
|
|
62
|
+
override: ({ zodSchema, jsonSchema }) => {
|
|
63
|
+
if (zodSchema === NetworkEntrySchema) {
|
|
64
|
+
jsonSchema.oneOf = ["response", "sequence", "remove"].map((key) => ({ required: [key] }));
|
|
65
|
+
}
|
|
66
|
+
if (zodSchema === ResponseSchema) {
|
|
67
|
+
jsonSchema.not = { required: ["body", "text"] };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
function parseScenario(data, source = "scenario") {
|
|
74
|
+
const result = ScenarioSchema.safeParse(data);
|
|
75
|
+
if (result.success) return result.data;
|
|
76
|
+
throw new Error(`Invalid scenario "${data.name || source}":
|
|
77
|
+
${z.prettifyError(result.error)}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
JsonSchema,
|
|
82
|
+
parseJson,
|
|
83
|
+
ScenarioSchema,
|
|
84
|
+
scenarioJsonSchema,
|
|
85
|
+
parseScenario
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=chunk-MLXYB7GV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema.ts"],"sourcesContent":["import { z } from 'zod';\n\n/** Scenarios live in JSON files and URLs, so everything they carry is JSON. */\nexport const JsonSchema = z.json();\n\nexport type Json = z.output<typeof JsonSchema>;\n\nexport type JsonObject = { [key: string]: Json; };\n\n/** Anything an app can hand over to be stored as JSON. */\nexport type Serializable = object | string | number | boolean | null;\n\n/**\n * Decode JSON text. Pair with `JSON.stringify` to copy app state into the format scenarios\n * are stored in: functions and undefined drop out, and the result is checked to be JSON.\n */\nexport const parseJson = (text: string): Json => JsonSchema.parse(JSON.parse(text));\n\nconst ResponseSchema = z\n .object({\n status: z.number().int().default(200),\n /** Sent as JSON. */\n body: JsonSchema.optional(),\n /** Sent as text, e.g. an HTML error page. */\n text: z.string().optional(),\n headers: z.record(z.string(), z.string()).optional(),\n /** Milliseconds, or \"infinite\" to hold the request open (loading states). */\n delay: z.union([z.number().nonnegative(), z.literal('infinite')]).optional(),\n })\n .refine((r) => r.body === undefined || r.text === undefined, {\n message: 'Provide \"body\" (JSON) or \"text\", not both',\n });\n\nconst NetworkEntrySchema = z\n .object({\n method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']).default('GET'),\n /** MSW path syntax: \"/api/users/:id\", absolute URLs and wildcards work. */\n path: z.string().min(1),\n /** Only match requests whose query string contains these values. */\n query: z.record(z.string(), z.string()).optional(),\n response: ResponseSchema.optional(),\n /** Successive responses for successive calls; the last one repeats. */\n sequence: z.array(ResponseSchema).min(1).optional(),\n /** Drop the inherited entry with the same method, path and query. */\n remove: z.literal(true).optional(),\n })\n .refine((e) => [e.response, e.sequence, e.remove].filter(Boolean).length === 1, {\n message: 'Provide exactly one of \"response\", \"sequence\" or \"remove\"',\n });\n\nexport const ScenarioSchema = z.object({\n $schema: z.string().optional(),\n // Kebab-case; a combined scenario is named by its parts, e.g. \"signed-in,server-error\".\n name: z.string().regex(/^[a-z0-9][a-z0-9-]*(,[a-z0-9][a-z0-9-]*)*$/, 'Use kebab-case, e.g. \"empty-search\"'),\n /** Not inherited. */\n description: z.string().optional(),\n /** Not inherited. */\n tags: z.array(z.string()).optional(),\n /** Scenario name(s) to build on. Later parents override earlier ones. */\n extends: z.union([z.string(), z.array(z.string())]).optional(),\n /** Query params the app should start with. null removes an inherited param. */\n url: z.record(z.string(), z.string().nullable()).optional(),\n /**\n * A child entry replaces the inherited entry with the same method, path and query.\n * Otherwise earlier entries win, and a scenario's own entries come before its parents'.\n */\n network: z.array(NetworkEntrySchema).optional(),\n /** Per-adapter state, keyed by adapter key. Objects deep-merge; null removes a key. */\n state: z.record(z.string(), JsonSchema).optional(),\n /**\n * Playwright selector that is visible once the UI shows this state,\n * e.g. \"text=Loading countries…\". Not inherited: each state looks different.\n */\n ready: z.string().optional(),\n});\n\nexport type Scenario = z.output<typeof ScenarioSchema>;\n\nexport type ScenarioInput = z.input<typeof ScenarioSchema>;\n\nexport type NetworkEntry = z.output<typeof NetworkEntrySchema>;\n\nexport type ScenarioResponse = z.output<typeof ResponseSchema>;\n\n/**\n * JSON Schema for editors, validators and AI generators.\n * Pass adapter state schemas so `state` is described too.\n */\nexport function scenarioJsonSchema({ state = {} }: { state?: Record<string, z.ZodType>; } = {}) {\n return z.toJSONSchema(\n ScenarioSchema.extend({ state: z.object(state).partial().catchall(JsonSchema).optional() }),\n {\n io: 'input',\n // Refinements aren't representable in JSON Schema; spell the exclusivity rules out.\n override: ({ zodSchema, jsonSchema }) => {\n if (zodSchema === NetworkEntrySchema) {\n jsonSchema.oneOf = ['response', 'sequence', 'remove'].map((key) => ({ required: [key] }));\n }\n\n if (zodSchema === ResponseSchema) {\n jsonSchema.not = { required: ['body', 'text'] };\n }\n },\n },\n );\n}\n\n/** Validate scenario input (from a file, URL or editor) into a Scenario, with readable errors. */\nexport function parseScenario(data: ScenarioInput, source = 'scenario'): Scenario {\n const result = ScenarioSchema.safeParse(data);\n\n if (result.success) return result.data;\n throw new Error(`Invalid scenario \"${data.name || source}\":\\n${z.prettifyError(result.error)}`);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/network.ts
|
|
2
|
+
import { delay, http, HttpResponse } from "msw";
|
|
3
|
+
var verbs = {
|
|
4
|
+
GET: http.get,
|
|
5
|
+
POST: http.post,
|
|
6
|
+
PUT: http.put,
|
|
7
|
+
PATCH: http.patch,
|
|
8
|
+
DELETE: http.delete,
|
|
9
|
+
HEAD: http.head,
|
|
10
|
+
OPTIONS: http.options
|
|
11
|
+
};
|
|
12
|
+
function toHandlers(entries = []) {
|
|
13
|
+
return entries.map((entry) => {
|
|
14
|
+
const responses = entry.sequence ?? [entry.response];
|
|
15
|
+
let calls = 0;
|
|
16
|
+
return verbs[entry.method](entry.path, async ({ request }) => {
|
|
17
|
+
if (entry.query) {
|
|
18
|
+
const params = new URL(request.url).searchParams;
|
|
19
|
+
const matches = Object.entries(entry.query).every(([k, v]) => params.get(k) === v);
|
|
20
|
+
if (!matches) return void 0;
|
|
21
|
+
}
|
|
22
|
+
const r = responses[Math.min(calls++, responses.length - 1)];
|
|
23
|
+
if (r.delay !== void 0) await delay(r.delay);
|
|
24
|
+
const init = { status: r.status, headers: r.headers };
|
|
25
|
+
if (r.text !== void 0) return HttpResponse.text(r.text, init);
|
|
26
|
+
if (r.body === void 0) return new HttpResponse(null, init);
|
|
27
|
+
return HttpResponse.json(r.body, init);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
toHandlers
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=chunk-U3I7ORTB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/network.ts"],"sourcesContent":["import { delay, http, HttpResponse } from 'msw';\nimport type { NetworkEntry } from './schema.js';\n\nconst verbs = {\n GET: http.get,\n POST: http.post,\n PUT: http.put,\n PATCH: http.patch,\n DELETE: http.delete,\n HEAD: http.head,\n OPTIONS: http.options,\n} satisfies Record<NetworkEntry['method'], typeof http.get>;\n\n/** Turn scenario network entries into MSW handlers. First match wins. */\nexport function toHandlers(entries: NetworkEntry[] = []) {\n return entries.map((entry) => {\n const responses = entry.sequence ?? [entry.response!];\n let calls = 0;\n\n return verbs[entry.method](entry.path, async ({ request }): Promise<Response | undefined> => {\n if (entry.query) {\n const params = new URL(request.url).searchParams;\n const matches = Object.entries(entry.query).every(([k, v]) => params.get(k) === v);\n\n if (!matches) return undefined; // fall through to the next handler\n }\n\n const r = responses[Math.min(calls++, responses.length - 1)];\n\n if (r.delay !== undefined) await delay(r.delay);\n const init = { status: r.status, headers: r.headers };\n\n if (r.text !== undefined) return HttpResponse.text(r.text, init);\n\n if (r.body === undefined) return new HttpResponse(null, init);\n\n return HttpResponse.json(r.body, init);\n });\n });\n}\n"],"mappings":";AAAA,SAAS,OAAO,MAAM,oBAAoB;AAG1C,IAAM,QAAQ;AAAA,EACZ,KAAK,KAAK;AAAA,EACV,MAAM,KAAK;AAAA,EACX,KAAK,KAAK;AAAA,EACV,OAAO,KAAK;AAAA,EACZ,QAAQ,KAAK;AAAA,EACb,MAAM,KAAK;AAAA,EACX,SAAS,KAAK;AAChB;AAGO,SAAS,WAAW,UAA0B,CAAC,GAAG;AACvD,SAAO,QAAQ,IAAI,CAAC,UAAU;AAC5B,UAAM,YAAY,MAAM,YAAY,CAAC,MAAM,QAAS;AACpD,QAAI,QAAQ;AAEZ,WAAO,MAAM,MAAM,MAAM,EAAE,MAAM,MAAM,OAAO,EAAE,QAAQ,MAAqC;AAC3F,UAAI,MAAM,OAAO;AACf,cAAM,SAAS,IAAI,IAAI,QAAQ,GAAG,EAAE;AACpC,cAAM,UAAU,OAAO,QAAQ,MAAM,KAAK,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,CAAC;AAEjF,YAAI,CAAC,QAAS,QAAO;AAAA,MACvB;AAEA,YAAM,IAAI,UAAU,KAAK,IAAI,SAAS,UAAU,SAAS,CAAC,CAAC;AAE3D,UAAI,EAAE,UAAU,OAAW,OAAM,MAAM,EAAE,KAAK;AAC9C,YAAM,OAAO,EAAE,QAAQ,EAAE,QAAQ,SAAS,EAAE,QAAQ;AAEpD,UAAI,EAAE,SAAS,OAAW,QAAO,aAAa,KAAK,EAAE,MAAM,IAAI;AAE/D,UAAI,EAAE,SAAS,OAAW,QAAO,IAAI,aAAa,MAAM,IAAI;AAE5D,aAAO,aAAa,KAAK,EAAE,MAAM,IAAI;AAAA,IACvC,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { type Catalog, createCatalog, deepMerge, defineScenarioRules, type ScenarioRule } from './catalog.js';
|
|
2
|
+
export { DATA_KEY, PARAM, readInline, scenarioHref, STRICT_PARAM } from './link.js';
|
|
3
|
+
export { toHandlers } from './network.js';
|
|
4
|
+
export { type Adapter, type Connection, currentSession, type JsonAdapter, type MswWorker, navigate, type ScenarioSession, type StartOptions, startScenarios, type TypedAdapter, } from './runtime.js';
|
|
5
|
+
export { type Json, type JsonObject, JsonSchema, type NetworkEntry, parseJson, parseScenario, type Scenario, type ScenarioInput, scenarioJsonSchema, ScenarioSchema, type Serializable, } from './schema.js';
|