@crvouga/mockingbird-service-postgres 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/wire/cli.js CHANGED
@@ -1,7 +1,73 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/wire/index.ts
4
- import { createServer } from "node:net";
3
+ // ../../adapters/node/dist/cli.js
4
+ import { readFile as readFile2 } from "node:fs/promises";
5
+ import { createRequire } from "node:module";
6
+ import { resolve } from "node:path";
7
+ import { pathToFileURL } from "node:url";
8
+ import { parseArgs } from "node:util";
9
+
10
+ // ../core/dist/clock.js
11
+ var createClock = (source = Date.now) => {
12
+ let offsetMs = 0;
13
+ let frozenAt;
14
+ const now = () => frozenAt ?? source() + offsetMs;
15
+ return {
16
+ now,
17
+ set: (epochMs) => {
18
+ if (frozenAt !== void 0)
19
+ frozenAt = epochMs;
20
+ else
21
+ offsetMs = epochMs - source();
22
+ },
23
+ advance: (deltaMs) => {
24
+ if (frozenAt !== void 0)
25
+ frozenAt += deltaMs;
26
+ else
27
+ offsetMs += deltaMs;
28
+ },
29
+ freeze: () => {
30
+ frozenAt = now();
31
+ },
32
+ unfreeze: () => {
33
+ if (frozenAt === void 0)
34
+ return;
35
+ offsetMs = frozenAt - source();
36
+ frozenAt = void 0;
37
+ },
38
+ reset: () => {
39
+ offsetMs = 0;
40
+ frozenAt = void 0;
41
+ },
42
+ state: () => ({ now: now(), frozen: frozenAt !== void 0, offsetMs })
43
+ };
44
+ };
45
+
46
+ // ../core/dist/control.js
47
+ var ADMIN_PREFIX = "/__admin";
48
+ var resolveAdminPrefix = (prefix = ADMIN_PREFIX) => {
49
+ if (!/^\/[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*$/.test(prefix)) {
50
+ throw new Error("adminPrefix must be an absolute non-root path of literal segments, without a trailing slash");
51
+ }
52
+ return prefix;
53
+ };
54
+ var UNITS = {
55
+ ms: 1,
56
+ s: 1e3,
57
+ m: 6e4,
58
+ h: 36e5,
59
+ d: 864e5
60
+ };
61
+ var parseDuration = (value) => {
62
+ if (typeof value === "number" && Number.isFinite(value))
63
+ return value;
64
+ if (typeof value !== "string")
65
+ return void 0;
66
+ const match = /^(-?\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/.exec(value.trim());
67
+ if (!match)
68
+ return void 0;
69
+ return Number(match[1]) * UNITS[match[2]];
70
+ };
5
71
 
6
72
  // ../../core/dist/timeline.js
7
73
  var BRANCH_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
@@ -164,6 +230,786 @@ var Timeline = class {
164
230
  }
165
231
  };
166
232
 
233
+ // ../../adapters/node/dist/fleet.js
234
+ import { randomUUID } from "node:crypto";
235
+ import { chmod, readFile, rename, rm, writeFile } from "node:fs/promises";
236
+
237
+ // ../../adapters/node/dist/serve.js
238
+ import { createServer } from "node:http";
239
+ var serve = async (api, options2 = {}) => {
240
+ const server2 = createServer(async (req, res) => {
241
+ const chunks = [];
242
+ for await (const chunk of req) {
243
+ chunks.push(Buffer.from(chunk));
244
+ }
245
+ const body = Buffer.concat(chunks);
246
+ const address = server2.address();
247
+ const port = typeof address === "object" && address !== null ? address.port : void 0;
248
+ const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`;
249
+ const raw = req.url ?? "/";
250
+ const url = new URL(raw.replace(/^\/+/, "/"), base);
251
+ const method = req.method ?? "GET";
252
+ const init = { method, headers: req.headers };
253
+ if (method !== "GET" && method !== "HEAD" && body.length > 0) {
254
+ init.body = body;
255
+ }
256
+ const aborted = new AbortController();
257
+ res.once("close", () => {
258
+ if (!res.writableFinished)
259
+ aborted.abort();
260
+ });
261
+ init.signal = aborted.signal;
262
+ const request = new Request(url, init);
263
+ let response;
264
+ try {
265
+ response = await api.fetch(request);
266
+ } catch (error) {
267
+ if (error.code === "MOCKINGBIRD_DROP") {
268
+ req.socket.destroy();
269
+ return;
270
+ }
271
+ res.writeHead(500, { "content-type": "application/json" });
272
+ res.end(JSON.stringify({
273
+ error: {
274
+ type: "mockingbird_internal",
275
+ message: error instanceof Error ? error.message : String(error)
276
+ }
277
+ }));
278
+ return;
279
+ }
280
+ const headers = Object.fromEntries(response.headers);
281
+ const cookies = response.headers.getSetCookie();
282
+ if (cookies.length > 0)
283
+ headers["set-cookie"] = cookies;
284
+ if (!response.body) {
285
+ res.writeHead(response.status, headers);
286
+ res.end();
287
+ return;
288
+ }
289
+ res.writeHead(response.status, headers);
290
+ res.flushHeaders();
291
+ const reader = response.body.getReader();
292
+ try {
293
+ for (; ; ) {
294
+ const { done, value } = await reader.read();
295
+ if (done)
296
+ break;
297
+ if (!res.write(value))
298
+ await new Promise((resolve2) => res.once("drain", resolve2));
299
+ }
300
+ res.end();
301
+ } catch {
302
+ res.destroy();
303
+ } finally {
304
+ reader.releaseLock();
305
+ }
306
+ });
307
+ await new Promise((resolve2, reject) => {
308
+ server2.once("error", reject);
309
+ server2.listen(options2.port ?? 0, options2.host, resolve2);
310
+ });
311
+ return server2;
312
+ };
313
+
314
+ // ../../adapters/node/dist/listen.js
315
+ var listen = async (api, options2 = {}) => {
316
+ const host = options2.host ?? "127.0.0.1";
317
+ const server2 = await serve(api, { port: options2.port ?? 0, host });
318
+ const address = server2.address();
319
+ const port = typeof address === "object" && address !== null ? address.port : options2.port ?? 0;
320
+ const shown = host.includes(":") ? `[${host}]` : host;
321
+ return {
322
+ url: `http://${shown}:${port}`,
323
+ port,
324
+ host,
325
+ server: server2,
326
+ close: () => new Promise((resolve2, reject) => {
327
+ server2.close((error) => {
328
+ if (error && error.code !== "ERR_SERVER_NOT_RUNNING")
329
+ reject(error);
330
+ else
331
+ resolve2();
332
+ });
333
+ server2.closeAllConnections?.();
334
+ })
335
+ };
336
+ };
337
+
338
+ // ../../adapters/node/dist/fleet.js
339
+ var EMPTY = {
340
+ activeRequests: 0,
341
+ activeConnections: 0,
342
+ blockedCommands: 0,
343
+ pendingJobs: 0,
344
+ pendingWebhooks: 0,
345
+ unmatchedRequests: 0
346
+ };
347
+ var NS = /^[A-Za-z0-9_.-]{1,64}$/;
348
+ var json = (status, value) => Response.json(value, { status });
349
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
350
+ var pending = (d) => d.activeRequests + d.blockedCommands + d.pendingJobs + d.pendingWebhooks;
351
+ var publicUrl = (raw) => {
352
+ const url = new URL(raw);
353
+ url.password = "";
354
+ return url.href;
355
+ };
356
+ var atomicWrite = async (path, value) => {
357
+ const temp = `${path}.${randomUUID()}.tmp`;
358
+ try {
359
+ await writeFile(temp, `${JSON.stringify(value, null, 2)}
360
+ `, { mode: 384, flag: "wx" });
361
+ await chmod(temp, 384);
362
+ await rename(temp, path);
363
+ } finally {
364
+ await rm(temp, { force: true });
365
+ }
366
+ };
367
+ var removeOwned = async (path, id) => {
368
+ if (!path)
369
+ return;
370
+ try {
371
+ const value = JSON.parse(await readFile(path, "utf8"));
372
+ if (isObject(value) && value.id === id)
373
+ await rm(path, { force: true });
374
+ } catch (error) {
375
+ if (error.code !== "ENOENT")
376
+ throw error;
377
+ }
378
+ };
379
+ var httpChild = async (target, entry, options2) => {
380
+ const runtime = await target.create(entry.options ?? {}, {
381
+ adminPrefix: resolveAdminPrefix(entry.adminPrefix),
382
+ adminKey: entry.adminKey,
383
+ seed: entry.seed,
384
+ onLog: options2.onLog
385
+ });
386
+ const adminPrefix = resolveAdminPrefix(entry.adminPrefix);
387
+ runtime.isolateNamespaces();
388
+ const locked = /* @__PURE__ */ new Set();
389
+ const active = /* @__PURE__ */ new Map();
390
+ const api = {
391
+ async fetch(request) {
392
+ const namespace = runtime.namespaceOf(request);
393
+ if (locked.has(namespace))
394
+ return json(409, { error: "fleet control in progress", namespace });
395
+ active.set(namespace, (active.get(namespace) ?? 0) + 1);
396
+ try {
397
+ return await runtime.fetch(request);
398
+ } finally {
399
+ active.set(namespace, (active.get(namespace) ?? 1) - 1);
400
+ }
401
+ }
402
+ };
403
+ let server2;
404
+ try {
405
+ server2 = await listen(api, {
406
+ port: entry.port ?? target.defaultPort,
407
+ host: entry.host ?? "127.0.0.1"
408
+ });
409
+ target.listening?.(server2);
410
+ } catch (error) {
411
+ runtime.stop?.();
412
+ await server2?.close();
413
+ runtime.webhooks?.clear();
414
+ await runtime.webhooks?.idle();
415
+ throw error;
416
+ }
417
+ return {
418
+ protocol: "http",
419
+ url: server2.url,
420
+ healthUrl: `${server2.url}${adminPrefix}/health`,
421
+ adminUrl: `${server2.url}${adminPrefix}`,
422
+ namespaces: { header: "x-mockingbird-namespace", path: `${adminPrefix}/ns/{name}` },
423
+ ready: async () => {
424
+ try {
425
+ return (await fetch(`${server2.url}${adminPrefix}/health`, { signal: AbortSignal.timeout(2e3) })).ok;
426
+ } catch {
427
+ return false;
428
+ }
429
+ },
430
+ async close() {
431
+ runtime.stop?.();
432
+ runtime.webhooks?.clear();
433
+ await server2.close();
434
+ await runtime.webhooks?.idle();
435
+ },
436
+ lock(namespace) {
437
+ if (locked.has(namespace))
438
+ throw new Error("namespace already locked");
439
+ locked.add(namespace);
440
+ return () => locked.delete(namespace);
441
+ },
442
+ diagnostics(namespace) {
443
+ return {
444
+ ...EMPTY,
445
+ activeRequests: active.get(namespace) ?? 0,
446
+ pendingWebhooks: runtime.webhooks?.pending(namespace) ?? 0,
447
+ unmatchedRequests: runtime.metrics.report(namespace).unmatched.reduce((n, r) => n + r.count, 0)
448
+ };
449
+ },
450
+ async checkpoint(namespace) {
451
+ return runtime.fleetSnapshot(namespace);
452
+ },
453
+ async restore(namespace, checkpoint) {
454
+ runtime.fleetRestore(checkpoint, namespace);
455
+ },
456
+ reset: (namespace) => runtime.reset(namespace),
457
+ clock: (namespace) => runtime.namespaceClock(namespace),
458
+ async settle(namespace) {
459
+ const instance = runtime.instance(namespace);
460
+ instance.tick?.();
461
+ await runtime.webhooks?.idle();
462
+ }
463
+ };
464
+ };
465
+ async function startFleet(config, options2) {
466
+ const id = randomUUID();
467
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
468
+ const children = /* @__PURE__ */ new Map();
469
+ const clocks = /* @__PURE__ */ new Map();
470
+ const checkpoints = /* @__PURE__ */ new Map();
471
+ let supervisor;
472
+ let stopped = false;
473
+ let controlling = false;
474
+ const close = async () => {
475
+ if (stopped)
476
+ return;
477
+ stopped = true;
478
+ await Promise.allSettled([...children.values()].map((child) => child.close()));
479
+ await supervisor?.close();
480
+ await removeOwned(options2.readyFile, id);
481
+ await removeOwned(options2.connectionsFile, id);
482
+ };
483
+ if (options2.readyFile)
484
+ await rm(options2.readyFile, { force: true });
485
+ if (options2.connectionsFile)
486
+ await rm(options2.connectionsFile, { force: true });
487
+ const clock = (namespace) => {
488
+ let value = clocks.get(namespace);
489
+ if (!value) {
490
+ value = createClock();
491
+ clocks.set(namespace, value);
492
+ }
493
+ return value;
494
+ };
495
+ const select = (body) => {
496
+ if (body.services === void 0)
497
+ return children;
498
+ if (!Array.isArray(body.services) || body.services.length === 0)
499
+ throw new Error("services must be a nonempty list");
500
+ const selected = /* @__PURE__ */ new Map();
501
+ for (const name of body.services) {
502
+ if (typeof name !== "string" || !children.has(name))
503
+ throw new Error("unknown service in selection");
504
+ selected.set(name, children.get(name));
505
+ }
506
+ return selected;
507
+ };
508
+ const diagnostics = (namespace) => Object.fromEntries([...children].map(([name, child]) => [name, child.diagnostics(namespace)]));
509
+ const transaction = async (namespace, selected, run) => {
510
+ if (controlling)
511
+ return json(409, { error: "another fleet operation is in progress" });
512
+ controlling = true;
513
+ const release = [];
514
+ const before = /* @__PURE__ */ new Map();
515
+ const results = {};
516
+ let failure;
517
+ try {
518
+ for (const [name, child] of selected) {
519
+ failure = name;
520
+ release.push(child.lock(namespace));
521
+ }
522
+ const busy = Object.fromEntries([...selected].filter(([, child]) => {
523
+ const state = child.diagnostics(namespace);
524
+ return state.activeRequests + state.blockedCommands + state.pendingWebhooks + (state.activeJobs ?? 0) > 0;
525
+ }).map(([name, child]) => [name, child.diagnostics(namespace)]));
526
+ if (Object.keys(busy).length)
527
+ return json(409, { error: "namespace is not quiescent", services: busy });
528
+ for (const [name, child] of selected) {
529
+ failure = name;
530
+ before.set(name, await child.checkpoint(namespace));
531
+ }
532
+ for (const [name, child] of selected) {
533
+ failure = name;
534
+ results[name] = { status: "ok", result: await run(name, child) };
535
+ }
536
+ return json(200, { status: "ok", namespace, services: results });
537
+ } catch {
538
+ const rollback = {};
539
+ for (const [name, point] of before) {
540
+ try {
541
+ await selected.get(name)?.restore(namespace, point);
542
+ rollback[name] = "restored";
543
+ } catch {
544
+ rollback[name] = "failed";
545
+ }
546
+ }
547
+ return json(500, {
548
+ status: "failed",
549
+ namespace,
550
+ failedService: failure,
551
+ services: results,
552
+ rollback,
553
+ rolledBack: before.size > 0 && Object.values(rollback).every((value) => value === "restored")
554
+ });
555
+ } finally {
556
+ for (const unlock of release.reverse())
557
+ unlock();
558
+ controlling = false;
559
+ }
560
+ };
561
+ const api = {
562
+ async fetch(request) {
563
+ const url = new URL(request.url);
564
+ const adminPrefix = resolveAdminPrefix(config.adminPrefix);
565
+ const path = url.pathname.startsWith(`${adminPrefix}/`) ? url.pathname.slice(adminPrefix.length) : "";
566
+ if (path === "/health" && request.method === "GET") {
567
+ const states = await Promise.all([...children].map(async ([name, child]) => {
568
+ let protocolReady = false;
569
+ try {
570
+ protocolReady = await child.ready();
571
+ } catch {
572
+ }
573
+ return [
574
+ name,
575
+ {
576
+ processReady: !stopped,
577
+ protocolReady,
578
+ status: protocolReady ? "ready" : "failed",
579
+ url: publicUrl(child.url)
580
+ }
581
+ ];
582
+ }));
583
+ const ready = !stopped && states.every(([, state]) => state.protocolReady);
584
+ return json(ready ? 200 : 503, {
585
+ status: ready ? "ready" : "failed",
586
+ id,
587
+ services: Object.fromEntries(states)
588
+ });
589
+ }
590
+ if (!path.startsWith("/fleet/"))
591
+ return json(404, { error: "not found" });
592
+ const adminKey = config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
593
+ if (adminKey !== void 0 && request.headers.get("x-mockingbird-admin-key") !== adminKey)
594
+ return json(401, { error: "admin key required" });
595
+ const route = /^\/fleet\/namespaces\/([^/]+)\/(reset|snapshots)(?:\/([^/]+)\/restore)?$/.exec(path);
596
+ const namespace = route ? decodeURIComponent(route[1]) : url.searchParams.get("namespace") ?? request.headers.get("x-mockingbird-namespace") ?? config.namespace ?? "default";
597
+ if (!NS.test(namespace))
598
+ return json(400, { error: "invalid namespace" });
599
+ if (request.method === "GET" && path === "/fleet/metrics")
600
+ return json(200, { namespace, services: diagnostics(namespace) });
601
+ if (request.method === "GET" && path === "/fleet/clock")
602
+ return json(200, {
603
+ namespace,
604
+ ...clock(namespace).state(),
605
+ services: Object.fromEntries([...children].map(([name, child]) => [name, child.clock(namespace).state()]))
606
+ });
607
+ if (request.method !== "POST")
608
+ return json(405, { error: "method not allowed" });
609
+ let body;
610
+ try {
611
+ const text = await request.text();
612
+ const value = text ? JSON.parse(text) : {};
613
+ if (!isObject(value))
614
+ throw new Error("expected object");
615
+ body = value;
616
+ } catch {
617
+ return json(400, { error: "expected JSON object" });
618
+ }
619
+ let selected;
620
+ try {
621
+ selected = select(body);
622
+ } catch (error) {
623
+ return json(400, { error: error instanceof Error ? error.message : "bad selection" });
624
+ }
625
+ if (path === "/fleet/wait-until-idle") {
626
+ const services = diagnostics(namespace);
627
+ const idle = Object.values(services).every((d) => pending(d) === 0);
628
+ return json(idle ? 200 : 409, { status: idle ? "idle" : "busy", namespace, services });
629
+ }
630
+ if (path === "/fleet/clock") {
631
+ const current = clock(namespace);
632
+ let target = current.now();
633
+ if (body.set !== void 0) {
634
+ target = typeof body.set === "number" ? body.set : typeof body.set === "string" ? Date.parse(body.set) : NaN;
635
+ if (!Number.isFinite(target))
636
+ return json(400, { error: "set must be epoch ms or ISO-8601" });
637
+ }
638
+ if (body.advance !== void 0) {
639
+ const delta = parseDuration(body.advance);
640
+ if (delta === void 0)
641
+ return json(400, { error: "invalid advance" });
642
+ target += delta;
643
+ }
644
+ const frozen = body.freeze === void 0 ? current.state().frozen : body.freeze;
645
+ if (typeof frozen !== "boolean")
646
+ return json(400, { error: "freeze must be boolean" });
647
+ const response2 = await transaction(namespace, selected, async (_name, child) => {
648
+ const childClock = child.clock(namespace);
649
+ childClock.freeze();
650
+ childClock.set(target);
651
+ if (!frozen)
652
+ childClock.unfreeze();
653
+ await child.settle?.(namespace);
654
+ return childClock.state();
655
+ });
656
+ if (response2.ok) {
657
+ current.freeze();
658
+ current.set(target);
659
+ if (!frozen)
660
+ current.unfreeze();
661
+ }
662
+ return response2;
663
+ }
664
+ if (!route)
665
+ return json(404, { error: "not found" });
666
+ if (route[2] === "reset") {
667
+ if (route[3])
668
+ return json(404, { error: "not found" });
669
+ const response2 = await transaction(namespace, selected, async (_name, child) => child.reset(namespace));
670
+ if (response2.ok && selected.size === children.size)
671
+ clock(namespace).reset();
672
+ return response2;
673
+ }
674
+ if (route[3]) {
675
+ const point = checkpoints.get(route[3]);
676
+ if (!point || point.namespace !== namespace)
677
+ return json(404, { error: "no snapshot in this namespace" });
678
+ const restored = new Map([...point.values].map(([name]) => [name, children.get(name)]));
679
+ const response2 = await transaction(namespace, restored, async (name, child) => child.restore(namespace, point.values.get(name)));
680
+ if (response2.ok) {
681
+ const current = clock(namespace);
682
+ current.freeze();
683
+ current.set(point.clock.now);
684
+ if (!point.clock.frozen)
685
+ current.unfreeze();
686
+ }
687
+ return response2;
688
+ }
689
+ const snapshotId = randomUUID();
690
+ const values = /* @__PURE__ */ new Map();
691
+ const response = await transaction(namespace, selected, async (name, child) => {
692
+ values.set(name, await child.checkpoint(namespace));
693
+ return { checkpoint: `${snapshotId}:${name}` };
694
+ });
695
+ if (!response.ok)
696
+ return response;
697
+ checkpoints.set(snapshotId, { namespace, values, clock: clock(namespace).state() });
698
+ return json(201, {
699
+ ...await response.json(),
700
+ id: snapshotId,
701
+ label: body.label
702
+ });
703
+ }
704
+ };
705
+ try {
706
+ const adminPrefix = resolveAdminPrefix(config.adminPrefix);
707
+ if (!isObject(config.services) || Object.keys(config.services).length === 0)
708
+ throw new Error("config must contain at least one service");
709
+ for (const [name, entry] of Object.entries(config.services)) {
710
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name))
711
+ throw new Error("invalid service name");
712
+ try {
713
+ const target = await options2.load(name);
714
+ if (entry.protocol && entry.protocol !== ("protocol" in target ? target.protocol : "http"))
715
+ throw new Error("configured protocol does not match target");
716
+ const adminKey = entry.adminKey ?? config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
717
+ const child = "start" in target ? await target.start(entry) : await httpChild(target, {
718
+ ...entry,
719
+ adminPrefix: resolveAdminPrefix(entry.adminPrefix ?? adminPrefix),
720
+ ...adminKey !== void 0 ? { adminKey } : {}
721
+ }, options2);
722
+ children.set(name, child);
723
+ if (!await child.ready())
724
+ throw new Error("protocol readiness failed");
725
+ } catch {
726
+ throw new Error(`service ${name} failed to start; all started listeners were closed`);
727
+ }
728
+ }
729
+ supervisor = await listen(api, {
730
+ port: config.control?.port ?? 0,
731
+ host: config.control?.host ?? "127.0.0.1"
732
+ });
733
+ const manifest = {
734
+ version: 1,
735
+ state: "ready",
736
+ pid: process.pid,
737
+ id,
738
+ startedAt,
739
+ adminBase: `${supervisor.url}${adminPrefix}/fleet`,
740
+ healthUrl: `${supervisor.url}${adminPrefix}/health`,
741
+ services: Object.fromEntries([...children].map(([name, child]) => [
742
+ name,
743
+ {
744
+ protocol: child.protocol,
745
+ url: publicUrl(child.url),
746
+ healthUrl: child.healthUrl ?? `${supervisor?.url}${adminPrefix}/health`,
747
+ adminUrl: child.adminUrl ?? `${supervisor?.url}${adminPrefix}/fleet`,
748
+ namespaces: child.namespaces,
749
+ processReady: true,
750
+ protocolReady: true
751
+ }
752
+ ]))
753
+ };
754
+ if (options2.connectionsFile)
755
+ await atomicWrite(options2.connectionsFile, {
756
+ version: 1,
757
+ id,
758
+ pid: process.pid,
759
+ services: Object.fromEntries([...children].map(([name, child]) => [
760
+ name,
761
+ { url: child.connection ?? child.url, namespaces: child.connections }
762
+ ]))
763
+ });
764
+ if (options2.readyFile)
765
+ await atomicWrite(options2.readyFile, manifest);
766
+ options2.onReady?.(manifest);
767
+ return { manifest, children, fetch: api.fetch, close };
768
+ } catch (error) {
769
+ await close();
770
+ throw error;
771
+ }
772
+ }
773
+
774
+ // ../../adapters/node/dist/cli.js
775
+ var optionHelp = (options2) => Object.entries(options2).map(([name, option]) => {
776
+ const flag = `--${name}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
777
+ const fallback2 = option.default !== void 0 ? ` (default: ${String(option.default)})` : "";
778
+ return ` ${flag.padEnd(30)} ${option.description}${fallback2}`;
779
+ });
780
+ var help = (spec) => [
781
+ `${spec.bin} \u2014 ${spec.description}`,
782
+ "",
783
+ "Usage:",
784
+ ` ${spec.bin} <command> [options]`,
785
+ "",
786
+ "Commands:",
787
+ ...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),
788
+ "",
789
+ `Run \`${spec.bin} <command> --help\` for a command's options.`
790
+ ].join("\n");
791
+ var commandHelp = (spec, name, command2) => [
792
+ `${spec.bin} ${name} \u2014 ${command2.summary}`,
793
+ "",
794
+ "Usage:",
795
+ ` ${command2.usage ?? `${spec.bin} ${name} [options]`}`,
796
+ ...command2.options ? ["", "Options:", ...optionHelp(command2.options)] : []
797
+ ].join("\n");
798
+ var runCli = async (spec, argv) => {
799
+ const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : void 0;
800
+ const words = pair !== void 0 && spec.commands[pair] ? 2 : 1;
801
+ const name = words === 2 ? pair : argv[0];
802
+ const rest = argv.slice(words);
803
+ if (name === void 0 || name === "--help" || name === "-h" || name === "help") {
804
+ console.log(help(spec));
805
+ return 0;
806
+ }
807
+ const command2 = spec.commands[name];
808
+ if (!command2) {
809
+ console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}
810
+
811
+ ${help(spec)}`);
812
+ return 2;
813
+ }
814
+ if (rest.includes("--help") || rest.includes("-h")) {
815
+ console.log(commandHelp(spec, name, command2));
816
+ return 0;
817
+ }
818
+ let parsed;
819
+ try {
820
+ parsed = parseArgs({
821
+ args: rest,
822
+ allowPositionals: true,
823
+ strict: true,
824
+ options: Object.fromEntries(Object.entries(command2.options ?? {}).map(([key, option]) => [
825
+ key,
826
+ {
827
+ type: option.type,
828
+ ...option.default !== void 0 ? { default: option.default } : {}
829
+ }
830
+ ]))
831
+ });
832
+ } catch (error) {
833
+ console.error(`${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}
834
+
835
+ ${commandHelp(spec, name, command2)}`);
836
+ return 2;
837
+ }
838
+ return command2.run(parsed.values, parsed.positionals);
839
+ };
840
+ var COMMON_SERVE_OPTIONS = {
841
+ port: { type: "string", value: "<port>", description: "Port to listen on" },
842
+ host: { type: "string", value: "<host>", description: "Interface to bind", default: "127.0.0.1" },
843
+ "admin-prefix": {
844
+ type: "string",
845
+ value: "<path>",
846
+ description: "Internal API prefix (default /__admin; env MOCKINGBIRD_ADMIN_PREFIX)"
847
+ },
848
+ "admin-key": {
849
+ type: "string",
850
+ value: "<key>",
851
+ description: "Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)"
852
+ },
853
+ seed: { type: "string", value: "<seed>", description: "Seed for every random choice" },
854
+ log: {
855
+ type: "string",
856
+ value: "<pretty|json|off>",
857
+ description: "Request log format",
858
+ default: "pretty"
859
+ },
860
+ "log-requests": {
861
+ type: "boolean",
862
+ description: "One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json"
863
+ },
864
+ config: {
865
+ type: "string",
866
+ value: "<file>",
867
+ description: "Serve every service in a mockingbird.json config instead"
868
+ },
869
+ "ready-file": {
870
+ type: "string",
871
+ description: "Atomically publish fleet endpoint JSON",
872
+ value: "<file>"
873
+ },
874
+ "connections-file": {
875
+ type: "string",
876
+ description: "Write private connection URLs with mode 0600",
877
+ value: "<file>"
878
+ },
879
+ "ready-json": {
880
+ type: "boolean",
881
+ description: "Emit one public JSON record after fleet readiness"
882
+ }
883
+ };
884
+ var formatLog = (format) => {
885
+ if (format === "off")
886
+ return void 0;
887
+ if (format === "json")
888
+ return (entry) => console.log(JSON.stringify(entry));
889
+ return (entry) => {
890
+ const op = entry.operationId ?? (entry.unmatched ? "UNMATCHED" : "-");
891
+ const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
892
+ const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
893
+ const adopted = entry.adopted ? " adopted" : "";
894
+ const received = entry.request ? ` received=${entry.request.bodyBytes === null ? "?" : `${entry.request.bodyBytes}B`} ct=${entry.request.contentType ?? "-"}${entry.request.transferEncoding ? ` te=${entry.request.transferEncoding}` : ""}` : "";
895
+ const issues = entry.issues?.length ? ` issues=${entry.issues.slice(0, 3).map((issue) => `${issue.path || "$"}: ${issue.message}`).join("; ")}${entry.issues.length > 3 ? ` (+${entry.issues.length - 3})` : ""}` : "";
896
+ console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}${received}${issues}`);
897
+ };
898
+ };
899
+ var asString = (value) => typeof value === "string" ? value : void 0;
900
+ var loadTarget = async (name, own) => {
901
+ if (name === own.name)
902
+ return own;
903
+ const specifier = `@crvouga/mockingbird-service-${name}/server`;
904
+ try {
905
+ let mod;
906
+ try {
907
+ mod = await import(specifier);
908
+ } catch {
909
+ const fromProject = createRequire(resolve(process.cwd(), "package.json"));
910
+ mod = await import(pathToFileURL(fromProject.resolve(specifier)).href);
911
+ }
912
+ if (!mod.serveTarget)
913
+ throw new Error(`${specifier} exports no serveTarget`);
914
+ return mod.serveTarget;
915
+ } catch (error) {
916
+ const reason = error instanceof Error ? error.message : String(error);
917
+ throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
918
+ }
919
+ };
920
+ var start = async (target, values, config) => {
921
+ const adminPrefix = resolveAdminPrefix(config.adminPrefix);
922
+ const runtime = await target.create(values, {
923
+ adminPrefix,
924
+ adminKey: config.adminKey,
925
+ seed: config.seed,
926
+ onLog: formatLog(config.log)
927
+ });
928
+ const listening = await listen(runtime, { port: config.port, host: config.host });
929
+ target.listening?.(listening);
930
+ console.log(`${target.name} mock listening on ${listening.url}`);
931
+ console.log(`${target.name} health: GET ${listening.url}${adminPrefix}/health`);
932
+ console.log(`${target.name} admin: ${listening.url}${adminPrefix} (${config.adminKey ? "x-mockingbird-admin-key required" : "open \u2014 pass --admin-key to lock"})`);
933
+ console.log(`${target.name} admin ui: ${listening.url}${adminPrefix}/ui`);
934
+ for (const line of target.banner?.(runtime) ?? [])
935
+ console.log(`${target.name} ${line.replaceAll("/__admin", adminPrefix)}`);
936
+ return listening;
937
+ };
938
+ var untilSignal = async (servers) => new Promise((resolve2) => {
939
+ const stop2 = async () => {
940
+ ;
941
+ process.removeListener("SIGINT", stop2);
942
+ process.removeListener("SIGTERM", stop2);
943
+ await Promise.allSettled(servers.map((s) => s.close()));
944
+ resolve2(0);
945
+ };
946
+ process.once("SIGINT", stop2);
947
+ process.once("SIGTERM", stop2);
948
+ });
949
+ var serveCommand = (target) => ({
950
+ summary: `Serve the ${target.name} mock or a configured fleet`,
951
+ options: { ...COMMON_SERVE_OPTIONS, ..."options" in target ? target.options : {} },
952
+ async run(values) {
953
+ const log2 = values["log-requests"] === true ? "json" : asString(values.log) ?? "pretty";
954
+ if (!["pretty", "json", "off"].includes(log2)) {
955
+ console.error(`--log must be pretty, json or off (got ${log2})`);
956
+ return 2;
957
+ }
958
+ const configPath = asString(values.config);
959
+ if (configPath !== void 0) {
960
+ try {
961
+ const config = JSON.parse(await readFile2(configPath, "utf8"));
962
+ config.adminPrefix = resolveAdminPrefix(config.adminPrefix ?? asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX);
963
+ const adminKey2 = config.adminKey ?? asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
964
+ if (adminKey2 !== void 0)
965
+ config.adminKey = adminKey2;
966
+ const fleet = await startFleet(config, {
967
+ load: (name) => loadTarget(name, target),
968
+ onLog: formatLog(config.log ?? log2),
969
+ ...asString(values["ready-file"]) ? { readyFile: asString(values["ready-file"]) } : {},
970
+ ...asString(values["connections-file"]) ? { connectionsFile: asString(values["connections-file"]) } : {},
971
+ onReady: (manifest) => {
972
+ if (values["ready-json"])
973
+ console.log(JSON.stringify(manifest));
974
+ else
975
+ for (const [name, endpoint] of Object.entries(manifest.services))
976
+ console.log(`${name} mock listening on ${endpoint.url}`);
977
+ }
978
+ });
979
+ return untilSignal([fleet]);
980
+ } catch (error) {
981
+ console.error(error instanceof Error ? error.message : String(error));
982
+ return 1;
983
+ }
984
+ }
985
+ if ("start" in target) {
986
+ console.error("Use --config to supervise protocol services with this command");
987
+ return 2;
988
+ }
989
+ const port = asString(values.port);
990
+ const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
991
+ const seed = asString(values.seed);
992
+ let listening;
993
+ try {
994
+ listening = await start(target, values, {
995
+ adminPrefix: resolveAdminPrefix(asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX),
996
+ port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
997
+ host: asString(values.host) ?? "127.0.0.1",
998
+ ...adminKey !== void 0 ? { adminKey } : {},
999
+ ...seed !== void 0 ? { seed } : {},
1000
+ log: log2
1001
+ });
1002
+ } catch (error) {
1003
+ console.error(error instanceof Error ? error.message : String(error));
1004
+ return 1;
1005
+ }
1006
+ return untilSignal([listening]);
1007
+ }
1008
+ });
1009
+
1010
+ // src/wire/index.ts
1011
+ import { createServer as createServer2 } from "node:net";
1012
+
167
1013
  // src/errors/error.ts
