@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,897 @@
1
+ /**
2
+ * Seeded PRNG (sfc32). Every scenario run owns one, seeded from the run's
3
+ * seed, so ids, latencies, jitter and fixture data replay identically for the
4
+ * same seed — never use Math.random or wall-clock ids inside a scenario.
5
+ */
6
+ declare class Rng {
7
+ readonly seed: string | number;
8
+ private a;
9
+ private b;
10
+ private c;
11
+ private d;
12
+ /** Draws so far; useful when asserting determinism across runs. */
13
+ draws: number;
14
+ constructor(seed: string | number);
15
+ /** Uniform in [0, 1). */
16
+ next(): number;
17
+ /** Integer in [min, max] inclusive. */
18
+ int(min: number, max: number): number;
19
+ float(min?: number, max?: number): number;
20
+ chance(probability: number): boolean;
21
+ pick<T>(items: readonly T[]): T;
22
+ shuffle<T>(items: readonly T[]): T[];
23
+ /** Deterministic opaque id: `<prefix>_<8 base36 chars>`. */
24
+ id(prefix?: string): string;
25
+ /** RFC-4122-shaped id (version 4 bits set) from the seeded stream. */
26
+ uuid(): string;
27
+ /** Fork a child generator with a derived seed (stable across runs). */
28
+ fork(label: string): Rng;
29
+ }
30
+
31
+ /**
32
+ * Virtual clock. Everything time-related in a scenario — response delays,
33
+ * scheduled stream events, disconnects, timestamps in fixtures — goes through
34
+ * the run's clock so it can be replayed:
35
+ *
36
+ * realtime timers fire on real setTimeout scaled by `speed` (default 1)
37
+ * manual nothing fires until `step(ms)` advances the clock (tests, and
38
+ * "step" in the control plane / panel / CLI)
39
+ *
40
+ * `now()` is virtual milliseconds since the run started, and `wall()` maps it
41
+ * onto a fixed epoch so fixture timestamps are stable across runs.
42
+ */
43
+ type ClockMode = "realtime" | "manual";
44
+ interface ClockTimer {
45
+ id: number;
46
+ at: number;
47
+ label: string;
48
+ cancel(): void;
49
+ }
50
+ declare class VirtualClock {
51
+ mode: ClockMode;
52
+ speed: number;
53
+ readonly epoch: number;
54
+ private entries;
55
+ private nextId;
56
+ private base;
57
+ private startedReal;
58
+ private manualNow;
59
+ constructor(mode?: ClockMode, speed?: number, epoch?: number);
60
+ /** Virtual ms since the run started. */
61
+ now(): number;
62
+ /** Virtual wall-clock ms (epoch + now). */
63
+ wall(): number;
64
+ iso(offsetMs?: number): string;
65
+ after(ms: number, fn: () => void, label?: string): ClockTimer;
66
+ /** Promise that resolves after `ms` virtual milliseconds. */
67
+ sleep(ms: number, label?: string): Promise<void>;
68
+ cancel(id: number): void;
69
+ /** Manual mode: advance by `ms`, firing due timers in order. Returns what fired. */
70
+ step(ms: number): {
71
+ now: number;
72
+ fired: {
73
+ id: number;
74
+ at: number;
75
+ label: string;
76
+ }[];
77
+ };
78
+ /** Switch modes; pending timers are re-armed (realtime) or parked (manual). */
79
+ setMode(mode: ClockMode): void;
80
+ pending(): {
81
+ id: number;
82
+ at: number;
83
+ label: string;
84
+ }[];
85
+ clear(): void;
86
+ private fire;
87
+ }
88
+
89
+ /**
90
+ * Per-run state. Collections of records keyed by id, with every mutation
91
+ * appended to a bounded event log and delivered to listeners — that is how
92
+ * "a PATCH publishes a stream event" is wired without coupling routes to
93
+ * transports: routes mutate state, stream routes subscribe to state.
94
+ */
95
+ type Record_ = Record<string, unknown> & {
96
+ id: string;
97
+ };
98
+ type StoreEventKind = "insert" | "update" | "remove" | "clear" | "custom";
99
+ interface StoreEvent {
100
+ seq: number;
101
+ /** Virtual clock ms when it happened. */
102
+ t: number;
103
+ kind: StoreEventKind;
104
+ collection: string;
105
+ id?: string;
106
+ record?: Record_;
107
+ previous?: Record_;
108
+ /** For `custom` events. */
109
+ name?: string;
110
+ data?: unknown;
111
+ }
112
+ type StoreListener = (event: StoreEvent) => void;
113
+ declare class Store {
114
+ private now;
115
+ private logLimit;
116
+ private collections;
117
+ private listeners;
118
+ private log;
119
+ private seq;
120
+ constructor(now: () => number, logLimit?: number);
121
+ collection(name: string): Map<string, Record_>;
122
+ collections_(): string[];
123
+ insert<T extends Record_>(collection: string, record: T): T;
124
+ upsert<T extends Record_>(collection: string, record: T): T;
125
+ get<T extends Record_ = Record_>(collection: string, id: string): T | undefined;
126
+ list<T extends Record_ = Record_>(collection: string, opts?: {
127
+ where?: (r: T) => boolean;
128
+ sort?: (a: T, b: T) => number;
129
+ offset?: number;
130
+ limit?: number;
131
+ }): T[];
132
+ count(collection: string): number;
133
+ update<T extends Record_ = Record_>(collection: string, id: string, patch: Partial<T> | ((current: T) => T)): T | undefined;
134
+ remove(collection: string, id: string): Record_ | undefined;
135
+ clear(collection?: string): void;
136
+ /** Application-level event (not tied to a record), e.g. "job.progress". */
137
+ custom(collection: string, name: string, data?: unknown, id?: string): void;
138
+ on(listener: StoreListener): () => boolean;
139
+ events(opts?: {
140
+ since?: number;
141
+ collection?: string;
142
+ limit?: number;
143
+ }): StoreEvent[];
144
+ get lastSeq(): number;
145
+ snapshot(): Record<string, Record_[]>;
146
+ counts(): Record<string, number>;
147
+ private emit;
148
+ }
149
+
150
+ /**
151
+ * Streams: topics with per-topic monotonic event ids and bounded replay, plus
152
+ * SSE and WebSocket routes whose connections the control plane can list,
153
+ * pause, disconnect or hard-drop. Transports are pluggable — a Bun/Node
154
+ * server, Vite's http server, or an in-page fake WebSocket — and the scenario
155
+ * code never sees the difference.
156
+ */
157
+
158
+ interface TopicEvent {
159
+ topic: string;
160
+ /** Monotonic per topic, as a decimal string (matches how most protocols carry it). */
161
+ eventID: string;
162
+ t: number;
163
+ data: Record<string, unknown>;
164
+ }
165
+ declare class TopicLog {
166
+ private now;
167
+ readonly retain: number;
168
+ private seqs;
169
+ private logs;
170
+ constructor(now: () => number, retain?: number);
171
+ publish(topic: string, data: Record<string, unknown>): TopicEvent;
172
+ last(topic: string): string;
173
+ /**
174
+ * Events after `since`. `missed` is true when `since` predates the retained
175
+ * window. `null`/`undefined` means "no resume point": live only, nothing is
176
+ * replayed (what a fresh EventSource without Last-Event-ID gets). Pass "0"
177
+ * to replay everything retained.
178
+ */
179
+ replay(topic: string, since: string | number | null | undefined): {
180
+ events: TopicEvent[];
181
+ missed: boolean;
182
+ };
183
+ topics(): string[];
184
+ clear(): void;
185
+ }
186
+ interface StreamContext {
187
+ request: Request;
188
+ url: URL;
189
+ params: Record<string, string>;
190
+ query: URLSearchParams;
191
+ state: Store;
192
+ rng: Rng;
193
+ clock: VirtualClock;
194
+ streams: StreamHub;
195
+ run: {
196
+ id: string;
197
+ scenario: string;
198
+ seed: string;
199
+ };
200
+ log(message: string, data?: unknown): void;
201
+ }
202
+ /** What a transport must provide for one WebSocket connection. */
203
+ interface SocketTransport {
204
+ send(data: string | ArrayBuffer): void;
205
+ /** Graceful close with a close frame. */
206
+ close(code?: number, reason?: string): void;
207
+ /** Hard drop: no close frame; the client sees an abnormal close (1006). */
208
+ drop(): void;
209
+ }
210
+ type Serializer = (event: TopicEvent) => string;
211
+ interface Subscription {
212
+ topic: string;
213
+ serialize: Serializer;
214
+ }
215
+ interface ConnectionBase {
216
+ id: string;
217
+ kind: "ws" | "sse";
218
+ path: string;
219
+ url: URL;
220
+ openedAt: number;
221
+ sent: number;
222
+ received: number;
223
+ paused: boolean;
224
+ subscriptions: Map<string, Subscription>;
225
+ meta: Record<string, unknown>;
226
+ }
227
+ interface SimSocket extends ConnectionBase {
228
+ kind: "ws";
229
+ protocol: string | null;
230
+ readyState: "open" | "closed";
231
+ send(data: string | ArrayBuffer | Record<string, unknown>): void;
232
+ close(code?: number, reason?: string): void;
233
+ drop(): void;
234
+ /** Replay `since` then stay subscribed; returns what was replayed. */
235
+ subscribe(topic: string, opts?: {
236
+ since?: string | number | null;
237
+ serialize?: Serializer;
238
+ }): {
239
+ replayed: number;
240
+ missed: boolean;
241
+ last: string;
242
+ };
243
+ unsubscribe(topic: string): void;
244
+ onClose(cb: (code: number, reason: string) => void): void;
245
+ }
246
+ interface SimSseStream extends ConnectionBase {
247
+ kind: "sse";
248
+ lastEventId: string | null;
249
+ /** Send one SSE message. Objects are JSON-encoded. */
250
+ send(data: unknown, opts?: {
251
+ event?: string;
252
+ id?: string;
253
+ retry?: number;
254
+ }): void;
255
+ comment(text: string): void;
256
+ close(): void;
257
+ drop(): void;
258
+ subscribe(topic: string, opts?: {
259
+ since?: string | number | null;
260
+ event?: (e: TopicEvent) => string;
261
+ }): {
262
+ replayed: number;
263
+ missed: boolean;
264
+ last: string;
265
+ };
266
+ unsubscribe(topic: string): void;
267
+ onClose(cb: () => void): void;
268
+ /** The Response to return to the client. */
269
+ response: Response;
270
+ }
271
+ interface WsRoute {
272
+ kind: "ws";
273
+ path: string;
274
+ name?: string;
275
+ /** Subprotocols the server will select from (first match with the client's list wins). */
276
+ protocols?: string[];
277
+ onOpen?(ctx: StreamContext, socket: SimSocket): void | Promise<void>;
278
+ onMessage?(ctx: StreamContext, socket: SimSocket, data: string | ArrayBuffer): void | Promise<void>;
279
+ onClose?(ctx: StreamContext, socket: SimSocket, code: number, reason: string): void;
280
+ }
281
+ interface SseRoute {
282
+ kind: "sse";
283
+ path: string;
284
+ name?: string;
285
+ /** Some APIs open SSE with POST (request body = the prompt). Default GET. */
286
+ method?: "GET" | "POST";
287
+ /** Keepalive comment interval in virtual ms (default 15000; 0 disables). */
288
+ keepaliveMs?: number;
289
+ onOpen(ctx: StreamContext, stream: SimSseStream): void | Promise<void>;
290
+ }
291
+ type StreamRoute = WsRoute | SseRoute;
292
+ interface ConnectionSummary {
293
+ id: string;
294
+ kind: "ws" | "sse";
295
+ path: string;
296
+ openedAt: number;
297
+ sent: number;
298
+ received: number;
299
+ paused: boolean;
300
+ topics: string[];
301
+ protocol?: string | null;
302
+ meta: Record<string, unknown>;
303
+ }
304
+ declare class StreamHub {
305
+ private clock;
306
+ private log;
307
+ readonly topics: TopicLog;
308
+ private connections;
309
+ private nextId;
310
+ /** Extra virtual latency applied to every outgoing stream message. */
311
+ latencyMs: number;
312
+ constructor(clock: VirtualClock, log: (message: string, data?: unknown) => void);
313
+ /** Publish to a topic: appended to the replay log and fanned out to subscribers. */
314
+ publish(topic: string, data: Record<string, unknown>): TopicEvent;
315
+ private deliver;
316
+ private deliverSse;
317
+ /** Called by a transport adapter once the WebSocket handshake completed. */
318
+ openSocket(route: WsRoute, ctx: StreamContext, transport: SocketTransport, protocol: string | null): SimSocket;
319
+ /** Transport → hub: a client frame arrived. */
320
+ receive(route: WsRoute, ctx: StreamContext, socket: SimSocket, data: string | ArrayBuffer): void;
321
+ /** Transport → hub: the client closed. */
322
+ clientClosed(route: WsRoute, ctx: StreamContext, socket: SimSocket, code: number, reason: string): void;
323
+ private finish;
324
+ /** Build the SSE Response for a route; the adapter just returns it. */
325
+ openSse(route: SseRoute, ctx: StreamContext): SimSseStream;
326
+ list(): ConnectionSummary[];
327
+ get(id: string): SimSocket | SimSseStream | null;
328
+ /**
329
+ * Disconnect connections: by id, by topic, by path, or all. `drop` cuts the
330
+ * connection without a close frame (what a network blip looks like).
331
+ */
332
+ disconnect(target: {
333
+ id?: string;
334
+ topic?: string;
335
+ path?: string;
336
+ all?: boolean;
337
+ }, opts?: {
338
+ code?: number;
339
+ reason?: string;
340
+ drop?: boolean;
341
+ }): string[];
342
+ /** Pause delivery on a connection (messages queue); resume flushes them in order. */
343
+ pause(id: string, paused: boolean): boolean;
344
+ closeAll(): void;
345
+ }
346
+
347
+ /**
348
+ * HTTP routing on Web `Request`/`Response`, so the same scenario runs on a
349
+ * Bun/Node server, inside Vite's dev server, or entirely in the browser.
350
+ */
351
+
352
+ type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "*";
353
+ interface RouteContext {
354
+ request: Request;
355
+ url: URL;
356
+ method: string;
357
+ /** `:name` captures. */
358
+ params: Record<string, string>;
359
+ query: URLSearchParams;
360
+ /** Parsed JSON body (or `{}` when absent/invalid). */
361
+ body<T = Record<string, unknown>>(): Promise<T>;
362
+ state: Store;
363
+ rng: Rng;
364
+ clock: VirtualClock;
365
+ streams: StreamHub;
366
+ /** Run id and scenario name, for logging or per-run behaviour. */
367
+ run: {
368
+ id: string;
369
+ scenario: string;
370
+ seed: string;
371
+ };
372
+ /** Per-route call counter (1-based) — handy for "fail on the 3rd call". */
373
+ calls: number;
374
+ log(message: string, data?: unknown): void;
375
+ }
376
+ type Handler = (ctx: RouteContext) => Response | Promise<Response>;
377
+ interface Route {
378
+ method: Method;
379
+ path: string;
380
+ handler: Handler;
381
+ /** Optional label for logs and the control plane. */
382
+ name?: string;
383
+ }
384
+ interface CompiledRoute extends Route {
385
+ match: (pathname: string) => Record<string, string> | null;
386
+ calls: number;
387
+ }
388
+ type MalformedKind = "invalid-json" | "wrong-content-type" | "truncated" | "empty-200" | "html-500" | "schema-drift";
389
+
390
+ /**
391
+ * Fault layer: latency (+ seeded jitter), fail modes and per-endpoint
392
+ * overrides. Overrides are checked before routing, so a test can force any
393
+ * endpoint — even one the scenario never defined — to answer with a given
394
+ * status/body, once (`times`) or until cleared — the classic in-bundle mock
395
+ * "endpoint override" contract, generalised.
396
+ */
397
+
398
+ type FailMode = "off" | "data" | "all";
399
+ interface OverrideInput {
400
+ /** Substring of the path, or `{ regex, flags }`. */
401
+ matcher: string | {
402
+ regex: string;
403
+ flags?: string;
404
+ };
405
+ method?: string;
406
+ status?: number;
407
+ body?: unknown;
408
+ /** Auto-expire after N matches. */
409
+ times?: number;
410
+ delayMs?: number;
411
+ malformed?: MalformedKind;
412
+ name?: string;
413
+ }
414
+ interface Override extends OverrideInput {
415
+ id: string;
416
+ remaining: number;
417
+ hits: number;
418
+ }
419
+ interface FaultSnapshot {
420
+ latencyMs: number;
421
+ jitterMs: number;
422
+ failMode: FailMode;
423
+ shellPaths: string[];
424
+ overrides: Override[];
425
+ }
426
+ declare class FaultLayer {
427
+ private rng;
428
+ latencyMs: number;
429
+ jitterMs: number;
430
+ failMode: FailMode;
431
+ /** Paths the `data` fail mode leaves alone so the app shell still boots. */
432
+ shellPaths: Set<string>;
433
+ private overrides;
434
+ private nextId;
435
+ constructor(rng: Rng);
436
+ configure(patch: Partial<{
437
+ latencyMs: number;
438
+ jitterMs: number;
439
+ failMode: FailMode;
440
+ shellPaths: string[];
441
+ }>): void;
442
+ /** Effective delay for one request (seeded jitter keeps it reproducible). */
443
+ delayFor(extra?: number): number;
444
+ setOverride(input: OverrideInput): Override;
445
+ clearOverride(target: string | OverrideInput["matcher"], method?: string): boolean;
446
+ clearOverrides(): void;
447
+ listOverrides(): Override[];
448
+ /** Find (and consume) the first matching override. */
449
+ matchOverride(path: string, method: string): Override | null;
450
+ /** Build the response an override dictates. */
451
+ overrideResponse(o: Override): Response;
452
+ /** Fail-mode response, or null when the request should proceed. */
453
+ failResponse(path: string): Response | null;
454
+ snapshot(): FaultSnapshot;
455
+ }
456
+
457
+ interface SetupContext {
458
+ state: Store;
459
+ rng: Rng;
460
+ clock: VirtualClock;
461
+ streams: StreamHub;
462
+ run: {
463
+ id: string;
464
+ scenario: string;
465
+ seed: string;
466
+ };
467
+ log(message: string, data?: unknown): void;
468
+ }
469
+ interface ActionContext extends SetupContext {
470
+ args: Record<string, unknown>;
471
+ }
472
+ interface ScenarioDefinition {
473
+ /** URL-safe identifier. */
474
+ name: string;
475
+ label?: string;
476
+ description?: string;
477
+ /** Default seed (default: the scenario name). Runs may override. */
478
+ seed?: string;
479
+ clock?: {
480
+ mode?: ClockMode;
481
+ speed?: number;
482
+ };
483
+ faults?: {
484
+ latencyMs?: number;
485
+ jitterMs?: number;
486
+ failMode?: FailMode;
487
+ shellPaths?: string[];
488
+ };
489
+ tags?: string[];
490
+ /** Seed the store. Runs once per run creation/reset, with the run's RNG. */
491
+ setup?(ctx: SetupContext): void | Promise<void>;
492
+ routes?: Route[];
493
+ streams?: StreamRoute[];
494
+ /**
495
+ * Named actions exposed as `POST /__sim/action {name,args}` — and therefore
496
+ * as `scenario.action` in the pulse panel/CLI. Use them for "emit a burst",
497
+ * "complete the running job", "drop the stream mid-turn".
498
+ */
499
+ actions?: Record<string, (ctx: ActionContext) => unknown | Promise<unknown>>;
500
+ /** Hints for QA harnesses (routes worth rendering, expected error states…). */
501
+ qa?: Record<string, unknown>;
502
+ }
503
+ interface RunLogEntry {
504
+ seq: number;
505
+ t: number;
506
+ message: string;
507
+ data?: unknown;
508
+ }
509
+ declare class Run {
510
+ readonly id: string;
511
+ readonly scenario: ScenarioDefinition;
512
+ readonly seed: string;
513
+ private readonly sink?;
514
+ readonly rng: Rng;
515
+ readonly clock: VirtualClock;
516
+ readonly state: Store;
517
+ readonly streams: StreamHub;
518
+ readonly faults: FaultLayer;
519
+ readonly routes: CompiledRoute[];
520
+ readonly wsRoutes: WsRoute[];
521
+ readonly sseRoutes: SseRoute[];
522
+ readonly createdWall: number;
523
+ requests: number;
524
+ private logEntries;
525
+ private logSeq;
526
+ ready: Promise<void>;
527
+ constructor(id: string, scenario: ScenarioDefinition, seed: string, sink?: ((line: string) => void) | undefined);
528
+ info(): {
529
+ id: string;
530
+ scenario: string;
531
+ seed: string;
532
+ };
533
+ setupContext(): SetupContext;
534
+ log(message: string, data?: unknown): void;
535
+ logs(opts?: {
536
+ since?: number;
537
+ limit?: number;
538
+ }): RunLogEntry[];
539
+ action(name: string, args?: Record<string, unknown>): Promise<unknown>;
540
+ status(): {
541
+ run: string;
542
+ scenario: string;
543
+ label: string | null;
544
+ seed: string;
545
+ createdWall: number;
546
+ requests: number;
547
+ clock: {
548
+ mode: ClockMode;
549
+ speed: number;
550
+ now: number;
551
+ wall: string;
552
+ pending: {
553
+ id: number;
554
+ at: number;
555
+ label: string;
556
+ }[];
557
+ };
558
+ state: {
559
+ collections: Record<string, number>;
560
+ lastSeq: number;
561
+ };
562
+ streams: ConnectionSummary[];
563
+ topics: {
564
+ topic: string;
565
+ last: string;
566
+ }[];
567
+ faults: FaultSnapshot;
568
+ routes: {
569
+ method: Method;
570
+ path: string;
571
+ calls: number;
572
+ name: string | null;
573
+ }[];
574
+ streamRoutes: ({
575
+ kind: string;
576
+ path: string;
577
+ protocols: string[];
578
+ } | {
579
+ kind: string;
580
+ path: string;
581
+ method: "GET" | "POST";
582
+ })[];
583
+ actions: string[];
584
+ };
585
+ dispose(): void;
586
+ }
587
+
588
+ interface SimulatorOptions {
589
+ scenarios: ScenarioDefinition[];
590
+ defaultScenario?: string;
591
+ /** Control-plane prefix (default `/__sim`). */
592
+ controlPath?: string;
593
+ /** Default run id (default `default`). */
594
+ defaultRun?: string;
595
+ /** Add permissive CORS headers (default true — the mock is a dev tool). */
596
+ cors?: boolean;
597
+ log?: (line: string) => void;
598
+ }
599
+ /** How an adapter completes a WebSocket upgrade for a matched route. */
600
+ type UpgradeHook = (route: WsRoute, ctx: StreamContext, run: Run, protocol: string | null) => Response | Promise<Response>;
601
+ interface HandleOptions {
602
+ upgrade?: UpgradeHook;
603
+ }
604
+ declare class Simulator {
605
+ readonly scenarios: Map<string, ScenarioDefinition>;
606
+ readonly runs: Map<string, Run>;
607
+ readonly controlPath: string;
608
+ readonly defaultRun: string;
609
+ readonly defaultScenario: string;
610
+ private readonly cors;
611
+ private readonly log;
612
+ constructor(options: SimulatorOptions);
613
+ private cookie;
614
+ runIdFor(request: Request, url: URL): string;
615
+ scenarioNameFor(request: Request, url: URL): string;
616
+ /** Get or lazily create the run for a request. */
617
+ runFor(request: Request, url: URL): Promise<Run>;
618
+ createRun(id: string, scenarioName: string, seed?: string): Run;
619
+ getRun(id?: string): Run | null;
620
+ handle(request: Request, options?: HandleOptions): Promise<Response>;
621
+ private routeContext;
622
+ streamContext(run: Run, request: Request, url: URL, params: Record<string, string>): StreamContext;
623
+ private withCors;
624
+ private control;
625
+ /** In-process control API — the same operations the HTTP control plane offers. */
626
+ readonly api: {
627
+ scenarios: () => {
628
+ name: string;
629
+ label: string | null;
630
+ description: string | null;
631
+ seed: string;
632
+ tags: string[];
633
+ clock: {
634
+ mode?: ClockMode;
635
+ speed?: number;
636
+ };
637
+ actions: string[];
638
+ qa: Record<string, unknown> | null;
639
+ }[];
640
+ status: (runId?: string) => Promise<{
641
+ run: string;
642
+ scenario: null;
643
+ exists: boolean;
644
+ defaultScenario: string;
645
+ } | {
646
+ run: string;
647
+ scenario: string;
648
+ label: string | null;
649
+ seed: string;
650
+ createdWall: number;
651
+ requests: number;
652
+ clock: {
653
+ mode: ClockMode;
654
+ speed: number;
655
+ now: number;
656
+ wall: string;
657
+ pending: {
658
+ id: number;
659
+ at: number;
660
+ label: string;
661
+ }[];
662
+ };
663
+ state: {
664
+ collections: Record<string, number>;
665
+ lastSeq: number;
666
+ };
667
+ streams: ConnectionSummary[];
668
+ topics: {
669
+ topic: string;
670
+ last: string;
671
+ }[];
672
+ faults: FaultSnapshot;
673
+ routes: {
674
+ method: Method;
675
+ path: string;
676
+ calls: number;
677
+ name: string | null;
678
+ }[];
679
+ streamRoutes: ({
680
+ kind: string;
681
+ path: string;
682
+ protocols: string[];
683
+ } | {
684
+ kind: string;
685
+ path: string;
686
+ method: "GET" | "POST";
687
+ })[];
688
+ actions: string[];
689
+ exists: boolean;
690
+ defaultScenario?: undefined;
691
+ }>;
692
+ select: (runId: string, scenario: string, seed?: string) => Promise<{
693
+ run: string;
694
+ scenario: string;
695
+ label: string | null;
696
+ seed: string;
697
+ createdWall: number;
698
+ requests: number;
699
+ clock: {
700
+ mode: ClockMode;
701
+ speed: number;
702
+ now: number;
703
+ wall: string;
704
+ pending: {
705
+ id: number;
706
+ at: number;
707
+ label: string;
708
+ }[];
709
+ };
710
+ state: {
711
+ collections: Record<string, number>;
712
+ lastSeq: number;
713
+ };
714
+ streams: ConnectionSummary[];
715
+ topics: {
716
+ topic: string;
717
+ last: string;
718
+ }[];
719
+ faults: FaultSnapshot;
720
+ routes: {
721
+ method: Method;
722
+ path: string;
723
+ calls: number;
724
+ name: string | null;
725
+ }[];
726
+ streamRoutes: ({
727
+ kind: string;
728
+ path: string;
729
+ protocols: string[];
730
+ } | {
731
+ kind: string;
732
+ path: string;
733
+ method: "GET" | "POST";
734
+ })[];
735
+ actions: string[];
736
+ }>;
737
+ reset: (runId: string, seed?: string) => Promise<{
738
+ run: string;
739
+ scenario: string;
740
+ label: string | null;
741
+ seed: string;
742
+ createdWall: number;
743
+ requests: number;
744
+ clock: {
745
+ mode: ClockMode;
746
+ speed: number;
747
+ now: number;
748
+ wall: string;
749
+ pending: {
750
+ id: number;
751
+ at: number;
752
+ label: string;
753
+ }[];
754
+ };
755
+ state: {
756
+ collections: Record<string, number>;
757
+ lastSeq: number;
758
+ };
759
+ streams: ConnectionSummary[];
760
+ topics: {
761
+ topic: string;
762
+ last: string;
763
+ }[];
764
+ faults: FaultSnapshot;
765
+ routes: {
766
+ method: Method;
767
+ path: string;
768
+ calls: number;
769
+ name: string | null;
770
+ }[];
771
+ streamRoutes: ({
772
+ kind: string;
773
+ path: string;
774
+ protocols: string[];
775
+ } | {
776
+ kind: string;
777
+ path: string;
778
+ method: "GET" | "POST";
779
+ })[];
780
+ actions: string[];
781
+ }>;
782
+ step: (runId: string, ms: number) => {
783
+ pending: {
784
+ id: number;
785
+ at: number;
786
+ label: string;
787
+ }[];
788
+ now: number;
789
+ fired: {
790
+ id: number;
791
+ at: number;
792
+ label: string;
793
+ }[];
794
+ };
795
+ clock: (runId: string, patch: {
796
+ mode?: "manual" | "realtime";
797
+ speed?: number;
798
+ }) => {
799
+ mode: ClockMode;
800
+ speed: number;
801
+ now: number;
802
+ pending: {
803
+ id: number;
804
+ at: number;
805
+ label: string;
806
+ }[];
807
+ };
808
+ state: (runId: string, collection?: string) => {
809
+ run: string;
810
+ collection: string;
811
+ items: Record_[];
812
+ counts?: undefined;
813
+ collections?: undefined;
814
+ } | {
815
+ run: string;
816
+ counts: Record<string, number>;
817
+ collections: Record<string, Record_[]>;
818
+ collection?: undefined;
819
+ items?: undefined;
820
+ };
821
+ events: (runId: string, opts: {
822
+ since?: number;
823
+ limit?: number;
824
+ collection?: string;
825
+ }) => {
826
+ run: string;
827
+ last: number;
828
+ count: number;
829
+ events: StoreEvent[];
830
+ };
831
+ log: (runId: string, opts: {
832
+ since?: number;
833
+ limit?: number;
834
+ }) => {
835
+ run: string;
836
+ entries: RunLogEntry[];
837
+ };
838
+ streams: (runId: string) => {
839
+ run: string;
840
+ connections: ConnectionSummary[];
841
+ topics: {
842
+ topic: string;
843
+ last: string;
844
+ }[];
845
+ latencyMs: number;
846
+ };
847
+ disconnect: (runId: string, target: {
848
+ id?: string;
849
+ topic?: string;
850
+ path?: string;
851
+ all?: boolean;
852
+ }, opts?: {
853
+ code?: number;
854
+ reason?: string;
855
+ drop?: boolean;
856
+ }) => {
857
+ closed: string[];
858
+ };
859
+ pause: (runId: string, id: string, paused: boolean) => boolean;
860
+ publish: (runId: string, topic: string, data: Record<string, unknown>) => TopicEvent;
861
+ overrides: (runId: string) => {
862
+ run: string;
863
+ overrides: Override[];
864
+ };
865
+ setOverride: (runId: string, input: OverrideInput) => Override;
866
+ clearOverrides: (runId: string, target?: string, method?: string) => {
867
+ cleared: boolean;
868
+ };
869
+ faults: (runId: string, patch: {
870
+ latencyMs?: number;
871
+ jitterMs?: number;
872
+ failMode?: "off" | "data" | "all";
873
+ shellPaths?: string[];
874
+ streamLatencyMs?: number;
875
+ }) => {
876
+ streamLatencyMs: number;
877
+ latencyMs: number;
878
+ jitterMs: number;
879
+ failMode: FailMode;
880
+ shellPaths: string[];
881
+ overrides: Override[];
882
+ };
883
+ action: (runId: string, name: string, args?: Record<string, unknown>) => Promise<unknown>;
884
+ runs: () => {
885
+ id: string;
886
+ scenario: string;
887
+ seed: string;
888
+ requests: number;
889
+ createdWall: number;
890
+ }[];
891
+ deleteRun: (runId: string) => boolean;
892
+ };
893
+ private require;
894
+ dispose(): void;
895
+ }
896
+
897
+ export { Simulator as S, type SimulatorOptions as a, type ScenarioDefinition as b };