@omniaura/scenario-sim 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.
@@ -0,0 +1,15 @@
1
+ import {
2
+ Simulator,
3
+ UPGRADED_HEADER,
4
+ isUpgraded,
5
+ matchPath,
6
+ upgradedResponse
7
+ } from "./chunk-KU4W4SKO.js";
8
+ export {
9
+ Simulator,
10
+ UPGRADED_HEADER,
11
+ isUpgraded,
12
+ matchPath,
13
+ upgradedResponse
14
+ };
15
+ //# sourceMappingURL=engine-HE7MEQHD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,38 @@
1
+ import { S as ScenarioDefinition } from './scenario--CvzkzX_.js';
2
+
3
+ /**
4
+ * Generic example world shipped with the package (also what the test-suite and
5
+ * consumer smoke test use). No product data — a notes CRUD API, a live notes
6
+ * feed over SSE, and a streaming-chat WebSocket protocol with per-topic event
7
+ * ids and resume. Scenario variants layer faults and timing on top.
8
+ *
9
+ * WebSocket protocol (`chat-v1` subprotocol), deliberately shaped like real
10
+ * chat backends so client reconnect logic gets exercised:
11
+ * client → { type:"subscribe", topic, sinceEventID? }
12
+ * → { type:"send", topic, clientMessageID, content }
13
+ * server → { type:"ready" } · { type:"subscribed", topic, replayed, last }
14
+ * → { type:"message", topic, eventID, id, role, content }
15
+ * → { type:"stream.start", streamID } · { type:"chat.content", streamID, seq, content }
16
+ * → { type:"stream.done", streamID } · { type:"resume.miss", topic } · { type:"error", error }
17
+ */
18
+
19
+ interface Note extends Record<string, unknown> {
20
+ id: string;
21
+ title: string;
22
+ body: string;
23
+ done: boolean;
24
+ createdAt: string;
25
+ updatedAt: string;
26
+ }
27
+ declare const NOTES_TOPIC = "notes";
28
+ declare const chatTopic: (room: string) => string;
29
+ declare const happy: ScenarioDefinition;
30
+ declare const empty: ScenarioDefinition;
31
+ declare const slow: ScenarioDefinition;
32
+ declare const flaky: ScenarioDefinition;
33
+ declare const malformedApi: ScenarioDefinition;
34
+ declare const streamDrop: ScenarioDefinition;
35
+ declare const manualClock: ScenarioDefinition;
36
+ declare const scenarios: ScenarioDefinition[];
37
+
38
+ export { NOTES_TOPIC, type Note, chatTopic, scenarios as default, empty, flaky, happy, malformedApi, manualClock, scenarios, slow, streamDrop };
@@ -0,0 +1,233 @@
1
+ import {
2
+ crud,
3
+ defineScenario,
4
+ json,
5
+ malformed,
6
+ problem,
7
+ route,
8
+ sequence,
9
+ sse,
10
+ ws
11
+ } from "./chunk-44ZHRYG6.js";
12
+
13
+ // src/examples/notes-chat.ts
14
+ var NOTES_TOPIC = "notes";
15
+ var chatTopic = (room) => `chat:${room}`;
16
+ var WORDS = ["alpha", "harbor", "signal", "quiet", "meadow", "copper", "lantern", "orbit", "velvet", "cinder"];
17
+ function baseRoutes() {
18
+ return [
19
+ route.get("/api/health", () => json({ ok: true })),
20
+ ...crud("/api/notes", "notes", {
21
+ sort: (a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id),
22
+ validate: (body) => body.title !== void 0 && typeof body.title !== "string" ? "title must be a string" : null,
23
+ create: (body, ctx) => ({
24
+ id: ctx.rng.id("note"),
25
+ title: String(body.title ?? "Untitled"),
26
+ body: String(body.body ?? ""),
27
+ done: Boolean(body.done ?? false),
28
+ createdAt: ctx.clock.iso(),
29
+ updatedAt: ctx.clock.iso()
30
+ }),
31
+ update: (cur, body, ctx) => ({ ...cur, ...body, id: cur.id, updatedAt: ctx.clock.iso() })
32
+ }),
33
+ // Bulk action with a side effect: marks everything done and emits one custom event.
34
+ route.post("/api/notes:complete-all", (ctx) => {
35
+ let n = 0;
36
+ for (const note of ctx.state.list("notes", { where: (x) => !x.done })) {
37
+ ctx.state.update("notes", note.id, { done: true, updatedAt: ctx.clock.iso() });
38
+ n++;
39
+ }
40
+ ctx.state.custom("notes", "completed-all", { count: n });
41
+ return json({ completed: n });
42
+ }),
43
+ route.get("/api/chat/rooms/:room/history", (ctx) => {
44
+ const topic = chatTopic(ctx.params.room);
45
+ const { events } = ctx.streams.topics.replay(topic, ctx.query.get("sinceEventID") ?? "0");
46
+ return json({ items: events.map((e) => ({ ...e.data, eventID: e.eventID, topic })) });
47
+ })
48
+ ];
49
+ }
50
+ function streams(opts = {}) {
51
+ const chunkDelay = opts.chunkDelayMs ?? 40;
52
+ return [
53
+ // Live notes feed: every store mutation on "notes" becomes an SSE event, resumable via Last-Event-ID.
54
+ sse("/api/notes/events", (ctx, stream) => {
55
+ stream.subscribe(NOTES_TOPIC);
56
+ stream.send({ type: "hello", run: ctx.run.id }, { event: "hello" });
57
+ }, { keepaliveMs: 15e3 }),
58
+ ws("/api/chat/ws", {
59
+ protocols: ["chat-v1"],
60
+ onOpen(ctx, socket) {
61
+ socket.send({ type: "ready", protocolVersion: 1, run: ctx.run.id });
62
+ },
63
+ onMessage(ctx, socket, raw) {
64
+ let frame;
65
+ try {
66
+ frame = JSON.parse(String(raw));
67
+ } catch {
68
+ return socket.send({ type: "error", error: "invalid json" });
69
+ }
70
+ if (frame.type === "subscribe" && frame.topic) {
71
+ const r = socket.subscribe(frame.topic, { since: frame.sinceEventID ?? null });
72
+ socket.send({ type: "subscribed", topic: frame.topic, replayed: r.replayed, last: r.last });
73
+ if (r.missed) socket.send({ type: "resume.miss", topic: frame.topic });
74
+ return;
75
+ }
76
+ if (frame.type === "send" && frame.topic && frame.content !== void 0) {
77
+ const id = ctx.rng.id("msg");
78
+ ctx.streams.publish(frame.topic, { type: "message", id, clientMessageID: frame.clientMessageID ?? "", role: "user", content: frame.content, createdAt: ctx.clock.iso() });
79
+ respond(ctx, socket, frame.topic, frame.content, chunkDelay, opts);
80
+ return;
81
+ }
82
+ socket.send({ type: "error", error: `unknown frame ${frame.type ?? "?"}` });
83
+ }
84
+ })
85
+ ];
86
+ function respond(ctx, socket, topic, prompt, delay, o) {
87
+ const streamID = ctx.rng.id("stream");
88
+ const words = prompt.split(/\s+/).filter(Boolean).slice(0, 12);
89
+ const reply = ["Echoing", ...words.map((w) => w.toUpperCase()), "\u2014", ctx.rng.pick(WORDS), ctx.rng.pick(WORDS)];
90
+ ctx.clock.after(o.streamDelayMs ?? 100, () => {
91
+ ctx.streams.publish(topic, { type: "stream.start", streamID });
92
+ reply.forEach((word, i) => {
93
+ ctx.clock.after(delay * (i + 1), () => {
94
+ if (o.dropMidStream && i === Math.floor(reply.length / 2)) {
95
+ ctx.streams.disconnect({ topic }, { drop: true });
96
+ ctx.log("dropped connection mid-stream", { streamID });
97
+ }
98
+ ctx.streams.publish(topic, { type: "chat.content", streamID, seq: i + 1, content: word + " " });
99
+ }, `chunk ${i + 1}`);
100
+ });
101
+ ctx.clock.after(delay * (reply.length + 1), () => {
102
+ const id = ctx.rng.id("msg");
103
+ ctx.streams.publish(topic, { type: "stream.done", streamID, messageID: id });
104
+ ctx.streams.publish(topic, { type: "message", id, role: "assistant", content: reply.join(" ").trim(), createdAt: ctx.clock.iso() });
105
+ }, "stream.done");
106
+ }, "stream.start");
107
+ }
108
+ }
109
+ function seedNotes(count) {
110
+ return ({ state, rng, clock, streams: streams2 }) => {
111
+ for (let i = 0; i < count; i++) {
112
+ const created = clock.iso(-(count - i) * 6e4);
113
+ state.insert("notes", { id: rng.id("note"), title: `${rng.pick(WORDS)} ${rng.pick(WORDS)}`, body: `Note ${i + 1}`, done: rng.chance(0.3), createdAt: created, updatedAt: created });
114
+ }
115
+ state.on((e) => {
116
+ if (e.collection !== "notes") return;
117
+ if (e.kind === "custom") streams2.publish(NOTES_TOPIC, { type: `notes.${e.name}`, ...e.data });
118
+ else streams2.publish(NOTES_TOPIC, { type: `notes.${e.kind}`, id: e.id, note: e.record ?? null, previous: e.previous ?? null });
119
+ });
120
+ };
121
+ }
122
+ var actions = {
123
+ /** Emit N synthetic note inserts quickly (list growth / scroll anchoring). */
124
+ burst: ({ state, rng, clock, args }) => {
125
+ const n = Number(args.count ?? 5);
126
+ for (let i = 0; i < n; i++) state.insert("notes", { id: rng.id("note"), title: `burst ${i + 1}`, body: "", done: false, createdAt: clock.iso(), updatedAt: clock.iso() });
127
+ return { inserted: n };
128
+ },
129
+ /** Flip the first note's done flag — a single-record update event. */
130
+ toggleFirst: ({ state, clock }) => {
131
+ const first = state.list("notes")[0];
132
+ if (!first) return { toggled: null };
133
+ state.update("notes", first.id, { done: !first.done, updatedAt: clock.iso() });
134
+ return { toggled: first.id };
135
+ },
136
+ /** Drop every chat socket without a close frame. */
137
+ dropChat: ({ streams: streams2 }) => ({ dropped: streams2.disconnect({ path: "/api/chat/ws" }, { drop: true }) })
138
+ };
139
+ var happy = defineScenario({
140
+ name: "notes-happy",
141
+ label: "Happy path",
142
+ description: "8 seeded notes, CRUD, live SSE feed, streaming chat over WebSocket.",
143
+ setup: seedNotes(8),
144
+ routes: baseRoutes(),
145
+ streams: streams(),
146
+ actions,
147
+ qa: { routes: ["/"], expectsErrors: false }
148
+ });
149
+ var empty = defineScenario({
150
+ name: "notes-empty",
151
+ label: "Empty state",
152
+ description: "No notes; every list shows its empty state.",
153
+ setup: seedNotes(0),
154
+ routes: baseRoutes(),
155
+ streams: streams(),
156
+ actions
157
+ });
158
+ var slow = defineScenario({
159
+ name: "notes-slow",
160
+ label: "Slow API",
161
+ description: "1200 ms (+ up to 400 ms seeded jitter) on every response; chat chunks every 250 ms.",
162
+ faults: { latencyMs: 1200, jitterMs: 400 },
163
+ setup: seedNotes(8),
164
+ routes: baseRoutes(),
165
+ streams: streams({ chunkDelayMs: 250 }),
166
+ actions,
167
+ qa: { expectsSlow: true }
168
+ });
169
+ var flaky = defineScenario({
170
+ name: "notes-flaky",
171
+ label: "Flaky list",
172
+ description: "GET /api/notes fails every third call with 503; creates succeed.",
173
+ setup: seedNotes(8),
174
+ routes: [
175
+ route.get("/api/notes", (ctx) => ctx.calls % 3 === 0 ? problem(503, "flaky upstream") : json({ items: ctx.state.list("notes") })),
176
+ ...baseRoutes().filter((r) => !(r.method === "GET" && r.path === "/api/notes"))
177
+ ],
178
+ streams: streams(),
179
+ actions,
180
+ qa: { expectsErrors: true }
181
+ });
182
+ var malformedApi = defineScenario({
183
+ name: "notes-malformed",
184
+ label: "Malformed responses",
185
+ description: "GET /api/notes answers, in order: invalid JSON, wrong content-type, schema drift, then a valid list forever.",
186
+ setup: seedNotes(3),
187
+ routes: [
188
+ route.get(
189
+ "/api/notes",
190
+ sequence([() => malformed("invalid-json"), () => malformed("wrong-content-type"), () => malformed("schema-drift"), (ctx) => json({ items: ctx.state.list("notes") })])
191
+ ),
192
+ ...baseRoutes().filter((r) => !(r.method === "GET" && r.path === "/api/notes"))
193
+ ],
194
+ streams: streams(),
195
+ actions,
196
+ qa: { expectsErrors: true }
197
+ });
198
+ var streamDrop = defineScenario({
199
+ name: "chat-stream-drop",
200
+ label: "Stream drop mid-turn",
201
+ description: "The chat socket is hard-dropped halfway through every assistant reply; clients must reconnect with sinceEventID and resume.",
202
+ setup: seedNotes(2),
203
+ routes: baseRoutes(),
204
+ streams: streams({ dropMidStream: true }),
205
+ actions
206
+ });
207
+ var manualClock = defineScenario({
208
+ name: "notes-manual-clock",
209
+ label: "Manual clock",
210
+ description: "Nothing time-based happens until the clock is stepped: deterministic chat streaming and latency for tests (`scenario.step ms=\u2026`).",
211
+ clock: { mode: "manual" },
212
+ faults: { latencyMs: 500 },
213
+ setup: seedNotes(4),
214
+ routes: baseRoutes(),
215
+ streams: streams({ streamDelayMs: 100, chunkDelayMs: 100 }),
216
+ actions
217
+ });
218
+ var scenarios = [happy, empty, slow, flaky, malformedApi, streamDrop, manualClock];
219
+ var notes_chat_default = scenarios;
220
+ export {
221
+ NOTES_TOPIC,
222
+ chatTopic,
223
+ notes_chat_default as default,
224
+ empty,
225
+ flaky,
226
+ happy,
227
+ malformedApi,
228
+ manualClock,
229
+ scenarios,
230
+ slow,
231
+ streamDrop
232
+ };
233
+ //# sourceMappingURL=examples.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/examples/notes-chat.ts"],"sourcesContent":["/**\n * Generic example world shipped with the package (also what the test-suite and\n * consumer smoke test use). No product data — a notes CRUD API, a live notes\n * feed over SSE, and a streaming-chat WebSocket protocol with per-topic event\n * ids and resume. Scenario variants layer faults and timing on top.\n *\n * WebSocket protocol (`chat-v1` subprotocol), deliberately shaped like real\n * chat backends so client reconnect logic gets exercised:\n * client → { type:\"subscribe\", topic, sinceEventID? }\n * → { type:\"send\", topic, clientMessageID, content }\n * server → { type:\"ready\" } · { type:\"subscribed\", topic, replayed, last }\n * → { type:\"message\", topic, eventID, id, role, content }\n * → { type:\"stream.start\", streamID } · { type:\"chat.content\", streamID, seq, content }\n * → { type:\"stream.done\", streamID } · { type:\"resume.miss\", topic } · { type:\"error\", error }\n */\n\nimport { defineScenario, type ScenarioDefinition } from \"../core/scenario.js\";\nimport { crud, json, problem, route, sequence, malformed } from \"../core/router.js\";\nimport { sse, ws, type SimSocket } from \"../core/streams.js\";\n\nexport interface Note extends Record<string, unknown> {\n id: string;\n title: string;\n body: string;\n done: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport const NOTES_TOPIC = \"notes\";\nexport const chatTopic = (room: string) => `chat:${room}`;\n\nconst WORDS = [\"alpha\", \"harbor\", \"signal\", \"quiet\", \"meadow\", \"copper\", \"lantern\", \"orbit\", \"velvet\", \"cinder\"];\n\nfunction baseRoutes() {\n return [\n route.get(\"/api/health\", () => json({ ok: true })),\n ...crud<Note>(\"/api/notes\", \"notes\", {\n sort: (a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id),\n validate: (body) => (body.title !== undefined && typeof body.title !== \"string\" ? \"title must be a string\" : null),\n create: (body, ctx) => ({\n id: ctx.rng.id(\"note\"),\n title: String(body.title ?? \"Untitled\"),\n body: String(body.body ?? \"\"),\n done: Boolean(body.done ?? false),\n createdAt: ctx.clock.iso(),\n updatedAt: ctx.clock.iso(),\n }),\n update: (cur, body, ctx) => ({ ...cur, ...body, id: cur.id, updatedAt: ctx.clock.iso() }),\n }),\n // Bulk action with a side effect: marks everything done and emits one custom event.\n route.post(\"/api/notes:complete-all\", (ctx) => {\n let n = 0;\n for (const note of ctx.state.list<Note>(\"notes\", { where: (x) => !x.done })) {\n ctx.state.update<Note>(\"notes\", note.id, { done: true, updatedAt: ctx.clock.iso() });\n n++;\n }\n ctx.state.custom(\"notes\", \"completed-all\", { count: n });\n return json({ completed: n });\n }),\n route.get(\"/api/chat/rooms/:room/history\", (ctx) => {\n const topic = chatTopic(ctx.params.room!);\n const { events } = ctx.streams.topics.replay(topic, ctx.query.get(\"sinceEventID\") ?? \"0\");\n return json({ items: events.map((e) => ({ ...e.data, eventID: e.eventID, topic })) });\n }),\n ];\n}\n\nfunction streams(opts: { streamDelayMs?: number; chunkDelayMs?: number; dropMidStream?: boolean } = {}) {\n const chunkDelay = opts.chunkDelayMs ?? 40;\n return [\n // Live notes feed: every store mutation on \"notes\" becomes an SSE event, resumable via Last-Event-ID.\n sse(\"/api/notes/events\", (ctx, stream) => {\n stream.subscribe(NOTES_TOPIC);\n stream.send({ type: \"hello\", run: ctx.run.id }, { event: \"hello\" });\n }, { keepaliveMs: 15_000 }),\n ws(\"/api/chat/ws\", {\n protocols: [\"chat-v1\"],\n onOpen(ctx, socket) {\n socket.send({ type: \"ready\", protocolVersion: 1, run: ctx.run.id });\n },\n onMessage(ctx, socket, raw) {\n let frame: { type?: string; topic?: string; sinceEventID?: string; clientMessageID?: string; content?: string };\n try {\n frame = JSON.parse(String(raw)) as typeof frame;\n } catch {\n return socket.send({ type: \"error\", error: \"invalid json\" });\n }\n if (frame.type === \"subscribe\" && frame.topic) {\n const r = socket.subscribe(frame.topic, { since: frame.sinceEventID ?? null });\n socket.send({ type: \"subscribed\", topic: frame.topic, replayed: r.replayed, last: r.last });\n if (r.missed) socket.send({ type: \"resume.miss\", topic: frame.topic });\n return;\n }\n if (frame.type === \"send\" && frame.topic && frame.content !== undefined) {\n const id = ctx.rng.id(\"msg\");\n ctx.streams.publish(frame.topic, { type: \"message\", id, clientMessageID: frame.clientMessageID ?? \"\", role: \"user\", content: frame.content, createdAt: ctx.clock.iso() });\n respond(ctx, socket, frame.topic, frame.content, chunkDelay, opts);\n return;\n }\n socket.send({ type: \"error\", error: `unknown frame ${frame.type ?? \"?\"}` });\n },\n }),\n ];\n\n function respond(ctx: Parameters<NonNullable<ReturnType<typeof ws>[\"onMessage\"]>>[0], socket: SimSocket, topic: string, prompt: string, delay: number, o: typeof opts) {\n const streamID = ctx.rng.id(\"stream\");\n const words = prompt.split(/\\s+/).filter(Boolean).slice(0, 12);\n const reply = [\"Echoing\", ...words.map((w) => w.toUpperCase()), \"—\", ctx.rng.pick(WORDS), ctx.rng.pick(WORDS)];\n ctx.clock.after(o.streamDelayMs ?? 100, () => {\n ctx.streams.publish(topic, { type: \"stream.start\", streamID });\n reply.forEach((word, i) => {\n ctx.clock.after(delay * (i + 1), () => {\n if (o.dropMidStream && i === Math.floor(reply.length / 2)) {\n // Cut the connection without a close frame — the client must reconnect and resume.\n ctx.streams.disconnect({ topic }, { drop: true });\n ctx.log(\"dropped connection mid-stream\", { streamID });\n }\n ctx.streams.publish(topic, { type: \"chat.content\", streamID, seq: i + 1, content: word + \" \" });\n }, `chunk ${i + 1}`);\n });\n ctx.clock.after(delay * (reply.length + 1), () => {\n const id = ctx.rng.id(\"msg\");\n ctx.streams.publish(topic, { type: \"stream.done\", streamID, messageID: id });\n ctx.streams.publish(topic, { type: \"message\", id, role: \"assistant\", content: reply.join(\" \").trim(), createdAt: ctx.clock.iso() });\n }, \"stream.done\");\n }, \"stream.start\");\n }\n}\n\nfunction seedNotes(count: number) {\n return ({ state, rng, clock, streams }: import(\"../core/scenario.js\").SetupContext) => {\n for (let i = 0; i < count; i++) {\n const created = clock.iso(-(count - i) * 60_000);\n state.insert<Note>(\"notes\", { id: rng.id(\"note\"), title: `${rng.pick(WORDS)} ${rng.pick(WORDS)}`, body: `Note ${i + 1}`, done: rng.chance(0.3), createdAt: created, updatedAt: created });\n }\n // Mutations → stream events: the feed publishes what the store does.\n state.on((e) => {\n if (e.collection !== \"notes\") return;\n if (e.kind === \"custom\") streams.publish(NOTES_TOPIC, { type: `notes.${e.name}`, ...(e.data as Record<string, unknown>) });\n else streams.publish(NOTES_TOPIC, { type: `notes.${e.kind}`, id: e.id, note: e.record ?? null, previous: e.previous ?? null });\n });\n };\n}\n\nconst actions: ScenarioDefinition[\"actions\"] = {\n /** Emit N synthetic note inserts quickly (list growth / scroll anchoring). */\n burst: ({ state, rng, clock, args }) => {\n const n = Number(args.count ?? 5);\n for (let i = 0; i < n; i++) state.insert<Note>(\"notes\", { id: rng.id(\"note\"), title: `burst ${i + 1}`, body: \"\", done: false, createdAt: clock.iso(), updatedAt: clock.iso() });\n return { inserted: n };\n },\n /** Flip the first note's done flag — a single-record update event. */\n toggleFirst: ({ state, clock }) => {\n const first = state.list<Note>(\"notes\")[0];\n if (!first) return { toggled: null };\n state.update<Note>(\"notes\", first.id, { done: !first.done, updatedAt: clock.iso() });\n return { toggled: first.id };\n },\n /** Drop every chat socket without a close frame. */\n dropChat: ({ streams }) => ({ dropped: streams.disconnect({ path: \"/api/chat/ws\" }, { drop: true }) }),\n};\n\nexport const happy = defineScenario({\n name: \"notes-happy\",\n label: \"Happy path\",\n description: \"8 seeded notes, CRUD, live SSE feed, streaming chat over WebSocket.\",\n setup: seedNotes(8),\n routes: baseRoutes(),\n streams: streams(),\n actions,\n qa: { routes: [\"/\"], expectsErrors: false },\n});\n\nexport const empty = defineScenario({\n name: \"notes-empty\",\n label: \"Empty state\",\n description: \"No notes; every list shows its empty state.\",\n setup: seedNotes(0),\n routes: baseRoutes(),\n streams: streams(),\n actions,\n});\n\nexport const slow = defineScenario({\n name: \"notes-slow\",\n label: \"Slow API\",\n description: \"1200 ms (+ up to 400 ms seeded jitter) on every response; chat chunks every 250 ms.\",\n faults: { latencyMs: 1200, jitterMs: 400 },\n setup: seedNotes(8),\n routes: baseRoutes(),\n streams: streams({ chunkDelayMs: 250 }),\n actions,\n qa: { expectsSlow: true },\n});\n\nexport const flaky = defineScenario({\n name: \"notes-flaky\",\n label: \"Flaky list\",\n description: \"GET /api/notes fails every third call with 503; creates succeed.\",\n setup: seedNotes(8),\n routes: [\n route.get(\"/api/notes\", (ctx) => (ctx.calls % 3 === 0 ? problem(503, \"flaky upstream\") : json({ items: ctx.state.list<Note>(\"notes\") }))),\n ...baseRoutes().filter((r) => !(r.method === \"GET\" && r.path === \"/api/notes\")),\n ],\n streams: streams(),\n actions,\n qa: { expectsErrors: true },\n});\n\nexport const malformedApi = defineScenario({\n name: \"notes-malformed\",\n label: \"Malformed responses\",\n description: \"GET /api/notes answers, in order: invalid JSON, wrong content-type, schema drift, then a valid list forever.\",\n setup: seedNotes(3),\n routes: [\n route.get(\n \"/api/notes\",\n sequence([() => malformed(\"invalid-json\"), () => malformed(\"wrong-content-type\"), () => malformed(\"schema-drift\"), (ctx) => json({ items: ctx.state.list<Note>(\"notes\") })]),\n ),\n ...baseRoutes().filter((r) => !(r.method === \"GET\" && r.path === \"/api/notes\")),\n ],\n streams: streams(),\n actions,\n qa: { expectsErrors: true },\n});\n\nexport const streamDrop = defineScenario({\n name: \"chat-stream-drop\",\n label: \"Stream drop mid-turn\",\n description: \"The chat socket is hard-dropped halfway through every assistant reply; clients must reconnect with sinceEventID and resume.\",\n setup: seedNotes(2),\n routes: baseRoutes(),\n streams: streams({ dropMidStream: true }),\n actions,\n});\n\nexport const manualClock = defineScenario({\n name: \"notes-manual-clock\",\n label: \"Manual clock\",\n description: \"Nothing time-based happens until the clock is stepped: deterministic chat streaming and latency for tests (`scenario.step ms=…`).\",\n clock: { mode: \"manual\" },\n faults: { latencyMs: 500 },\n setup: seedNotes(4),\n routes: baseRoutes(),\n streams: streams({ streamDelayMs: 100, chunkDelayMs: 100 }),\n actions,\n});\n\nexport const scenarios: ScenarioDefinition[] = [happy, empty, slow, flaky, malformedApi, streamDrop, manualClock];\nexport default scenarios;\n"],"mappings":"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","names":["streams"]}
@@ -0,0 +1,322 @@
1
+ import { S as ScenarioDefinition, R as Run, W as WsRoute, a as StreamContext, C as ClockMode, b as ConnectionSummary, F as FaultSnapshot, M as Method, c as Record_, d as StoreEvent, e as RunLogEntry, T as TopicEvent, O as Override, f as OverrideInput, g as FailMode } from './scenario--CvzkzX_.js';
2
+ export { A as ActionContext, h as ClockTimer, i as FaultLayer, H as Handler, j as MalformedKind, k as Rng, l as Route, m as RouteContext, n as Serializer, o as SetupContext, p as SimSocket, q as SimSseStream, r as SocketTransport, s as SseRoute, t as Store, u as StoreEventKind, v as StoreListener, w as StreamHub, x as StreamRoute, y as TopicLog, V as VIRTUAL_EPOCH, z as VirtualClock, B as compileRoute, D as crud, E as defineScenario, G as delayed, I as empty, J as json, K as malformed, L as problem, N as route, P as sequence, Q as sse, U as statusText, X as text, Y as ws } from './scenario--CvzkzX_.js';
3
+
4
+ interface SimulatorOptions {
5
+ scenarios: ScenarioDefinition[];
6
+ defaultScenario?: string;
7
+ /** Control-plane prefix (default `/__sim`). */
8
+ controlPath?: string;
9
+ /** Default run id (default `default`). */
10
+ defaultRun?: string;
11
+ /** Add permissive CORS headers (default true — the mock is a dev tool). */
12
+ cors?: boolean;
13
+ log?: (line: string) => void;
14
+ }
15
+ /**
16
+ * Marker for "the adapter completed the WebSocket upgrade". Node's undici
17
+ * refuses `new Response(null, { status: 101 })`, so adapters return a 200 with
18
+ * this header instead of a 101.
19
+ */
20
+ declare const UPGRADED_HEADER = "x-sim-upgraded";
21
+ declare const upgradedResponse: () => Response;
22
+ declare const isUpgraded: (response: Response) => boolean;
23
+ /** How an adapter completes a WebSocket upgrade for a matched route. */
24
+ type UpgradeHook = (route: WsRoute, ctx: StreamContext, run: Run, protocol: string | null) => Response | Promise<Response>;
25
+ interface HandleOptions {
26
+ upgrade?: UpgradeHook;
27
+ }
28
+ declare class Simulator {
29
+ readonly scenarios: Map<string, ScenarioDefinition>;
30
+ readonly runs: Map<string, Run>;
31
+ readonly controlPath: string;
32
+ readonly defaultRun: string;
33
+ readonly defaultScenario: string;
34
+ private readonly cors;
35
+ private readonly log;
36
+ constructor(options: SimulatorOptions);
37
+ private cookie;
38
+ runIdFor(request: Request, url: URL): string;
39
+ scenarioNameFor(request: Request, url: URL): string;
40
+ /** Get or lazily create the run for a request. */
41
+ runFor(request: Request, url: URL): Promise<Run>;
42
+ createRun(id: string, scenarioName: string, seed?: string): Run;
43
+ getRun(id?: string): Run | null;
44
+ handle(request: Request, options?: HandleOptions): Promise<Response>;
45
+ private routeContext;
46
+ streamContext(run: Run, request: Request, url: URL, params: Record<string, string>): StreamContext;
47
+ private withCors;
48
+ private control;
49
+ /** In-process control API — the same operations the HTTP control plane offers. */
50
+ readonly api: {
51
+ scenarios: () => {
52
+ name: string;
53
+ label: string | null;
54
+ description: string | null;
55
+ seed: string;
56
+ tags: string[];
57
+ clock: {
58
+ mode?: ClockMode;
59
+ speed?: number;
60
+ };
61
+ actions: string[];
62
+ qa: Record<string, unknown> | null;
63
+ }[];
64
+ status: (runId?: string) => Promise<{
65
+ run: string;
66
+ scenario: null;
67
+ exists: boolean;
68
+ defaultScenario: string;
69
+ } | {
70
+ run: string;
71
+ scenario: string;
72
+ label: string | null;
73
+ seed: string;
74
+ createdWall: number;
75
+ requests: number;
76
+ clock: {
77
+ mode: ClockMode;
78
+ speed: number;
79
+ now: number;
80
+ wall: string;
81
+ pending: {
82
+ id: number;
83
+ at: number;
84
+ label: string;
85
+ }[];
86
+ };
87
+ state: {
88
+ collections: Record<string, number>;
89
+ lastSeq: number;
90
+ };
91
+ streams: ConnectionSummary[];
92
+ topics: {
93
+ topic: string;
94
+ last: string;
95
+ }[];
96
+ faults: FaultSnapshot;
97
+ routes: {
98
+ method: Method;
99
+ path: string;
100
+ calls: number;
101
+ name: string | null;
102
+ }[];
103
+ streamRoutes: ({
104
+ kind: string;
105
+ path: string;
106
+ protocols: string[];
107
+ } | {
108
+ kind: string;
109
+ path: string;
110
+ method: "GET" | "POST";
111
+ })[];
112
+ actions: string[];
113
+ exists: boolean;
114
+ defaultScenario?: undefined;
115
+ }>;
116
+ select: (runId: string, scenario: string, seed?: string) => Promise<{
117
+ run: string;
118
+ scenario: string;
119
+ label: string | null;
120
+ seed: string;
121
+ createdWall: number;
122
+ requests: number;
123
+ clock: {
124
+ mode: ClockMode;
125
+ speed: number;
126
+ now: number;
127
+ wall: string;
128
+ pending: {
129
+ id: number;
130
+ at: number;
131
+ label: string;
132
+ }[];
133
+ };
134
+ state: {
135
+ collections: Record<string, number>;
136
+ lastSeq: number;
137
+ };
138
+ streams: ConnectionSummary[];
139
+ topics: {
140
+ topic: string;
141
+ last: string;
142
+ }[];
143
+ faults: FaultSnapshot;
144
+ routes: {
145
+ method: Method;
146
+ path: string;
147
+ calls: number;
148
+ name: string | null;
149
+ }[];
150
+ streamRoutes: ({
151
+ kind: string;
152
+ path: string;
153
+ protocols: string[];
154
+ } | {
155
+ kind: string;
156
+ path: string;
157
+ method: "GET" | "POST";
158
+ })[];
159
+ actions: string[];
160
+ }>;
161
+ reset: (runId: string, seed?: string) => Promise<{
162
+ run: string;
163
+ scenario: string;
164
+ label: string | null;
165
+ seed: string;
166
+ createdWall: number;
167
+ requests: number;
168
+ clock: {
169
+ mode: ClockMode;
170
+ speed: number;
171
+ now: number;
172
+ wall: string;
173
+ pending: {
174
+ id: number;
175
+ at: number;
176
+ label: string;
177
+ }[];
178
+ };
179
+ state: {
180
+ collections: Record<string, number>;
181
+ lastSeq: number;
182
+ };
183
+ streams: ConnectionSummary[];
184
+ topics: {
185
+ topic: string;
186
+ last: string;
187
+ }[];
188
+ faults: FaultSnapshot;
189
+ routes: {
190
+ method: Method;
191
+ path: string;
192
+ calls: number;
193
+ name: string | null;
194
+ }[];
195
+ streamRoutes: ({
196
+ kind: string;
197
+ path: string;
198
+ protocols: string[];
199
+ } | {
200
+ kind: string;
201
+ path: string;
202
+ method: "GET" | "POST";
203
+ })[];
204
+ actions: string[];
205
+ }>;
206
+ step: (runId: string, ms: number) => {
207
+ pending: {
208
+ id: number;
209
+ at: number;
210
+ label: string;
211
+ }[];
212
+ now: number;
213
+ fired: {
214
+ id: number;
215
+ at: number;
216
+ label: string;
217
+ }[];
218
+ };
219
+ clock: (runId: string, patch: {
220
+ mode?: "manual" | "realtime";
221
+ speed?: number;
222
+ }) => {
223
+ mode: ClockMode;
224
+ speed: number;
225
+ now: number;
226
+ pending: {
227
+ id: number;
228
+ at: number;
229
+ label: string;
230
+ }[];
231
+ };
232
+ state: (runId: string, collection?: string) => {
233
+ run: string;
234
+ collection: string;
235
+ items: Record_[];
236
+ counts?: undefined;
237
+ collections?: undefined;
238
+ } | {
239
+ run: string;
240
+ counts: Record<string, number>;
241
+ collections: Record<string, Record_[]>;
242
+ collection?: undefined;
243
+ items?: undefined;
244
+ };
245
+ events: (runId: string, opts: {
246
+ since?: number;
247
+ limit?: number;
248
+ collection?: string;
249
+ }) => {
250
+ run: string;
251
+ last: number;
252
+ count: number;
253
+ events: StoreEvent[];
254
+ };
255
+ log: (runId: string, opts: {
256
+ since?: number;
257
+ limit?: number;
258
+ }) => {
259
+ run: string;
260
+ entries: RunLogEntry[];
261
+ };
262
+ streams: (runId: string) => {
263
+ run: string;
264
+ connections: ConnectionSummary[];
265
+ topics: {
266
+ topic: string;
267
+ last: string;
268
+ }[];
269
+ latencyMs: number;
270
+ };
271
+ disconnect: (runId: string, target: {
272
+ id?: string;
273
+ topic?: string;
274
+ path?: string;
275
+ all?: boolean;
276
+ }, opts?: {
277
+ code?: number;
278
+ reason?: string;
279
+ drop?: boolean;
280
+ }) => {
281
+ closed: string[];
282
+ };
283
+ pause: (runId: string, id: string, paused: boolean) => boolean;
284
+ publish: (runId: string, topic: string, data: Record<string, unknown>) => TopicEvent;
285
+ overrides: (runId: string) => {
286
+ run: string;
287
+ overrides: Override[];
288
+ };
289
+ setOverride: (runId: string, input: OverrideInput) => Override;
290
+ clearOverrides: (runId: string, target?: string, method?: string) => {
291
+ cleared: boolean;
292
+ };
293
+ faults: (runId: string, patch: {
294
+ latencyMs?: number;
295
+ jitterMs?: number;
296
+ failMode?: "off" | "data" | "all";
297
+ shellPaths?: string[];
298
+ streamLatencyMs?: number;
299
+ }) => {
300
+ streamLatencyMs: number;
301
+ latencyMs: number;
302
+ jitterMs: number;
303
+ failMode: FailMode;
304
+ shellPaths: string[];
305
+ overrides: Override[];
306
+ };
307
+ action: (runId: string, name: string, args?: Record<string, unknown>) => Promise<unknown>;
308
+ runs: () => {
309
+ id: string;
310
+ scenario: string;
311
+ seed: string;
312
+ requests: number;
313
+ createdWall: number;
314
+ }[];
315
+ deleteRun: (runId: string) => boolean;
316
+ };
317
+ private require;
318
+ dispose(): void;
319
+ }
320
+ declare function matchPath(pattern: string, pathname: string): Record<string, string> | null;
321
+
322
+ export { ClockMode, ConnectionSummary, FailMode, FaultSnapshot, type HandleOptions, Method, Override, OverrideInput, Run, RunLogEntry, ScenarioDefinition, Simulator, type SimulatorOptions, StoreEvent, StreamContext, TopicEvent, UPGRADED_HEADER, type UpgradeHook, WsRoute, isUpgraded, matchPath, upgradedResponse };