168
1014
  var CATEGORY_TO_SQLSTATE = {
169
1015
  syntax: "42601",
@@ -2344,11 +3190,11 @@ var JsonParser = class {
2344
3190
  }
2345
3191
  }
2346
3192
  parseNumber() {
2347
- const start = this.pos;
3193
+ const start2 = this.pos;
2348
3194
  const re = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
2349
3195
  re.lastIndex = this.pos;
2350
3196
  const m = re.exec(this.text);
2351
- if (!m || m.index !== start) this.fail("bad number");
3197
+ if (!m || m.index !== start2) this.fail("bad number");
2352
3198
  this.pos = re.lastIndex;
2353
3199
  return jsonbNum(parseNumeric(m[0]));
2354
3200
  }
@@ -4700,18 +5546,18 @@ function isoWeek(days) {
4700
5546
  return jan4 - (isoDw - 1);
4701
5547
  };
4702
5548
  let year = y;
4703
- let start = week1Monday(y);
4704
- if (days < start) {
5549
+ let start2 = week1Monday(y);
5550
+ if (days < start2) {
4705
5551
  year = y - 1;
4706
- start = week1Monday(year);
5552
+ start2 = week1Monday(year);
4707
5553
  } else {
4708
5554
  const nextStart = week1Monday(y + 1);
4709
5555
  if (days >= nextStart) {
4710
5556
  year = y + 1;
4711
- start = nextStart;
5557
+ start2 = nextStart;
4712
5558
  }
4713
5559
  }
4714
- return { week: Math.floor((days - start) / 7) + 1, year };
5560
+ return { week: Math.floor((days - start2) / 7) + 1, year };
4715
5561
  }
