@crvouga/mockingbird-service-livekit 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,2963 @@
1
+ // ../core/dist/clock.js
2
+ var createClock = (source = Date.now) => {
3
+ let offsetMs = 0;
4
+ let frozenAt;
5
+ const now = () => frozenAt ?? source() + offsetMs;
6
+ return {
7
+ now,
8
+ set: (epochMs) => {
9
+ if (frozenAt !== void 0)
10
+ frozenAt = epochMs;
11
+ else
12
+ offsetMs = epochMs - source();
13
+ },
14
+ advance: (deltaMs) => {
15
+ if (frozenAt !== void 0)
16
+ frozenAt += deltaMs;
17
+ else
18
+ offsetMs += deltaMs;
19
+ },
20
+ freeze: () => {
21
+ frozenAt = now();
22
+ },
23
+ unfreeze: () => {
24
+ if (frozenAt === void 0)
25
+ return;
26
+ offsetMs = frozenAt - source();
27
+ frozenAt = void 0;
28
+ },
29
+ reset: () => {
30
+ offsetMs = 0;
31
+ frozenAt = void 0;
32
+ },
33
+ state: () => ({ now: now(), frozen: frozenAt !== void 0, offsetMs })
34
+ };
35
+ };
36
+
37
+ // ../core/dist/collection.js
38
+ var Collection = class {
39
+ sqlite;
40
+ namespace;
41
+ name;
42
+ constructor(sqlite, namespace, name) {
43
+ this.sqlite = sqlite;
44
+ this.namespace = namespace;
45
+ this.name = name;
46
+ }
47
+ bumpCollectionSeq() {
48
+ const row = this.sqlite.prepare("SELECT value FROM mockingbird_sequences WHERE namespace = ? AND name = ? AND kind = 'collection'").get(this.namespace, this.name);
49
+ const next = (row?.value ?? 0) + 1;
50
+ this.sqlite.prepare(`INSERT INTO mockingbird_sequences (namespace, name, kind, value) VALUES (?, ?, 'collection', ?)
51
+ ON CONFLICT(namespace, name, kind) DO UPDATE SET value = excluded.value`).run(this.namespace, this.name, next);
52
+ return next;
53
+ }
54
+ nextSequence() {
55
+ return this.sqlite.transaction(() => this.bumpCollectionSeq());
56
+ }
57
+ get(id) {
58
+ const row = this.sqlite.prepare("SELECT value FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.name, id);
59
+ if (!row)
60
+ return void 0;
61
+ return JSON.parse(row.value).value;
62
+ }
63
+ has(id) {
64
+ const row = this.sqlite.prepare("SELECT 1 AS ok FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.name, id);
65
+ return row !== void 0;
66
+ }
67
+ /** Insert a new record, assigning it the next sequence number. */
68
+ insert(id, value) {
69
+ return this.sqlite.transaction(() => {
70
+ const seq = this.bumpCollectionSeq();
71
+ const stored = { seq, value };
72
+ this.sqlite.prepare(`INSERT INTO mockingbird_records (namespace, collection, id, seq, value)
73
+ VALUES (?, ?, ?, ?, ?)
74
+ ON CONFLICT(namespace, collection, id) DO UPDATE SET seq = excluded.seq, value = excluded.value`).run(this.namespace, this.name, id, seq, JSON.stringify(stored));
75
+ return stored;
76
+ });
77
+ }
78
+ /** Replace an existing record's value, keeping its position. */
79
+ update(id, value) {
80
+ return this.sqlite.transaction(() => {
81
+ const row = this.sqlite.prepare("SELECT seq, value FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.name, id);
82
+ if (!row)
83
+ return void 0;
84
+ const stored = { seq: row.seq, value };
85
+ this.sqlite.prepare("UPDATE mockingbird_records SET value = ? WHERE namespace = ? AND collection = ? AND id = ?").run(JSON.stringify(stored), this.namespace, this.name, id);
86
+ return stored;
87
+ });
88
+ }
89
+ delete(id) {
90
+ const result = this.sqlite.prepare("DELETE FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").run(this.namespace, this.name, id);
91
+ return result.changes > 0;
92
+ }
93
+ /** How many records the collection holds, without reading them. */
94
+ count() {
95
+ const row = this.sqlite.prepare("SELECT COUNT(*) AS n FROM mockingbird_records WHERE namespace = ? AND collection = ?").get(this.namespace, this.name);
96
+ return Number(row?.n ?? 0);
97
+ }
98
+ list(options = {}) {
99
+ const rows = this.sqlite.prepare("SELECT id, seq, value FROM mockingbird_records WHERE namespace = ? AND collection = ?").all(this.namespace, this.name);
100
+ const out = [];
101
+ for (const row of rows) {
102
+ const stored = JSON.parse(row.value);
103
+ if (options.where && !options.where(stored.value, stored.seq))
104
+ continue;
105
+ out.push({ id: row.id, seq: stored.seq, value: stored.value });
106
+ }
107
+ out.sort((a, b) => options.order === "oldest" ? a.seq - b.seq : b.seq - a.seq);
108
+ return out;
109
+ }
110
+ };
111
+
112
+ // ../core/dist/control.js
113
+ var HEALTH_PATH = "/health";
114
+ var ADMIN_PREFIX = "/__admin";
115
+ var ADMIN_KEY_HEADER = "x-mockingbird-admin-key";
116
+ var NAMESPACE_HEADER = "x-mockingbird-namespace";
117
+ var json = (status, body) => new Response(JSON.stringify(body), {
118
+ status,
119
+ headers: { "content-type": "application/json" }
120
+ });
121
+ var adminError = (status, message) => json(status, { error: { type: "mockingbird_admin", message } });
122
+ var UNITS = {
123
+ ms: 1,
124
+ s: 1e3,
125
+ m: 6e4,
126
+ h: 36e5,
127
+ d: 864e5
128
+ };
129
+ var parseDuration = (value) => {
130
+ if (typeof value === "number" && Number.isFinite(value))
131
+ return value;
132
+ if (typeof value !== "string")
133
+ return void 0;
134
+ const match2 = /^(-?\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/.exec(value.trim());
135
+ if (!match2)
136
+ return void 0;
137
+ return Number(match2[1]) * UNITS[match2[2]];
138
+ };
139
+ var parseInstant = (value) => {
140
+ if (typeof value === "number" && Number.isFinite(value))
141
+ return value;
142
+ if (typeof value !== "string")
143
+ return void 0;
144
+ const parsed = Date.parse(value);
145
+ return Number.isNaN(parsed) ? void 0 : parsed;
146
+ };
147
+ var matchRoute = (pattern, path) => {
148
+ const want = pattern.split("/").filter(Boolean);
149
+ const have = path.split("/").filter(Boolean);
150
+ if (want.length !== have.length)
151
+ return void 0;
152
+ const params = {};
153
+ for (let i = 0; i < want.length; i++) {
154
+ const segment = want[i];
155
+ const actual = have[i];
156
+ if (segment.startsWith(":"))
157
+ params[segment.slice(1)] = decodeURIComponent(actual);
158
+ else if (segment !== actual)
159
+ return void 0;
160
+ }
161
+ return params;
162
+ };
163
+ var readJson = async (request) => {
164
+ const text = await request.text();
165
+ if (text.trim() === "")
166
+ return void 0;
167
+ return JSON.parse(text);
168
+ };
169
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
170
+ var createControlPlane = (context) => {
171
+ const snapshots = /* @__PURE__ */ new Map();
172
+ let snapshotCounter = 0;
173
+ const headerNamespace = (request) => request.headers.get(NAMESPACE_HEADER) ?? context.defaultNamespace;
174
+ const adminNamespace = (request, url) => url.searchParams.get("namespace") ?? headerNamespace(request);
175
+ const builtin = {
176
+ "GET /": () => json(200, {
177
+ service: context.name,
178
+ routes: [...Object.keys(builtin), ...Object.keys(context.routes)].sort()
179
+ }),
180
+ "POST /reset": async ({ url, namespace }) => {
181
+ const target = url.searchParams.get("all") === "1" ? "*" : namespace;
182
+ await context.reset(target);
183
+ return json(200, { status: "ok", reset: target === "*" ? context.namespaces() : [target] });
184
+ },
185
+ "GET /namespaces": () => json(200, { default: context.defaultNamespace, namespaces: context.namespaces() }),
186
+ "GET /clock": () => json(200, context.clock.state()),
187
+ "POST /clock": ({ body }) => {
188
+ if (!isRecord(body))
189
+ return adminError(400, "expected a JSON object");
190
+ if (body.reset === true)
191
+ context.clock.reset();
192
+ if (body.set !== void 0) {
193
+ const instant = parseInstant(body.set);
194
+ if (instant === void 0)
195
+ return adminError(400, "set: expected epoch ms or ISO-8601");
196
+ context.clock.set(instant);
197
+ }
198
+ if (body.advance !== void 0) {
199
+ const delta = parseDuration(body.advance);
200
+ if (delta === void 0)
201
+ return adminError(400, 'advance: expected ms or "15m"-style');
202
+ context.clock.advance(delta);
203
+ }
204
+ if (body.freeze === true)
205
+ context.clock.freeze();
206
+ if (body.freeze === false)
207
+ context.clock.unfreeze();
208
+ return json(200, context.clock.state());
209
+ },
210
+ "GET /faults": () => json(200, { faults: context.faults.list() }),
211
+ "POST /faults": ({ body, namespace }) => {
212
+ if (isRecord(body) && typeof body.preset === "string") {
213
+ if (!context.applyPreset)
214
+ return adminError(400, `${context.name} has no fault presets`);
215
+ const { preset, ...overrides } = body;
216
+ try {
217
+ return json(201, {
218
+ preset,
219
+ rules: context.applyPreset(preset, namespace, overrides)
220
+ });
221
+ } catch (error2) {
222
+ return adminError(404, error2 instanceof Error ? error2.message : String(error2));
223
+ }
224
+ }
225
+ if (!isRecord(body) || typeof body.status !== "number" && typeof body.delayMs !== "number" && typeof body.latencyMs !== "number" && body.drop !== true && typeof body.effect !== "string") {
226
+ return adminError(400, "a fault needs a numeric status, a delayMs/latencyMs, drop: true, an effect, or a preset");
227
+ }
228
+ const rule = {
229
+ // Scoped to the caller's namespace unless it asks for every one, so one worker's
230
+ // injected failure never lands on another's request.
231
+ namespace,
232
+ ...body,
233
+ id: typeof body.id === "string" ? body.id : `fault_${context.faults.list().length + 1}`
234
+ };
235
+ return json(201, context.faults.add(rule));
236
+ },
237
+ "DELETE /faults": ({ url }) => {
238
+ const id = url.searchParams.get("id");
239
+ if (id === null) {
240
+ context.faults.clear();
241
+ return json(200, { status: "ok" });
242
+ }
243
+ return context.faults.remove(id) ? json(200, { status: "ok" }) : adminError(404, `no fault ${id}`);
244
+ },
245
+ "POST /snapshots": ({ namespace }) => {
246
+ const point = context.timeTravel.checkpoint(namespace, "main");
247
+ context.timeTravel.retain(namespace, point.id);
248
+ snapshotCounter++;
249
+ const id = `snap_${snapshotCounter}`;
250
+ snapshots.set(id, { namespace, checkpoint: point.id });
251
+ return json(201, { id, namespace, records: point.records ?? 0 });
252
+ },
253
+ "POST /snapshots/:id/restore": ({ params, namespace }) => {
254
+ const alias = snapshots.get(params.id);
255
+ if (!alias)
256
+ return adminError(404, `no snapshot ${params.id}`);
257
+ if (alias.namespace !== namespace) {
258
+ return adminError(409, `snapshot ${params.id} belongs to namespace ${alias.namespace}`);
259
+ }
260
+ context.timeTravel.checkout(alias.checkpoint, { namespace, branch: "main" });
261
+ return json(200, { status: "ok", id: params.id, namespace });
262
+ },
263
+ "DELETE /snapshots/:id": ({ params }) => {
264
+ const id = params.id;
265
+ const alias = snapshots.get(id);
266
+ if (!alias)
267
+ return adminError(404, `no snapshot ${id}`);
268
+ snapshots.delete(id);
269
+ context.timeTravel.release(alias.namespace, alias.checkpoint);
270
+ return json(200, { status: "ok" });
271
+ },
272
+ "GET /timeline": ({ namespace }) => json(200, context.timeTravel.inspect(namespace)),
273
+ "POST /checkpoints": ({ body, namespace }) => {
274
+ const branch = isRecord(body) && typeof body.branch === "string" ? body.branch : "main";
275
+ try {
276
+ return json(201, context.timeTravel.checkpoint(namespace, branch));
277
+ } catch (error2) {
278
+ return adminError(409, error2 instanceof Error ? error2.message : String(error2));
279
+ }
280
+ },
281
+ "POST /branches/:name": ({ params, body, namespace }) => {
282
+ const at = isRecord(body) && typeof body.at === "string" ? body.at : void 0;
283
+ try {
284
+ return json(201, context.timeTravel.branch(params.name, {
285
+ namespace,
286
+ ...at !== void 0 ? { at } : {}
287
+ }));
288
+ } catch (error2) {
289
+ return adminError(409, error2 instanceof Error ? error2.message : String(error2));
290
+ }
291
+ },
292
+ "POST /branches/:name/checkout": ({ params, body, namespace }) => {
293
+ if (!isRecord(body) || typeof body.checkpoint !== "string") {
294
+ return adminError(400, 'expected {"checkpoint":"cp_..."}');
295
+ }
296
+ try {
297
+ context.timeTravel.checkout(body.checkpoint, {
298
+ namespace,
299
+ branch: params.name
300
+ });
301
+ return json(200, { status: "ok", branch: params.name, checkpoint: body.checkpoint });
302
+ } catch (error2) {
303
+ return adminError(409, error2 instanceof Error ? error2.message : String(error2));
304
+ }
305
+ },
306
+ "GET /requests": ({ url, namespace }) => {
307
+ const status = url.searchParams.get("status");
308
+ const since = url.searchParams.get("since");
309
+ const limit = url.searchParams.get("limit");
310
+ const sinceMs = since === null ? void 0 : parseInstant(/^\d+$/.test(since) ? Number(since) : since);
311
+ if (since !== null && sinceMs === void 0) {
312
+ return adminError(400, "since: expected epoch ms or ISO-8601");
313
+ }
314
+ if (status !== null && !/^\d{3}$/.test(status))
315
+ return adminError(400, "status: expected an HTTP status");
316
+ if (limit !== null && !/^\d+$/.test(limit))
317
+ return adminError(400, "limit: expected a count");
318
+ const operationId = url.searchParams.get("operationId");
319
+ const everyNamespace = url.searchParams.get("all") === "1";
320
+ return json(200, {
321
+ size: context.journal.size,
322
+ requests: context.journal.list({
323
+ ...everyNamespace ? {} : { namespace },
324
+ ...operationId !== null ? { operationId } : {},
325
+ ...status !== null ? { status: Number(status) } : {},
326
+ ...sinceMs !== void 0 ? { since: sinceMs } : {},
327
+ ...limit !== null ? { limit: Number(limit) } : {}
328
+ })
329
+ });
330
+ },
331
+ "DELETE /requests": ({ url, namespace }) => {
332
+ context.journal.clear(url.searchParams.get("all") === "1" ? void 0 : namespace);
333
+ return json(200, { status: "ok" });
334
+ },
335
+ "GET /metrics": () => json(200, context.metrics.report()),
336
+ "DELETE /metrics": () => {
337
+ context.metrics.reset();
338
+ return json(200, { status: "ok" });
339
+ }
340
+ };
341
+ const routes = [...Object.entries(context.routes), ...Object.entries(builtin)].map(([key, handler]) => {
342
+ const space = key.indexOf(" ");
343
+ return { method: key.slice(0, space), pattern: key.slice(space + 1), handler };
344
+ });
345
+ return {
346
+ namespaceOf: headerNamespace,
347
+ async handle(request) {
348
+ const url = new URL(request.url);
349
+ if (url.pathname === HEALTH_PATH && request.method === "GET") {
350
+ return json(200, {
351
+ status: "ok",
352
+ service: context.name,
353
+ uptimeMs: context.wallNow() - context.startedAt,
354
+ clock: context.clock.state(),
355
+ namespaces: context.namespaces().length,
356
+ ...context.describe()
357
+ });
358
+ }
359
+ if (url.pathname !== ADMIN_PREFIX && !url.pathname.startsWith(`${ADMIN_PREFIX}/`)) {
360
+ return void 0;
361
+ }
362
+ if (context.adminKey !== void 0 && request.headers.get(ADMIN_KEY_HEADER) !== context.adminKey) {
363
+ return adminError(401, `missing or wrong ${ADMIN_KEY_HEADER}`);
364
+ }
365
+ const path = url.pathname.slice(ADMIN_PREFIX.length) || "/";
366
+ for (const route of routes) {
367
+ if (route.method !== request.method)
368
+ continue;
369
+ const params = matchRoute(route.pattern, path);
370
+ if (!params)
371
+ continue;
372
+ let body;
373
+ try {
374
+ body = await readJson(request);
375
+ } catch {
376
+ return adminError(400, "request body is not valid JSON");
377
+ }
378
+ return route.handler({
379
+ request,
380
+ url,
381
+ params,
382
+ namespace: adminNamespace(request, url),
383
+ body
384
+ });
385
+ }
386
+ return adminError(404, `no admin route ${request.method} ${path}; GET ${ADMIN_PREFIX} lists them`);
387
+ }
388
+ };
389
+ };
390
+
391
+ // ../core/dist/credentials.js
392
+ var createCredentialRegistry = () => {
393
+ const map = /* @__PURE__ */ new Map();
394
+ return {
395
+ set: (credential, namespace) => {
396
+ map.set(credential, namespace);
397
+ },
398
+ get: (credential) => map.get(credential),
399
+ remove: (credential) => map.delete(credential),
400
+ clear: () => map.clear(),
401
+ entries: () => [...map].map(([credential, namespace]) => ({ credential, namespace })).sort((a, b) => a.credential.localeCompare(b.credential))
402
+ };
403
+ };
404
+ var maskCredential = (credential) => credential.length <= 8 ? `${credential.slice(0, 2)}\u2026` : `${credential.slice(0, 6)}\u2026${credential.slice(-2)}`;
405
+
406
+ // ../core/dist/rng.js
407
+ var seedFrom = (value) => {
408
+ let hash = 2166136261;
409
+ for (let i = 0; i < value.length; i++) {
410
+ hash ^= value.charCodeAt(i);
411
+ hash = Math.imul(hash, 16777619);
412
+ }
413
+ return hash >>> 0;
414
+ };
415
+ var createRng = (seed = 0) => {
416
+ const numeric = typeof seed === "string" ? seedFrom(seed) : seed >>> 0;
417
+ let state = numeric;
418
+ const next = () => {
419
+ state = state + 1831565813 >>> 0;
420
+ let t = state;
421
+ t = Math.imul(t ^ t >>> 15, t | 1);
422
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
423
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
424
+ };
425
+ return {
426
+ next,
427
+ int: (min, max) => min + Math.floor(next() * (max - min + 1)),
428
+ reset: () => {
429
+ state = numeric;
430
+ },
431
+ state: () => state,
432
+ setState: (next2) => {
433
+ if (!Number.isSafeInteger(next2) || next2 < 0 || next2 > 4294967295) {
434
+ throw new RangeError("rng state must be an unsigned 32-bit integer");
435
+ }
436
+ state = next2 >>> 0;
437
+ },
438
+ seed: numeric
439
+ };
440
+ };
441
+
442
+ // ../core/dist/faults.js
443
+ var matches = (rule, candidate) => {
444
+ if (rule.namespace !== void 0 && rule.namespace !== "*" && rule.namespace !== candidate.namespace) {
445
+ return false;
446
+ }
447
+ if (rule.operationId !== void 0 && rule.operationId !== candidate.operationId)
448
+ return false;
449
+ if (rule.method !== void 0 && rule.method.toUpperCase() !== candidate.method.toUpperCase()) {
450
+ return false;
451
+ }
452
+ if (rule.pathPrefix !== void 0 && !candidate.path.startsWith(rule.pathPrefix))
453
+ return false;
454
+ return true;
455
+ };
456
+ var faultResponse = (rule) => {
457
+ const status = rule.status ?? 500;
458
+ const headers = { "content-type": "application/json", ...rule.headers };
459
+ if (typeof rule.body === "string")
460
+ return new Response(rule.body, { status, headers });
461
+ if (rule.body === null)
462
+ return new Response(null, { status, headers: rule.headers ?? {} });
463
+ const body = rule.body === void 0 ? { detail: "Injected by Mockingbird" } : rule.body;
464
+ return new Response(JSON.stringify(body), { status, headers });
465
+ };
466
+ var createFaultRegistry = (rng = createRng(0), sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) => {
467
+ const entries = [];
468
+ return {
469
+ add(rule) {
470
+ const existing = entries.findIndex((e) => e.rule.id === rule.id);
471
+ const entry = { rule, remaining: rule.count ?? null, hits: 0 };
472
+ if (existing >= 0)
473
+ entries[existing] = entry;
474
+ else
475
+ entries.push(entry);
476
+ return rule;
477
+ },
478
+ list: () => entries.map((e) => ({ ...e.rule, remaining: e.remaining, hits: e.hits })),
479
+ remove(id) {
480
+ const index = entries.findIndex((e) => e.rule.id === id);
481
+ if (index < 0)
482
+ return false;
483
+ entries.splice(index, 1);
484
+ return true;
485
+ },
486
+ clear() {
487
+ entries.length = 0;
488
+ },
489
+ async take(candidate) {
490
+ const hits = [];
491
+ for (const entry of entries) {
492
+ if (entry.remaining === 0)
493
+ continue;
494
+ if (!matches(entry.rule, candidate))
495
+ continue;
496
+ const rate = entry.rule.rate ?? 1;
497
+ if (rng.next() >= rate)
498
+ continue;
499
+ entry.hits++;
500
+ if (entry.remaining !== null)
501
+ entry.remaining--;
502
+ const delay = entry.rule.delayMs ?? entry.rule.latencyMs;
503
+ if (delay !== void 0 && delay > 0) {
504
+ await sleep(delay);
505
+ }
506
+ const hit = { id: entry.rule.id };
507
+ if (entry.rule.effect !== void 0) {
508
+ hit.effect = { name: entry.rule.effect, params: entry.rule.params ?? {} };
509
+ }
510
+ if (entry.rule.drop === true)
511
+ hit.drop = true;
512
+ else if (entry.rule.status !== void 0)
513
+ hit.response = faultResponse(entry.rule);
514
+ hits.push(hit);
515
+ if (hit.drop || hit.response)
516
+ break;
517
+ }
518
+ return hits;
519
+ }
520
+ };
521
+ };
522
+
523
+ // ../../openapi/core/dist/refs.js
524
+ var OpenAPIReferenceError = class extends Error {
525
+ ref;
526
+ constructor(ref) {
527
+ super(`unresolvable $ref: ${ref}`);
528
+ this.ref = ref;
529
+ this.name = "OpenAPIReferenceError";
530
+ }
531
+ };
532
+ var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
533
+ var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
534
+ var resolveRef = (document2, ref) => {
535
+ if (!ref.startsWith("#/"))
536
+ throw new OpenAPIReferenceError(ref);
537
+ let cursor = document2;
538
+ for (const raw of ref.slice(2).split("/")) {
539
+ const segment = unescapePointer(raw);
540
+ if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
541
+ throw new OpenAPIReferenceError(ref);
542
+ }
543
+ cursor = cursor[segment];
544
+ }
545
+ if (cursor === void 0)
546
+ throw new OpenAPIReferenceError(ref);
547
+ return cursor;
548
+ };
549
+ var deref = (document2, value) => {
550
+ let current = value;
551
+ const seen = /* @__PURE__ */ new Set();
552
+ while (isReference(current)) {
553
+ if (seen.has(current.$ref))
554
+ throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
555
+ seen.add(current.$ref);
556
+ current = resolveRef(document2, current.$ref);
557
+ }
558
+ return current;
559
+ };
560
+
561
+ // ../../openapi/core/dist/types.js
562
+ var HTTP_METHODS = [
563
+ "get",
564
+ "put",
565
+ "post",
566
+ "delete",
567
+ "options",
568
+ "head",
569
+ "patch",
570
+ "trace"
571
+ ];
572
+
573
+ // ../../openapi/core/dist/document.js
574
+ var mergeParameters = (document2, item, own) => {
575
+ const merged = /* @__PURE__ */ new Map();
576
+ for (const raw of item.parameters ?? []) {
577
+ const parameter = deref(document2, raw);
578
+ merged.set(`${parameter.in}:${parameter.name}`, parameter);
579
+ }
580
+ for (const raw of own ?? []) {
581
+ const parameter = deref(document2, raw);
582
+ merged.set(`${parameter.in}:${parameter.name}`, parameter);
583
+ }
584
+ return [...merged.values()];
585
+ };
586
+ var listOperations = (document2) => {
587
+ const operations = [];
588
+ for (const [path, item] of Object.entries(document2.paths)) {
589
+ for (const method of HTTP_METHODS) {
590
+ const operation = item[method];
591
+ if (operation?.operationId === void 0)
592
+ continue;
593
+ const responses = {};
594
+ for (const [status, response] of Object.entries(operation.responses)) {
595
+ responses[status] = deref(document2, response);
596
+ }
597
+ operations.push({
598
+ operationId: operation.operationId,
599
+ method,
600
+ path,
601
+ operation,
602
+ parameters: mergeParameters(document2, item, operation.parameters),
603
+ requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
604
+ responses
605
+ });
606
+ }
607
+ }
608
+ return operations;
609
+ };
610
+
611
+ // ../core/dist/ids.js
612
+ var ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
613
+ var mix = (input) => {
614
+ let hash = 2166136261;
615
+ for (let i = 0; i < input.length; i++) {
616
+ hash ^= input.charCodeAt(i);
617
+ hash = Math.imul(hash, 16777619) >>> 0;
618
+ }
619
+ hash ^= hash >>> 16;
620
+ hash = Math.imul(hash, 2246822507) >>> 0;
621
+ hash ^= hash >>> 13;
622
+ return hash >>> 0;
623
+ };
624
+ var opaqueToken = (input, length) => {
625
+ let out = "";
626
+ let round = 0;
627
+ while (out.length < length) {
628
+ let hash = mix(`${input}:${round++}`);
629
+ for (let i = 0; i < 5 && out.length < length; i++) {
630
+ out += ALPHABET.charAt(hash % ALPHABET.length);
631
+ hash = Math.floor(hash / ALPHABET.length);
632
+ }
633
+ }
634
+ return out;
635
+ };
636
+ var IdSequence = class {
637
+ sqlite;
638
+ namespace;
639
+ salt;
640
+ constructor(sqlite, namespace, salt = "mockingbird") {
641
+ this.sqlite = sqlite;
642
+ this.namespace = namespace;
643
+ this.salt = salt;
644
+ }
645
+ next(prefix, length = 14) {
646
+ return this.sqlite.transaction(() => {
647
+ const row = this.sqlite.prepare("SELECT value FROM mockingbird_sequences WHERE namespace = ? AND name = ? AND kind = 'id'").get(this.namespace, prefix);
648
+ const value = (row?.value ?? 0) + 1;
649
+ this.sqlite.prepare(`INSERT INTO mockingbird_sequences (namespace, name, kind, value) VALUES (?, ?, 'id', ?)
650
+ ON CONFLICT(namespace, name, kind) DO UPDATE SET value = excluded.value`).run(this.namespace, prefix, value);
651
+ return `${prefix}${opaqueToken(`${this.salt}:${prefix}:${value}`, length)}`;
652
+ });
653
+ }
654
+ };
655
+
656
+ // ../core/dist/journal.js
657
+ var DEFAULT_JOURNAL_SIZE = 1e3;
658
+ var createJournal = (size = DEFAULT_JOURNAL_SIZE) => {
659
+ const capacity = Math.max(0, Math.floor(size));
660
+ const rings = /* @__PURE__ */ new Map();
661
+ let sequence = 0;
662
+ const order = /* @__PURE__ */ new WeakMap();
663
+ const inOrder = (ring) => ring.entries.length < capacity ? ring.entries : [...ring.entries.slice(ring.next), ...ring.entries.slice(0, ring.next)];
664
+ return {
665
+ size: capacity,
666
+ record(entry) {
667
+ if (capacity === 0)
668
+ return;
669
+ order.set(entry, sequence++);
670
+ let ring = rings.get(entry.namespace);
671
+ if (!ring) {
672
+ ring = { entries: [], next: 0 };
673
+ rings.set(entry.namespace, ring);
674
+ }
675
+ if (ring.entries.length < capacity)
676
+ ring.entries.push(entry);
677
+ else {
678
+ ring.entries[ring.next] = entry;
679
+ ring.next = (ring.next + 1) % capacity;
680
+ }
681
+ },
682
+ list(query = {}) {
683
+ const source = query.namespace !== void 0 ? inOrder(rings.get(query.namespace) ?? { entries: [], next: 0 }) : [...rings.values()].flatMap(inOrder).sort((a, b) => (order.get(a) ?? 0) - (order.get(b) ?? 0));
684
+ const matched = source.filter((entry) => (query.operationId === void 0 || entry.operationId === query.operationId) && (query.status === void 0 || entry.status === query.status) && (query.since === void 0 || Date.parse(entry.at) >= query.since));
685
+ return query.limit !== void 0 ? matched.slice(-Math.max(0, query.limit)) : matched;
686
+ },
687
+ clear(namespace) {
688
+ if (namespace === void 0)
689
+ rings.clear();
690
+ else
691
+ rings.delete(namespace);
692
+ }
693
+ };
694
+ };
695
+ var notes = /* @__PURE__ */ new WeakMap();
696
+ var responseNotes = (response) => notes.get(response);
697
+
698
+ // ../core/dist/metrics.js
699
+ var createMetrics = () => {
700
+ let requests = 0;
701
+ let faults = 0;
702
+ let totalDurationMs = 0;
703
+ const byOperation = /* @__PURE__ */ new Map();
704
+ const unmatched = /* @__PURE__ */ new Map();
705
+ return {
706
+ record(entry) {
707
+ requests++;
708
+ totalDurationMs += entry.durationMs;
709
+ if (entry.faultId !== void 0)
710
+ faults++;
711
+ const key = `${entry.operationId ?? "(unmatched)"} ${entry.status}`;
712
+ byOperation.set(key, (byOperation.get(key) ?? 0) + 1);
713
+ if (entry.unmatched) {
714
+ const route = `${entry.method} ${entry.path}`;
715
+ unmatched.set(route, (unmatched.get(route) ?? 0) + 1);
716
+ }
717
+ },
718
+ report: () => ({
719
+ requests,
720
+ byOperation: Object.fromEntries([...byOperation].sort(([a], [b]) => a.localeCompare(b))),
721
+ unmatched: [...unmatched].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([route, count]) => {
722
+ const space = route.indexOf(" ");
723
+ return { method: route.slice(0, space), path: route.slice(space + 1), count };
724
+ }),
725
+ faults,
726
+ totalDurationMs
727
+ }),
728
+ reset() {
729
+ requests = 0;
730
+ faults = 0;
731
+ totalDurationMs = 0;
732
+ byOperation.clear();
733
+ unmatched.clear();
734
+ }
735
+ };
736
+ };
737
+
738
+ // ../../core/dist/timeline.js
739
+ var BRANCH_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
740
+ var Timeline = class {
741
+ maxCheckpoints;
742
+ now;
743
+ makeId;
744
+ nodes = /* @__PURE__ */ new Map();
745
+ heads = /* @__PURE__ */ new Map();
746
+ /** Unreferenced nodes in the exact order they became collectible. */
747
+ evictable = /* @__PURE__ */ new Set();
748
+ /** Branch heads plus explicit retainers. Absent means zero. */
749
+ references = /* @__PURE__ */ new Map();
750
+ explicitPins = /* @__PURE__ */ new Map();
751
+ sequence = 0;
752
+ constructor(options = {}) {
753
+ const max = options.maxCheckpoints ?? 1e3;
754
+ if (!Number.isSafeInteger(max) || max < 1)
755
+ throw new RangeError("maxCheckpoints must be a positive integer");
756
+ this.maxCheckpoints = max;
757
+ this.now = options.now ?? (() => this.sequence);
758
+ this.makeId = options.id ?? ((sequence) => `cp_${sequence.toString(36).padStart(8, "0")}`);
759
+ }
760
+ /** Capture a new immutable value and move `branch` to it. */
761
+ commit(value, options = {}) {
762
+ const branch = options.branch ?? "main";
763
+ this.assertBranch(branch);
764
+ const parent = options.parent === void 0 ? this.heads.get(branch) ?? null : options.parent;
765
+ if (parent !== null && !this.nodes.has(parent))
766
+ throw new RangeError(`no checkpoint ${parent}`);
767
+ const id = this.makeId(++this.sequence);
768
+ if (this.nodes.has(id))
769
+ throw new RangeError(`duplicate checkpoint id ${id}`);
770
+ const checkpoint = Object.freeze({ id, branch, parent, at: this.now(), value });
771
+ this.nodes.set(id, checkpoint);
772
+ this.moveHead(branch, id);
773
+ this.collect(this.maxCheckpoints);
774
+ return checkpoint;
775
+ }
776
+ /** Create a branch pointer without copying its checkpoint value. */
777
+ fork(branch, options = {}) {
778
+ this.assertBranch(branch);
779
+ if (this.heads.has(branch))
780
+ throw new RangeError(`branch already exists: ${branch}`);
781
+ const from = options.from ?? this.heads.get("main");
782
+ if (from === void 0)
783
+ return void 0;
784
+ const checkpoint = this.get(from);
785
+ this.moveHead(branch, checkpoint.id);
786
+ return checkpoint;
787
+ }
788
+ /** Move a branch pointer to an existing checkpoint. */
789
+ checkout(branch, id) {
790
+ this.assertBranch(branch);
791
+ const checkpoint = this.get(id);
792
+ this.moveHead(branch, checkpoint.id);
793
+ return checkpoint;
794
+ }
795
+ get(id) {
796
+ const checkpoint = this.nodes.get(id);
797
+ if (!checkpoint)
798
+ throw new RangeError(`no checkpoint ${id}`);
799
+ return checkpoint;
800
+ }
801
+ head(branch = "main") {
802
+ const id = this.heads.get(branch);
803
+ return id === void 0 ? void 0 : this.get(id);
804
+ }
805
+ hasBranch(branch) {
806
+ return this.heads.has(branch);
807
+ }
808
+ branches() {
809
+ return Object.freeze(Object.fromEntries([...this.heads].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)));
810
+ }
811
+ checkpoints() {
812
+ return [...this.nodes.values()];
813
+ }
814
+ /** Number of retained checkpoints without allocating an array. */
815
+ get size() {
816
+ return this.nodes.size;
817
+ }
818
+ /** Pin a checkpoint independently of branch heads (used by compatibility snapshot handles). */
819
+ retain(id) {
820
+ const checkpoint = this.get(id);
821
+ this.explicitPins.set(id, (this.explicitPins.get(id) ?? 0) + 1);
822
+ this.addReference(id);
823
+ return checkpoint;
824
+ }
825
+ /** Release one explicit pin. Branch heads remain pinned until moved or deleted. */
826
+ release(id) {
827
+ if (!this.nodes.has(id))
828
+ return false;
829
+ const pins = this.explicitPins.get(id) ?? 0;
830
+ if (pins === 0)
831
+ return false;
832
+ if (pins === 1)
833
+ this.explicitPins.delete(id);
834
+ else
835
+ this.explicitPins.set(id, pins - 1);
836
+ this.removeReference(id);
837
+ this.collect(this.maxCheckpoints);
838
+ return true;
839
+ }
840
+ deleteBranch(branch) {
841
+ if (branch === "main")
842
+ throw new RangeError("cannot delete main branch");
843
+ const previous = this.heads.get(branch);
844
+ const deleted = this.heads.delete(branch);
845
+ if (previous !== void 0)
846
+ this.removeReference(previous);
847
+ this.collect(this.maxCheckpoints);
848
+ return deleted;
849
+ }
850
+ /**
851
+ * Deterministically discard oldest unpinned checkpoints. Collection is O(number removed):
852
+ * commits never scan pinned nodes or the retained history. Parents are metadata rather than a
853
+ * storage dependency, so a retained node remains usable after pruning.
854
+ */
855
+ gc(max = this.maxCheckpoints) {
856
+ if (!Number.isSafeInteger(max) || max < 1)
857
+ throw new RangeError("max must be a positive integer");
858
+ const removed = [];
859
+ this.collect(max, removed);
860
+ return removed;
861
+ }
862
+ collect(max, removed) {
863
+ while (this.nodes.size > max && this.evictable.size > 0) {
864
+ const id = this.evictable.values().next().value;
865
+ this.evictable.delete(id);
866
+ this.nodes.delete(id);
867
+ removed?.push(id);
868
+ }
869
+ }
870
+ moveHead(branch, id) {
871
+ const previous = this.heads.get(branch);
872
+ if (previous === id)
873
+ return;
874
+ if (previous !== void 0)
875
+ this.removeReference(previous);
876
+ this.heads.set(branch, id);
877
+ this.addReference(id);
878
+ }
879
+ addReference(id) {
880
+ this.references.set(id, (this.references.get(id) ?? 0) + 1);
881
+ this.evictable.delete(id);
882
+ }
883
+ removeReference(id) {
884
+ const next = (this.references.get(id) ?? 0) - 1;
885
+ if (next > 0)
886
+ this.references.set(id, next);
887
+ else {
888
+ this.references.delete(id);
889
+ if (this.nodes.has(id))
890
+ this.evictable.add(id);
891
+ }
892
+ }
893
+ assertBranch(branch) {
894
+ if (!BRANCH_PATTERN.test(branch))
895
+ throw new RangeError(`branch must match ${BRANCH_PATTERN}`);
896
+ }
897
+ };
898
+
899
+ // ../../sqlite/dist/default.js
900
+ import { Database } from "@crvouga/mockingbird-service-sqlite";
901
+ var createDefaultSqlite = () => new Database();
902
+ var resolveSqlite = (sqlite) => sqlite ?? createDefaultSqlite();
903
+
904
+ // ../../sqlite/dist/migrate.js
905
+ var ensureMigrationsTable = (sqlite) => {
906
+ sqlite.exec(`
907
+ CREATE TABLE IF NOT EXISTS schema_migrations (
908
+ id TEXT PRIMARY KEY NOT NULL,
909
+ applied_at INTEGER NOT NULL
910
+ )
911
+ `);
912
+ };
913
+ var migrate = (sqlite, migrations) => {
914
+ ensureMigrationsTable(sqlite);
915
+ const applied = new Set(sqlite.prepare("SELECT id FROM schema_migrations").all().map((row) => row.id));
916
+ const pending = migrations.filter((migration) => !applied.has(migration.id));
917
+ if (pending.length === 0)
918
+ return;
919
+ const insert = sqlite.prepare("INSERT INTO schema_migrations (id, applied_at) VALUES (?, ?)");
920
+ const now = Math.floor(Date.now() / 1e3);
921
+ sqlite.transaction(() => {
922
+ for (const migration of pending) {
923
+ sqlite.exec(migration.sql);
924
+ insert.run(migration.id, now);
925
+ }
926
+ });
927
+ };
928
+
929
+ // ../../sqlite/dist/schema.js
930
+ var CORE_MIGRATIONS = [
931
+ {
932
+ id: "20260322_core_records_sequences",
933
+ sql: `
934
+ CREATE TABLE IF NOT EXISTS mockingbird_records (
935
+ namespace TEXT NOT NULL,
936
+ collection TEXT NOT NULL,
937
+ id TEXT NOT NULL,
938
+ seq INTEGER NOT NULL,
939
+ value TEXT NOT NULL,
940
+ PRIMARY KEY (namespace, collection, id)
941
+ );
942
+ CREATE INDEX IF NOT EXISTS mockingbird_records_seq
943
+ ON mockingbird_records (namespace, collection, seq);
944
+ CREATE TABLE IF NOT EXISTS mockingbird_sequences (
945
+ namespace TEXT NOT NULL,
946
+ name TEXT NOT NULL,
947
+ kind TEXT NOT NULL,
948
+ value INTEGER NOT NULL,
949
+ PRIMARY KEY (namespace, name, kind)
950
+ );
951
+ `
952
+ }
953
+ ];
954
+ var migrateCore = (sqlite) => {
955
+ migrate(sqlite, CORE_MIGRATIONS);
956
+ };
957
+ var clearNamespace = (sqlite, namespace) => {
958
+ sqlite.transaction(() => {
959
+ sqlite.prepare("DELETE FROM mockingbird_records WHERE namespace = ?").run(namespace);
960
+ sqlite.prepare("DELETE FROM mockingbird_sequences WHERE namespace = ?").run(namespace);
961
+ });
962
+ };
963
+
964
+ // ../../../node_modules/.bun/hono@4.11.9/node_modules/hono/dist/request/constants.js
965
+ var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
966
+
967
+ // ../../../node_modules/.bun/hono@4.11.9/node_modules/hono/dist/utils/body.js
968
+ var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
969
+ const { all = false, dot = false } = options;
970
+ const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
971
+ const contentType = headers.get("Content-Type");
972
+ if (contentType?.startsWith("multipart/form-data") || contentType?.startsWith("application/x-www-form-urlencoded")) {
973
+ return parseFormData(request, { all, dot });
974
+ }
975
+ return {};
976
+ };
977
+ async function parseFormData(request, options) {
978
+ const formData = await request.formData();
979
+ if (formData) {
980
+ return convertFormDataToBodyData(formData, options);
981
+ }
982
+ return {};
983
+ }
984
+ function convertFormDataToBodyData(formData, options) {
985
+ const form = /* @__PURE__ */ Object.create(null);
986
+ formData.forEach((value, key) => {
987
+ const shouldParseAllValues = options.all || key.endsWith("[]");
988
+ if (!shouldParseAllValues) {
989
+ form[key] = value;
990
+ } else {
991
+ handleParsingAllValues(form, key, value);
992
+ }
993
+ });
994
+ if (options.dot) {
995
+ Object.entries(form).forEach(([key, value]) => {
996
+ const shouldParseDotValues = key.includes(".");
997
+ if (shouldParseDotValues) {
998
+ handleParsingNestedValues(form, key, value);
999
+ delete form[key];
1000
+ }
1001
+ });
1002
+ }
1003
+ return form;
1004
+ }
1005
+ var handleParsingAllValues = (form, key, value) => {
1006
+ if (form[key] !== void 0) {
1007
+ if (Array.isArray(form[key])) {
1008
+ ;
1009
+ form[key].push(value);
1010
+ } else {
1011
+ form[key] = [form[key], value];
1012
+ }
1013
+ } else {
1014
+ if (!key.endsWith("[]")) {
1015
+ form[key] = value;
1016
+ } else {
1017
+ form[key] = [value];
1018
+ }
1019
+ }
1020
+ };
1021
+ var handleParsingNestedValues = (form, key, value) => {
1022
+ let nestedForm = form;
1023
+ const keys = key.split(".");
1024
+ keys.forEach((key2, index) => {
1025
+ if (index === keys.length - 1) {
1026
+ nestedForm[key2] = value;
1027
+ } else {
1028
+ if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
1029
+ nestedForm[key2] = /* @__PURE__ */ Object.create(null);
1030
+ }
1031
+ nestedForm = nestedForm[key2];
1032
+ }
1033
+ });
1034
+ };
1035
+
1036
+ // ../../../node_modules/.bun/hono@4.11.9/node_modules/hono/dist/utils/url.js
1037
+ var tryDecode = (str, decoder) => {
1038
+ try {
1039
+ return decoder(str);
1040
+ } catch {
1041
+ return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match2) => {
1042
+ try {
1043
+ return decoder(match2);
1044
+ } catch {
1045
+ return match2;
1046
+ }
1047
+ });
1048
+ }
1049
+ };
1050
+ var _decodeURI = (value) => {
1051
+ if (!/[%+]/.test(value)) {
1052
+ return value;
1053
+ }
1054
+ if (value.indexOf("+") !== -1) {
1055
+ value = value.replace(/\+/g, " ");
1056
+ }
1057
+ return value.indexOf("%") !== -1 ? tryDecode(value, decodeURIComponent_) : value;
1058
+ };
1059
+ var _getQueryParam = (url, key, multiple) => {
1060
+ let encoded;
1061
+ if (!multiple && key && !/[%+]/.test(key)) {
1062
+ let keyIndex2 = url.indexOf("?", 8);
1063
+ if (keyIndex2 === -1) {
1064
+ return void 0;
1065
+ }
1066
+ if (!url.startsWith(key, keyIndex2 + 1)) {
1067
+ keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
1068
+ }
1069
+ while (keyIndex2 !== -1) {
1070
+ const trailingKeyCode = url.charCodeAt(keyIndex2 + key.length + 1);
1071
+ if (trailingKeyCode === 61) {
1072
+ const valueIndex = keyIndex2 + key.length + 2;
1073
+ const endIndex = url.indexOf("&", valueIndex);
1074
+ return _decodeURI(url.slice(valueIndex, endIndex === -1 ? void 0 : endIndex));
1075
+ } else if (trailingKeyCode == 38 || isNaN(trailingKeyCode)) {
1076
+ return "";
1077
+ }
1078
+ keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
1079
+ }
1080
+ encoded = /[%+]/.test(url);
1081
+ if (!encoded) {
1082
+ return void 0;
1083
+ }
1084
+ }
1085
+ const results = {};
1086
+ encoded ??= /[%+]/.test(url);
1087
+ let keyIndex = url.indexOf("?", 8);
1088
+ while (keyIndex !== -1) {
1089
+ const nextKeyIndex = url.indexOf("&", keyIndex + 1);
1090
+ let valueIndex = url.indexOf("=", keyIndex);
1091
+ if (valueIndex > nextKeyIndex && nextKeyIndex !== -1) {
1092
+ valueIndex = -1;
1093
+ }
1094
+ let name = url.slice(
1095
+ keyIndex + 1,
1096
+ valueIndex === -1 ? nextKeyIndex === -1 ? void 0 : nextKeyIndex : valueIndex
1097
+ );
1098
+ if (encoded) {
1099
+ name = _decodeURI(name);
1100
+ }
1101
+ keyIndex = nextKeyIndex;
1102
+ if (name === "") {
1103
+ continue;
1104
+ }
1105
+ let value;
1106
+ if (valueIndex === -1) {
1107
+ value = "";
1108
+ } else {
1109
+ value = url.slice(valueIndex + 1, nextKeyIndex === -1 ? void 0 : nextKeyIndex);
1110
+ if (encoded) {
1111
+ value = _decodeURI(value);
1112
+ }
1113
+ }
1114
+ if (multiple) {
1115
+ if (!(results[name] && Array.isArray(results[name]))) {
1116
+ results[name] = [];
1117
+ }
1118
+ ;
1119
+ results[name].push(value);
1120
+ } else {
1121
+ results[name] ??= value;
1122
+ }
1123
+ }
1124
+ return key ? results[key] : results;
1125
+ };
1126
+ var getQueryParam = _getQueryParam;
1127
+ var getQueryParams = (url, key) => {
1128
+ return _getQueryParam(url, key, true);
1129
+ };
1130
+ var decodeURIComponent_ = decodeURIComponent;
1131
+
1132
+ // ../../../node_modules/.bun/hono@4.11.9/node_modules/hono/dist/request.js
1133
+ var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
1134
+ var HonoRequest = class {
1135
+ /**
1136
+ * `.raw` can get the raw Request object.
1137
+ *
1138
+ * @see {@link https://hono.dev/docs/api/request#raw}
1139
+ *
1140
+ * @example
1141
+ * ```ts
1142
+ * // For Cloudflare Workers
1143
+ * app.post('/', async (c) => {
1144
+ * const metadata = c.req.raw.cf?.hostMetadata?
1145
+ * ...
1146
+ * })
1147
+ * ```
1148
+ */
1149
+ raw;
1150
+ #validatedData;
1151
+ // Short name of validatedData
1152
+ #matchResult;
1153
+ routeIndex = 0;
1154
+ /**
1155
+ * `.path` can get the pathname of the request.
1156
+ *
1157
+ * @see {@link https://hono.dev/docs/api/request#path}
1158
+ *
1159
+ * @example
1160
+ * ```ts
1161
+ * app.get('/about/me', (c) => {
1162
+ * const pathname = c.req.path // `/about/me`
1163
+ * })
1164
+ * ```
1165
+ */
1166
+ path;
1167
+ bodyCache = {};
1168
+ constructor(request, path = "/", matchResult = [[]]) {
1169
+ this.raw = request;
1170
+ this.path = path;
1171
+ this.#matchResult = matchResult;
1172
+ this.#validatedData = {};
1173
+ }
1174
+ param(key) {
1175
+ return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
1176
+ }
1177
+ #getDecodedParam(key) {
1178
+ const paramKey = this.#matchResult[0][this.routeIndex][1][key];
1179
+ const param = this.#getParamValue(paramKey);
1180
+ return param && /\%/.test(param) ? tryDecodeURIComponent(param) : param;
1181
+ }
1182
+ #getAllDecodedParams() {
1183
+ const decoded = {};
1184
+ const keys = Object.keys(this.#matchResult[0][this.routeIndex][1]);
1185
+ for (const key of keys) {
1186
+ const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
1187
+ if (value !== void 0) {
1188
+ decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value;
1189
+ }
1190
+ }
1191
+ return decoded;
1192
+ }
1193
+ #getParamValue(paramKey) {
1194
+ return this.#matchResult[1] ? this.#matchResult[1][paramKey] : paramKey;
1195
+ }
1196
+ query(key) {
1197
+ return getQueryParam(this.url, key);
1198
+ }
1199
+ queries(key) {
1200
+ return getQueryParams(this.url, key);
1201
+ }
1202
+ header(name) {
1203
+ if (name) {
1204
+ return this.raw.headers.get(name) ?? void 0;
1205
+ }
1206
+ const headerData = {};
1207
+ this.raw.headers.forEach((value, key) => {
1208
+ headerData[key] = value;
1209
+ });
1210
+ return headerData;
1211
+ }
1212
+ async parseBody(options) {
1213
+ return this.bodyCache.parsedBody ??= await parseBody(this, options);
1214
+ }
1215
+ #cachedBody = (key) => {
1216
+ const { bodyCache, raw } = this;
1217
+ const cachedBody = bodyCache[key];
1218
+ if (cachedBody) {
1219
+ return cachedBody;
1220
+ }
1221
+ const anyCachedKey = Object.keys(bodyCache)[0];
1222
+ if (anyCachedKey) {
1223
+ return bodyCache[anyCachedKey].then((body) => {
1224
+ if (anyCachedKey === "json") {
1225
+ body = JSON.stringify(body);
1226
+ }
1227
+ return new Response(body)[key]();
1228
+ });
1229
+ }
1230
+ return bodyCache[key] = raw[key]();
1231
+ };
1232
+ /**
1233
+ * `.json()` can parse Request body of type `application/json`
1234
+ *
1235
+ * @see {@link https://hono.dev/docs/api/request#json}
1236
+ *
1237
+ * @example
1238
+ * ```ts
1239
+ * app.post('/entry', async (c) => {
1240
+ * const body = await c.req.json()
1241
+ * })
1242
+ * ```
1243
+ */
1244
+ json() {
1245
+ return this.#cachedBody("text").then((text) => JSON.parse(text));
1246
+ }
1247
+ /**
1248
+ * `.text()` can parse Request body of type `text/plain`
1249
+ *
1250
+ * @see {@link https://hono.dev/docs/api/request#text}
1251
+ *
1252
+ * @example
1253
+ * ```ts
1254
+ * app.post('/entry', async (c) => {
1255
+ * const body = await c.req.text()
1256
+ * })
1257
+ * ```
1258
+ */
1259
+ text() {
1260
+ return this.#cachedBody("text");
1261
+ }
1262
+ /**
1263
+ * `.arrayBuffer()` parse Request body as an `ArrayBuffer`
1264
+ *
1265
+ * @see {@link https://hono.dev/docs/api/request#arraybuffer}
1266
+ *
1267
+ * @example
1268
+ * ```ts
1269
+ * app.post('/entry', async (c) => {
1270
+ * const body = await c.req.arrayBuffer()
1271
+ * })
1272
+ * ```
1273
+ */
1274
+ arrayBuffer() {
1275
+ return this.#cachedBody("arrayBuffer");
1276
+ }
1277
+ /**
1278
+ * Parses the request body as a `Blob`.
1279
+ * @example
1280
+ * ```ts
1281
+ * app.post('/entry', async (c) => {
1282
+ * const body = await c.req.blob();
1283
+ * });
1284
+ * ```
1285
+ * @see https://hono.dev/docs/api/request#blob
1286
+ */
1287
+ blob() {
1288
+ return this.#cachedBody("blob");
1289
+ }
1290
+ /**
1291
+ * Parses the request body as `FormData`.
1292
+ * @example
1293
+ * ```ts
1294
+ * app.post('/entry', async (c) => {
1295
+ * const body = await c.req.formData();
1296
+ * });
1297
+ * ```
1298
+ * @see https://hono.dev/docs/api/request#formdata
1299
+ */
1300
+ formData() {
1301
+ return this.#cachedBody("formData");
1302
+ }
1303
+ /**
1304
+ * Adds validated data to the request.
1305
+ *
1306
+ * @param target - The target of the validation.
1307
+ * @param data - The validated data to add.
1308
+ */
1309
+ addValidatedData(target, data) {
1310
+ this.#validatedData[target] = data;
1311
+ }
1312
+ valid(target) {
1313
+ return this.#validatedData[target];
1314
+ }
1315
+ /**
1316
+ * `.url()` can get the request url strings.
1317
+ *
1318
+ * @see {@link https://hono.dev/docs/api/request#url}
1319
+ *
1320
+ * @example
1321
+ * ```ts
1322
+ * app.get('/about/me', (c) => {
1323
+ * const url = c.req.url // `http://localhost:8787/about/me`
1324
+ * ...
1325
+ * })
1326
+ * ```
1327
+ */
1328
+ get url() {
1329
+ return this.raw.url;
1330
+ }
1331
+ /**
1332
+ * `.method()` can get the method name of the request.
1333
+ *
1334
+ * @see {@link https://hono.dev/docs/api/request#method}
1335
+ *
1336
+ * @example
1337
+ * ```ts
1338
+ * app.get('/about/me', (c) => {
1339
+ * const method = c.req.method // `GET`
1340
+ * })
1341
+ * ```
1342
+ */
1343
+ get method() {
1344
+ return this.raw.method;
1345
+ }
1346
+ get [GET_MATCH_RESULT]() {
1347
+ return this.#matchResult;
1348
+ }
1349
+ /**
1350
+ * `.matchedRoutes()` can return a matched route in the handler
1351
+ *
1352
+ * @deprecated
1353
+ *
1354
+ * Use matchedRoutes helper defined in "hono/route" instead.
1355
+ *
1356
+ * @see {@link https://hono.dev/docs/api/request#matchedroutes}
1357
+ *
1358
+ * @example
1359
+ * ```ts
1360
+ * app.use('*', async function logger(c, next) {
1361
+ * await next()
1362
+ * c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
1363
+ * const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
1364
+ * console.log(
1365
+ * method,
1366
+ * ' ',
1367
+ * path,
1368
+ * ' '.repeat(Math.max(10 - path.length, 0)),
1369
+ * name,
1370
+ * i === c.req.routeIndex ? '<- respond from here' : ''
1371
+ * )
1372
+ * })
1373
+ * })
1374
+ * ```
1375
+ */
1376
+ get matchedRoutes() {
1377
+ return this.#matchResult[0].map(([[, route]]) => route);
1378
+ }
1379
+ /**
1380
+ * `routePath()` can retrieve the path registered within the handler
1381
+ *
1382
+ * @deprecated
1383
+ *
1384
+ * Use routePath helper defined in "hono/route" instead.
1385
+ *
1386
+ * @see {@link https://hono.dev/docs/api/request#routepath}
1387
+ *
1388
+ * @example
1389
+ * ```ts
1390
+ * app.get('/posts/:id', (c) => {
1391
+ * return c.json({ path: c.req.routePath })
1392
+ * })
1393
+ * ```
1394
+ */
1395
+ get routePath() {
1396
+ return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path;
1397
+ }
1398
+ };
1399
+
1400
+ // ../../../node_modules/.bun/hono@4.11.9/node_modules/hono/dist/router/reg-exp-router/node.js
1401
+ var regExpMetaChars = new Set(".\\+*[^]$()");
1402
+
1403
+ // ../core/dist/service.js
1404
+ var bootSqlite = (sqlite) => {
1405
+ const client = resolveSqlite(sqlite);
1406
+ migrateCore(client);
1407
+ return client;
1408
+ };
1409
+
1410
+ // ../core/dist/snapshot.js
1411
+ var snapshotNamespace = (sqlite, namespace) => ({
1412
+ namespace,
1413
+ records: sqlite.prepare("SELECT collection, id, seq, value FROM mockingbird_records WHERE namespace = ? ORDER BY collection, seq").all(namespace),
1414
+ sequences: sqlite.prepare("SELECT name, kind, value FROM mockingbird_sequences WHERE namespace = ? ORDER BY name, kind").all(namespace)
1415
+ });
1416
+ var restoreNamespace = (sqlite, namespace, snapshot) => {
1417
+ sqlite.transaction(() => {
1418
+ sqlite.prepare("DELETE FROM mockingbird_records WHERE namespace = ?").run(namespace);
1419
+ sqlite.prepare("DELETE FROM mockingbird_sequences WHERE namespace = ?").run(namespace);
1420
+ const record2 = sqlite.prepare("INSERT INTO mockingbird_records (namespace, collection, id, seq, value) VALUES (?, ?, ?, ?, ?)");
1421
+ for (const row of snapshot.records) {
1422
+ record2.run(namespace, row.collection, row.id, row.seq, row.value);
1423
+ }
1424
+ const sequence = sqlite.prepare("INSERT INTO mockingbird_sequences (namespace, name, kind, value) VALUES (?, ?, ?, ?)");
1425
+ for (const row of snapshot.sequences) {
1426
+ sequence.run(namespace, row.name, row.kind, row.value);
1427
+ }
1428
+ });
1429
+ };
1430
+
1431
+ // ../core/dist/version.js
1432
+ var PACKAGE_VERSION = true ? "0.1.0" : UNRELEASED_VERSION;
1433
+
1434
+ // ../core/dist/signing.js
1435
+ var encoder = new TextEncoder();
1436
+ var toBase64 = (bytes) => {
1437
+ let binary = "";
1438
+ for (const byte of bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)) {
1439
+ binary += String.fromCharCode(byte);
1440
+ }
1441
+ return btoa(binary);
1442
+ };
1443
+ var fromBase64 = (value) => Uint8Array.from(atob(value), (char) => char.charCodeAt(0));
1444
+ var toHex = (bytes) => [...bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
1445
+ var keyBytes = (key) => typeof key === "string" ? encoder.encode(key) : key;
1446
+ var hmac = async (algorithm, key, message, encoding = "hex") => {
1447
+ const imported = await crypto.subtle.importKey("raw", keyBytes(key), { name: "HMAC", hash: algorithm }, false, ["sign"]);
1448
+ const signed = await crypto.subtle.sign("HMAC", imported, typeof message === "string" ? encoder.encode(message) : message);
1449
+ return encoding === "hex" ? toHex(signed) : toBase64(signed);
1450
+ };
1451
+ var svixSecretBytes = (secret) => {
1452
+ const raw = secret.replace(/^f?whsec_/, "");
1453
+ try {
1454
+ return fromBase64(raw);
1455
+ } catch {
1456
+ throw new TypeError("webhook secret must be whsec_<base64> (as Svix issues it)");
1457
+ }
1458
+ };
1459
+ var signSvix = async (secret, messageId, timestampSeconds, body) => `v1,${await hmac("SHA-256", svixSecretBytes(secret), `${messageId}.${timestampSeconds}.${body}`, "base64")}`;
1460
+ var signTimestamped = async (secret, timestampSeconds, body) => `t=${timestampSeconds},v1=${await hmac("SHA-256", secret, `${timestampSeconds}.${body}`, "hex")}`;
1461
+ var signTwilio = async (authToken, url, params) => {
1462
+ const payload = url + Object.keys(params).sort().map((key) => `${key}${params[key]}`).join("");
1463
+ return hmac("SHA-1", authToken, payload, "base64");
1464
+ };
1465
+
1466
+ // ../core/dist/webhooks.js
1467
+ var signers = {
1468
+ /** No signature. */
1469
+ none: () => () => ({}),
1470
+ /** Svix (`svix-id`, `svix-timestamp`, `svix-signature: v1,<b64>`): Junction, Flex, Resend. */
1471
+ svix: (options = {}) => async ({ messageId, body, timestampSeconds, secret }) => {
1472
+ if (!secret)
1473
+ return {};
1474
+ const prefix = options.prefix ?? "svix";
1475
+ return {
1476
+ [`${prefix}-id`]: messageId,
1477
+ [`${prefix}-timestamp`]: String(timestampSeconds),
1478
+ [`${prefix}-signature`]: await signSvix(secret, messageId, timestampSeconds, body)
1479
+ };
1480
+ },
1481
+ /** `<header>: t=<unix>,v1=<hex HMAC-SHA256(secret, "t.body")>`: Stripe, Persona, Fullscript. */
1482
+ timestamped: (header = "Stripe-Signature") => async ({ body, timestampSeconds, secret }) => secret ? { [header]: await signTimestamped(secret, timestampSeconds, body) } : {},
1483
+ /** `X-Twilio-Signature` over the public URL plus the sorted form parameters. */
1484
+ twilio: () => async ({ signUrl, form, secret }) => secret ? { "X-Twilio-Signature": await signTwilio(secret, signUrl, form ?? {}) } : {},
1485
+ /** The secret itself in a header (optionally templated): RxVortex, Pharmetika, AHA `Token <s>`. */
1486
+ header: (header, format = (s) => s) => ({ secret }) => secret ? { [header]: format(secret) } : {},
1487
+ /** Anything else: the service computes the headers itself. */
1488
+ custom: (sign) => sign
1489
+ };
1490
+ var DEFAULT_DELAYS = [0, 5e3, 3e5, 18e5, 72e5];
1491
+ var unref = (timer) => {
1492
+ ;
1493
+ timer.unref?.();
1494
+ };
1495
+ var randomId = (prefix) => `${prefix}${crypto.randomUUID().replace(/-/g, "").slice(0, 24)}`;
1496
+ var matchesEndpoint = (endpoint, message) => {
1497
+ const events = endpoint.events ?? ["*"];
1498
+ if (!events.includes("*") && !events.includes(message.type))
1499
+ return false;
1500
+ for (const [key, value] of Object.entries(endpoint.tags ?? {})) {
1501
+ if (message.tags[key] !== value)
1502
+ return false;
1503
+ }
1504
+ return true;
1505
+ };
1506
+ var createWebhookHub = (options) => {
1507
+ const delays = options.retryDelaysMs ?? DEFAULT_DELAYS;
1508
+ const timeoutMs = options.timeoutMs ?? 15e3;
1509
+ const delivered = options.delivered ?? ((status) => status >= 200 && status < 300);
1510
+ const send = options.fetch ?? ((request) => fetch(request));
1511
+ const keep = options.keep ?? 500;
1512
+ const now = options.now ?? Date.now;
1513
+ const id = options.id ?? randomId;
1514
+ const scheduleTimer = options.schedule ?? ((callback, delayMs) => setTimeout(callback, delayMs));
1515
+ const cancel = options.cancel ?? ((handle) => clearTimeout(handle));
1516
+ const global = (options.endpoints ?? []).map((e, i) => ({ ...e, id: e.id ?? `we_global_${i}` }));
1517
+ const own = /* @__PURE__ */ new Map();
1518
+ const messages = [];
1519
+ const deliveries = /* @__PURE__ */ new Map();
1520
+ const pending = /* @__PURE__ */ new Map();
1521
+ const payloads = /* @__PURE__ */ new Map();
1522
+ const faults = /* @__PURE__ */ new Map();
1523
+ const held = /* @__PURE__ */ new Map();
1524
+ const inFlight = /* @__PURE__ */ new Set();
1525
+ const track = (work) => {
1526
+ inFlight.add(work);
1527
+ void work.finally(() => inFlight.delete(work));
1528
+ };
1529
+ const attempt = async (delivery) => {
1530
+ const entry = payloads.get(delivery.id);
1531
+ if (!entry)
1532
+ return false;
1533
+ const { message, endpoint } = entry;
1534
+ const timestampSeconds = Math.floor(now() / 1e3);
1535
+ const started = now();
1536
+ const record2 = {
1537
+ attempt: delivery.attempts.length + 1,
1538
+ at: new Date(started).toISOString(),
1539
+ status: null,
1540
+ error: null,
1541
+ durationMs: 0,
1542
+ responseBody: null
1543
+ };
1544
+ const controller = new AbortController();
1545
+ const timer = scheduleTimer(() => controller.abort(), timeoutMs);
1546
+ try {
1547
+ const signed = await options.signer({
1548
+ messageId: message.id,
1549
+ body: message.body,
1550
+ timestampSeconds,
1551
+ url: endpoint.url,
1552
+ secret: endpoint.secret,
1553
+ signUrl: endpoint.signUrl ?? endpoint.url,
1554
+ form: message.contentType.startsWith("application/x-www-form-urlencoded") ? Object.fromEntries(new URLSearchParams(message.body)) : void 0,
1555
+ type: message.type,
1556
+ tags: message.tags
1557
+ });
1558
+ const response = await send(new Request(endpoint.url, {
1559
+ method: "POST",
1560
+ headers: {
1561
+ "content-type": message.contentType,
1562
+ ...endpoint.headers,
1563
+ ...message.headers,
1564
+ ...signed
1565
+ },
1566
+ body: message.body,
1567
+ signal: controller.signal
1568
+ }));
1569
+ record2.status = response.status;
1570
+ record2.responseBody = await response.text();
1571
+ } catch (error2) {
1572
+ record2.error = controller.signal.aborted ? `timed out after ${timeoutMs}ms` : error2 instanceof Error ? error2.message : String(error2);
1573
+ } finally {
1574
+ cancel(timer);
1575
+ record2.durationMs = now() - started;
1576
+ delivery.attempts.push(record2);
1577
+ }
1578
+ return record2.status !== null && delivered(record2.status);
1579
+ };
1580
+ const schedule = (delivery) => {
1581
+ const index = delivery.attempts.length;
1582
+ if (index >= delays.length) {
1583
+ delivery.state = "failed";
1584
+ pending.delete(delivery.id);
1585
+ return;
1586
+ }
1587
+ const run = () => {
1588
+ pending.delete(delivery.id);
1589
+ track(attempt(delivery).then((ok) => {
1590
+ if (ok)
1591
+ delivery.state = "delivered";
1592
+ else
1593
+ schedule(delivery);
1594
+ }));
1595
+ };
1596
+ const delay = delays[index] ?? 0;
1597
+ if (delay <= 0) {
1598
+ pending.set(delivery.id, void 0);
1599
+ run();
1600
+ return;
1601
+ }
1602
+ const timer = scheduleTimer(run, delay);
1603
+ unref(timer);
1604
+ pending.set(delivery.id, timer);
1605
+ };
1606
+ const endpointsFor = (namespace) => [...own.get(namespace) ?? [], ...global];
1607
+ const fanOut = (message, state = "pending") => {
1608
+ for (const endpoint of endpointsFor(message.namespace)) {
1609
+ if (!matchesEndpoint(endpoint, message))
1610
+ continue;
1611
+ const delivery = {
1612
+ id: id("dlv_"),
1613
+ messageId: message.id,
1614
+ namespace: message.namespace,
1615
+ type: message.type,
1616
+ endpointId: endpoint.id ?? "we_unknown",
1617
+ url: endpoint.url,
1618
+ state,
1619
+ attempts: []
1620
+ };
1621
+ deliveries.set(delivery.id, delivery);
1622
+ payloads.set(delivery.id, { message, endpoint });
1623
+ if (state === "pending")
1624
+ schedule(delivery);
1625
+ }
1626
+ };
1627
+ const takeFault = (namespace) => {
1628
+ const queue = faults.get(namespace);
1629
+ const head = queue?.[0];
1630
+ if (!queue || !head)
1631
+ return void 0;
1632
+ head.remaining--;
1633
+ if (head.remaining <= 0)
1634
+ queue.shift();
1635
+ return head.mode;
1636
+ };
1637
+ const releaseHeld = (namespace) => {
1638
+ const waiting = held.get(namespace);
1639
+ if (!waiting)
1640
+ return;
1641
+ held.delete(namespace);
1642
+ for (const message of waiting)
1643
+ fanOut(message);
1644
+ };
1645
+ const hub = {
1646
+ publish(input) {
1647
+ const contentType = input.contentType ?? (input.form ? "application/x-www-form-urlencoded" : "application/json");
1648
+ const body = typeof input.body === "string" ? input.body : input.form ? new URLSearchParams(input.form).toString() : JSON.stringify(input.body);
1649
+ const message = {
1650
+ id: input.id ?? id("msg_"),
1651
+ namespace: input.namespace,
1652
+ type: input.type,
1653
+ body,
1654
+ contentType,
1655
+ tags: input.tags ?? {},
1656
+ headers: input.headers ?? {},
1657
+ publishedAt: new Date(now()).toISOString()
1658
+ };
1659
+ messages.push(message);
1660
+ const ofNamespace = messages.filter((m) => m.namespace === message.namespace);
1661
+ const oldest = ofNamespace[0];
1662
+ if (ofNamespace.length > keep && oldest)
1663
+ messages.splice(messages.indexOf(oldest), 1);
1664
+ options.onMessage?.(message);
1665
+ const fault = takeFault(message.namespace);
1666
+ if (fault === "drop") {
1667
+ fanOut(message, "dropped");
1668
+ return message;
1669
+ }
1670
+ if (fault === "reorder") {
1671
+ held.set(message.namespace, [...held.get(message.namespace) ?? [], message]);
1672
+ return message;
1673
+ }
1674
+ fanOut(message);
1675
+ if (fault === "duplicate")
1676
+ fanOut(message);
1677
+ releaseHeld(message.namespace);
1678
+ return message;
1679
+ },
1680
+ setEndpoints(namespace, endpoints) {
1681
+ const withIds = endpoints.map((e, i) => ({ ...e, id: e.id ?? `we_${namespace}_${i}` }));
1682
+ own.set(namespace, withIds);
1683
+ return withIds;
1684
+ },
1685
+ endpoints: endpointsFor,
1686
+ messages: (namespace) => namespace === void 0 ? [...messages] : messages.filter((m) => m.namespace === namespace),
1687
+ deliveries: (namespace) => [...deliveries.values()].filter((d) => namespace === void 0 || d.namespace === namespace),
1688
+ async replay(id2) {
1689
+ const delivery = deliveries.get(id2);
1690
+ if (!delivery)
1691
+ return void 0;
1692
+ const ok = await attempt(delivery);
1693
+ if (ok)
1694
+ delivery.state = "delivered";
1695
+ return delivery;
1696
+ },
1697
+ async flush() {
1698
+ for (const namespace of [...held.keys()])
1699
+ releaseHeld(namespace);
1700
+ const waiting = [...pending.entries()];
1701
+ for (const [id2, timer] of waiting) {
1702
+ if (timer === void 0)
1703
+ continue;
1704
+ cancel(timer);
1705
+ pending.delete(id2);
1706
+ const delivery = deliveries.get(id2);
1707
+ if (!delivery)
1708
+ continue;
1709
+ track(attempt(delivery).then((ok) => {
1710
+ if (ok)
1711
+ delivery.state = "delivered";
1712
+ else
1713
+ schedule(delivery);
1714
+ }));
1715
+ }
1716
+ await hub.idle();
1717
+ },
1718
+ async idle() {
1719
+ while (inFlight.size > 0)
1720
+ await Promise.allSettled([...inFlight]);
1721
+ },
1722
+ fault(namespace, fault) {
1723
+ const queue = faults.get(namespace) ?? [];
1724
+ queue.push({ mode: fault.mode, remaining: Math.max(1, fault.count ?? 1) });
1725
+ faults.set(namespace, queue);
1726
+ },
1727
+ clear(namespace) {
1728
+ for (const [id2, delivery] of deliveries) {
1729
+ if (namespace !== void 0 && delivery.namespace !== namespace)
1730
+ continue;
1731
+ const timer = pending.get(id2);
1732
+ if (timer !== void 0)
1733
+ cancel(timer);
1734
+ pending.delete(id2);
1735
+ deliveries.delete(id2);
1736
+ payloads.delete(id2);
1737
+ }
1738
+ for (let i = messages.length - 1; i >= 0; i--) {
1739
+ if (namespace === void 0 || messages[i]?.namespace === namespace)
1740
+ messages.splice(i, 1);
1741
+ }
1742
+ if (namespace === void 0) {
1743
+ held.clear();
1744
+ faults.clear();
1745
+ own.clear();
1746
+ } else {
1747
+ held.delete(namespace);
1748
+ faults.delete(namespace);
1749
+ own.delete(namespace);
1750
+ }
1751
+ }
1752
+ };
1753
+ return hub;
1754
+ };
1755
+ var json2 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
1756
+ var adminError2 = (status, message) => json2(status, { error: { type: "mockingbird_admin", message } });
1757
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1758
+ var parseEndpoint = (value) => {
1759
+ if (!isRecord2(value) || typeof value.url !== "string")
1760
+ return "each endpoint needs a url";
1761
+ try {
1762
+ new URL(value.url);
1763
+ } catch {
1764
+ return `not a URL: ${value.url}`;
1765
+ }
1766
+ const endpoint = { url: value.url };
1767
+ if (typeof value.id === "string")
1768
+ endpoint.id = value.id;
1769
+ if (typeof value.secret === "string")
1770
+ endpoint.secret = value.secret;
1771
+ if (typeof value.signUrl === "string")
1772
+ endpoint.signUrl = value.signUrl;
1773
+ const events = value.events ?? value.enabledEvents;
1774
+ if (Array.isArray(events))
1775
+ endpoint.events = events.map(String);
1776
+ if (isRecord2(value.tags)) {
1777
+ endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
1778
+ }
1779
+ if (typeof value.account === "string")
1780
+ endpoint.tags = { ...endpoint.tags, account: value.account };
1781
+ if (isRecord2(value.headers)) {
1782
+ endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
1783
+ }
1784
+ return endpoint;
1785
+ };
1786
+ var webhookAdminRoutes = (hub) => ({
1787
+ "GET /webhooks": ({ url, namespace }) => json2(200, {
1788
+ deliveries: hub.deliveries(url.searchParams.get("all") === "1" ? void 0 : namespace).filter((d) => {
1789
+ const type = url.searchParams.get("type");
1790
+ return type === null || d.type === type;
1791
+ })
1792
+ }),
1793
+ "GET /webhooks/events": ({ url, namespace }) => {
1794
+ const type = url.searchParams.get("type");
1795
+ return json2(200, {
1796
+ events: hub.messages(url.searchParams.get("all") === "1" ? void 0 : namespace).filter((m) => type === null || m.type === type).map((m) => ({ ...m, payload: parsePayload(m) }))
1797
+ });
1798
+ },
1799
+ "POST /webhooks/:id/replay": async ({ params }) => {
1800
+ const replayed = await hub.replay(params.id);
1801
+ return replayed ? json2(200, replayed) : adminError2(404, `no delivery ${params.id}`);
1802
+ },
1803
+ "POST /webhooks/flush": async () => {
1804
+ await hub.flush();
1805
+ return json2(200, { status: "ok" });
1806
+ },
1807
+ "POST /webhooks/faults": ({ body, namespace }) => {
1808
+ if (!isRecord2(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
1809
+ return adminError2(400, "mode must be duplicate, reorder or drop");
1810
+ }
1811
+ const fault = { mode: body.mode };
1812
+ if (typeof body.count === "number")
1813
+ fault.count = body.count;
1814
+ hub.fault(namespace, fault);
1815
+ return json2(201, { namespace, ...fault });
1816
+ },
1817
+ "GET /webhook-endpoints": ({ namespace }) => json2(200, {
1818
+ endpoints: hub.endpoints(namespace).map(({ secret, ...rest }) => ({
1819
+ ...rest,
1820
+ secret: secret ? "(set)" : null
1821
+ }))
1822
+ }),
1823
+ "PUT /webhook-endpoints": ({ body, namespace }) => {
1824
+ const list = Array.isArray(body) ? body : isRecord2(body) ? body.endpoints : void 0;
1825
+ if (!Array.isArray(list))
1826
+ return adminError2(400, "expected [{url, secret?, events?}]");
1827
+ const parsed = [];
1828
+ for (const each of list) {
1829
+ const endpoint = parseEndpoint(each);
1830
+ if (typeof endpoint === "string")
1831
+ return adminError2(400, endpoint);
1832
+ parsed.push(endpoint);
1833
+ }
1834
+ const set = hub.setEndpoints(namespace, parsed);
1835
+ return json2(200, { endpoints: set.map((e) => ({ ...e, secret: e.secret ? "(set)" : null })) });
1836
+ },
1837
+ "DELETE /webhook-endpoints": ({ namespace }) => {
1838
+ hub.setEndpoints(namespace, []);
1839
+ return json2(200, { status: "ok" });
1840
+ }
1841
+ });
1842
+ var parsePayload = (message) => {
1843
+ if (message.contentType.startsWith("application/json")) {
1844
+ try {
1845
+ return JSON.parse(message.body);
1846
+ } catch {
1847
+ return message.body;
1848
+ }
1849
+ }
1850
+ if (message.contentType.startsWith("application/x-www-form-urlencoded")) {
1851
+ return Object.fromEntries(new URLSearchParams(message.body));
1852
+ }
1853
+ return message.body;
1854
+ };
1855
+
1856
+ // ../core/dist/runtime.js
1857
+ var MOCKINGBIRD_HEADER = "x-mockingbird";
1858
+ var BRANCH_HEADER = "x-mockingbird-branch";
1859
+ var AT_HEADER = "x-mockingbird-at";
1860
+ var CHECKPOINT_HEADER = "x-mockingbird-checkpoint";
1861
+ var DEFAULT_NAMESPACE = "default";
1862
+ var NAMESPACE_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
1863
+ var PATH_PREFIX = /^\/ns\/([^/]+)(\/.*)?$/;
1864
+ var BRANCH_PATTERN2 = /^[A-Za-z0-9_.-]{1,64}$/;
1865
+ var MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
1866
+ var effects = /* @__PURE__ */ new WeakMap();
1867
+ var reuseSorted = (fresh, previous, compare, equal) => {
1868
+ if (!previous || previous.length === 0)
1869
+ return fresh.map((row) => Object.freeze(row));
1870
+ const result = new Array(fresh.length);
1871
+ let unchanged = fresh.length === previous.length;
1872
+ let oldIndex = 0;
1873
+ for (let index = 0; index < fresh.length; index++) {
1874
+ const row = fresh[index];
1875
+ while (oldIndex < previous.length && compare(previous[oldIndex], row) < 0) {
1876
+ oldIndex++;
1877
+ }
1878
+ const old = previous[oldIndex];
1879
+ result[index] = old !== void 0 && compare(old, row) === 0 && equal(old, row) ? old : Object.freeze(row);
1880
+ if (result[index] !== previous[index])
1881
+ unchanged = false;
1882
+ }
1883
+ return unchanged ? previous : result;
1884
+ };
1885
+ var DroppedConnectionError = class extends TypeError {
1886
+ code = "MOCKINGBIRD_DROP";
1887
+ constructor() {
1888
+ super("fetch failed: connection dropped by Mockingbird fault");
1889
+ this.name = "TypeError";
1890
+ }
1891
+ };
1892
+ var operationMatcher = (document2) => {
1893
+ const matchers = listOperations(document2).map((operation) => ({
1894
+ operationId: operation.operationId,
1895
+ method: operation.method.toUpperCase(),
1896
+ pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
1897
+ params: (operation.path.match(/\{/g) ?? []).length
1898
+ })).sort((a, b) => a.params - b.params);
1899
+ return (request, path) => matchers.find((m) => m.method === request.method && m.pattern.test(path))?.operationId;
1900
+ };
1901
+ var createRuntime = (options) => {
1902
+ const sqlite = bootSqlite(options.sqlite);
1903
+ const clock = options.clock ?? createClock();
1904
+ const rng = createRng(options.seed ?? 0);
1905
+ const wallNow = options.io?.wallNow ?? Date.now;
1906
+ const monotonicNow = options.io?.monotonicNow ?? (() => performance.now());
1907
+ const sleep = options.io?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1908
+ const faults = createFaultRegistry(createRng(options.seed ?? 0), sleep);
1909
+ const metrics = createMetrics();
1910
+ const journal = createJournal(options.journalSize ?? DEFAULT_JOURNAL_SIZE);
1911
+ const version = options.version ?? PACKAGE_VERSION;
1912
+ const instances = /* @__PURE__ */ new Map();
1913
+ const publicNamespaces = /* @__PURE__ */ new Set();
1914
+ const branchRngs = /* @__PURE__ */ new Map();
1915
+ const timelines = /* @__PURE__ */ new Map();
1916
+ const branchStorage = /* @__PURE__ */ new Map();
1917
+ const captured = /* @__PURE__ */ new Map();
1918
+ const credentials = createCredentialRegistry();
1919
+ const operationIdFor = options.document ? operationMatcher(options.document) : () => void 0;
1920
+ const storageNamespace = (name) => name === DEFAULT_NAMESPACE ? options.name : `${options.name}:${name}`;
1921
+ const instanceFor = (key, publicNamespace = key, isolatedRng) => {
1922
+ const existing = instances.get(key);
1923
+ if (existing)
1924
+ return existing;
1925
+ if (!NAMESPACE_PATTERN.test(key) || !NAMESPACE_PATTERN.test(publicNamespace)) {
1926
+ throw new RangeError(`namespace must match ${NAMESPACE_PATTERN}: ${JSON.stringify(publicNamespace)}`);
1927
+ }
1928
+ const created = options.create({
1929
+ namespace: storageNamespace(key),
1930
+ publicNamespace,
1931
+ sqlite,
1932
+ clock,
1933
+ rng: isolatedRng ?? rng
1934
+ });
1935
+ instances.set(key, created);
1936
+ publicNamespaces.add(publicNamespace);
1937
+ if (isolatedRng)
1938
+ branchRngs.set(key, isolatedRng);
1939
+ return created;
1940
+ };
1941
+ const instance = (name = DEFAULT_NAMESPACE) => instanceFor(name);
1942
+ const capture = (storage) => {
1943
+ const fresh = snapshotNamespace(sqlite, storageNamespace(storage));
1944
+ const previous = captured.get(storage);
1945
+ const snapshot2 = {
1946
+ namespace: fresh.namespace,
1947
+ records: reuseSorted(fresh.records, previous?.records, (left, right) => left.collection < right.collection ? -1 : left.collection > right.collection ? 1 : left.seq - right.seq, (left, right) => left.id === right.id && left.value === right.value),
1948
+ sequences: reuseSorted(fresh.sequences, previous?.sequences, (left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : left.kind < right.kind ? -1 : left.kind > right.kind ? 1 : 0, (left, right) => left.value === right.value)
1949
+ };
1950
+ Object.freeze(snapshot2.records);
1951
+ Object.freeze(snapshot2.sequences);
1952
+ Object.freeze(snapshot2);
1953
+ captured.set(storage, snapshot2);
1954
+ return Object.freeze({
1955
+ snapshot: snapshot2,
1956
+ clock: Object.freeze(clock.state()),
1957
+ rngState: (branchRngs.get(storage) ?? rng).state()
1958
+ });
1959
+ };
1960
+ const timeline = (name = DEFAULT_NAMESPACE) => {
1961
+ let found = timelines.get(name);
1962
+ if (found)
1963
+ return found;
1964
+ instance(name);
1965
+ found = new Timeline({
1966
+ now: clock.now,
1967
+ ...options.maxCheckpoints !== void 0 ? { maxCheckpoints: options.maxCheckpoints } : {}
1968
+ });
1969
+ found.commit(capture(name));
1970
+ timelines.set(name, found);
1971
+ return found;
1972
+ };
1973
+ const physicalBranch = (namespace, branch2) => {
1974
+ if (branch2 === "main")
1975
+ return namespace;
1976
+ const mapKey = `${namespace}\0${branch2}`;
1977
+ const existing = branchStorage.get(mapKey);
1978
+ if (existing)
1979
+ return existing;
1980
+ const key = `branch_${seedFrom(`${options.name}\0${namespace}\0${branch2}`).toString(36)}`;
1981
+ branchStorage.set(mapKey, key);
1982
+ return key;
1983
+ };
1984
+ const ensureBranch = (namespace, branch2, at) => {
1985
+ if (!BRANCH_PATTERN2.test(branch2))
1986
+ throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
1987
+ const history = timeline(namespace);
1988
+ if (branch2 === "main") {
1989
+ if (at !== void 0) {
1990
+ const point = history.checkout("main", at);
1991
+ restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
1992
+ captured.set(namespace, point.value.snapshot);
1993
+ rng.setState(point.value.rngState);
1994
+ clock.set(point.value.clock.now);
1995
+ if (point.value.clock.frozen)
1996
+ clock.freeze();
1997
+ else
1998
+ clock.unfreeze();
1999
+ }
2000
+ return namespace;
2001
+ }
2002
+ const storage = physicalBranch(namespace, branch2);
2003
+ if (!history.hasBranch(branch2)) {
2004
+ if (at === void 0)
2005
+ history.commit(capture(namespace));
2006
+ const point = history.fork(branch2, at === void 0 ? {} : { from: at });
2007
+ const branchRng = createRng(options.seed ?? 0);
2008
+ if (point)
2009
+ branchRng.setState(point.value.rngState);
2010
+ instanceFor(storage, namespace, branchRng);
2011
+ if (point)
2012
+ restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
2013
+ if (point)
2014
+ captured.set(storage, point.value.snapshot);
2015
+ } else if (at !== void 0 && history.head(branch2)?.id !== at) {
2016
+ const point = history.checkout(branch2, at);
2017
+ if (!instances.has(storage)) {
2018
+ const branchRng = createRng(options.seed ?? 0);
2019
+ branchRng.setState(point.value.rngState);
2020
+ instanceFor(storage, namespace, branchRng);
2021
+ }
2022
+ branchRngs.get(storage)?.setState(point.value.rngState);
2023
+ restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
2024
+ captured.set(storage, point.value.snapshot);
2025
+ } else {
2026
+ if (!instances.has(storage)) {
2027
+ const point = history.head(branch2);
2028
+ const branchRng = createRng(options.seed ?? 0);
2029
+ if (point)
2030
+ branchRng.setState(point.value.rngState);
2031
+ instanceFor(storage, namespace, branchRng);
2032
+ }
2033
+ }
2034
+ return storage;
2035
+ };
2036
+ const checkpoint = (namespace = DEFAULT_NAMESPACE, branch2 = "main") => {
2037
+ const storage = ensureBranch(namespace, branch2);
2038
+ return timeline(namespace).commit(capture(storage), { branch: branch2 });
2039
+ };
2040
+ const branch = (name, branchOptions = {}) => {
2041
+ const namespace = branchOptions.namespace ?? DEFAULT_NAMESPACE;
2042
+ ensureBranch(namespace, name, branchOptions.at);
2043
+ const head = timeline(namespace).head(name);
2044
+ if (!head)
2045
+ throw new RangeError(`branch ${name} has no checkpoint`);
2046
+ return head;
2047
+ };
2048
+ const checkout = (checkpointId, checkoutOptions = {}) => {
2049
+ const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
2050
+ const branchName = checkoutOptions.branch ?? "main";
2051
+ const history = timeline(namespace);
2052
+ const point = history.checkout(branchName, checkpointId);
2053
+ const storage = ensureBranch(namespace, branchName);
2054
+ restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
2055
+ captured.set(storage, point.value.snapshot);
2056
+ clock.set(point.value.clock.now);
2057
+ if (point.value.clock.frozen)
2058
+ clock.freeze();
2059
+ else
2060
+ clock.unfreeze();
2061
+ (branchRngs.get(storage) ?? rng).setState(point.value.rngState);
2062
+ };
2063
+ const reset = async (name = DEFAULT_NAMESPACE) => {
2064
+ if (name === "*") {
2065
+ options.webhooks?.clear();
2066
+ for (const each of instances.values())
2067
+ await each.reset();
2068
+ timelines.clear();
2069
+ branchStorage.clear();
2070
+ branchRngs.clear();
2071
+ captured.clear();
2072
+ return;
2073
+ }
2074
+ options.webhooks?.clear(name);
2075
+ const target = instances.get(name);
2076
+ if (target)
2077
+ await target.reset();
2078
+ else
2079
+ clearNamespace(sqlite, storageNamespace(name));
2080
+ for (const [mapping, storage] of branchStorage) {
2081
+ if (!mapping.startsWith(`${name}\0`))
2082
+ continue;
2083
+ const branchInstance = instances.get(storage);
2084
+ if (branchInstance)
2085
+ await branchInstance.reset();
2086
+ else
2087
+ clearNamespace(sqlite, storageNamespace(storage));
2088
+ branchStorage.delete(mapping);
2089
+ branchRngs.delete(storage);
2090
+ captured.delete(storage);
2091
+ }
2092
+ timelines.delete(name);
2093
+ captured.delete(name);
2094
+ };
2095
+ const snapshot = (name = DEFAULT_NAMESPACE) => {
2096
+ return checkpoint(name, "main").value.snapshot;
2097
+ };
2098
+ const restore = (from, name = DEFAULT_NAMESPACE) => {
2099
+ instance(name);
2100
+ restoreNamespace(sqlite, storageNamespace(name), from);
2101
+ captured.set(name, from);
2102
+ const history = timelines.get(name);
2103
+ if (history)
2104
+ history.commit(capture(name), { branch: "main" });
2105
+ else
2106
+ timeline(name);
2107
+ };
2108
+ const runtime = {
2109
+ name: options.name,
2110
+ sqlite,
2111
+ clock,
2112
+ faults,
2113
+ metrics,
2114
+ journal,
2115
+ rng,
2116
+ credentials,
2117
+ webhooks: options.webhooks,
2118
+ applyPreset: (name, namespace = DEFAULT_NAMESPACE, overrides = {}) => {
2119
+ const preset = options.presets?.[name];
2120
+ if (!preset)
2121
+ throw new RangeError(`no fault preset ${JSON.stringify(name)}`);
2122
+ const added = (preset.rules ?? []).map((rule, index) => faults.add({
2123
+ namespace,
2124
+ ...rule,
2125
+ ...overrides,
2126
+ preset: name,
2127
+ id: `${overrides.id ?? name}${(preset.rules ?? []).length > 1 ? `_${index + 1}` : ""}`
2128
+ }));
2129
+ if (preset.webhook && options.webhooks) {
2130
+ options.webhooks.fault(namespace, {
2131
+ ...preset.webhook,
2132
+ ...overrides.count !== void 0 ? { count: overrides.count } : {}
2133
+ });
2134
+ }
2135
+ return added;
2136
+ },
2137
+ instance,
2138
+ namespaces: () => [...publicNamespaces].sort(),
2139
+ reset,
2140
+ snapshot,
2141
+ restore,
2142
+ checkpoint,
2143
+ branch,
2144
+ checkout,
2145
+ timeline,
2146
+ fetch: async (incoming) => {
2147
+ let request = incoming;
2148
+ const prefixed = PATH_PREFIX.exec(new URL(request.url).pathname);
2149
+ if (prefixed) {
2150
+ const url2 = new URL(request.url);
2151
+ url2.pathname = prefixed[2] ?? "/";
2152
+ const headers = new Headers(request.headers);
2153
+ if (!headers.has(NAMESPACE_HEADER)) {
2154
+ headers.set(NAMESPACE_HEADER, decodeURIComponent(prefixed[1]));
2155
+ }
2156
+ const hasBody = request.method !== "GET" && request.method !== "HEAD";
2157
+ request = new Request(url2, {
2158
+ method: request.method,
2159
+ headers,
2160
+ ...hasBody ? { body: await request.arrayBuffer() } : {},
2161
+ signal: request.signal
2162
+ });
2163
+ }
2164
+ let namespace = control.namespaceOf(request);
2165
+ if (!request.headers.has(NAMESPACE_HEADER) && options.credential) {
2166
+ const credential = options.credential(request);
2167
+ const mapped = credential !== void 0 ? credentials.get(credential) : void 0;
2168
+ if (mapped !== void 0)
2169
+ namespace = mapped;
2170
+ }
2171
+ const selectedBranch = request.headers.get(BRANCH_HEADER) ?? "main";
2172
+ const at = request.headers.get(AT_HEADER) ?? void 0;
2173
+ const stamp = (response2) => {
2174
+ const value = NAMESPACE_PATTERN.test(namespace) ? `${options.name}@${version}; ns=${namespace}` : `${options.name}@${version}`;
2175
+ try {
2176
+ response2.headers.set(MOCKINGBIRD_HEADER, value);
2177
+ return response2;
2178
+ } catch {
2179
+ const copy = new Response(response2.body, response2);
2180
+ copy.headers.set(MOCKINGBIRD_HEADER, value);
2181
+ return copy;
2182
+ }
2183
+ };
2184
+ const handled = await control.handle(request);
2185
+ if (handled)
2186
+ return stamp(handled);
2187
+ const started = monotonicNow();
2188
+ const url = new URL(request.url);
2189
+ const operationId = operationIdFor(request, url.pathname);
2190
+ const log = (status, faultId, response2) => {
2191
+ const noted = response2 ? responseNotes(response2) : void 0;
2192
+ const entry = {
2193
+ service: options.name,
2194
+ namespace,
2195
+ operationId,
2196
+ method: request.method,
2197
+ path: url.pathname,
2198
+ status,
2199
+ durationMs: Math.round((monotonicNow() - started) * 100) / 100,
2200
+ unmatched: options.document !== void 0 && operationId === void 0,
2201
+ ...faultId !== void 0 ? { faultId } : {},
2202
+ ...noted?.ids && Object.keys(noted.ids).length > 0 ? { ids: noted.ids } : {},
2203
+ ...noted?.adopted ? { adopted: true } : {}
2204
+ };
2205
+ metrics.record(entry);
2206
+ journal.record({ ...entry, at: new Date(clock.now()).toISOString() });
2207
+ options.onLog?.(entry);
2208
+ };
2209
+ if (!NAMESPACE_PATTERN.test(namespace)) {
2210
+ log(400);
2211
+ return stamp(new Response(JSON.stringify({
2212
+ error: {
2213
+ type: "mockingbird_admin",
2214
+ message: `${NAMESPACE_HEADER} must match ${NAMESPACE_PATTERN}`
2215
+ }
2216
+ }), { status: 400, headers: { "content-type": "application/json" } }));
2217
+ }
2218
+ if (!BRANCH_PATTERN2.test(selectedBranch)) {
2219
+ log(400);
2220
+ return stamp(adminFail(400, `${BRANCH_HEADER} must match ${BRANCH_PATTERN2}`));
2221
+ }
2222
+ let storage;
2223
+ try {
2224
+ if (at !== void 0 && selectedBranch === "main" && !MUTATING_METHODS.has(request.method)) {
2225
+ const point = timeline(namespace).get(at);
2226
+ storage = physicalBranch(namespace, `at_${at}`);
2227
+ let viewRng = branchRngs.get(storage);
2228
+ if (!viewRng) {
2229
+ viewRng = createRng(options.seed ?? 0);
2230
+ instanceFor(storage, namespace, viewRng);
2231
+ }
2232
+ viewRng.setState(point.value.rngState);
2233
+ restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
2234
+ captured.set(storage, point.value.snapshot);
2235
+ } else {
2236
+ storage = ensureBranch(namespace, selectedBranch, at);
2237
+ }
2238
+ } catch (error2) {
2239
+ log(409);
2240
+ return stamp(adminFail(409, error2 instanceof Error ? error2.message : String(error2)));
2241
+ }
2242
+ const hits = await faults.take({
2243
+ operationId,
2244
+ method: request.method,
2245
+ path: url.pathname,
2246
+ namespace
2247
+ });
2248
+ const final = hits.find((hit) => hit.drop || hit.response);
2249
+ if (final?.drop) {
2250
+ log(0, final.id);
2251
+ throw new DroppedConnectionError();
2252
+ }
2253
+ if (final?.response) {
2254
+ log(final.response.status, final.id);
2255
+ return stamp(final.response);
2256
+ }
2257
+ const fired = hits.filter((hit) => hit.effect !== void 0);
2258
+ if (fired.length > 0)
2259
+ effects.set(request, fired.map((hit) => hit.effect));
2260
+ let response = await instanceFor(storage, namespace).fetch(request);
2261
+ if (MUTATING_METHODS.has(request.method) && response.status >= 200 && response.status < 400) {
2262
+ const point = timeline(namespace).commit(capture(storage), { branch: selectedBranch });
2263
+ response = mutableResponse(response);
2264
+ response.headers.set(CHECKPOINT_HEADER, point.id);
2265
+ }
2266
+ if (selectedBranch !== "main") {
2267
+ response = mutableResponse(response);
2268
+ response.headers.set(BRANCH_HEADER, selectedBranch);
2269
+ }
2270
+ if (at !== void 0) {
2271
+ response = mutableResponse(response);
2272
+ response.headers.set(AT_HEADER, at);
2273
+ }
2274
+ log(response.status, fired[0]?.id, response);
2275
+ return stamp(response);
2276
+ }
2277
+ };
2278
+ const control = createControlPlane({
2279
+ name: options.name,
2280
+ startedAt: wallNow(),
2281
+ wallNow,
2282
+ clock,
2283
+ faults,
2284
+ metrics,
2285
+ journal,
2286
+ defaultNamespace: DEFAULT_NAMESPACE,
2287
+ namespaces: runtime.namespaces,
2288
+ reset,
2289
+ timeTravel: {
2290
+ checkpoint: (name, branchName) => {
2291
+ const point = checkpoint(name, branchName);
2292
+ return {
2293
+ id: point.id,
2294
+ branch: point.branch,
2295
+ parent: point.parent,
2296
+ at: point.at,
2297
+ records: point.value.snapshot.records.length
2298
+ };
2299
+ },
2300
+ branch: (branchName, branchOptions) => {
2301
+ const point = branch(branchName, branchOptions);
2302
+ return { id: point.id, branch: point.branch, parent: point.parent, at: point.at };
2303
+ },
2304
+ checkout: (checkpointId, checkoutOptions) => checkout(checkpointId, checkoutOptions),
2305
+ retain: (name, checkpointId) => {
2306
+ timeline(name).retain(checkpointId);
2307
+ },
2308
+ release: (name, checkpointId) => timeline(name).release(checkpointId),
2309
+ inspect: (name) => {
2310
+ const history = timeline(name);
2311
+ return {
2312
+ branches: history.branches(),
2313
+ checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
2314
+ id,
2315
+ branch: branchName,
2316
+ parent,
2317
+ at
2318
+ }))
2319
+ };
2320
+ }
2321
+ },
2322
+ describe: options.describe ?? (() => ({})),
2323
+ ...options.presets ? {
2324
+ applyPreset: (name, namespace, overrides) => runtime.applyPreset(name, namespace, overrides)
2325
+ } : {},
2326
+ routes: {
2327
+ ...credentialRoutes(credentials),
2328
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
2329
+ ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
2330
+ ...options.admin?.(runtime) ?? {}
2331
+ },
2332
+ adminKey: options.adminKey
2333
+ });
2334
+ return runtime;
2335
+ };
2336
+ var mutableResponse = (response) => {
2337
+ try {
2338
+ response.headers.set("x-mockingbird-mutable-probe", "1");
2339
+ response.headers.delete("x-mockingbird-mutable-probe");
2340
+ return response;
2341
+ } catch {
2342
+ return new Response(response.body, response);
2343
+ }
2344
+ };
2345
+ var adminJson = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2346
+ var adminFail = (status, message) => adminJson(status, { error: { type: "mockingbird_admin", message } });
2347
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2348
+ var credentialRoutes = (registry) => ({
2349
+ "GET /credentials": () => adminJson(200, {
2350
+ credentials: registry.entries().map(({ credential, namespace }) => ({
2351
+ credential: maskCredential(credential),
2352
+ namespace
2353
+ }))
2354
+ }),
2355
+ "PUT /credentials": ({ body, namespace }) => {
2356
+ const pairs = [];
2357
+ const list = Array.isArray(body) ? body : isObject(body) ? body.credentials : void 0;
2358
+ if (Array.isArray(list)) {
2359
+ for (const each of list) {
2360
+ if (typeof each === "string")
2361
+ pairs.push([each, namespace]);
2362
+ else if (isObject(each) && typeof each.credential === "string") {
2363
+ pairs.push([
2364
+ each.credential,
2365
+ typeof each.namespace === "string" ? each.namespace : namespace
2366
+ ]);
2367
+ } else
2368
+ return adminFail(400, "each entry is a credential string or {credential, namespace}");
2369
+ }
2370
+ } else if (isObject(list)) {
2371
+ for (const [credential, target] of Object.entries(list)) {
2372
+ if (typeof target !== "string")
2373
+ return adminFail(400, `namespace for ${credential} must be a string`);
2374
+ pairs.push([credential, target]);
2375
+ }
2376
+ } else if (isObject(body) && typeof body.credential === "string") {
2377
+ pairs.push([body.credential, typeof body.namespace === "string" ? body.namespace : namespace]);
2378
+ } else {
2379
+ return adminFail(400, 'expected {"credentials": {"<credential>": "<namespace>"}}');
2380
+ }
2381
+ for (const [credential, target] of pairs) {
2382
+ if (!NAMESPACE_PATTERN.test(target))
2383
+ return adminFail(400, `bad namespace ${JSON.stringify(target)}`);
2384
+ registry.set(credential, target);
2385
+ }
2386
+ return adminJson(200, { mapped: pairs.length });
2387
+ },
2388
+ "DELETE /credentials": ({ url }) => {
2389
+ const credential = url.searchParams.get("credential");
2390
+ if (credential === null)
2391
+ registry.clear();
2392
+ else
2393
+ registry.remove(credential);
2394
+ return adminJson(200, { status: "ok" });
2395
+ }
2396
+ });
2397
+ var presetRoutes = (presets, runtime) => ({
2398
+ "GET /faults/presets": () => adminJson(200, {
2399
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
2400
+ }),
2401
+ "POST /faults/presets/:name": ({ params, body, namespace }) => {
2402
+ const name = params.name;
2403
+ if (!presets[name])
2404
+ return adminFail(404, `no fault preset ${name}; GET /__admin/faults/presets`);
2405
+ const overrides = isObject(body) ? body : {};
2406
+ return adminJson(201, { preset: name, rules: runtime.applyPreset(name, namespace, overrides) });
2407
+ }
2408
+ });
2409
+
2410
+ // src/crypto.ts
2411
+ var encoder2 = new TextEncoder();
2412
+ var bytesToBase64 = (bytes) => {
2413
+ let binary = "";
2414
+ for (const byte of bytes) binary += String.fromCharCode(byte);
2415
+ return btoa(binary);
2416
+ };
2417
+ var base64url = (bytes) => bytesToBase64(bytes).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
2418
+ var decode64url = (value) => {
2419
+ const binary = atob(value.replace(/-/g, "+").replace(/_/g, "/"));
2420
+ return Uint8Array.from(binary, (char) => char.charCodeAt(0));
2421
+ };
2422
+ var hmac2 = async (secret, value) => {
2423
+ const key = await crypto.subtle.importKey(
2424
+ "raw",
2425
+ encoder2.encode(secret),
2426
+ { name: "HMAC", hash: "SHA-256" },
2427
+ false,
2428
+ ["sign"]
2429
+ );
2430
+ return new Uint8Array(await crypto.subtle.sign("HMAC", key, encoder2.encode(value)));
2431
+ };
2432
+ var jwt = async (secret, claims) => {
2433
+ const head = base64url(encoder2.encode(JSON.stringify({ alg: "HS256", typ: "JWT" })));
2434
+ const payload = base64url(encoder2.encode(JSON.stringify(claims)));
2435
+ return `${head}.${payload}.${base64url(await hmac2(secret, `${head}.${payload}`))}`;
2436
+ };
2437
+ var verifyJwt = async (token, secrets, now) => {
2438
+ const [head, body, signature] = token.split(".");
2439
+ if (!head || !body || !signature) return void 0;
2440
+ try {
2441
+ const header = JSON.parse(new TextDecoder().decode(decode64url(head)));
2442
+ const claims = JSON.parse(new TextDecoder().decode(decode64url(body)));
2443
+ if (header.alg !== "HS256" || typeof claims.iss !== "string" || !secrets[claims.iss])
2444
+ return void 0;
2445
+ const expected = base64url(await hmac2(secrets[claims.iss], `${head}.${body}`));
2446
+ if (signature !== expected) return void 0;
2447
+ const seconds = Math.floor(now / 1e3);
2448
+ if (typeof claims.exp !== "number" || claims.exp < seconds || typeof claims.nbf === "number" && claims.nbf > seconds + 10)
2449
+ return void 0;
2450
+ return claims;
2451
+ } catch {
2452
+ return void 0;
2453
+ }
2454
+ };
2455
+ var bodySha256 = async (body) => bytesToBase64(new Uint8Array(await crypto.subtle.digest("SHA-256", encoder2.encode(body))));
2456
+
2457
+ // src/generated/openapi.ts
2458
+ var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"LiveKit Server API (Mockingbird subset)","version":"2.19.1","description":"Twirp JSON room, participant, egress, and SIP contract."},"servers":[{"url":"https://example.livekit.cloud"}],"paths":{"/twirp/livekit.RoomService/{method}":{"post":{"operationId":"RoomService","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"method","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Protobuf JSON response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Twirp error","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/twirp/livekit.Egress/{method}":{"post":{"operationId":"EgressService","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"method","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Protobuf JSON response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/twirp/livekit.SIP/{method}":{"post":{"operationId":"SipService","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"method","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Protobuf JSON response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}}}}`);
2459
+ var operationIds = ["RoomService", "EgressService", "SipService"];
2460
+ var supportedOperationIds = ["RoomService", "EgressService", "SipService"];
2461
+
2462
+ // src/state.ts
2463
+ var LiveKitState = class {
2464
+ rooms;
2465
+ participants;
2466
+ inbox;
2467
+ resources;
2468
+ ids;
2469
+ constructor(sqlite, namespace) {
2470
+ this.rooms = new Collection(sqlite, namespace, "livekit_rooms");
2471
+ this.participants = new Collection(sqlite, namespace, "livekit_participants");
2472
+ this.inbox = new Collection(sqlite, namespace, "livekit_inbox");
2473
+ this.resources = new Collection(sqlite, namespace, "livekit_resources");
2474
+ this.ids = new IdSequence(sqlite, namespace, "livekit");
2475
+ }
2476
+ participantId(room, identity) {
2477
+ return `${room}\0${identity}`;
2478
+ }
2479
+ };
2480
+
2481
+ // src/runtime.ts
2482
+ var LIVEKIT_PRESETS = {
2483
+ unavailable: { description: "The next request loses its connection", rules: [{ drop: true }] },
2484
+ rate_limited: {
2485
+ description: "LiveKit answers resource_exhausted",
2486
+ rules: [
2487
+ {
2488
+ status: 429,
2489
+ body: { code: "resource_exhausted", msg: "rate limit exceeded", meta: {} },
2490
+ headers: { "content-type": "application/json" }
2491
+ }
2492
+ ]
2493
+ },
2494
+ webhook_duplicate: {
2495
+ description: "The next webhook is delivered twice",
2496
+ webhook: { mode: "duplicate" }
2497
+ },
2498
+ webhook_reorder: {
2499
+ description: "The next two webhooks arrive swapped",
2500
+ webhook: { mode: "reorder" }
2501
+ },
2502
+ webhook_drop: { description: "The next webhook is dropped", webhook: { mode: "drop" } }
2503
+ };
2504
+ var error = (status, message) => Response.json({ error: { type: "mockingbird_admin", message } }, { status });
2505
+ var admin = (runtime) => ({
2506
+ "GET /rooms": ({ namespace }) => Response.json({
2507
+ rooms: runtime.instance(namespace).state.rooms.list({ order: "oldest" }).map(({ value }) => value)
2508
+ }),
2509
+ "POST /rooms/:room/participants": ({ namespace, params, body }) => {
2510
+ const input = body;
2511
+ if (!input || typeof input.identity !== "string") return error(400, "identity is required");
2512
+ const participant = runtime.instance(namespace).join(params.room, {
2513
+ identity: input.identity,
2514
+ ...typeof input.name === "string" ? { name: input.name } : {},
2515
+ ...typeof input.metadata === "string" ? { metadata: input.metadata } : {},
2516
+ ...input.attributes && typeof input.attributes === "object" ? { attributes: input.attributes } : {},
2517
+ ...input.permission && typeof input.permission === "object" ? { permission: input.permission } : {}
2518
+ });
2519
+ return participant ? Response.json(participant, { status: 201 }) : error(409, "participant identity already exists");
2520
+ },
2521
+ "DELETE /rooms/:room/participants/:identity": ({ namespace, params }) => runtime.instance(namespace).remove(params.room, params.identity) ? new Response(null, { status: 204 }) : error(404, "participant not found"),
2522
+ "POST /rooms/:room/participants/:identity/tracks": ({ namespace, params, body }) => {
2523
+ const input = body;
2524
+ const track = runtime.instance(namespace).publish(params.room, params.identity, input ?? {});
2525
+ return track ? Response.json(track, { status: 201 }) : error(404, "participant not found");
2526
+ },
2527
+ "GET /rooms/:room/participants/:identity/inbox": ({ namespace, params }) => {
2528
+ const participant = runtime.instance(namespace).state.participants.get(
2529
+ runtime.instance(namespace).state.participantId(params.room, params.identity)
2530
+ );
2531
+ return participant ? Response.json({
2532
+ messages: runtime.instance(namespace).state.inbox.list({
2533
+ order: "oldest",
2534
+ where: (message) => message.participantSid === participant.sid
2535
+ }).map(({ value }) => value)
2536
+ }) : error(404, "participant not found");
2537
+ },
2538
+ "GET /resources": ({ namespace }) => Response.json({
2539
+ resources: runtime.instance(namespace).state.resources.list({ order: "oldest" }).map(({ value }) => value)
2540
+ }),
2541
+ "POST /resources/:id/transition": ({ namespace, params, body }) => {
2542
+ const input = body;
2543
+ const api = runtime.instance(namespace);
2544
+ const current = api.state.resources.get(params.id);
2545
+ if (!current || !input || typeof input.status !== "string")
2546
+ return error(current ? 400 : 404, current ? "status is required" : "resource not found");
2547
+ const next = api.transitionResource(
2548
+ current.id,
2549
+ input.status,
2550
+ typeof input.error === "string" ? input.error : void 0
2551
+ );
2552
+ return Response.json(next);
2553
+ }
2554
+ });
2555
+ var createRuntime2 = (options = {}) => {
2556
+ const keys = options.keys ?? { fixture: "fixture-secret-that-is-at-least-32-chars" };
2557
+ const [apiKey, apiSecret] = Object.entries(keys)[0] ?? [
2558
+ "fixture",
2559
+ "fixture-secret-that-is-at-least-32-chars"
2560
+ ];
2561
+ const hub = createWebhookHub({
2562
+ signer: signers.custom(async ({ body, timestampSeconds }) => ({
2563
+ Authorization: await jwt(apiSecret, {
2564
+ iss: apiKey,
2565
+ nbf: timestampSeconds,
2566
+ exp: timestampSeconds + 600,
2567
+ sha256: await bodySha256(body)
2568
+ })
2569
+ })),
2570
+ endpoints: options.webhooks?.endpoints ?? [],
2571
+ ...options.webhooks?.retryDelaysMs ? { retryDelaysMs: options.webhooks.retryDelaysMs } : {},
2572
+ ...options.webhooks?.fetch ? { fetch: options.webhooks.fetch } : {}
2573
+ });
2574
+ const runtime = createRuntime({
2575
+ name: LIVEKIT_NAMESPACE,
2576
+ document,
2577
+ presets: LIVEKIT_PRESETS,
2578
+ ...options.sqlite ? { sqlite: options.sqlite } : {},
2579
+ ...options.clock ? { clock: options.clock } : {},
2580
+ ...options.seed !== void 0 ? { seed: options.seed } : {},
2581
+ ...options.adminKey !== void 0 ? { adminKey: options.adminKey } : {},
2582
+ ...options.onLog ? { onLog: options.onLog } : {},
2583
+ create: ({ sqlite, namespace, clock }) => new LiveKitAPI({
2584
+ sqlite,
2585
+ namespace,
2586
+ now: clock.now,
2587
+ keys,
2588
+ onEvent: (event) => hub.publish({ namespace, type: event.event, body: event })
2589
+ }),
2590
+ admin
2591
+ });
2592
+ return Object.assign(runtime, { webhooks: hub });
2593
+ };
2594
+
2595
+ // src/index.ts
2596
+ var LIVEKIT_NAMESPACE = "livekit";
2597
+ var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
2598
+ var LiveKitAPI = class {
2599
+ constructor(options = {}) {
2600
+ this.options = options;
2601
+ this.sqlite = bootSqlite(options.sqlite);
2602
+ this.namespace = options.namespace ?? LIVEKIT_NAMESPACE;
2603
+ this.now = options.now ?? Date.now;
2604
+ this.keys = options.keys ?? { fixture: "fixture-secret-that-is-at-least-32-chars" };
2605
+ this.state = new LiveKitState(this.sqlite, this.namespace);
2606
+ }
2607
+ options;
2608
+ state;
2609
+ sqlite;
2610
+ namespace;
2611
+ now;
2612
+ keys;
2613
+ async reset() {
2614
+ clearNamespace(this.sqlite, this.namespace);
2615
+ }
2616
+ json(body, status = 200) {
2617
+ return new Response(JSON.stringify(body), {
2618
+ status,
2619
+ headers: {
2620
+ "content-type": "application/json",
2621
+ "x-livekit-request-id": this.state.ids.next("req_", 20)
2622
+ }
2623
+ });
2624
+ }
2625
+ error(code, msg, status = 400, meta = {}) {
2626
+ return this.json({ code, msg, meta }, status);
2627
+ }
2628
+ publicRoom(room) {
2629
+ return { ...room, creationTime: String(Math.floor(room.creationTime / 1e3)) };
2630
+ }
2631
+ publicParticipant(participant) {
2632
+ return {
2633
+ sid: participant.sid,
2634
+ identity: participant.identity,
2635
+ name: participant.name,
2636
+ state: "ACTIVE",
2637
+ joinedAt: String(Math.floor(participant.joinedAt / 1e3)),
2638
+ metadata: participant.metadata,
2639
+ attributes: participant.attributes,
2640
+ permission: participant.permission,
2641
+ tracks: participant.tracks
2642
+ };
2643
+ }
2644
+ emit(event) {
2645
+ this.options.onEvent?.({
2646
+ ...event,
2647
+ id: this.state.ids.next("EV_", 20),
2648
+ createdAt: String(Math.floor(this.now() / 1e3))
2649
+ });
2650
+ }
2651
+ room(name) {
2652
+ return typeof name === "string" ? this.state.rooms.get(name) : void 0;
2653
+ }
2654
+ participant(room, identity) {
2655
+ return typeof room === "string" && typeof identity === "string" ? this.state.participants.get(this.state.participantId(room, identity)) : void 0;
2656
+ }
2657
+ updateRoomCount(name) {
2658
+ const room = this.state.rooms.get(name);
2659
+ if (!room) return;
2660
+ const participants = this.state.participants.list({ where: (p) => p.room === name }).map(({ value }) => value);
2661
+ this.state.rooms.insert(name, {
2662
+ ...room,
2663
+ numParticipants: participants.length,
2664
+ numPublishers: participants.filter((p) => p.tracks.length).length
2665
+ });
2666
+ }
2667
+ createRoom(input) {
2668
+ const name = typeof input.name === "string" ? input.name : "";
2669
+ if (!name) return void 0;
2670
+ const prior = this.state.rooms.get(name);
2671
+ if (prior) return prior;
2672
+ const room = {
2673
+ sid: this.state.ids.next("RM_", 24),
2674
+ name,
2675
+ emptyTimeout: Number(input.emptyTimeout ?? 300),
2676
+ departureTimeout: Number(input.departureTimeout ?? 20),
2677
+ maxParticipants: Number(input.maxParticipants ?? 0),
2678
+ creationTime: this.now(),
2679
+ metadata: typeof input.metadata === "string" ? input.metadata : "",
2680
+ numParticipants: 0,
2681
+ numPublishers: 0,
2682
+ activeRecording: false
2683
+ };
2684
+ this.state.rooms.insert(name, room);
2685
+ this.emit({ event: "room_started", room: this.publicRoom(room) });
2686
+ return room;
2687
+ }
2688
+ join(roomName, input) {
2689
+ const room = this.state.rooms.get(roomName) ?? this.createRoom({ name: roomName });
2690
+ if (!room || this.participant(roomName, input.identity)) return void 0;
2691
+ const participant = {
2692
+ room: roomName,
2693
+ sid: this.state.ids.next("PA_", 24),
2694
+ identity: input.identity,
2695
+ name: input.name ?? input.identity,
2696
+ metadata: input.metadata ?? "",
2697
+ attributes: input.attributes ?? {},
2698
+ joinedAt: this.now(),
2699
+ permission: {
2700
+ canSubscribe: true,
2701
+ canPublish: true,
2702
+ canPublishData: true,
2703
+ ...input.permission
2704
+ },
2705
+ tracks: []
2706
+ };
2707
+ this.state.participants.insert(this.state.participantId(roomName, input.identity), participant);
2708
+ this.updateRoomCount(roomName);
2709
+ this.emit({
2710
+ event: "participant_joined",
2711
+ room: this.publicRoom(this.state.rooms.get(roomName)),
2712
+ participant: this.publicParticipant(participant)
2713
+ });
2714
+ return participant;
2715
+ }
2716
+ remove(roomName, identity) {
2717
+ const participant = this.participant(roomName, identity);
2718
+ if (!participant) return void 0;
2719
+ const room = this.publicRoom(this.state.rooms.get(roomName));
2720
+ for (const track of participant.tracks)
2721
+ this.emit({
2722
+ event: "track_unpublished",
2723
+ room,
2724
+ participant: this.publicParticipant(participant),
2725
+ track
2726
+ });
2727
+ this.state.participants.delete(this.state.participantId(roomName, identity));
2728
+ this.updateRoomCount(roomName);
2729
+ this.emit({ event: "participant_left", room, participant: this.publicParticipant(participant) });
2730
+ return participant;
2731
+ }
2732
+ deleteRoom(name) {
2733
+ const room = this.state.rooms.get(name);
2734
+ if (!room) return void 0;
2735
+ for (const { value } of this.state.participants.list({ where: (p) => p.room === name }))
2736
+ this.remove(name, value.identity);
2737
+ this.state.rooms.delete(name);
2738
+ this.emit({ event: "room_finished", room: this.publicRoom(room) });
2739
+ return room;
2740
+ }
2741
+ publish(roomName, identity, input) {
2742
+ const participant = this.participant(roomName, identity);
2743
+ if (!participant) return void 0;
2744
+ const track = {
2745
+ sid: input.sid ?? this.state.ids.next("TR_", 24),
2746
+ name: input.name ?? "track",
2747
+ type: input.type ?? "AUDIO",
2748
+ source: input.source ?? "MICROPHONE",
2749
+ muted: input.muted ?? false,
2750
+ ...input.width ? { width: input.width } : {},
2751
+ ...input.height ? { height: input.height } : {}
2752
+ };
2753
+ const next = { ...participant, tracks: [...participant.tracks, track] };
2754
+ this.state.participants.insert(this.state.participantId(roomName, identity), next);
2755
+ this.updateRoomCount(roomName);
2756
+ this.emit({
2757
+ event: "track_published",
2758
+ room: this.publicRoom(this.state.rooms.get(roomName)),
2759
+ participant: this.publicParticipant(next),
2760
+ track
2761
+ });
2762
+ return track;
2763
+ }
2764
+ expireRooms() {
2765
+ for (const { value: room } of this.state.rooms.list()) {
2766
+ if (room.numParticipants === 0 && room.emptyTimeout > 0 && room.creationTime + room.emptyTimeout * 1e3 <= this.now())
2767
+ this.deleteRoom(room.name);
2768
+ }
2769
+ }
2770
+ transitionResource(id, status, error2) {
2771
+ const current = this.state.resources.get(id);
2772
+ if (!current) return void 0;
2773
+ const input = { ...current.input, status, ...error2 ? { error: error2 } : {} };
2774
+ const next = { ...current, status, input, ...error2 ? { error: error2 } : {} };
2775
+ this.state.resources.insert(id, next);
2776
+ this.emit(
2777
+ current.kind === "egress" ? {
2778
+ event: status === "EGRESS_COMPLETE" ? "egress_ended" : "egress_updated",
2779
+ egressInfo: input
2780
+ } : {
2781
+ event: status === "DISCONNECTED" ? "sip_call_ended" : "sip_call_updated",
2782
+ sipCall: input
2783
+ }
2784
+ );
2785
+ return next;
2786
+ }
2787
+ async authorize(request, service, method, input) {
2788
+ const raw = request.headers.get("authorization")?.replace(/^Bearer\s+/i, "");
2789
+ const claims = raw ? await verifyJwt(raw, this.keys, this.now()) : void 0;
2790
+ if (!claims) return void 0;
2791
+ const video = record(claims.video);
2792
+ const sip = record(claims.sip);
2793
+ if (service === "SIP") return sip.admin === true || sip.call === true ? claims : void 0;
2794
+ if (service === "Egress") return video.roomRecord === true ? claims : void 0;
2795
+ const required = method === "CreateRoom" || method === "DeleteRoom" ? "roomCreate" : method === "ListRooms" ? "roomList" : "roomAdmin";
2796
+ if (video[required] !== true) return void 0;
2797
+ const room = input.room ?? input.roomName;
2798
+ return typeof video.room === "string" && typeof room === "string" && video.room !== room ? void 0 : claims;
2799
+ }
2800
+ async roomService(method, input) {
2801
+ if (method === "CreateRoom") {
2802
+ const room = this.createRoom(input);
2803
+ return room ? this.json(this.publicRoom(room)) : this.error("invalid_argument", "room name is required");
2804
+ }
2805
+ if (method === "ListRooms") {
2806
+ this.expireRooms();
2807
+ const names = Array.isArray(input.names) ? new Set(input.names) : void 0;
2808
+ const rooms = this.state.rooms.list({ order: "oldest", where: (r) => !names?.size || names.has(r.name) }).map(({ value }) => this.publicRoom(value));
2809
+ return this.json({ rooms });
2810
+ }
2811
+ if (method === "DeleteRoom")
2812
+ return typeof input.room === "string" && this.deleteRoom(input.room) ? this.json({}) : this.error("not_found", "room not found", 404);
2813
+ if (method === "UpdateRoomMetadata") {
2814
+ const room = this.room(input.room);
2815
+ if (!room) return this.error("not_found", "room not found", 404);
2816
+ const next = { ...room, metadata: typeof input.metadata === "string" ? input.metadata : "" };
2817
+ this.state.rooms.insert(room.name, next);
2818
+ return this.json(this.publicRoom(next));
2819
+ }
2820
+ if (method === "ListParticipants") {
2821
+ if (!this.room(input.room)) return this.error("not_found", "room not found", 404);
2822
+ return this.json({
2823
+ participants: this.state.participants.list({ where: (p) => p.room === input.room, order: "oldest" }).map(({ value }) => this.publicParticipant(value))
2824
+ });
2825
+ }
2826
+ if (method === "GetParticipant") {
2827
+ const participant = this.participant(input.room, input.identity);
2828
+ return participant ? this.json(this.publicParticipant(participant)) : this.error("not_found", "participant not found", 404);
2829
+ }
2830
+ if (method === "RemoveParticipant")
2831
+ return typeof input.room === "string" && typeof input.identity === "string" && this.remove(input.room, input.identity) ? this.json({}) : this.error("not_found", "participant not found", 404);
2832
+ if (method === "UpdateParticipant") {
2833
+ const participant = this.participant(input.room, input.identity);
2834
+ if (!participant || typeof input.room !== "string" || typeof input.identity !== "string")
2835
+ return this.error("not_found", "participant not found", 404);
2836
+ const next = {
2837
+ ...participant,
2838
+ ...typeof input.metadata === "string" ? { metadata: input.metadata } : {},
2839
+ ...typeof input.name === "string" ? { name: input.name } : {},
2840
+ ...input.attributes && typeof input.attributes === "object" ? {
2841
+ attributes: {
2842
+ ...participant.attributes,
2843
+ ...input.attributes
2844
+ }
2845
+ } : {},
2846
+ ...input.permission && typeof input.permission === "object" ? { permission: input.permission } : {}
2847
+ };
2848
+ this.state.participants.insert(this.state.participantId(input.room, input.identity), next);
2849
+ return this.json(this.publicParticipant(next));
2850
+ }
2851
+ if (method === "MutePublishedTrack") {
2852
+ const participant = this.participant(input.room, input.identity);
2853
+ const index = participant?.tracks.findIndex((track2) => track2.sid === input.trackSid) ?? -1;
2854
+ if (!participant || index < 0 || typeof input.room !== "string" || typeof input.identity !== "string")
2855
+ return this.error("not_found", "track not found", 404);
2856
+ const track = { ...participant.tracks[index], muted: input.muted === true };
2857
+ const tracks = [...participant.tracks];
2858
+ tracks[index] = track;
2859
+ this.state.participants.insert(this.state.participantId(input.room, input.identity), {
2860
+ ...participant,
2861
+ tracks
2862
+ });
2863
+ return this.json({ track });
2864
+ }
2865
+ if (method === "SendData") {
2866
+ if (typeof input.room !== "string" || !this.room(input.room))
2867
+ return this.error("not_found", "room not found", 404);
2868
+ const identities = Array.isArray(input.destinationIdentities) ? input.destinationIdentities : [];
2869
+ const sids = Array.isArray(input.destinationSids) ? input.destinationSids : [];
2870
+ const recipients = this.state.participants.list({
2871
+ where: (p) => p.room === input.room && (!identities.length && !sids.length || identities.includes(p.identity) || sids.includes(p.sid))
2872
+ }).map(({ value }) => value);
2873
+ const message = {
2874
+ id: this.state.ids.next("DP_", 20),
2875
+ room: input.room,
2876
+ data: typeof input.data === "string" ? input.data : "",
2877
+ kind: typeof input.kind === "string" ? input.kind : "RELIABLE",
2878
+ ...typeof input.topic === "string" ? { topic: input.topic } : {},
2879
+ createdAt: this.now()
2880
+ };
2881
+ for (const participant of recipients)
2882
+ this.state.inbox.insert(`${participant.sid}\0${message.id}`, {
2883
+ ...message,
2884
+ participantSid: participant.sid
2885
+ });
2886
+ return this.json({});
2887
+ }
2888
+ return this.error("unimplemented", `RoomService.${method} is not implemented`, 501);
2889
+ }
2890
+ async asyncService(service, method, input) {
2891
+ const kind = service === "Egress" ? "egress" : "sip";
2892
+ if (method.startsWith("List"))
2893
+ return this.json({
2894
+ items: this.state.resources.list({ where: (r) => r.kind === kind }).map(({ value }) => value.input)
2895
+ });
2896
+ if (method.startsWith("Stop") || method.startsWith("Delete")) {
2897
+ const id = String(
2898
+ input.egressId ?? input.sipCallId ?? input.sipTrunkId ?? input.sipDispatchRuleId ?? ""
2899
+ );
2900
+ const current = this.state.resources.get(id);
2901
+ if (!current) return this.error("not_found", `${kind} resource not found`, 404);
2902
+ const next = this.transitionResource(
2903
+ id,
2904
+ kind === "egress" ? "EGRESS_COMPLETE" : "DISCONNECTED"
2905
+ );
2906
+ return this.json(next?.input ?? {});
2907
+ }
2908
+ if (method.startsWith("Start") || method.startsWith("Create")) {
2909
+ const id = this.state.ids.next(kind === "egress" ? "EG_" : "SC_", 24);
2910
+ const roomName = typeof input.roomName === "string" ? input.roomName : typeof input.room === "string" ? input.room : void 0;
2911
+ const output = kind === "egress" ? {
2912
+ egressId: id,
2913
+ roomName: roomName ?? "",
2914
+ status: "EGRESS_STARTING",
2915
+ startedAt: String(this.now() * 1e6),
2916
+ ...input
2917
+ } : {
2918
+ participantId: this.state.ids.next("PA_", 24),
2919
+ participantIdentity: typeof input.participantIdentity === "string" ? input.participantIdentity : `sip-${id}`,
2920
+ roomName: roomName ?? "",
2921
+ sipCallId: id
2922
+ };
2923
+ const resource = {
2924
+ id,
2925
+ kind,
2926
+ status: kind === "egress" ? "EGRESS_STARTING" : "DIALING",
2927
+ ...roomName ? { roomName } : {},
2928
+ input: output
2929
+ };
2930
+ this.state.resources.insert(id, resource);
2931
+ this.emit(
2932
+ kind === "egress" ? { event: "egress_started", egressInfo: output } : { event: "sip_call_started", sipCall: output }
2933
+ );
2934
+ return this.json(output);
2935
+ }
2936
+ return this.error("unimplemented", `${service}.${method} is not implemented`, 501);
2937
+ }
2938
+ async fetch(request) {
2939
+ if (request.method !== "POST")
2940
+ return this.error("bad_route", "Twirp endpoints require POST", 404);
2941
+ const match2 = new URL(request.url).pathname.match(
2942
+ /^\/twirp\/livekit\.(RoomService|Egress|SIP)\/([^/]+)$/
2943
+ );
2944
+ if (!match2) return this.error("bad_route", "unknown Twirp route", 404);
2945
+ const service = match2[1];
2946
+ const method = match2[2];
2947
+ const input = await request.json().catch(() => ({}));
2948
+ if (!await this.authorize(request, service, method, input))
2949
+ return this.error("unauthenticated", "invalid or insufficient LiveKit token", 401);
2950
+ return service === "RoomService" ? this.roomService(method, input) : this.asyncService(service, method, input);
2951
+ }
2952
+ };
2953
+
2954
+ export {
2955
+ document,
2956
+ operationIds,
2957
+ supportedOperationIds,
2958
+ LIVEKIT_PRESETS,
2959
+ createRuntime2 as createRuntime,
2960
+ LIVEKIT_NAMESPACE,
2961
+ LiveKitAPI
2962
+ };
2963
+ //# sourceMappingURL=chunk-4Y4K4CKO.js.map