@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,637 @@
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
+ /** 2026-01-01T00:00:00Z — the virtual epoch every run starts at. */
51
+ declare const VIRTUAL_EPOCH: number;
52
+ declare class VirtualClock {
53
+ mode: ClockMode;
54
+ speed: number;
55
+ readonly epoch: number;
56
+ private entries;
57
+ private nextId;
58
+ private base;
59
+ private startedReal;
60
+ private manualNow;
61
+ constructor(mode?: ClockMode, speed?: number, epoch?: number);
62
+ /** Virtual ms since the run started. */
63
+ now(): number;
64
+ /** Virtual wall-clock ms (epoch + now). */
65
+ wall(): number;
66
+ iso(offsetMs?: number): string;
67
+ after(ms: number, fn: () => void, label?: string): ClockTimer;
68
+ /** Promise that resolves after `ms` virtual milliseconds. */
69
+ sleep(ms: number, label?: string): Promise<void>;
70
+ cancel(id: number): void;
71
+ /** Manual mode: advance by `ms`, firing due timers in order. Returns what fired. */
72
+ step(ms: number): {
73
+ now: number;
74
+ fired: {
75
+ id: number;
76
+ at: number;
77
+ label: string;
78
+ }[];
79
+ };
80
+ /** Switch modes; pending timers are re-armed (realtime) or parked (manual). */
81
+ setMode(mode: ClockMode): void;
82
+ pending(): {
83
+ id: number;
84
+ at: number;
85
+ label: string;
86
+ }[];
87
+ clear(): void;
88
+ private fire;
89
+ }
90
+
91
+ /**
92
+ * Per-run state. Collections of records keyed by id, with every mutation
93
+ * appended to a bounded event log and delivered to listeners — that is how
94
+ * "a PATCH publishes a stream event" is wired without coupling routes to
95
+ * transports: routes mutate state, stream routes subscribe to state.
96
+ */
97
+ type Record_ = Record<string, unknown> & {
98
+ id: string;
99
+ };
100
+ type StoreEventKind = "insert" | "update" | "remove" | "clear" | "custom";
101
+ interface StoreEvent {
102
+ seq: number;
103
+ /** Virtual clock ms when it happened. */
104
+ t: number;
105
+ kind: StoreEventKind;
106
+ collection: string;
107
+ id?: string;
108
+ record?: Record_;
109
+ previous?: Record_;
110
+ /** For `custom` events. */
111
+ name?: string;
112
+ data?: unknown;
113
+ }
114
+ type StoreListener = (event: StoreEvent) => void;
115
+ declare class Store {
116
+ private now;
117
+ private logLimit;
118
+ private collections;
119
+ private listeners;
120
+ private log;
121
+ private seq;
122
+ constructor(now: () => number, logLimit?: number);
123
+ collection(name: string): Map<string, Record_>;
124
+ collections_(): string[];
125
+ insert<T extends Record_>(collection: string, record: T): T;
126
+ upsert<T extends Record_>(collection: string, record: T): T;
127
+ get<T extends Record_ = Record_>(collection: string, id: string): T | undefined;
128
+ list<T extends Record_ = Record_>(collection: string, opts?: {
129
+ where?: (r: T) => boolean;
130
+ sort?: (a: T, b: T) => number;
131
+ offset?: number;
132
+ limit?: number;
133
+ }): T[];
134
+ count(collection: string): number;
135
+ update<T extends Record_ = Record_>(collection: string, id: string, patch: Partial<T> | ((current: T) => T)): T | undefined;
136
+ remove(collection: string, id: string): Record_ | undefined;
137
+ clear(collection?: string): void;
138
+ /** Application-level event (not tied to a record), e.g. "job.progress". */
139
+ custom(collection: string, name: string, data?: unknown, id?: string): void;
140
+ on(listener: StoreListener): () => boolean;
141
+ events(opts?: {
142
+ since?: number;
143
+ collection?: string;
144
+ limit?: number;
145
+ }): StoreEvent[];
146
+ get lastSeq(): number;
147
+ snapshot(): Record<string, Record_[]>;
148
+ counts(): Record<string, number>;
149
+ private emit;
150
+ }
151
+
152
+ /**
153
+ * Streams: topics with per-topic monotonic event ids and bounded replay, plus
154
+ * SSE and WebSocket routes whose connections the control plane can list,
155
+ * pause, disconnect or hard-drop. Transports are pluggable — a Bun/Node
156
+ * server, Vite's http server, or an in-page fake WebSocket — and the scenario
157
+ * code never sees the difference.
158
+ */
159
+
160
+ interface TopicEvent {
161
+ topic: string;
162
+ /** Monotonic per topic, as a decimal string (matches how most protocols carry it). */
163
+ eventID: string;
164
+ t: number;
165
+ data: Record<string, unknown>;
166
+ }
167
+ declare class TopicLog {
168
+ private now;
169
+ readonly retain: number;
170
+ private seqs;
171
+ private logs;
172
+ constructor(now: () => number, retain?: number);
173
+ publish(topic: string, data: Record<string, unknown>): TopicEvent;
174
+ last(topic: string): string;
175
+ /**
176
+ * Events after `since`. `missed` is true when `since` predates the retained
177
+ * window. `null`/`undefined` means "no resume point": live only, nothing is
178
+ * replayed (what a fresh EventSource without Last-Event-ID gets). Pass "0"
179
+ * to replay everything retained.
180
+ */
181
+ replay(topic: string, since: string | number | null | undefined): {
182
+ events: TopicEvent[];
183
+ missed: boolean;
184
+ };
185
+ topics(): string[];
186
+ clear(): void;
187
+ }
188
+ interface StreamContext {
189
+ request: Request;
190
+ url: URL;
191
+ params: Record<string, string>;
192
+ query: URLSearchParams;
193
+ state: Store;
194
+ rng: Rng;
195
+ clock: VirtualClock;
196
+ streams: StreamHub;
197
+ run: {
198
+ id: string;
199
+ scenario: string;
200
+ seed: string;
201
+ };
202
+ log(message: string, data?: unknown): void;
203
+ }
204
+ /** What a transport must provide for one WebSocket connection. */
205
+ interface SocketTransport {
206
+ send(data: string | ArrayBuffer): void;
207
+ /** Graceful close with a close frame. */
208
+ close(code?: number, reason?: string): void;
209
+ /** Hard drop: no close frame; the client sees an abnormal close (1006). */
210
+ drop(): void;
211
+ }
212
+ type Serializer = (event: TopicEvent) => string;
213
+ interface Subscription {
214
+ topic: string;
215
+ serialize: Serializer;
216
+ }
217
+ interface ConnectionBase {
218
+ id: string;
219
+ kind: "ws" | "sse";
220
+ path: string;
221
+ url: URL;
222
+ openedAt: number;
223
+ sent: number;
224
+ received: number;
225
+ paused: boolean;
226
+ subscriptions: Map<string, Subscription>;
227
+ meta: Record<string, unknown>;
228
+ }
229
+ interface SimSocket extends ConnectionBase {
230
+ kind: "ws";
231
+ protocol: string | null;
232
+ readyState: "open" | "closed";
233
+ send(data: string | ArrayBuffer | Record<string, unknown>): void;
234
+ close(code?: number, reason?: string): void;
235
+ drop(): void;
236
+ /** Replay `since` then stay subscribed; returns what was replayed. */
237
+ subscribe(topic: string, opts?: {
238
+ since?: string | number | null;
239
+ serialize?: Serializer;
240
+ }): {
241
+ replayed: number;
242
+ missed: boolean;
243
+ last: string;
244
+ };
245
+ unsubscribe(topic: string): void;
246
+ onClose(cb: (code: number, reason: string) => void): void;
247
+ }
248
+ interface SimSseStream extends ConnectionBase {
249
+ kind: "sse";
250
+ lastEventId: string | null;
251
+ /** Send one SSE message. Objects are JSON-encoded. */
252
+ send(data: unknown, opts?: {
253
+ event?: string;
254
+ id?: string;
255
+ retry?: number;
256
+ }): void;
257
+ comment(text: string): void;
258
+ close(): void;
259
+ drop(): void;
260
+ subscribe(topic: string, opts?: {
261
+ since?: string | number | null;
262
+ event?: (e: TopicEvent) => string;
263
+ }): {
264
+ replayed: number;
265
+ missed: boolean;
266
+ last: string;
267
+ };
268
+ unsubscribe(topic: string): void;
269
+ onClose(cb: () => void): void;
270
+ /** The Response to return to the client. */
271
+ response: Response;
272
+ }
273
+ interface WsRoute {
274
+ kind: "ws";
275
+ path: string;
276
+ name?: string;
277
+ /** Subprotocols the server will select from (first match with the client's list wins). */
278
+ protocols?: string[];
279
+ onOpen?(ctx: StreamContext, socket: SimSocket): void | Promise<void>;
280
+ onMessage?(ctx: StreamContext, socket: SimSocket, data: string | ArrayBuffer): void | Promise<void>;
281
+ onClose?(ctx: StreamContext, socket: SimSocket, code: number, reason: string): void;
282
+ }
283
+ interface SseRoute {
284
+ kind: "sse";
285
+ path: string;
286
+ name?: string;
287
+ /** Some APIs open SSE with POST (request body = the prompt). Default GET. */
288
+ method?: "GET" | "POST";
289
+ /** Keepalive comment interval in virtual ms (default 15000; 0 disables). */
290
+ keepaliveMs?: number;
291
+ onOpen(ctx: StreamContext, stream: SimSseStream): void | Promise<void>;
292
+ }
293
+ type StreamRoute = WsRoute | SseRoute;
294
+ declare const ws: (path: string, def: Omit<WsRoute, "kind" | "path">) => WsRoute;
295
+ declare const sse: (path: string, onOpen: SseRoute["onOpen"], def?: Omit<SseRoute, "kind" | "path" | "onOpen">) => SseRoute;
296
+ interface ConnectionSummary {
297
+ id: string;
298
+ kind: "ws" | "sse";
299
+ path: string;
300
+ openedAt: number;
301
+ sent: number;
302
+ received: number;
303
+ paused: boolean;
304
+ topics: string[];
305
+ protocol?: string | null;
306
+ meta: Record<string, unknown>;
307
+ }
308
+ declare class StreamHub {
309
+ private clock;
310
+ private log;
311
+ readonly topics: TopicLog;
312
+ private connections;
313
+ private nextId;
314
+ /** Extra virtual latency applied to every outgoing stream message. */
315
+ latencyMs: number;
316
+ constructor(clock: VirtualClock, log: (message: string, data?: unknown) => void);
317
+ /** Publish to a topic: appended to the replay log and fanned out to subscribers. */
318
+ publish(topic: string, data: Record<string, unknown>): TopicEvent;
319
+ private deliver;
320
+ private deliverSse;
321
+ /** Called by a transport adapter once the WebSocket handshake completed. */
322
+ openSocket(route: WsRoute, ctx: StreamContext, transport: SocketTransport, protocol: string | null): SimSocket;
323
+ /** Transport → hub: a client frame arrived. */
324
+ receive(route: WsRoute, ctx: StreamContext, socket: SimSocket, data: string | ArrayBuffer): void;
325
+ /** Transport → hub: the client closed. */
326
+ clientClosed(route: WsRoute, ctx: StreamContext, socket: SimSocket, code: number, reason: string): void;
327
+ private finish;
328
+ /** Build the SSE Response for a route; the adapter just returns it. */
329
+ openSse(route: SseRoute, ctx: StreamContext): SimSseStream;
330
+ list(): ConnectionSummary[];
331
+ get(id: string): SimSocket | SimSseStream | null;
332
+ /**
333
+ * Disconnect connections: by id, by topic, by path, or all. `drop` cuts the
334
+ * connection without a close frame (what a network blip looks like).
335
+ */
336
+ disconnect(target: {
337
+ id?: string;
338
+ topic?: string;
339
+ path?: string;
340
+ all?: boolean;
341
+ }, opts?: {
342
+ code?: number;
343
+ reason?: string;
344
+ drop?: boolean;
345
+ }): string[];
346
+ /** Pause delivery on a connection (messages queue); resume flushes them in order. */
347
+ pause(id: string, paused: boolean): boolean;
348
+ closeAll(): void;
349
+ }
350
+
351
+ /**
352
+ * HTTP routing on Web `Request`/`Response`, so the same scenario runs on a
353
+ * Bun/Node server, inside Vite's dev server, or entirely in the browser.
354
+ */
355
+
356
+ type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "*";
357
+ interface RouteContext {
358
+ request: Request;
359
+ url: URL;
360
+ method: string;
361
+ /** `:name` captures. */
362
+ params: Record<string, string>;
363
+ query: URLSearchParams;
364
+ /** Parsed JSON body (or `{}` when absent/invalid). */
365
+ body<T = Record<string, unknown>>(): Promise<T>;
366
+ state: Store;
367
+ rng: Rng;
368
+ clock: VirtualClock;
369
+ streams: StreamHub;
370
+ /** Run id and scenario name, for logging or per-run behaviour. */
371
+ run: {
372
+ id: string;
373
+ scenario: string;
374
+ seed: string;
375
+ };
376
+ /** Per-route call counter (1-based) — handy for "fail on the 3rd call". */
377
+ calls: number;
378
+ log(message: string, data?: unknown): void;
379
+ }
380
+ type Handler = (ctx: RouteContext) => Response | Promise<Response>;
381
+ interface Route {
382
+ method: Method;
383
+ path: string;
384
+ handler: Handler;
385
+ /** Optional label for logs and the control plane. */
386
+ name?: string;
387
+ }
388
+ interface CompiledRoute extends Route {
389
+ match: (pathname: string) => Record<string, string> | null;
390
+ calls: number;
391
+ }
392
+ declare function json(body: unknown, init?: ResponseInit & {
393
+ status?: number;
394
+ }): Response;
395
+ declare function text(body: string, init?: ResponseInit): Response;
396
+ declare function empty(status?: number, init?: ResponseInit): Response;
397
+ /** RFC 9457 problem details, the shape most APIs use for errors. */
398
+ declare function problem(status: number, detail: string, extra?: Record<string, unknown>): Response;
399
+ type MalformedKind = "invalid-json" | "wrong-content-type" | "truncated" | "empty-200" | "html-500" | "schema-drift";
400
+ /** Deliberately broken responses for exercising client validation paths. */
401
+ declare function malformed(kind: MalformedKind, extra?: unknown): Response;
402
+ /**
403
+ * Cycle through responses call by call: `sequence([ok, fail, ok])` answers
404
+ * ok, fail, ok, then repeats the last unless `loop` is set. Each entry may be a
405
+ * Response factory or a handler.
406
+ */
407
+ declare function sequence(steps: Array<Handler | Response>, opts?: {
408
+ loop?: boolean;
409
+ }): Handler;
410
+ /** Wrap a handler so it answers after `ms` virtual milliseconds. */
411
+ declare function delayed(ms: number | ((ctx: RouteContext) => number), handler: Handler): Handler;
412
+ declare function compileRoute(route: Route): CompiledRoute;
413
+ declare function statusText(status: number): string;
414
+ /** Route helpers so scenario files read like a table. */
415
+ declare const route: {
416
+ get: (path: string, handler: Handler, name?: string) => Route;
417
+ post: (path: string, handler: Handler, name?: string) => Route;
418
+ put: (path: string, handler: Handler, name?: string) => Route;
419
+ patch: (path: string, handler: Handler, name?: string) => Route;
420
+ delete: (path: string, handler: Handler, name?: string) => Route;
421
+ any: (path: string, handler: Handler, name?: string) => Route;
422
+ };
423
+ /**
424
+ * A full CRUD resource in one line:
425
+ * crud("/api/notes", "notes", { create: (body, ctx) => ({ id: ctx.rng.id("note"), ...body }) })
426
+ * Emits store events on every mutation, which stream routes can forward.
427
+ */
428
+ declare function crud<T extends Record<string, unknown> & {
429
+ id: string;
430
+ }>(path: string, collection: string, opts: {
431
+ create: (body: Record<string, unknown>, ctx: RouteContext) => T;
432
+ update?: (current: T, body: Record<string, unknown>, ctx: RouteContext) => T;
433
+ list?: (items: T[], ctx: RouteContext) => unknown;
434
+ sort?: (a: T, b: T) => number;
435
+ validate?: (body: Record<string, unknown>, ctx: RouteContext) => string | null;
436
+ }): Route[];
437
+
438
+ /**
439
+ * Fault layer: latency (+ seeded jitter), fail modes and per-endpoint
440
+ * overrides. Overrides are checked before routing, so a test can force any
441
+ * endpoint — even one the scenario never defined — to answer with a given
442
+ * status/body, once (`times`) or until cleared — the classic in-bundle mock
443
+ * "endpoint override" contract, generalised.
444
+ */
445
+
446
+ type FailMode = "off" | "data" | "all";
447
+ interface OverrideInput {
448
+ /** Substring of the path, or `{ regex, flags }`. */
449
+ matcher: string | {
450
+ regex: string;
451
+ flags?: string;
452
+ };
453
+ method?: string;
454
+ status?: number;
455
+ body?: unknown;
456
+ /** Auto-expire after N matches. */
457
+ times?: number;
458
+ delayMs?: number;
459
+ malformed?: MalformedKind;
460
+ name?: string;
461
+ }
462
+ interface Override extends OverrideInput {
463
+ id: string;
464
+ remaining: number;
465
+ hits: number;
466
+ }
467
+ interface FaultSnapshot {
468
+ latencyMs: number;
469
+ jitterMs: number;
470
+ failMode: FailMode;
471
+ shellPaths: string[];
472
+ overrides: Override[];
473
+ }
474
+ declare class FaultLayer {
475
+ private rng;
476
+ latencyMs: number;
477
+ jitterMs: number;
478
+ failMode: FailMode;
479
+ /** Paths the `data` fail mode leaves alone so the app shell still boots. */
480
+ shellPaths: Set<string>;
481
+ private overrides;
482
+ private nextId;
483
+ constructor(rng: Rng);
484
+ configure(patch: Partial<{
485
+ latencyMs: number;
486
+ jitterMs: number;
487
+ failMode: FailMode;
488
+ shellPaths: string[];
489
+ }>): void;
490
+ /** Effective delay for one request (seeded jitter keeps it reproducible). */
491
+ delayFor(extra?: number): number;
492
+ setOverride(input: OverrideInput): Override;
493
+ clearOverride(target: string | OverrideInput["matcher"], method?: string): boolean;
494
+ clearOverrides(): void;
495
+ listOverrides(): Override[];
496
+ /** Find (and consume) the first matching override. */
497
+ matchOverride(path: string, method: string): Override | null;
498
+ /** Build the response an override dictates. */
499
+ overrideResponse(o: Override): Response;
500
+ /** Fail-mode response, or null when the request should proceed. */
501
+ failResponse(path: string): Response | null;
502
+ snapshot(): FaultSnapshot;
503
+ }
504
+
505
+ interface SetupContext {
506
+ state: Store;
507
+ rng: Rng;
508
+ clock: VirtualClock;
509
+ streams: StreamHub;
510
+ run: {
511
+ id: string;
512
+ scenario: string;
513
+ seed: string;
514
+ };
515
+ log(message: string, data?: unknown): void;
516
+ }
517
+ interface ActionContext extends SetupContext {
518
+ args: Record<string, unknown>;
519
+ }
520
+ interface ScenarioDefinition {
521
+ /** URL-safe identifier. */
522
+ name: string;
523
+ label?: string;
524
+ description?: string;
525
+ /** Default seed (default: the scenario name). Runs may override. */
526
+ seed?: string;
527
+ clock?: {
528
+ mode?: ClockMode;
529
+ speed?: number;
530
+ };
531
+ faults?: {
532
+ latencyMs?: number;
533
+ jitterMs?: number;
534
+ failMode?: FailMode;
535
+ shellPaths?: string[];
536
+ };
537
+ tags?: string[];
538
+ /** Seed the store. Runs once per run creation/reset, with the run's RNG. */
539
+ setup?(ctx: SetupContext): void | Promise<void>;
540
+ routes?: Route[];
541
+ streams?: StreamRoute[];
542
+ /**
543
+ * Named actions exposed as `POST /__sim/action {name,args}` — and therefore
544
+ * as `scenario.action` in the pulse panel/CLI. Use them for "emit a burst",
545
+ * "complete the running job", "drop the stream mid-turn".
546
+ */
547
+ actions?: Record<string, (ctx: ActionContext) => unknown | Promise<unknown>>;
548
+ /** Hints for QA harnesses (routes worth rendering, expected error states…). */
549
+ qa?: Record<string, unknown>;
550
+ }
551
+ declare function defineScenario(def: ScenarioDefinition): ScenarioDefinition;
552
+ interface RunLogEntry {
553
+ seq: number;
554
+ t: number;
555
+ message: string;
556
+ data?: unknown;
557
+ }
558
+ declare class Run {
559
+ readonly id: string;
560
+ readonly scenario: ScenarioDefinition;
561
+ readonly seed: string;
562
+ private readonly sink?;
563
+ readonly rng: Rng;
564
+ readonly clock: VirtualClock;
565
+ readonly state: Store;
566
+ readonly streams: StreamHub;
567
+ readonly faults: FaultLayer;
568
+ readonly routes: CompiledRoute[];
569
+ readonly wsRoutes: WsRoute[];
570
+ readonly sseRoutes: SseRoute[];
571
+ readonly createdWall: number;
572
+ requests: number;
573
+ private logEntries;
574
+ private logSeq;
575
+ ready: Promise<void>;
576
+ constructor(id: string, scenario: ScenarioDefinition, seed: string, sink?: ((line: string) => void) | undefined);
577
+ info(): {
578
+ id: string;
579
+ scenario: string;
580
+ seed: string;
581
+ };
582
+ setupContext(): SetupContext;
583
+ log(message: string, data?: unknown): void;
584
+ logs(opts?: {
585
+ since?: number;
586
+ limit?: number;
587
+ }): RunLogEntry[];
588
+ action(name: string, args?: Record<string, unknown>): Promise<unknown>;
589
+ status(): {
590
+ run: string;
591
+ scenario: string;
592
+ label: string | null;
593
+ seed: string;
594
+ createdWall: number;
595
+ requests: number;
596
+ clock: {
597
+ mode: ClockMode;
598
+ speed: number;
599
+ now: number;
600
+ wall: string;
601
+ pending: {
602
+ id: number;
603
+ at: number;
604
+ label: string;
605
+ }[];
606
+ };
607
+ state: {
608
+ collections: Record<string, number>;
609
+ lastSeq: number;
610
+ };
611
+ streams: ConnectionSummary[];
612
+ topics: {
613
+ topic: string;
614
+ last: string;
615
+ }[];
616
+ faults: FaultSnapshot;
617
+ routes: {
618
+ method: Method;
619
+ path: string;
620
+ calls: number;
621
+ name: string | null;
622
+ }[];
623
+ streamRoutes: ({
624
+ kind: string;
625
+ path: string;
626
+ protocols: string[];
627
+ } | {
628
+ kind: string;
629
+ path: string;
630
+ method: "GET" | "POST";
631
+ })[];
632
+ actions: string[];
633
+ };
634
+ dispose(): void;
635
+ }
636
+
637
+ export { type ActionContext as A, compileRoute as B, type ClockMode as C, crud as D, defineScenario as E, type FaultSnapshot as F, delayed as G, type Handler as H, empty as I, json as J, malformed as K, problem as L, type Method as M, route as N, type Override as O, sequence as P, sse as Q, Run as R, type ScenarioDefinition as S, type TopicEvent as T, statusText as U, VIRTUAL_EPOCH as V, type WsRoute as W, text as X, ws as Y, type StreamContext as a, type ConnectionSummary as b, type Record_ as c, type StoreEvent as d, type RunLogEntry as e, type OverrideInput as f, type FailMode as g, type ClockTimer as h, FaultLayer as i, type MalformedKind as j, Rng as k, type Route as l, type RouteContext as m, type Serializer as n, type SetupContext as o, type SimSocket as p, type SimSseStream as q, type SocketTransport as r, type SseRoute as s, Store as t, type StoreEventKind as u, type StoreListener as v, StreamHub as w, type StreamRoute as x, TopicLog as y, VirtualClock as z };
@@ -0,0 +1,29 @@
1
+ import * as ws from 'ws';
2
+ import { WebSocket } from 'ws';
3
+ import { Server, IncomingMessage, ServerResponse } from 'node:http';
4
+ import { S as Simulator } from './engine-2w32ngB2.js';
5
+
6
+ declare function toWebRequest(req: IncomingMessage, base: string): Request;
7
+ declare function sendWebResponse(res: ServerResponse, response: Response): Promise<undefined>;
8
+ /**
9
+ * Wire WebSocket upgrades on an http server to the simulator's ws routes.
10
+ * Returns the UpgradeHook to pass into `sim.handle(request, { upgrade })`.
11
+ */
12
+ declare function attachWebSockets(sim: Simulator, server: Server, opts?: {
13
+ base: () => string;
14
+ match?: (pathname: string) => boolean;
15
+ }): ws.Server<typeof WebSocket, typeof IncomingMessage>;
16
+ interface ServeOptions {
17
+ port?: number;
18
+ host?: string;
19
+ log?: (line: string) => void;
20
+ }
21
+ declare function serveSimulator(sim: Simulator, options?: ServeOptions): Promise<{
22
+ server: Server<typeof IncomingMessage, typeof ServerResponse>;
23
+ port: number;
24
+ url: string;
25
+ controlUrl: string;
26
+ close: () => Promise<void>;
27
+ }>;
28
+
29
+ export { type ServeOptions, attachWebSockets, sendWebResponse, serveSimulator, toWebRequest };
package/dist/server.js ADDED
@@ -0,0 +1,14 @@
1
+ import {
2
+ attachWebSockets,
3
+ sendWebResponse,
4
+ serveSimulator,
5
+ toWebRequest
6
+ } from "./chunk-7HBAY3QY.js";
7
+ import "./chunk-KU4W4SKO.js";
8
+ export {
9
+ attachWebSockets,
10
+ sendWebResponse,
11
+ serveSimulator,
12
+ toWebRequest
13
+ };
14
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}