4716
5562
  function dateTruncDays(field, days) {
4717
5563
  const { y, m } = daysToCivil(days);
@@ -5281,9 +6127,9 @@ function getArrayFunctions() {
5281
6127
  }
5282
6128
  const elemT = arrayElemType(a.t);
5283
6129
  const target = castTo(ctx, args2[1], elemT, {});
5284
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
6130
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
5285
6131
  const lb = lbsOf(arr)[0] ?? 1;
5286
- for (let i = Math.max(start - lb, 0); i < arr.items.length; i++) {
6132
+ for (let i = Math.max(start2 - lb, 0); i < arr.items.length; i++) {
5287
6133
  const item = arr.items[i];
5288
6134
  const eq = item === null ? target.v === null : target.v !== null && datumEquals(elemT, item, target.v, ctx);
5289
6135
  if (eq) return tv("int4", i + lb);
@@ -5763,12 +6609,12 @@ function parseByFormat(input, fmt) {
5763
6609
  };
5764
6610
  const readNum = (maxLen) => {
5765
6611
  skipInputSeparators();
5766
- const start = i;
5767
- while (i < input.length && /[0-9]/.test(input[i]) && i - start < maxLen) i++;
5768
- if (i === start) {
6612
+ const start2 = i;
6613
+ while (i < input.length && /[0-9]/.test(input[i]) && i - start2 < maxLen) i++;
6614
+ if (i === start2) {
5769
6615
  throw pgError("invalid_datetime", `invalid value for field in source string "${input}"`, "22007");
5770
6616
  }
5771
- return Number(input.slice(start, i));
6617
+ return Number(input.slice(start2, i));
5772
6618
  };
5773
6619
  const readMonthName = () => {
5774
6620
  skipInputSeparators();
@@ -7603,15 +8449,15 @@ var decoderUtf8 = new TextDecoder("utf-8", { fatal: false });
7603
8449
  function chars(s) {
7604
8450
  return [...s];
7605
8451
  }
7606
- function substrImpl(s, start, len) {
8452
+ function substrImpl(s, start2, len) {
7607
8453
  const cs = chars(s);
7608
8454
  if (len === void 0) {
7609
- const from2 = Math.max(start, 1);
8455
+ const from2 = Math.max(start2, 1);
7610
8456
  return cs.slice(from2 - 1).join("");
7611
8457
  }
7612
8458
  if (len < 0) throw pgError("substring_error", "negative substring length not allowed", "22011");
7613
- const end = start + len;
7614
- const from = Math.max(start, 1);
8459
+ const end = start2 + len;
8460
+ const from = Math.max(start2, 1);
7615
8461
  const to = Math.max(end, 1);
7616
8462
  if (to <= from) return "";
7617
8463
  return cs.slice(from - 1, to - 1).join("");
@@ -7675,14 +8521,14 @@ function getStringFunctions() {
7675
8521
  const a = args2[0];
7676
8522
  if (a.t === "bytea") {
7677
8523
  const bytes = a.v;
7678
- const start2 = argInt(ctx, args2[1]);
8524
+ const start3 = argInt(ctx, args2[1]);
7679
8525
  const len2 = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
7680
- return tv("bytea", byteaSubstr(bytes, start2, len2));
8526
+ return tv("bytea", byteaSubstr(bytes, start3, len2));
7681
8527
  }
7682
8528
  const s = argText(ctx, a);
7683
- const start = argInt(ctx, args2[1]);
8529
+ const start2 = argInt(ctx, args2[1]);
7684
8530
  const len = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
7685
- return tv("text", substrImpl(s, start, len));
8531
+ return tv("text", substrImpl(s, start2, len));
7686
8532
  })
7687
8533
  );
7688
8534
  m.set(
@@ -7978,11 +8824,11 @@ function getStringFunctions() {
7978
8824
  strict("int4", (ctx, args2) => {
7979
8825
  const s = argText(ctx, args2[0]);
7980
8826
  const pattern = argText(ctx, args2[1]);
7981
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8827
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
7982
8828
  const flags = args2.length > 3 ? argText(ctx, args2[3]) : "";
7983
- if (start < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start}`);
8829
+ if (start2 < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start2}`);
7984
8830
  const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
7985
- const sub = chars(s).slice(start - 1).join("");
8831
+ const sub = chars(s).slice(start2 - 1).join("");
7986
8832
  let count = 0;
7987
8833
  for (const _ of sub.matchAll(js)) count++;
7988
8834
  return tv("int4", count);
@@ -8003,11 +8849,11 @@ function getStringFunctions() {
8003
8849
  strict("text", (ctx, args2) => {
8004
8850
  const s = argText(ctx, args2[0]);
8005
8851
  const pattern = argText(ctx, args2[1]);
8006
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8852
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8007
8853
  const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
8008
8854
  const flags = args2.length > 4 ? argText(ctx, args2[4]) : "";
8009
8855
  const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
8010
- const sub = chars(s).slice(start - 1).join("");
8856
+ const sub = chars(s).slice(start2 - 1).join("");
8011
8857
  let i = 0;
8012
8858
  for (const match of sub.matchAll(js)) {
8013
8859
  i++;
@@ -8021,21 +8867,21 @@ function getStringFunctions() {
8021
8867
  strict("int4", (ctx, args2) => {
8022
8868
  const s = argText(ctx, args2[0]);
8023
8869
  const pattern = argText(ctx, args2[1]);
8024
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8870
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8025
8871
  const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
8026
8872
  const endoption = args2.length > 4 ? argInt(ctx, args2[4]) : 0;
8027
8873
  const flags = args2.length > 5 ? argText(ctx, args2[5]) : "";
8028
- if (start < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start}`);
8874
+ if (start2 < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start2}`);
8029
8875
  if (endoption !== 0 && endoption !== 1) {
8030
8876
  throw pgError("invalid_parameter_value", `invalid value for parameter "endoption": ${endoption}`);
8031
8877
  }
8032
8878
  const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
8033
- const sub = chars(s).slice(start - 1).join("");
8879
+ const sub = chars(s).slice(start2 - 1).join("");
8034
8880
  let i = 0;
8035
8881
  for (const match of sub.matchAll(js)) {
8036
8882
  i++;
8037
8883
  if (i === nth) {
8038
- const base = start - 1 + chars(sub.slice(0, match.index)).length;
8884
+ const base = start2 - 1 + chars(sub.slice(0, match.index)).length;
8039
8885
  return tv("int4", endoption === 0 ? base + 1 : base + chars(match[0]).length + 1);
8040
8886
  }
8041
8887
  }
@@ -8149,10 +8995,10 @@ function getStringFunctions() {
8149
8995
  );
8150
8996
  return m;
8151
8997
  }
8152
- function byteaSubstr(bytes, start, len) {
8998
+ function byteaSubstr(bytes, start2, len) {
8153
8999
  if (len !== void 0 && len < 0) throw pgError("substring_error", "negative substring length not allowed", "22011");
8154
- const from = Math.max(start, 1) - 1;
8155
- const to = len === void 0 ? bytes.length : Math.max(start + len - 1, 0);
9000
+ const from = Math.max(start2, 1) - 1;
9001
+ const to = len === void 0 ? bytes.length : Math.max(start2 + len - 1, 0);
8156
9002
  return bytes.slice(from, to);
8157
9003
  }
8158
9004
  function pad(s, len, fill, left) {
@@ -10714,7 +11560,7 @@ function evalOverlaps(ctx, scope, args2) {
10714
11560
  };
10715
11561
  const [s1r, e1r] = pair(args2[0]);
10716
11562
  const [s2r, e2r] = pair(args2[1]);
10717
- const resolveEnd = (start, end) => end.t === "interval" ? evalBinary(ctx, "+", start, end) : end;
11563
+ const resolveEnd = (start2, end) => end.t === "interval" ? evalBinary(ctx, "+", start2, end) : end;
10718
11564
  const e1 = resolveEnd(s1r, e1r);
10719
11565
  const e2 = resolveEnd(s2r, e2r);
10720
11566
  const target = [s1r, e1, s2r, e2].map((x) => x.t).find((t) => t !== "unknown") ?? "timestamp";
@@ -11973,7 +12819,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
11973
12819
  }
11974
12820
  if (a.t === "timestamp" || a.t === "timestamptz" || b.t === "timestamp" || b.t === "timestamptz" || a.t === "date") {
11975
12821
  const t2 = a.t === "timestamptz" || b.t === "timestamptz" ? "timestamptz" : "timestamp";
11976
- const start2 = castTo(ctx, a, t2, { explicit: true }).v;
12822
+ const start3 = castTo(ctx, a, t2, { explicit: true }).v;
11977
12823
  const stop3 = castTo(ctx, b, t2, { explicit: true }).v;
11978
12824
  const stepArg = args2[2];
11979
12825
  if (stepArg === void 0 || !isInterval(stepArg.v)) {
@@ -11988,7 +12834,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
11988
12834
  if (step2.months !== 0) {
11989
12835
  let i = 0;
11990
12836
  for (; ; ) {
11991
- const { days, tod } = splitTs(start2);
12837
+ const { days, tod } = splitTs(start3);
11992
12838
  const c = daysToCivil(days);
11993
12839
  const totalMonths = c.y * 12 + (c.m - 1) + step2.months * i;
11994
12840
  const y = Math.floor(totalMonths / 12);
@@ -12005,19 +12851,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
12005
12851
  const stepMicros = intervalTotalMicros(step2);
12006
12852
  if (stepMicros === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
12007
12853
  if (stepMicros > 0n) {
12008
- for (let cur = start2; cur <= stop3; cur += stepMicros) rows2.push([cur]);
12854
+ for (let cur = start3; cur <= stop3; cur += stepMicros) rows2.push([cur]);
12009
12855
  } else {
12010
- for (let cur = start2; cur >= stop3; cur += stepMicros) rows2.push([cur]);
12856
+ for (let cur = start3; cur >= stop3; cur += stepMicros) rows2.push([cur]);
12011
12857
  }
12012
12858
  return { columns: [{ name: alias, type: t2 }], rows: rows2 };
12013
12859
  }
12014
12860
  if (a.t === "numeric" || b.t === "numeric" || args2[2]?.t === "numeric") {
12015
- const start2 = castTo(ctx, a, "numeric", { explicit: true }).v;
12861
+ const start3 = castTo(ctx, a, "numeric", { explicit: true }).v;
12016
12862
  const stop3 = castTo(ctx, b, "numeric", { explicit: true }).v;
12017
12863
  const step2 = args2[2] ? castTo(ctx, args2[2], "numeric", { explicit: true }).v : castTo(ctx, tv("int4", 1), "numeric", { explicit: true }).v;
12018
12864
  if (numericSign(step2) === 0) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
12019
12865
  const rows2 = [];
12020
- let cur = start2;
12866
+ let cur = start3;
12021
12867
  const ascending = numericSign(step2) > 0;
12022
12868
  while (ascending ? numericCmp(cur, stop3) <= 0 : numericCmp(cur, stop3) >= 0) {
12023
12869
  rows2.push([cur]);
@@ -12029,19 +12875,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
12029
12875
  }
12030
12876
  const useBig = a.t === "int8" || b.t === "int8" || args2[2]?.t === "int8";
12031
12877
  const t = useBig ? "int8" : "int4";
12032
- const start = castTo(ctx, a, "int8", {}).v;
12878
+ const start2 = castTo(ctx, a, "int8", {}).v;
12033
12879
  const stop2 = castTo(ctx, b, "int8", {}).v;
12034
12880
  const step = args2[2] ? castTo(ctx, args2[2], "int8", {}).v : 1n;
12035
12881
  if (step === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
12036
12882
  const rows = [];
12037
12883
  if (step > 0n) {
12038
- for (let cur = start; cur <= stop2; cur += step) {
12884
+ for (let cur = start2; cur <= stop2; cur += step) {
12039
12885
  rows.push([useBig ? cur : Number(cur)]);
12040
12886
  if (rows.length > 1e7)
12041
12887
  throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
12042
12888
  }
12043
12889
  } else {
12044
- for (let cur = start; cur >= stop2; cur += step) {
12890
+ for (let cur = start2; cur >= stop2; cur += step) {
12045
12891
  rows.push([useBig ? cur : Number(cur)]);
12046
12892
  if (rows.length > 1e7)
12047
12893
  throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
@@ -12471,10 +13317,10 @@ function tokenize(sql) {
12471
13317
  const m = /^\$([A-Za-z_\u0080-\uffff][A-Za-z0-9_\u0080-\uffff]*)?\$/.exec(sql.slice(i));
12472
13318
  if (m) {
12473
13319
  const open = m[0];
12474
- const start = i + open.length;
12475
- const end = sql.indexOf(open, start);
13320
+ const start2 = i + open.length;
13321
+ const end = sql.indexOf(open, start2);
12476
13322
  if (end === -1) syntaxError("unterminated dollar-quoted string");
12477
- push("string", sql.slice(start, end), i);
13323
+ push("string", sql.slice(start2, end), i);
12478
13324
  i = end + open.length;
12479
13325
  continue;
12480
13326
  }
@@ -12541,14 +13387,14 @@ function tokenize(sql) {
12541
13387
  continue;
12542
13388
  }
12543
13389
  if (/\d/.test(c) || c === "." && /\d/.test(sql[i + 1] ?? "")) {
12544
- const start = i;
13390
+ const start2 = i;
12545
13391
  const radix = /^0[xX][0-9a-fA-F_]+|^0[oO][0-7_]+|^0[bB][01_]+/.exec(sql.slice(i));
12546
13392
  if (radix) {
12547
13393
  const raw2 = radix[0];
12548
13394
  i += raw2.length;
12549
13395
  const clean = raw2.replaceAll("_", "");
12550
13396
  const v = BigInt(clean);
12551
- push("number", v.toString(), start);
13397
+ push("number", v.toString(), start2);
12552
13398
  continue;
12553
13399
  }
12554
13400
  let j = i;
@@ -12570,7 +13416,7 @@ function tokenize(sql) {
12570
13416
  if (j < n && IDENT_START.test(sql[j])) {
12571
13417
  syntaxError(`trailing junk after numeric literal at or near "${sql.slice(i, j + 1)}"`);
12572
13418
  }
12573
- push("number", raw, start);
13419
+ push("number", raw, start2);
12574
13420
  i = j;
12575
13421
  continue;
12576
13422
  }
@@ -12628,9 +13474,9 @@ function tokenize(sql) {
12628
13474
  push("eof", "", i);
12629
13475
  return tokens;
12630
13476
  }
12631
- function scanStandardString(sql, start) {
13477
+ function scanStandardString(sql, start2) {
12632
13478
  let out = "";
12633
- let i = start + 1;
13479
+ let i = start2 + 1;
12634
13480
  const n = sql.length;
12635
13481
  for (; ; ) {
12636
13482
  if (i >= n) syntaxError("unterminated quoted string");
@@ -12658,9 +13504,9 @@ function scanStandardString(sql, start) {
12658
13504
  i++;
12659
13505
  }
12660
13506
  }
12661
- function scanEscapeString(sql, start) {
13507
+ function scanEscapeString(sql, start2) {
12662
13508
  let out = "";
12663
- let i = start + 1;
13509
+ let i = start2 + 1;
12664
13510
  const n = sql.length;
12665
13511
  for (; ; ) {
12666
13512
  if (i >= n) syntaxError("unterminated quoted string");
@@ -16400,14 +17246,14 @@ var Parser = class {
16400
17246
  let frame = null;
16401
17247
  if (this.atKw("range") || this.atKw("rows") || this.atKw("groups")) {
16402
17248
  const mode = this.next().value;
16403
- let start;
17249
+ let start2;
16404
17250
  let end = null;
16405
17251
  if (this.eatKw("between")) {
16406
- start = this.parseFrameBound();
17252
+ start2 = this.parseFrameBound();
16407
17253
  this.expectKw("and");
16408
17254
  end = this.parseFrameBound();
16409
17255
  } else {
16410
- start = this.parseFrameBound();
17256
+ start2 = this.parseFrameBound();
16411
17257
  }
16412
17258
  let exclusion = null;
16413
17259
  if (this.eatKw("exclude")) {
@@ -16424,7 +17270,7 @@ var Parser = class {
16424
17270
  exclusion = "no_others";
16425
17271
  }
16426
17272
  }
16427
- frame = { mode, start, end, exclusion };
17273
+ frame = { mode, start: start2, end, exclusion };
16428
17274
  }
16429
17275
  return { name, partitionBy, orderBy, frame };
16430
17276
  }
@@ -16670,7 +17516,7 @@ var PlParser = class {
16670
17516
  return { kind: "assign", target, expr: this.parseExprUntilSemi() };
16671
17517
  }
16672
17518
  parseSqlTextUntilSemi() {
16673
- const start = this.peek().pos;
17519
+ const start2 = this.peek().pos;
16674
17520
  let depth = 0;
16675
17521
  for (; ; ) {
16676
17522
  const t = this.peek();
@@ -16678,7 +17524,7 @@ var PlParser = class {
16678
17524
  if (t.type === "punct" && t.value === "(") depth++;
16679
17525
  if (t.type === "punct" && t.value === ")") depth--;
16680
17526
  if (t.type === "punct" && t.value === ";" && depth === 0) {
16681
- const text = this.src.slice(start, t.pos).trim();
17527
+ const text = this.src.slice(start2, t.pos).trim();
16682
17528
  this.pos++;
16683
17529
  return text;
16684
17530
  }
@@ -16686,7 +17532,7 @@ var PlParser = class {
16686
17532
  }
16687
17533
  }
16688
17534
  parseExprUntilSemi() {
16689
- const start = this.peek().pos;
17535
+ const start2 = this.peek().pos;
16690
17536
  let depth = 0;
16691
17537
  for (; ; ) {
16692
17538
  const t = this.peek();
@@ -16694,7 +17540,7 @@ var PlParser = class {
16694
17540
  if (t.type === "punct" && t.value === "(") depth++;
16695
17541
  if (t.type === "punct" && t.value === ")") depth--;
16696
17542
  if (t.type === "punct" && t.value === ";" && depth === 0) {
16697
- const text = this.src.slice(start, t.pos);
17543
+ const text = this.src.slice(start2, t.pos);
16698
17544
  this.pos++;
16699
17545
  return parseSqlExpr(text);
16700
17546
  }
@@ -16702,7 +17548,7 @@ var PlParser = class {
16702
17548
  }
16703
17549
  }
16704
17550
  parseExprUntilKw(kw) {
16705
- const start = this.peek().pos;
17551
+ const start2 = this.peek().pos;
16706
17552
  let depth = 0;
16707
17553
  let caseDepth = 0;
16708
17554
  for (; ; ) {
@@ -16713,7 +17559,7 @@ var PlParser = class {
16713
17559
  if (t.type === "ident" && t.value === "case" && depth === 0) caseDepth++;
16714
17560
  if (t.type === "ident" && t.value === "end" && depth === 0 && caseDepth > 0) caseDepth--;
16715
17561
  if (t.type === "ident" && t.value === kw && depth === 0 && caseDepth === 0) {
16716
- const text = this.src.slice(start, t.pos);
17562
+ const text = this.src.slice(start2, t.pos);
16717
17563
  this.pos++;
16718
17564
  return parseSqlExpr(text);
16719
17565
  }
@@ -16721,7 +17567,7 @@ var PlParser = class {
16721
17567
  }
16722
17568
  }
16723
17569
  parseQueryUntilKw(kw) {
16724
- const start = this.peek().pos;
17570
+ const start2 = this.peek().pos;
16725
17571
  let depth = 0;
16726
17572
  for (; ; ) {
16727
17573
  const t = this.peek();
@@ -16729,7 +17575,7 @@ var PlParser = class {
16729
17575
  if (t.type === "punct" && t.value === "(") depth++;
16730
17576
  if (t.type === "punct" && t.value === ")") depth--;
16731
17577
  if (t.type === "ident" && t.value === kw && depth === 0) {
16732
- const text = this.src.slice(start, t.pos).trim();
17578
+ const text = this.src.slice(start2, t.pos).trim();
16733
17579
  this.pos++;
16734
17580
  const stmts = parse(text);
16735
17581
  const q = stmts[0];
@@ -18272,7 +19118,7 @@ function buildFrom(env, items, where = null) {
18272
19118
  }
18273
19119
  }
18274
19120
  if (cols === null) {
18275
- const probe = materializeItem(
19121
+ const probe2 = materializeItem(
18276
19122
  env,
18277
19123
  item,
18278
19124
  new RowScope(
@@ -18282,8 +19128,8 @@ function buildFrom(env, items, where = null) {
18282
19128
  accVars
18283
19129
  )
18284
19130
  );
18285
- cols = [...accRel.columns, ...probe.rel.columns];
18286
- vars = /* @__PURE__ */ new Set([...accVars, ...probe.rangeVars]);
19131
+ cols = [...accRel.columns, ...probe2.rel.columns];
19132
+ vars = /* @__PURE__ */ new Set([...accVars, ...probe2.rangeVars]);
18287
19133
  }
18288
19134
  acc = { rel: { columns: cols, rows }, rangeVars: vars };
18289
19135
  } else {
@@ -19043,13 +19889,13 @@ function expandTargets(core, columns) {
19043
19889
  return items;
19044
19890
  }
19045
19891
  function checkPredicateType(env, columns, rangeVars, e, kind, extras) {
19046
- const probe = new RowScope(
19892
+ const probe2 = new RowScope(
19047
19893
  columns,
19048
19894
  columns.map(() => null),
19049
19895
  env.outer,
19050
19896
  rangeVars
19051
19897
  );
19052
- checkBoolExprType(env.ctx, makeEvalScope(env, probe, extras), e, kind);
19898
+ checkBoolExprType(env.ctx, makeEvalScope(env, probe2, extras), e, kind);
19053
19899
  }
19054
19900
  function executeCore(env0, core, orderBy, opts) {
19055
19901
  const env = env0;
@@ -19139,8 +19985,8 @@ function executeCore(env0, core, orderBy, opts) {
19139
19985
  const groupValues = groupDefs.map((d, di) => {
19140
19986
  if (!activeSet[di]) {
19141
19987
  if (rows.length > 0) {
19142
- const probe = evalScalar(env, repScope, d.expr);
19143
- return tv(probe.t, null);
19988
+ const probe2 = evalScalar(env, repScope, d.expr);
19989
+ return tv(probe2.t, null);
19144
19990
  }
19145
19991
  return tv(UNKNOWN, null);
19146
19992
  }
@@ -20293,8 +21139,8 @@ function executeTransaction(env, stmt) {
20293
21139
  var defaultSettings = null;
20294
21140
  function getDefaultSettings() {
20295
21141
  if (!defaultSettings) {
20296
- const probe = new DatabaseState(new Prng(1), () => /* @__PURE__ */ new Date(0));
20297
- defaultSettings = new Map(probe.settings);
21142
+ const probe2 = new DatabaseState(new Prng(1), () => /* @__PURE__ */ new Date(0));
21143
+ defaultSettings = new Map(probe2.settings);
20298
21144
  }
20299
21145
  return defaultSettings;
20300
21146
  }
@@ -20561,7 +21407,7 @@ function parseCsvLine(line, delim, quote) {
20561
21407
  }
20562
21408
  function splitCsvRecords(data, quote) {
20563
21409
  const records = [];
20564
- let start = 0;
21410
+ let start2 = 0;
20565
21411
  let quoted = false;
20566
21412
  for (let i = 0; i < data.length; i++) {
20567
21413
  const char = data[i];
@@ -20571,13 +21417,13 @@ function splitCsvRecords(data, quote) {
20571
21417
  continue;
20572
21418
  }
20573
21419
  if (char === "\n" && !quoted) {
20574
- const end = i > start && data[i - 1] === "\r" ? i - 1 : i;
20575
- records.push(data.slice(start, end));
20576
- start = i + 1;
21420
+ const end = i > start2 && data[i - 1] === "\r" ? i - 1 : i;
21421
+ records.push(data.slice(start2, end));
21422
+ start2 = i + 1;
20577
21423
  }
20578
21424
  }
20579
21425
  if (quoted) throw pgError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
20580
- if (start < data.length) records.push(data.slice(start));
21426
+ if (start2 < data.length) records.push(data.slice(start2));
20581
21427
  return records;
20582
21428
  }
20583
21429
  function executeCopyFromData(env, stmt, data) {
@@ -23735,6 +24581,16 @@ var Cluster = class {
23735
24581
  databaseNames() {
23736
24582
  return [...this.databases.keys()].sort();
23737
24583
  }
24584
+ snapshotAll() {
24585
+ return new Map([...this.databases].map(([name, db]) => [name, db.snapshot()]));
24586
+ }
24587
+ restoreAll(points) {
24588
+ if (this.sessions.size > 0) throw new Error("cannot restore a catalog with active sessions");
24589
+ if (!points.has(this.defaultDatabaseName)) throw new Error("snapshot lacks default database");
24590
+ for (const db of this.databases.values()) db.close();
24591
+ this.databases.clear();
24592
+ for (const [name, point] of points) this.installDatabase(name, point.open());
24593
+ }
23738
24594
  getDatabase(name) {
23739
24595
  return this.databases.get(name);
23740
24596
  }
@@ -23747,7 +24603,7 @@ var Cluster = class {
23747
24603
  if (this.databases.has(name)) {
23748
24604
  throw new PostgresError("duplicate_object", `database "${name}" already exists`, "42P04");
23749
24605
  }
23750
- const db = template === void 0 ? new Database() : this.requireTemplate(template).snapshot().open();
24606
+ const db = template === void 0 ? new Database({ now: this.db.now, seed: this.db.seed }) : this.requireTemplate(template).snapshot().open();
23751
24607
  this.installDatabase(name, db);
23752
24608
  }
23753
24609
  dropDatabase(name, requester) {
@@ -23811,8 +24667,8 @@ var Cluster = class {
23811
24667
  if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
23812
24668
  return Promise.reject(deadlock());
23813
24669
  }
23814
- return new Promise((resolve, reject) => {
23815
- const waiter = { session, resolve, reject };
24670
+ return new Promise((resolve2, reject) => {
24671
+ const waiter = { session, resolve: resolve2, reject };
23816
24672
  session.waitingForTurn = waiter;
23817
24673
  this.turnQueue.push(waiter);
23818
24674
  });
@@ -23843,8 +24699,8 @@ var Cluster = class {
23843
24699
  if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
23844
24700
  return Promise.reject(deadlock());
23845
24701
  }
23846
- return new Promise((resolve, reject) => {
23847
- const waiter = { session, resolve, reject };
24702
+ return new Promise((resolve2, reject) => {
24703
+ const waiter = { session, resolve: resolve2, reject };
23848
24704
  session.waitingForLock = key;
23849
24705
  const queue = this.lockQueues.get(key) ?? [];
23850
24706
  queue.push(waiter);
@@ -24103,9 +24959,9 @@ var backend = {
24103
24959
  no: () => new Uint8Array([78])
24104
24960
  };
24105
24961
  var ByteReader = class {
24106
- constructor(bytes, start = 0) {
24962
+ constructor(bytes, start2 = 0) {
24107
24963
  this.bytes = bytes;
24108
- this.pos = start;
24964
+ this.pos = start2;
24109
24965
  this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
24110
24966
  }
24111
24967
  bytes;
@@ -24265,11 +25121,11 @@ var ScramServer = class {
24265
25121
  // src/wire/split.ts
24266
25122
  var splitStatements = (script) => {
24267
25123
  const out = [];
24268
- let start = 0;
25124
+ let start2 = 0;
24269
25125
  let i = 0;
24270
25126
  const n = script.length;
24271
25127
  const push = (end) => {
24272
- const text = script.slice(start, end).trim();
25128
+ const text = script.slice(start2, end).trim();
24273
25129
  if (text.length > 0) out.push(text);
24274
25130
  };
24275
25131
  while (i < n) {
@@ -24325,7 +25181,7 @@ var splitStatements = (script) => {
24325
25181
  }
24326
25182
  if (c === ";") {
24327
25183
  push(i);
24328
- start = i + 1;
25184
+ start2 = i + 1;
24329
25185
  }
24330
25186
  i++;
24331
25187
  }
@@ -24786,8 +25642,8 @@ var Connection = class {
24786
25642
  "25P02"
24787
25643
  );
24788
25644
  }
24789
- const listen = LISTEN.exec(text);
24790
- if (listen) return this.listen(listen[2]);
25645
+ const listen2 = LISTEN.exec(text);
25646
+ if (listen2) return this.listen(listen2[2]);
24791
25647
  const unlisten = UNLISTEN.exec(text);
24792
25648
  if (unlisten) return this.unlisten(unlisten[1].replaceAll('"', ""));
24793
25649
  const notify = NOTIFY.exec(text);
@@ -24804,7 +25660,7 @@ var Connection = class {
24804
25660
  if (faults.delayStatementMs !== void 0) {
24805
25661
  const ms = faults.delayStatementMs;
24806
25662
  faults.delayStatementMs = void 0;
24807
- await new Promise((resolve) => setTimeout(resolve, ms));
25663
+ await new Promise((resolve2) => setTimeout(resolve2, ms));
24808
25664
  }
24809
25665
  if (faults.dropConnection && this.inTransaction) {
24810
25666
  faults.dropConnection = void 0;
@@ -24828,7 +25684,7 @@ var Connection = class {
24828
25684
  }
24829
25685
  }
24830
25686
  waitForLock(key) {
24831
- return new Promise((resolve, reject) => {
25687
+ return new Promise((resolve2, reject) => {
24832
25688
  this.lockWaiter = (error) => {
24833
25689
  this.lockWaiter = null;
24834
25690
  this.waitingForLock = null;
@@ -24837,7 +25693,7 @@ var Connection = class {
24837
25693
  this.cluster.waitLock(this, key).then(
24838
25694
  () => {
24839
25695
  this.lockWaiter = null;
24840
- resolve();
25696
+ resolve2();
24841
25697
  },
24842
25698
  (error) => {
24843
25699
  this.lockWaiter = null;
@@ -25029,10 +25885,10 @@ var Connection = class {
25029
25885
  this.cluster.dropDatabase(name, this);
25030
25886
  return command("DROP DATABASE");
25031
25887
  }
25032
- const rename = RENAME_DATABASE.exec(text);
25033
- if (rename) {
25034
- const from = identifier(rename[1], rename[2]);
25035
- const to = identifier(rename[3], rename[4]);
25888
+ const rename2 = RENAME_DATABASE.exec(text);
25889
+ if (rename2) {
25890
+ const from = identifier(rename2[1], rename2[2]);
25891
+ const to = identifier(rename2[3], rename2[4]);
25036
25892
  this.cluster.renameDatabase(from, to, this);
25037
25893
  return command("ALTER DATABASE");
25038
25894
  }
@@ -25455,7 +26311,7 @@ var normalize = (input) => {
25455
26311
  };
25456
26312
  };
25457
26313
  var isSnapshot = (value) => typeof value === "object" && value !== null && "open" in value && typeof value.open === "function";
25458
- var serve = (input = {}) => {
26314
+ var serve2 = (input = {}) => {
25459
26315
  const options2 = normalize(input);
25460
26316
  const database = isSnapshot(options2.database) ? options2.database.open() : options2.database ?? new Database();
25461
26317
  const initialName = options2.databaseName ?? "postgres";
@@ -25464,20 +26320,20 @@ var serve = (input = {}) => {
25464
26320
  const connections = /* @__PURE__ */ new Set();
25465
26321
  const host = options2.host ?? "127.0.0.1";
25466
26322
  const user = options2.user || "postgres";
25467
- const server2 = createServer((socket) => {
26323
+ const server2 = createServer2((socket) => {
25468
26324
  socket.setNoDelay(true);
25469
26325
  const connection = new Connection(socket, cluster, {
25470
26326
  ...options2.password !== void 0 ? { password: options2.password } : {},
25471
26327
  serverVersion: options2.serverVersion ?? "18.3",
25472
26328
  parameters: options2.parameters ?? {},
25473
26329
  faults,
25474
- ...options2.databaseName !== void 0 ? { database: options2.databaseName } : {},
26330
+ ...options2.databaseName !== void 0 && !options2.allowDatabaseSelection ? { database: options2.databaseName } : {},
25475
26331
  ...options2.onLog ? { onLog: options2.onLog } : {}
25476
26332
  });
25477
26333
  connections.add(connection);
25478
26334
  socket.on("close", () => connections.delete(connection));
25479
26335
  });
25480
- return new Promise((resolve, reject) => {
26336
+ return new Promise((resolve2, reject) => {
25481
26337
  server2.once("error", reject);
25482
26338
  server2.listen(options2.port ?? 0, host, () => {
25483
26339
  server2.removeListener("error", reject);
@@ -25485,11 +26341,13 @@ var serve = (input = {}) => {
25485
26341
  const port = typeof address === "object" && address !== null ? address.port : 0;
25486
26342
  const name = initialName;
25487
26343
  const auth = options2.password ? `${encodeURIComponent(user)}:${encodeURIComponent(options2.password)}@` : `${encodeURIComponent(user)}@`;
25488
- resolve({
26344
+ resolve2({
25489
26345
  port,
25490
26346
  host,
25491
26347
  connectionString: `postgres://${auth}${host}:${port}/${encodeURIComponent(name)}`,
25492
- database,
26348
+ get database() {
26349
+ return cluster.db;
26350
+ },
25493
26351
  databaseNames() {
25494
26352
  return cluster.databaseNames();
25495
26353
  },
@@ -25503,6 +26361,14 @@ var serve = (input = {}) => {
25503
26361
  fault(next) {
25504
26362
  Object.assign(faults, next);
25505
26363
  },
26364
+ clearFaults() {
26365
+ for (const key of Object.keys(faults)) delete faults[key];
26366
+ },
26367
+ faultState() {
26368
+ return structuredClone(faults);
26369
+ },
26370
+ snapshotAll: () => cluster.snapshotAll(),
26371
+ restoreAll: (points) => cluster.restoreAll(points),
25506
26372
  snapshot(databaseName = initialName) {
25507
26373
  return cluster.requireDatabase(databaseName).snapshot();
25508
26374
  },
@@ -25518,9 +26384,251 @@ var serve = (input = {}) => {
25518
26384
  });
25519
26385
  };
25520
26386
 
26387
+ // src/wire/probe.ts
26388
+ import { createHash as createHash2, createHmac as createHmac2, pbkdf2Sync as pbkdf2Sync2, randomBytes as randomBytes2 } from "node:crypto";
26389
+ import { createConnection } from "node:net";
26390
+ function probe(connectionString) {
26391
+ const url = new URL(connectionString);
26392
+ return new Promise((resolve2) => {
26393
+ const socket = createConnection({ host: url.hostname, port: Number(url.port) });
26394
+ let buffer = Buffer.alloc(0);
26395
+ let result = false;
26396
+ let querying = false;
26397
+ let row = false;
26398
+ const nonce = randomBytes2(18).toString("base64");
26399
+ const first = `n=,r=${nonce}`;
26400
+ const finish = (ok) => {
26401
+ result = ok;
26402
+ socket.destroy();
26403
+ };
26404
+ const timeout = setTimeout(() => finish(false), 2e3);
26405
+ const send = (type, body) => {
26406
+ const header = Buffer.alloc(5);
26407
+ header.write(type);
26408
+ header.writeInt32BE(body.length + 4, 1);
26409
+ socket.write(Buffer.concat([header, body]));
26410
+ };
26411
+ socket.once("close", () => {
26412
+ clearTimeout(timeout);
26413
+ resolve2(result);
26414
+ });
26415
+ socket.on("error", () => finish(false));
26416
+ socket.once("connect", () => {
26417
+ const parameters = Buffer.from(
26418
+ `user\0${decodeURIComponent(url.username)}\0database\0${decodeURIComponent(url.pathname.slice(1))}\0\0`
26419
+ );
26420
+ const header = Buffer.alloc(8);
26421
+ header.writeInt32BE(parameters.length + 8);
26422
+ header.writeInt32BE(196608, 4);
26423
+ socket.write(Buffer.concat([header, parameters]));
26424
+ });
26425
+ socket.on("data", (data) => {
26426
+ buffer = Buffer.concat([buffer, data]);
26427
+ while (buffer.length >= 5) {
26428
+ const length = buffer.readInt32BE(1);
26429
+ if (length < 4 || length > 1e6) {
26430
+ finish(false);
26431
+ return;
26432
+ }
26433
+ if (buffer.length < length + 1) return;
26434
+ const type = String.fromCharCode(buffer[0]);
26435
+ const body = buffer.subarray(5, length + 1);
26436
+ buffer = buffer.subarray(length + 1);
26437
+ if (type === "E") {
26438
+ finish(false);
26439
+ return;
26440
+ }
26441
+ if (type === "R") {
26442
+ const auth = body.readInt32BE();
26443
+ if (auth === 10) {
26444
+ const initial = Buffer.from(`n,,${first}`);
26445
+ const size = Buffer.alloc(4);
26446
+ size.writeInt32BE(initial.length);
26447
+ send("p", Buffer.concat([Buffer.from("SCRAM-SHA-256\0"), size, initial]));
26448
+ } else if (auth === 11) {
26449
+ const serverFirst = body.subarray(4).toString();
26450
+ const fields = Object.fromEntries(serverFirst.split(",").map((field) => [field[0], field.slice(2)]));
26451
+ if (!fields.r?.startsWith(nonce) || !fields.s || fields.i !== "4096") {
26452
+ finish(false);
26453
+ return;
26454
+ }
26455
+ const salted = pbkdf2Sync2(
26456
+ decodeURIComponent(url.password),
26457
+ Buffer.from(fields.s, "base64"),
26458
+ 4096,
26459
+ 32,
26460
+ "sha256"
26461
+ );
26462
+ const key = createHmac2("sha256", salted).update("Client Key").digest();
26463
+ const stored = createHash2("sha256").update(key).digest();
26464
+ const final = `c=biws,r=${fields.r}`;
26465
+ const signature = createHmac2("sha256", stored).update(`${first},${serverFirst},${final}`).digest();
26466
+ const proof = Buffer.from(key.map((value, index) => value ^ signature[index]));
26467
+ send("p", Buffer.from(`${final},p=${proof.toString("base64")}`));
26468
+ } else if (auth !== 0 && auth !== 12) {
26469
+ finish(false);
26470
+ return;
26471
+ }
26472
+ }
26473
+ if (type === "D") row = body.readInt16BE() === 1 && body.subarray(6).toString() === "1";
26474
+ if (type === "Z") {
26475
+ if (querying) {
26476
+ finish(row);
26477
+ return;
26478
+ }
26479
+ querying = true;
26480
+ send("Q", Buffer.from("SELECT 1\0"));
26481
+ }
26482
+ }
26483
+ });
26484
+ });
26485
+ }
26486
+
26487
+ // src/wire/fleet.ts
26488
+ async function startProtocol(entry) {
26489
+ const namespaces = /* @__PURE__ */ new Map();
26490
+ const selectors = { default: entry.database ?? "postgres", ...entry.namespaces };
26491
+ try {
26492
+ for (const [name, databaseName] of Object.entries(selectors)) {
26493
+ if (!/^[A-Za-z0-9_.-]{1,64}$/.test(name) || typeof databaseName !== "string")
26494
+ throw new Error("invalid Postgres namespace/database name");
26495
+ const clock = createClock();
26496
+ const database = new Database({
26497
+ now: () => new Date(clock.now()),
26498
+ ...entry.seed !== void 0 ? { seed: Number(entry.seed) } : {}
26499
+ });
26500
+ const server2 = await serve2({
26501
+ database,
26502
+ databaseName,
26503
+ allowDatabaseSelection: true,
26504
+ host: entry.host ?? "127.0.0.1",
26505
+ port: name === "default" ? entry.port ?? 0 : 0,
26506
+ ...entry.password !== void 0 ? { password: entry.password } : {},
26507
+ ...entry.user !== void 0 ? { user: entry.user } : {}
26508
+ });
26509
+ namespaces.set(name, { server: server2, clock });
26510
+ }
26511
+ } catch (error) {
26512
+ await Promise.allSettled([...namespaces.values()].map(({ server: server2 }) => server2.close()));
26513
+ throw error;
26514
+ }
26515
+ const get = (name) => {
26516
+ const found = namespaces.get(name);
26517
+ if (!found) throw new Error(`Postgres namespace ${name} is not configured`);
26518
+ return found;
26519
+ };
26520
+ const publicUrl2 = (name) => {
26521
+ const url = new URL(get(name).server.connectionString);
26522
+ url.password = "";
26523
+ return url.href;
26524
+ };
26525
+ const locked = /* @__PURE__ */ new Set();
26526
+ const snapshots = /* @__PURE__ */ new WeakMap();
26527
+ const origins = new Map([...namespaces].map(([name, { server: server2 }]) => [name, server2.snapshotAll()]));
26528
+ return {
26529
+ protocol: "postgres",
26530
+ url: publicUrl2("default"),
26531
+ connection: get("default").server.connectionString,
26532
+ connections: Object.fromEntries([...namespaces].map(([name, { server: server2 }]) => [name, server2.connectionString])),
26533
+ namespaces: {
26534
+ mechanism: "endpoint",
26535
+ endpoints: Object.fromEntries([...namespaces.keys()].map((name) => [name, publicUrl2(name)]))
26536
+ },
26537
+ async ready() {
26538
+ const results = await Promise.all(
26539
+ [...namespaces.values()].map(
26540
+ ({ server: server2 }) => server2.server.listening ? probe(server2.connectionString) : false
26541
+ )
26542
+ );
26543
+ return results.every(Boolean);
26544
+ },
26545
+ async close() {
26546
+ await Promise.all(
26547
+ [...namespaces.values()].map(async ({ server: server2 }) => {
26548
+ await server2.close();
26549
+ for (const name of server2.databaseNames()) server2.getDatabase(name).close();
26550
+ })
26551
+ );
26552
+ },
26553
+ lock(namespace) {
26554
+ const { server: server2 } = get(namespace);
26555
+ if (locked.has(namespace)) throw new Error("Postgres namespace already locked");
26556
+ locked.add(namespace);
26557
+ const reject = (socket) => socket.destroy();
26558
+ server2.server.prependListener("connection", reject);
26559
+ return () => {
26560
+ locked.delete(namespace);
26561
+ server2.server.off("connection", reject);
26562
+ };
26563
+ },
26564
+ diagnostics(namespace) {
26565
+ const { server: server2 } = get(namespace);
26566
+ return {
26567
+ activeRequests: server2.connections,
26568
+ activeConnections: server2.connections,
26569
+ blockedCommands: 0,
26570
+ pendingJobs: 0,
26571
+ pendingWebhooks: 0,
26572
+ unmatchedRequests: 0
26573
+ };
26574
+ },
26575
+ async checkpoint(namespace) {
26576
+ const { server: server2, clock } = get(namespace);
26577
+ const handle = {};
26578
+ snapshots.set(handle, {
26579
+ namespace,
26580
+ databases: server2.snapshotAll(),
26581
+ clock: clock.state(),
26582
+ faults: server2.faultState()
26583
+ });
26584
+ return handle;
26585
+ },
26586
+ async restore(namespace, handle) {
26587
+ const point = typeof handle === "object" && handle !== null ? snapshots.get(handle) : void 0;
26588
+ if (!point || point.namespace !== namespace) throw new Error("invalid Postgres checkpoint");
26589
+ const { server: server2, clock } = get(namespace);
26590
+ server2.restoreAll(point.databases);
26591
+ for (const name of server2.databaseNames()) server2.getDatabase(name).now = () => new Date(clock.now());
26592
+ server2.clearFaults();
26593
+ server2.fault(point.faults);
26594
+ clock.freeze();
26595
+ clock.set(point.clock.now);
26596
+ if (!point.clock.frozen) clock.unfreeze();
26597
+ },
26598
+ async reset(namespace) {
26599
+ const { server: server2, clock } = get(namespace);
26600
+ server2.restoreAll(origins.get(namespace));
26601
+ for (const name of server2.databaseNames()) server2.getDatabase(name).now = () => new Date(clock.now());
26602
+ server2.clearFaults();
26603
+ clock.reset();
26604
+ },
26605
+ clock: (namespace) => get(namespace).clock
26606
+ };
26607
+ }
26608
+
26609
+ // src/server.ts
26610
+ var serveTarget = {
26611
+ name: "postgres",
26612
+ protocol: "postgres",
26613
+ defaultPort: 0,
26614
+ start: startProtocol
26615
+ };
26616
+
25521
26617
  // src/wire/cli.ts
25522
26618
  var usage = `mockingbird-postgres serve [postgres://URI] [--port <n>] [--host <h>] [--password <p>] [--server-version <v>] [--log]`;
25523
26619
  var args = process.argv.slice(2);
26620
+ if (args[0] === "serve" && args.includes("--config")) {
26621
+ process.exit(
26622
+ await runCli(
26623
+ {
26624
+ bin: "mockingbird-postgres",
26625
+ description: "Postgres and fleet server",
26626
+ commands: { serve: serveCommand(serveTarget) }
26627
+ },
26628
+ args
26629
+ )
26630
+ );
26631
+ }
25524
26632
  if (args[0] !== "serve") {
25525
26633
  console.error(usage);
25526
26634
  process.exit(args[0] === "--help" || args[0] === "-h" ? 0 : 2);
@@ -25569,7 +26677,7 @@ if (log) {
25569
26677
  `[pid ${entry.pid}] ${entry.status} ${entry.durationMs.toFixed(1)}ms ${entry.sql.replace(/\s+/g, " ").slice(0, 200)}`
25570
26678
  );
25571
26679
  }
25572
- var server = await serve(options);
26680
+ var server = await serve2(options);
25573
26681
  console.log(`mockingbird-postgres listening on ${server.connectionString}`);
25574
26682
  var stop = async () => {
25575
26683
  await server.close();