@crvouga/mockingbird-service-postgres 1.6.0 → 1.7.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,70 @@
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
+ // ../../adapters/node/dist/fleet.js
11
+ import { randomUUID } from "node:crypto";
12
+ import { chmod, readFile, rename, rm, writeFile } from "node:fs/promises";
13
+
14
+ // ../core/dist/clock.js
15
+ var createClock = (source = Date.now) => {
16
+ let offsetMs = 0;
17
+ let frozenAt;
18
+ const now = () => frozenAt ?? source() + offsetMs;
19
+ return {
20
+ now,
21
+ set: (epochMs) => {
22
+ if (frozenAt !== void 0)
23
+ frozenAt = epochMs;
24
+ else
25
+ offsetMs = epochMs - source();
26
+ },
27
+ advance: (deltaMs) => {
28
+ if (frozenAt !== void 0)
29
+ frozenAt += deltaMs;
30
+ else
31
+ offsetMs += deltaMs;
32
+ },
33
+ freeze: () => {
34
+ frozenAt = now();
35
+ },
36
+ unfreeze: () => {
37
+ if (frozenAt === void 0)
38
+ return;
39
+ offsetMs = frozenAt - source();
40
+ frozenAt = void 0;
41
+ },
42
+ reset: () => {
43
+ offsetMs = 0;
44
+ frozenAt = void 0;
45
+ },
46
+ state: () => ({ now: now(), frozen: frozenAt !== void 0, offsetMs })
47
+ };
48
+ };
49
+
50
+ // ../core/dist/control.js
51
+ var UNITS = {
52
+ ms: 1,
53
+ s: 1e3,
54
+ m: 6e4,
55
+ h: 36e5,
56
+ d: 864e5
57
+ };
58
+ var parseDuration = (value) => {
59
+ if (typeof value === "number" && Number.isFinite(value))
60
+ return value;
61
+ if (typeof value !== "string")
62
+ return void 0;
63
+ const match = /^(-?\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/.exec(value.trim());
64
+ if (!match)
65
+ return void 0;
66
+ return Number(match[1]) * UNITS[match[2]];
67
+ };
5
68
 
6
69
  // ../../core/dist/timeline.js
7
70
  var BRANCH_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
@@ -164,6 +227,764 @@ var Timeline = class {
164
227
  }
165
228
  };
166
229
 
230
+ // ../../adapters/node/dist/serve.js
231
+ import { createServer } from "node:http";
232
+ var serve = async (api, options2 = {}) => {
233
+ const server2 = createServer(async (req, res) => {
234
+ const chunks = [];
235
+ for await (const chunk of req) {
236
+ chunks.push(Buffer.from(chunk));
237
+ }
238
+ const body = Buffer.concat(chunks);
239
+ const address = server2.address();
240
+ const port = typeof address === "object" && address !== null ? address.port : void 0;
241
+ const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`;
242
+ const raw = req.url ?? "/";
243
+ const url = new URL(raw.replace(/^\/+/, "/"), base);
244
+ const method = req.method ?? "GET";
245
+ const init = { method, headers: req.headers };
246
+ if (method !== "GET" && method !== "HEAD" && body.length > 0) {
247
+ init.body = body;
248
+ }
249
+ const aborted = new AbortController();
250
+ res.once("close", () => {
251
+ if (!res.writableFinished)
252
+ aborted.abort();
253
+ });
254
+ init.signal = aborted.signal;
255
+ const request = new Request(url, init);
256
+ let response;
257
+ try {
258
+ response = await api.fetch(request);
259
+ } catch (error) {
260
+ if (error.code === "MOCKINGBIRD_DROP") {
261
+ req.socket.destroy();
262
+ return;
263
+ }
264
+ res.writeHead(500, { "content-type": "application/json" });
265
+ res.end(JSON.stringify({
266
+ error: {
267
+ type: "mockingbird_internal",
268
+ message: error instanceof Error ? error.message : String(error)
269
+ }
270
+ }));
271
+ return;
272
+ }
273
+ const headers = Object.fromEntries(response.headers);
274
+ const cookies = response.headers.getSetCookie();
275
+ if (cookies.length > 0)
276
+ headers["set-cookie"] = cookies;
277
+ if (!response.body) {
278
+ res.writeHead(response.status, headers);
279
+ res.end();
280
+ return;
281
+ }
282
+ res.writeHead(response.status, headers);
283
+ res.flushHeaders();
284
+ const reader = response.body.getReader();
285
+ try {
286
+ for (; ; ) {
287
+ const { done, value } = await reader.read();
288
+ if (done)
289
+ break;
290
+ if (!res.write(value))
291
+ await new Promise((resolve2) => res.once("drain", resolve2));
292
+ }
293
+ res.end();
294
+ } catch {
295
+ res.destroy();
296
+ } finally {
297
+ reader.releaseLock();
298
+ }
299
+ });
300
+ await new Promise((resolve2, reject) => {
301
+ server2.once("error", reject);
302
+ server2.listen(options2.port ?? 0, options2.host, resolve2);
303
+ });
304
+ return server2;
305
+ };
306
+
307
+ // ../../adapters/node/dist/listen.js
308
+ var listen = async (api, options2 = {}) => {
309
+ const host = options2.host ?? "127.0.0.1";
310
+ const server2 = await serve(api, { port: options2.port ?? 0, host });
311
+ const address = server2.address();
312
+ const port = typeof address === "object" && address !== null ? address.port : options2.port ?? 0;
313
+ const shown = host.includes(":") ? `[${host}]` : host;
314
+ return {
315
+ url: `http://${shown}:${port}`,
316
+ port,
317
+ host,
318
+ server: server2,
319
+ close: () => new Promise((resolve2, reject) => {
320
+ server2.close((error) => {
321
+ if (error && error.code !== "ERR_SERVER_NOT_RUNNING")
322
+ reject(error);
323
+ else
324
+ resolve2();
325
+ });
326
+ server2.closeAllConnections?.();
327
+ })
328
+ };
329
+ };
330
+
331
+ // ../../adapters/node/dist/fleet.js
332
+ var EMPTY = {
333
+ activeRequests: 0,
334
+ activeConnections: 0,
335
+ blockedCommands: 0,
336
+ pendingJobs: 0,
337
+ pendingWebhooks: 0,
338
+ unmatchedRequests: 0
339
+ };
340
+ var NS = /^[A-Za-z0-9_.-]{1,64}$/;
341
+ var json = (status, value) => Response.json(value, { status });
342
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
343
+ var pending = (d) => d.activeRequests + d.blockedCommands + d.pendingJobs + d.pendingWebhooks;
344
+ var publicUrl = (raw) => {
345
+ const url = new URL(raw);
346
+ url.password = "";
347
+ return url.href;
348
+ };
349
+ var atomicWrite = async (path, value) => {
350
+ const temp = `${path}.${randomUUID()}.tmp`;
351
+ try {
352
+ await writeFile(temp, `${JSON.stringify(value, null, 2)}
353
+ `, { mode: 384, flag: "wx" });
354
+ await chmod(temp, 384);
355
+ await rename(temp, path);
356
+ } finally {
357
+ await rm(temp, { force: true });
358
+ }
359
+ };
360
+ var removeOwned = async (path, id) => {
361
+ if (!path)
362
+ return;
363
+ try {
364
+ const value = JSON.parse(await readFile(path, "utf8"));
365
+ if (isObject(value) && value.id === id)
366
+ await rm(path, { force: true });
367
+ } catch (error) {
368
+ if (error.code !== "ENOENT")
369
+ throw error;
370
+ }
371
+ };
372
+ var httpChild = async (target, entry, options2) => {
373
+ const runtime = await target.create(entry.options ?? {}, {
374
+ adminKey: entry.adminKey,
375
+ seed: entry.seed,
376
+ onLog: options2.onLog
377
+ });
378
+ runtime.isolateNamespaces();
379
+ const locked = /* @__PURE__ */ new Set();
380
+ const active = /* @__PURE__ */ new Map();
381
+ const api = {
382
+ async fetch(request) {
383
+ const namespace = runtime.namespaceOf(request);
384
+ if (locked.has(namespace))
385
+ return json(409, { error: "fleet control in progress", namespace });
386
+ active.set(namespace, (active.get(namespace) ?? 0) + 1);
387
+ try {
388
+ return await runtime.fetch(request);
389
+ } finally {
390
+ active.set(namespace, (active.get(namespace) ?? 1) - 1);
391
+ }
392
+ }
393
+ };
394
+ let server2;
395
+ try {
396
+ server2 = await listen(api, {
397
+ port: entry.port ?? target.defaultPort,
398
+ host: entry.host ?? "127.0.0.1"
399
+ });
400
+ target.listening?.(server2);
401
+ } catch (error) {
402
+ runtime.stop?.();
403
+ await server2?.close();
404
+ runtime.webhooks?.clear();
405
+ await runtime.webhooks?.idle();
406
+ throw error;
407
+ }
408
+ return {
409
+ protocol: "http",
410
+ url: server2.url,
411
+ healthUrl: `${server2.url}/health`,
412
+ adminUrl: `${server2.url}/__admin`,
413
+ namespaces: { header: "x-mockingbird-namespace", path: "/ns/{name}" },
414
+ ready: async () => {
415
+ try {
416
+ return (await fetch(`${server2.url}/health`, { signal: AbortSignal.timeout(2e3) })).ok;
417
+ } catch {
418
+ return false;
419
+ }
420
+ },
421
+ async close() {
422
+ runtime.stop?.();
423
+ runtime.webhooks?.clear();
424
+ await server2.close();
425
+ await runtime.webhooks?.idle();
426
+ },
427
+ lock(namespace) {
428
+ if (locked.has(namespace))
429
+ throw new Error("namespace already locked");
430
+ locked.add(namespace);
431
+ return () => locked.delete(namespace);
432
+ },
433
+ diagnostics(namespace) {
434
+ return {
435
+ ...EMPTY,
436
+ activeRequests: active.get(namespace) ?? 0,
437
+ pendingWebhooks: runtime.webhooks?.pending(namespace) ?? 0,
438
+ unmatchedRequests: runtime.metrics.report(namespace).unmatched.reduce((n, r) => n + r.count, 0)
439
+ };
440
+ },
441
+ async checkpoint(namespace) {
442
+ return runtime.fleetSnapshot(namespace);
443
+ },
444
+ async restore(namespace, checkpoint) {
445
+ runtime.fleetRestore(checkpoint, namespace);
446
+ },
447
+ reset: (namespace) => runtime.reset(namespace),
448
+ clock: (namespace) => runtime.namespaceClock(namespace),
449
+ async settle(namespace) {
450
+ const instance = runtime.instance(namespace);
451
+ instance.tick?.();
452
+ await runtime.webhooks?.idle();
453
+ }
454
+ };
455
+ };
456
+ async function startFleet(config, options2) {
457
+ const id = randomUUID();
458
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
459
+ const children = /* @__PURE__ */ new Map();
460
+ const clocks = /* @__PURE__ */ new Map();
461
+ const checkpoints = /* @__PURE__ */ new Map();
462
+ let supervisor;
463
+ let stopped = false;
464
+ let controlling = false;
465
+ const close = async () => {
466
+ if (stopped)
467
+ return;
468
+ stopped = true;
469
+ await Promise.allSettled([...children.values()].map((child) => child.close()));
470
+ await supervisor?.close();
471
+ await removeOwned(options2.readyFile, id);
472
+ await removeOwned(options2.connectionsFile, id);
473
+ };
474
+ if (options2.readyFile)
475
+ await rm(options2.readyFile, { force: true });
476
+ if (options2.connectionsFile)
477
+ await rm(options2.connectionsFile, { force: true });
478
+ const clock = (namespace) => {
479
+ let value = clocks.get(namespace);
480
+ if (!value) {
481
+ value = createClock();
482
+ clocks.set(namespace, value);
483
+ }
484
+ return value;
485
+ };
486
+ const select = (body) => {
487
+ if (body.services === void 0)
488
+ return children;
489
+ if (!Array.isArray(body.services) || body.services.length === 0)
490
+ throw new Error("services must be a nonempty list");
491
+ const selected = /* @__PURE__ */ new Map();
492
+ for (const name of body.services) {
493
+ if (typeof name !== "string" || !children.has(name))
494
+ throw new Error("unknown service in selection");
495
+ selected.set(name, children.get(name));
496
+ }
497
+ return selected;
498
+ };
499
+ const diagnostics = (namespace) => Object.fromEntries([...children].map(([name, child]) => [name, child.diagnostics(namespace)]));
500
+ const transaction = async (namespace, selected, run) => {
501
+ if (controlling)
502
+ return json(409, { error: "another fleet operation is in progress" });
503
+ controlling = true;
504
+ const release = [];
505
+ const before = /* @__PURE__ */ new Map();
506
+ const results = {};
507
+ let failure;
508
+ try {
509
+ for (const [name, child] of selected) {
510
+ failure = name;
511
+ release.push(child.lock(namespace));
512
+ }
513
+ const busy = Object.fromEntries([...selected].filter(([, child]) => {
514
+ const state = child.diagnostics(namespace);
515
+ return state.activeRequests + state.blockedCommands + state.pendingWebhooks + (state.activeJobs ?? 0) > 0;
516
+ }).map(([name, child]) => [name, child.diagnostics(namespace)]));
517
+ if (Object.keys(busy).length)
518
+ return json(409, { error: "namespace is not quiescent", services: busy });
519
+ for (const [name, child] of selected) {
520
+ failure = name;
521
+ before.set(name, await child.checkpoint(namespace));
522
+ }
523
+ for (const [name, child] of selected) {
524
+ failure = name;
525
+ results[name] = { status: "ok", result: await run(name, child) };
526
+ }
527
+ return json(200, { status: "ok", namespace, services: results });
528
+ } catch {
529
+ const rollback = {};
530
+ for (const [name, point] of before) {
531
+ try {
532
+ await selected.get(name)?.restore(namespace, point);
533
+ rollback[name] = "restored";
534
+ } catch {
535
+ rollback[name] = "failed";
536
+ }
537
+ }
538
+ return json(500, {
539
+ status: "failed",
540
+ namespace,
541
+ failedService: failure,
542
+ services: results,
543
+ rollback,
544
+ rolledBack: before.size > 0 && Object.values(rollback).every((value) => value === "restored")
545
+ });
546
+ } finally {
547
+ for (const unlock of release.reverse())
548
+ unlock();
549
+ controlling = false;
550
+ }
551
+ };
552
+ const api = {
553
+ async fetch(request) {
554
+ const url = new URL(request.url);
555
+ if (url.pathname === "/health" && request.method === "GET") {
556
+ const states = await Promise.all([...children].map(async ([name, child]) => {
557
+ let protocolReady = false;
558
+ try {
559
+ protocolReady = await child.ready();
560
+ } catch {
561
+ }
562
+ return [
563
+ name,
564
+ {
565
+ processReady: !stopped,
566
+ protocolReady,
567
+ status: protocolReady ? "ready" : "failed",
568
+ url: publicUrl(child.url)
569
+ }
570
+ ];
571
+ }));
572
+ const ready = !stopped && states.every(([, state]) => state.protocolReady);
573
+ return json(ready ? 200 : 503, {
574
+ status: ready ? "ready" : "failed",
575
+ id,
576
+ services: Object.fromEntries(states)
577
+ });
578
+ }
579
+ if (!url.pathname.startsWith("/__fleet/"))
580
+ return json(404, { error: "not found" });
581
+ const adminKey = config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
582
+ if (adminKey !== void 0 && request.headers.get("x-mockingbird-admin-key") !== adminKey)
583
+ return json(401, { error: "admin key required" });
584
+ const route = /^\/__fleet\/namespaces\/([^/]+)\/(reset|snapshots)(?:\/([^/]+)\/restore)?$/.exec(url.pathname);
585
+ const namespace = route ? decodeURIComponent(route[1]) : url.searchParams.get("namespace") ?? request.headers.get("x-mockingbird-namespace") ?? config.namespace ?? "default";
586
+ if (!NS.test(namespace))
587
+ return json(400, { error: "invalid namespace" });
588
+ if (request.method === "GET" && url.pathname === "/__fleet/metrics")
589
+ return json(200, { namespace, services: diagnostics(namespace) });
590
+ if (request.method === "GET" && url.pathname === "/__fleet/clock")
591
+ return json(200, {
592
+ namespace,
593
+ ...clock(namespace).state(),
594
+ services: Object.fromEntries([...children].map(([name, child]) => [name, child.clock(namespace).state()]))
595
+ });
596
+ if (request.method !== "POST")
597
+ return json(405, { error: "method not allowed" });
598
+ let body;
599
+ try {
600
+ const text = await request.text();
601
+ const value = text ? JSON.parse(text) : {};
602
+ if (!isObject(value))
603
+ throw new Error("expected object");
604
+ body = value;
605
+ } catch {
606
+ return json(400, { error: "expected JSON object" });
607
+ }
608
+ let selected;
609
+ try {
610
+ selected = select(body);
611
+ } catch (error) {
612
+ return json(400, { error: error instanceof Error ? error.message : "bad selection" });
613
+ }
614
+ if (url.pathname === "/__fleet/wait-until-idle") {
615
+ const services = diagnostics(namespace);
616
+ const idle = Object.values(services).every((d) => pending(d) === 0);
617
+ return json(idle ? 200 : 409, { status: idle ? "idle" : "busy", namespace, services });
618
+ }
619
+ if (url.pathname === "/__fleet/clock") {
620
+ const current = clock(namespace);
621
+ let target = current.now();
622
+ if (body.set !== void 0) {
623
+ target = typeof body.set === "number" ? body.set : typeof body.set === "string" ? Date.parse(body.set) : NaN;
624
+ if (!Number.isFinite(target))
625
+ return json(400, { error: "set must be epoch ms or ISO-8601" });
626
+ }
627
+ if (body.advance !== void 0) {
628
+ const delta = parseDuration(body.advance);
629
+ if (delta === void 0)
630
+ return json(400, { error: "invalid advance" });
631
+ target += delta;
632
+ }
633
+ const frozen = body.freeze === void 0 ? current.state().frozen : body.freeze;
634
+ if (typeof frozen !== "boolean")
635
+ return json(400, { error: "freeze must be boolean" });
636
+ const response2 = await transaction(namespace, selected, async (_name, child) => {
637
+ const childClock = child.clock(namespace);
638
+ childClock.freeze();
639
+ childClock.set(target);
640
+ if (!frozen)
641
+ childClock.unfreeze();
642
+ await child.settle?.(namespace);
643
+ return childClock.state();
644
+ });
645
+ if (response2.ok) {
646
+ current.freeze();
647
+ current.set(target);
648
+ if (!frozen)
649
+ current.unfreeze();
650
+ }
651
+ return response2;
652
+ }
653
+ if (!route)
654
+ return json(404, { error: "not found" });
655
+ if (route[2] === "reset") {
656
+ if (route[3])
657
+ return json(404, { error: "not found" });
658
+ const response2 = await transaction(namespace, selected, async (_name, child) => child.reset(namespace));
659
+ if (response2.ok && selected.size === children.size)
660
+ clock(namespace).reset();
661
+ return response2;
662
+ }
663
+ if (route[3]) {
664
+ const point = checkpoints.get(route[3]);
665
+ if (!point || point.namespace !== namespace)
666
+ return json(404, { error: "no snapshot in this namespace" });
667
+ const restored = new Map([...point.values].map(([name]) => [name, children.get(name)]));
668
+ const response2 = await transaction(namespace, restored, async (name, child) => child.restore(namespace, point.values.get(name)));
669
+ if (response2.ok) {
670
+ const current = clock(namespace);
671
+ current.freeze();
672
+ current.set(point.clock.now);
673
+ if (!point.clock.frozen)
674
+ current.unfreeze();
675
+ }
676
+ return response2;
677
+ }
678
+ const snapshotId = randomUUID();
679
+ const values = /* @__PURE__ */ new Map();
680
+ const response = await transaction(namespace, selected, async (name, child) => {
681
+ values.set(name, await child.checkpoint(namespace));
682
+ return { checkpoint: `${snapshotId}:${name}` };
683
+ });
684
+ if (!response.ok)
685
+ return response;
686
+ checkpoints.set(snapshotId, { namespace, values, clock: clock(namespace).state() });
687
+ return json(201, {
688
+ ...await response.json(),
689
+ id: snapshotId,
690
+ label: body.label
691
+ });
692
+ }
693
+ };
694
+ try {
695
+ if (!isObject(config.services) || Object.keys(config.services).length === 0)
696
+ throw new Error("config must contain at least one service");
697
+ for (const [name, entry] of Object.entries(config.services)) {
698
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name))
699
+ throw new Error("invalid service name");
700
+ try {
701
+ const target = await options2.load(name);
702
+ if (entry.protocol && entry.protocol !== ("protocol" in target ? target.protocol : "http"))
703
+ throw new Error("configured protocol does not match target");
704
+ const adminKey = entry.adminKey ?? config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
705
+ const child = "start" in target ? await target.start(entry) : await httpChild(target, { ...entry, ...adminKey !== void 0 ? { adminKey } : {} }, options2);
706
+ children.set(name, child);
707
+ if (!await child.ready())
708
+ throw new Error("protocol readiness failed");
709
+ } catch {
710
+ throw new Error(`service ${name} failed to start; all started listeners were closed`);
711
+ }
712
+ }
713
+ supervisor = await listen(api, {
714
+ port: config.control?.port ?? 0,
715
+ host: config.control?.host ?? "127.0.0.1"
716
+ });
717
+ const manifest = {
718
+ version: 1,
719
+ state: "ready",
720
+ pid: process.pid,
721
+ id,
722
+ startedAt,
723
+ adminBase: `${supervisor.url}/__fleet`,
724
+ healthUrl: `${supervisor.url}/health`,
725
+ services: Object.fromEntries([...children].map(([name, child]) => [
726
+ name,
727
+ {
728
+ protocol: child.protocol,
729
+ url: publicUrl(child.url),
730
+ healthUrl: child.healthUrl ?? `${supervisor?.url}/health`,
731
+ adminUrl: child.adminUrl ?? `${supervisor?.url}/__fleet`,
732
+ namespaces: child.namespaces,
733
+ processReady: true,
734
+ protocolReady: true
735
+ }
736
+ ]))
737
+ };
738
+ if (options2.connectionsFile)
739
+ await atomicWrite(options2.connectionsFile, {
740
+ version: 1,
741
+ id,
742
+ pid: process.pid,
743
+ services: Object.fromEntries([...children].map(([name, child]) => [
744
+ name,
745
+ { url: child.connection ?? child.url, namespaces: child.connections }
746
+ ]))
747
+ });
748
+ if (options2.readyFile)
749
+ await atomicWrite(options2.readyFile, manifest);
750
+ options2.onReady?.(manifest);
751
+ return { manifest, children, fetch: api.fetch, close };
752
+ } catch (error) {
753
+ await close();
754
+ throw error;
755
+ }
756
+ }
757
+
758
+ // ../../adapters/node/dist/cli.js
759
+ var optionHelp = (options2) => Object.entries(options2).map(([name, option]) => {
760
+ const flag = `--${name}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
761
+ const fallback2 = option.default !== void 0 ? ` (default: ${String(option.default)})` : "";
762
+ return ` ${flag.padEnd(30)} ${option.description}${fallback2}`;
763
+ });
764
+ var help = (spec) => [
765
+ `${spec.bin} \u2014 ${spec.description}`,
766
+ "",
767
+ "Usage:",
768
+ ` ${spec.bin} <command> [options]`,
769
+ "",
770
+ "Commands:",
771
+ ...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),
772
+ "",
773
+ `Run \`${spec.bin} <command> --help\` for a command's options.`
774
+ ].join("\n");
775
+ var commandHelp = (spec, name, command2) => [
776
+ `${spec.bin} ${name} \u2014 ${command2.summary}`,
777
+ "",
778
+ "Usage:",
779
+ ` ${command2.usage ?? `${spec.bin} ${name} [options]`}`,
780
+ ...command2.options ? ["", "Options:", ...optionHelp(command2.options)] : []
781
+ ].join("\n");
782
+ var runCli = async (spec, argv) => {
783
+ const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : void 0;
784
+ const words = pair !== void 0 && spec.commands[pair] ? 2 : 1;
785
+ const name = words === 2 ? pair : argv[0];
786
+ const rest = argv.slice(words);
787
+ if (name === void 0 || name === "--help" || name === "-h" || name === "help") {
788
+ console.log(help(spec));
789
+ return 0;
790
+ }
791
+ const command2 = spec.commands[name];
792
+ if (!command2) {
793
+ console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}
794
+
795
+ ${help(spec)}`);
796
+ return 2;
797
+ }
798
+ if (rest.includes("--help") || rest.includes("-h")) {
799
+ console.log(commandHelp(spec, name, command2));
800
+ return 0;
801
+ }
802
+ let parsed;
803
+ try {
804
+ parsed = parseArgs({
805
+ args: rest,
806
+ allowPositionals: true,
807
+ strict: true,
808
+ options: Object.fromEntries(Object.entries(command2.options ?? {}).map(([key, option]) => [
809
+ key,
810
+ {
811
+ type: option.type,
812
+ ...option.default !== void 0 ? { default: option.default } : {}
813
+ }
814
+ ]))
815
+ });
816
+ } catch (error) {
817
+ console.error(`${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}
818
+
819
+ ${commandHelp(spec, name, command2)}`);
820
+ return 2;
821
+ }
822
+ return command2.run(parsed.values, parsed.positionals);
823
+ };
824
+ var COMMON_SERVE_OPTIONS = {
825
+ port: { type: "string", value: "<port>", description: "Port to listen on" },
826
+ host: { type: "string", value: "<host>", description: "Interface to bind", default: "127.0.0.1" },
827
+ "admin-key": {
828
+ type: "string",
829
+ value: "<key>",
830
+ description: "Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)"
831
+ },
832
+ seed: { type: "string", value: "<seed>", description: "Seed for every random choice" },
833
+ log: {
834
+ type: "string",
835
+ value: "<pretty|json|off>",
836
+ description: "Request log format",
837
+ default: "pretty"
838
+ },
839
+ "log-requests": {
840
+ type: "boolean",
841
+ description: "One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json"
842
+ },
843
+ config: {
844
+ type: "string",
845
+ value: "<file>",
846
+ description: "Serve every service in a mockingbird.json config instead"
847
+ },
848
+ "ready-file": {
849
+ type: "string",
850
+ description: "Atomically publish fleet endpoint JSON",
851
+ value: "<file>"
852
+ },
853
+ "connections-file": {
854
+ type: "string",
855
+ description: "Write private connection URLs with mode 0600",
856
+ value: "<file>"
857
+ },
858
+ "ready-json": {
859
+ type: "boolean",
860
+ description: "Emit one public JSON record after fleet readiness"
861
+ }
862
+ };
863
+ var formatLog = (format) => {
864
+ if (format === "off")
865
+ return void 0;
866
+ if (format === "json")
867
+ return (entry) => console.log(JSON.stringify(entry));
868
+ return (entry) => {
869
+ const op = entry.operationId ?? (entry.unmatched ? "UNMATCHED" : "-");
870
+ const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
871
+ const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
872
+ const adopted = entry.adopted ? " adopted" : "";
873
+ const received = entry.request ? ` received=${entry.request.bodyBytes === null ? "?" : `${entry.request.bodyBytes}B`} ct=${entry.request.contentType ?? "-"}${entry.request.transferEncoding ? ` te=${entry.request.transferEncoding}` : ""}` : "";
874
+ 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})` : ""}` : "";
875
+ console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}${received}${issues}`);
876
+ };
877
+ };
878
+ var asString = (value) => typeof value === "string" ? value : void 0;
879
+ var loadTarget = async (name, own) => {
880
+ if (name === own.name)
881
+ return own;
882
+ const specifier = `@crvouga/mockingbird-service-${name}/server`;
883
+ try {
884
+ let mod;
885
+ try {
886
+ mod = await import(specifier);
887
+ } catch {
888
+ const fromProject = createRequire(resolve(process.cwd(), "package.json"));
889
+ mod = await import(pathToFileURL(fromProject.resolve(specifier)).href);
890
+ }
891
+ if (!mod.serveTarget)
892
+ throw new Error(`${specifier} exports no serveTarget`);
893
+ return mod.serveTarget;
894
+ } catch (error) {
895
+ const reason = error instanceof Error ? error.message : String(error);
896
+ throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
897
+ }
898
+ };
899
+ var start = async (target, values, config) => {
900
+ const runtime = await target.create(values, {
901
+ adminKey: config.adminKey,
902
+ seed: config.seed,
903
+ onLog: formatLog(config.log)
904
+ });
905
+ const listening = await listen(runtime, { port: config.port, host: config.host });
906
+ target.listening?.(listening);
907
+ console.log(`${target.name} mock listening on ${listening.url}`);
908
+ console.log(`${target.name} health: GET ${listening.url}/health`);
909
+ console.log(`${target.name} admin: ${listening.url}/__admin (${config.adminKey ? "x-mockingbird-admin-key required" : "open \u2014 pass --admin-key to lock"})`);
910
+ console.log(`${target.name} admin ui: ${listening.url}/__admin/ui`);
911
+ for (const line of target.banner?.(runtime) ?? [])
912
+ console.log(`${target.name} ${line}`);
913
+ return listening;
914
+ };
915
+ var untilSignal = async (servers) => new Promise((resolve2) => {
916
+ const stop2 = async () => {
917
+ ;
918
+ process.removeListener("SIGINT", stop2);
919
+ process.removeListener("SIGTERM", stop2);
920
+ await Promise.allSettled(servers.map((s) => s.close()));
921
+ resolve2(0);
922
+ };
923
+ process.once("SIGINT", stop2);
924
+ process.once("SIGTERM", stop2);
925
+ });
926
+ var serveCommand = (target) => ({
927
+ summary: `Serve the ${target.name} mock or a configured fleet`,
928
+ options: { ...COMMON_SERVE_OPTIONS, ..."options" in target ? target.options : {} },
929
+ async run(values) {
930
+ const log2 = values["log-requests"] === true ? "json" : asString(values.log) ?? "pretty";
931
+ if (!["pretty", "json", "off"].includes(log2)) {
932
+ console.error(`--log must be pretty, json or off (got ${log2})`);
933
+ return 2;
934
+ }
935
+ const configPath = asString(values.config);
936
+ if (configPath !== void 0) {
937
+ try {
938
+ const config = JSON.parse(await readFile2(configPath, "utf8"));
939
+ const adminKey2 = config.adminKey ?? asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
940
+ if (adminKey2 !== void 0)
941
+ config.adminKey = adminKey2;
942
+ const fleet = await startFleet(config, {
943
+ load: (name) => loadTarget(name, target),
944
+ onLog: formatLog(config.log ?? log2),
945
+ ...asString(values["ready-file"]) ? { readyFile: asString(values["ready-file"]) } : {},
946
+ ...asString(values["connections-file"]) ? { connectionsFile: asString(values["connections-file"]) } : {},
947
+ onReady: (manifest) => {
948
+ if (values["ready-json"])
949
+ console.log(JSON.stringify(manifest));
950
+ else
951
+ for (const [name, endpoint] of Object.entries(manifest.services))
952
+ console.log(`${name} mock listening on ${endpoint.url}`);
953
+ }
954
+ });
955
+ return untilSignal([fleet]);
956
+ } catch (error) {
957
+ console.error(error instanceof Error ? error.message : String(error));
958
+ return 1;
959
+ }
960
+ }
961
+ if ("start" in target) {
962
+ console.error("Use --config to supervise protocol services with this command");
963
+ return 2;
964
+ }
965
+ const port = asString(values.port);
966
+ const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
967
+ const seed = asString(values.seed);
968
+ let listening;
969
+ try {
970
+ listening = await start(target, values, {
971
+ port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
972
+ host: asString(values.host) ?? "127.0.0.1",
973
+ ...adminKey !== void 0 ? { adminKey } : {},
974
+ ...seed !== void 0 ? { seed } : {},
975
+ log: log2
976
+ });
977
+ } catch (error) {
978
+ console.error(error instanceof Error ? error.message : String(error));
979
+ return 1;
980
+ }
981
+ return untilSignal([listening]);
982
+ }
983
+ });
984
+
985
+ // src/wire/index.ts
986
+ import { createServer as createServer2 } from "node:net";
987
+
167
988
  // src/errors/error.ts
168
989
  var CATEGORY_TO_SQLSTATE = {
169
990
  syntax: "42601",
@@ -2344,11 +3165,11 @@ var JsonParser = class {
2344
3165
  }
2345
3166
  }
2346
3167
  parseNumber() {
2347
- const start = this.pos;
3168
+ const start2 = this.pos;
2348
3169
  const re = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
2349
3170
  re.lastIndex = this.pos;
2350
3171
  const m = re.exec(this.text);
2351
- if (!m || m.index !== start) this.fail("bad number");
3172
+ if (!m || m.index !== start2) this.fail("bad number");
2352
3173
  this.pos = re.lastIndex;
2353
3174
  return jsonbNum(parseNumeric(m[0]));
2354
3175
  }
@@ -4700,18 +5521,18 @@ function isoWeek(days) {
4700
5521
  return jan4 - (isoDw - 1);
4701
5522
  };
4702
5523
  let year = y;
4703
- let start = week1Monday(y);
4704
- if (days < start) {
5524
+ let start2 = week1Monday(y);
5525
+ if (days < start2) {
4705
5526
  year = y - 1;
4706
- start = week1Monday(year);
5527
+ start2 = week1Monday(year);
4707
5528
  } else {
4708
5529
  const nextStart = week1Monday(y + 1);
4709
5530
  if (days >= nextStart) {
4710
5531
  year = y + 1;
4711
- start = nextStart;
5532
+ start2 = nextStart;
4712
5533
  }
4713
5534
  }
4714
- return { week: Math.floor((days - start) / 7) + 1, year };
5535
+ return { week: Math.floor((days - start2) / 7) + 1, year };
4715
5536
  }
4716
5537
  function dateTruncDays(field, days) {
4717
5538
  const { y, m } = daysToCivil(days);
@@ -5281,9 +6102,9 @@ function getArrayFunctions() {
5281
6102
  }
5282
6103
  const elemT = arrayElemType(a.t);
5283
6104
  const target = castTo(ctx, args2[1], elemT, {});
5284
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
6105
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
5285
6106
  const lb = lbsOf(arr)[0] ?? 1;
5286
- for (let i = Math.max(start - lb, 0); i < arr.items.length; i++) {
6107
+ for (let i = Math.max(start2 - lb, 0); i < arr.items.length; i++) {
5287
6108
  const item = arr.items[i];
5288
6109
  const eq = item === null ? target.v === null : target.v !== null && datumEquals(elemT, item, target.v, ctx);
5289
6110
  if (eq) return tv("int4", i + lb);
@@ -5763,12 +6584,12 @@ function parseByFormat(input, fmt) {
5763
6584
  };
5764
6585
  const readNum = (maxLen) => {
5765
6586
  skipInputSeparators();
5766
- const start = i;
5767
- while (i < input.length && /[0-9]/.test(input[i]) && i - start < maxLen) i++;
5768
- if (i === start) {
6587
+ const start2 = i;
6588
+ while (i < input.length && /[0-9]/.test(input[i]) && i - start2 < maxLen) i++;
6589
+ if (i === start2) {
5769
6590
  throw pgError("invalid_datetime", `invalid value for field in source string "${input}"`, "22007");
5770
6591
  }
5771
- return Number(input.slice(start, i));
6592
+ return Number(input.slice(start2, i));
5772
6593
  };
5773
6594
  const readMonthName = () => {
5774
6595
  skipInputSeparators();
@@ -7603,15 +8424,15 @@ var decoderUtf8 = new TextDecoder("utf-8", { fatal: false });
7603
8424
  function chars(s) {
7604
8425
  return [...s];
7605
8426
  }
7606
- function substrImpl(s, start, len) {
8427
+ function substrImpl(s, start2, len) {
7607
8428
  const cs = chars(s);
7608
8429
  if (len === void 0) {
7609
- const from2 = Math.max(start, 1);
8430
+ const from2 = Math.max(start2, 1);
7610
8431
  return cs.slice(from2 - 1).join("");
7611
8432
  }
7612
8433
  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);
8434
+ const end = start2 + len;
8435
+ const from = Math.max(start2, 1);
7615
8436
  const to = Math.max(end, 1);
7616
8437
  if (to <= from) return "";
7617
8438
  return cs.slice(from - 1, to - 1).join("");
@@ -7675,14 +8496,14 @@ function getStringFunctions() {
7675
8496
  const a = args2[0];
7676
8497
  if (a.t === "bytea") {
7677
8498
  const bytes = a.v;
7678
- const start2 = argInt(ctx, args2[1]);
8499
+ const start3 = argInt(ctx, args2[1]);
7679
8500
  const len2 = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
7680
- return tv("bytea", byteaSubstr(bytes, start2, len2));
8501
+ return tv("bytea", byteaSubstr(bytes, start3, len2));
7681
8502
  }
7682
8503
  const s = argText(ctx, a);
7683
- const start = argInt(ctx, args2[1]);
8504
+ const start2 = argInt(ctx, args2[1]);
7684
8505
  const len = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
7685
- return tv("text", substrImpl(s, start, len));
8506
+ return tv("text", substrImpl(s, start2, len));
7686
8507
  })
7687
8508
  );
7688
8509
  m.set(
@@ -7978,11 +8799,11 @@ function getStringFunctions() {
7978
8799
  strict("int4", (ctx, args2) => {
7979
8800
  const s = argText(ctx, args2[0]);
7980
8801
  const pattern = argText(ctx, args2[1]);
7981
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8802
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
7982
8803
  const flags = args2.length > 3 ? argText(ctx, args2[3]) : "";
7983
- if (start < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start}`);
8804
+ if (start2 < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start2}`);
7984
8805
  const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
7985
- const sub = chars(s).slice(start - 1).join("");
8806
+ const sub = chars(s).slice(start2 - 1).join("");
7986
8807
  let count = 0;
7987
8808
  for (const _ of sub.matchAll(js)) count++;
7988
8809
  return tv("int4", count);
@@ -8003,11 +8824,11 @@ function getStringFunctions() {
8003
8824
  strict("text", (ctx, args2) => {
8004
8825
  const s = argText(ctx, args2[0]);
8005
8826
  const pattern = argText(ctx, args2[1]);
8006
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8827
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8007
8828
  const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
8008
8829
  const flags = args2.length > 4 ? argText(ctx, args2[4]) : "";
8009
8830
  const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
8010
- const sub = chars(s).slice(start - 1).join("");
8831
+ const sub = chars(s).slice(start2 - 1).join("");
8011
8832
  let i = 0;
8012
8833
  for (const match of sub.matchAll(js)) {
8013
8834
  i++;
@@ -8021,21 +8842,21 @@ function getStringFunctions() {
8021
8842
  strict("int4", (ctx, args2) => {
8022
8843
  const s = argText(ctx, args2[0]);
8023
8844
  const pattern = argText(ctx, args2[1]);
8024
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8845
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8025
8846
  const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
8026
8847
  const endoption = args2.length > 4 ? argInt(ctx, args2[4]) : 0;
8027
8848
  const flags = args2.length > 5 ? argText(ctx, args2[5]) : "";
8028
- if (start < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start}`);
8849
+ if (start2 < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start2}`);
8029
8850
  if (endoption !== 0 && endoption !== 1) {
8030
8851
  throw pgError("invalid_parameter_value", `invalid value for parameter "endoption": ${endoption}`);
8031
8852
  }
8032
8853
  const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
8033
- const sub = chars(s).slice(start - 1).join("");
8854
+ const sub = chars(s).slice(start2 - 1).join("");
8034
8855
  let i = 0;
8035
8856
  for (const match of sub.matchAll(js)) {
8036
8857
  i++;
8037
8858
  if (i === nth) {
8038
- const base = start - 1 + chars(sub.slice(0, match.index)).length;
8859
+ const base = start2 - 1 + chars(sub.slice(0, match.index)).length;
8039
8860
  return tv("int4", endoption === 0 ? base + 1 : base + chars(match[0]).length + 1);
8040
8861
  }
8041
8862
  }
@@ -8149,10 +8970,10 @@ function getStringFunctions() {
8149
8970
  );
8150
8971
  return m;
8151
8972
  }
8152
- function byteaSubstr(bytes, start, len) {
8973
+ function byteaSubstr(bytes, start2, len) {
8153
8974
  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);
8975
+ const from = Math.max(start2, 1) - 1;
8976
+ const to = len === void 0 ? bytes.length : Math.max(start2 + len - 1, 0);
8156
8977
  return bytes.slice(from, to);
8157
8978
  }
8158
8979
  function pad(s, len, fill, left) {
@@ -10714,7 +11535,7 @@ function evalOverlaps(ctx, scope, args2) {
10714
11535
  };
10715
11536
  const [s1r, e1r] = pair(args2[0]);
10716
11537
  const [s2r, e2r] = pair(args2[1]);
10717
- const resolveEnd = (start, end) => end.t === "interval" ? evalBinary(ctx, "+", start, end) : end;
11538
+ const resolveEnd = (start2, end) => end.t === "interval" ? evalBinary(ctx, "+", start2, end) : end;
10718
11539
  const e1 = resolveEnd(s1r, e1r);
10719
11540
  const e2 = resolveEnd(s2r, e2r);
10720
11541
  const target = [s1r, e1, s2r, e2].map((x) => x.t).find((t) => t !== "unknown") ?? "timestamp";
@@ -11973,7 +12794,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
11973
12794
  }
11974
12795
  if (a.t === "timestamp" || a.t === "timestamptz" || b.t === "timestamp" || b.t === "timestamptz" || a.t === "date") {
11975
12796
  const t2 = a.t === "timestamptz" || b.t === "timestamptz" ? "timestamptz" : "timestamp";
11976
- const start2 = castTo(ctx, a, t2, { explicit: true }).v;
12797
+ const start3 = castTo(ctx, a, t2, { explicit: true }).v;
11977
12798
  const stop3 = castTo(ctx, b, t2, { explicit: true }).v;
11978
12799
  const stepArg = args2[2];
11979
12800
  if (stepArg === void 0 || !isInterval(stepArg.v)) {
@@ -11988,7 +12809,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
11988
12809
  if (step2.months !== 0) {
11989
12810
  let i = 0;
11990
12811
  for (; ; ) {
11991
- const { days, tod } = splitTs(start2);
12812
+ const { days, tod } = splitTs(start3);
11992
12813
  const c = daysToCivil(days);
11993
12814
  const totalMonths = c.y * 12 + (c.m - 1) + step2.months * i;
11994
12815
  const y = Math.floor(totalMonths / 12);
@@ -12005,19 +12826,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
12005
12826
  const stepMicros = intervalTotalMicros(step2);
12006
12827
  if (stepMicros === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
12007
12828
  if (stepMicros > 0n) {
12008
- for (let cur = start2; cur <= stop3; cur += stepMicros) rows2.push([cur]);
12829
+ for (let cur = start3; cur <= stop3; cur += stepMicros) rows2.push([cur]);
12009
12830
  } else {
12010
- for (let cur = start2; cur >= stop3; cur += stepMicros) rows2.push([cur]);
12831
+ for (let cur = start3; cur >= stop3; cur += stepMicros) rows2.push([cur]);
12011
12832
  }
12012
12833
  return { columns: [{ name: alias, type: t2 }], rows: rows2 };
12013
12834
  }
12014
12835
  if (a.t === "numeric" || b.t === "numeric" || args2[2]?.t === "numeric") {
12015
- const start2 = castTo(ctx, a, "numeric", { explicit: true }).v;
12836
+ const start3 = castTo(ctx, a, "numeric", { explicit: true }).v;
12016
12837
  const stop3 = castTo(ctx, b, "numeric", { explicit: true }).v;
12017
12838
  const step2 = args2[2] ? castTo(ctx, args2[2], "numeric", { explicit: true }).v : castTo(ctx, tv("int4", 1), "numeric", { explicit: true }).v;
12018
12839
  if (numericSign(step2) === 0) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
12019
12840
  const rows2 = [];
12020
- let cur = start2;
12841
+ let cur = start3;
12021
12842
  const ascending = numericSign(step2) > 0;
12022
12843
  while (ascending ? numericCmp(cur, stop3) <= 0 : numericCmp(cur, stop3) >= 0) {
12023
12844
  rows2.push([cur]);
@@ -12029,19 +12850,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
12029
12850
  }
12030
12851
  const useBig = a.t === "int8" || b.t === "int8" || args2[2]?.t === "int8";
12031
12852
  const t = useBig ? "int8" : "int4";
12032
- const start = castTo(ctx, a, "int8", {}).v;
12853
+ const start2 = castTo(ctx, a, "int8", {}).v;
12033
12854
  const stop2 = castTo(ctx, b, "int8", {}).v;
12034
12855
  const step = args2[2] ? castTo(ctx, args2[2], "int8", {}).v : 1n;
12035
12856
  if (step === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
12036
12857
  const rows = [];
12037
12858
  if (step > 0n) {
12038
- for (let cur = start; cur <= stop2; cur += step) {
12859
+ for (let cur = start2; cur <= stop2; cur += step) {
12039
12860
  rows.push([useBig ? cur : Number(cur)]);
12040
12861
  if (rows.length > 1e7)
12041
12862
  throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
12042
12863
  }
12043
12864
  } else {
12044
- for (let cur = start; cur >= stop2; cur += step) {
12865
+ for (let cur = start2; cur >= stop2; cur += step) {
12045
12866
  rows.push([useBig ? cur : Number(cur)]);
12046
12867
  if (rows.length > 1e7)
12047
12868
  throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
@@ -12471,10 +13292,10 @@ function tokenize(sql) {
12471
13292
  const m = /^\$([A-Za-z_\u0080-\uffff][A-Za-z0-9_\u0080-\uffff]*)?\$/.exec(sql.slice(i));
12472
13293
  if (m) {
12473
13294
  const open = m[0];
12474
- const start = i + open.length;
12475
- const end = sql.indexOf(open, start);
13295
+ const start2 = i + open.length;
13296
+ const end = sql.indexOf(open, start2);
12476
13297
  if (end === -1) syntaxError("unterminated dollar-quoted string");
12477
- push("string", sql.slice(start, end), i);
13298
+ push("string", sql.slice(start2, end), i);
12478
13299
  i = end + open.length;
12479
13300
  continue;
12480
13301
  }
@@ -12541,14 +13362,14 @@ function tokenize(sql) {
12541
13362
  continue;
12542
13363
  }
12543
13364
  if (/\d/.test(c) || c === "." && /\d/.test(sql[i + 1] ?? "")) {
12544
- const start = i;
13365
+ const start2 = i;
12545
13366
  const radix = /^0[xX][0-9a-fA-F_]+|^0[oO][0-7_]+|^0[bB][01_]+/.exec(sql.slice(i));
12546
13367
  if (radix) {
12547
13368
  const raw2 = radix[0];
12548
13369
  i += raw2.length;
12549
13370
  const clean = raw2.replaceAll("_", "");
12550
13371
  const v = BigInt(clean);
12551
- push("number", v.toString(), start);
13372
+ push("number", v.toString(), start2);
12552
13373
  continue;
12553
13374
  }
12554
13375
  let j = i;
@@ -12570,7 +13391,7 @@ function tokenize(sql) {
12570
13391
  if (j < n && IDENT_START.test(sql[j])) {
12571
13392
  syntaxError(`trailing junk after numeric literal at or near "${sql.slice(i, j + 1)}"`);
12572
13393
  }
12573
- push("number", raw, start);
13394
+ push("number", raw, start2);
12574
13395
  i = j;
12575
13396
  continue;
12576
13397
  }
@@ -12628,9 +13449,9 @@ function tokenize(sql) {
12628
13449
  push("eof", "", i);
12629
13450
  return tokens;
12630
13451
  }
12631
- function scanStandardString(sql, start) {
13452
+ function scanStandardString(sql, start2) {
12632
13453
  let out = "";
12633
- let i = start + 1;
13454
+ let i = start2 + 1;
12634
13455
  const n = sql.length;
12635
13456
  for (; ; ) {
12636
13457
  if (i >= n) syntaxError("unterminated quoted string");
@@ -12658,9 +13479,9 @@ function scanStandardString(sql, start) {
12658
13479
  i++;
12659
13480
  }
12660
13481
  }
12661
- function scanEscapeString(sql, start) {
13482
+ function scanEscapeString(sql, start2) {
12662
13483
  let out = "";
12663
- let i = start + 1;
13484
+ let i = start2 + 1;
12664
13485
  const n = sql.length;
12665
13486
  for (; ; ) {
12666
13487
  if (i >= n) syntaxError("unterminated quoted string");
@@ -16400,14 +17221,14 @@ var Parser = class {
16400
17221
  let frame = null;
16401
17222
  if (this.atKw("range") || this.atKw("rows") || this.atKw("groups")) {
16402
17223
  const mode = this.next().value;
16403
- let start;
17224
+ let start2;
16404
17225
  let end = null;
16405
17226
  if (this.eatKw("between")) {
16406
- start = this.parseFrameBound();
17227
+ start2 = this.parseFrameBound();
16407
17228
  this.expectKw("and");
16408
17229
  end = this.parseFrameBound();
16409
17230
  } else {
16410
- start = this.parseFrameBound();
17231
+ start2 = this.parseFrameBound();
16411
17232
  }
16412
17233
  let exclusion = null;
16413
17234
  if (this.eatKw("exclude")) {
@@ -16424,7 +17245,7 @@ var Parser = class {
16424
17245
  exclusion = "no_others";
16425
17246
  }
16426
17247
  }
16427
- frame = { mode, start, end, exclusion };
17248
+ frame = { mode, start: start2, end, exclusion };
16428
17249
  }
16429
17250
  return { name, partitionBy, orderBy, frame };
16430
17251
  }
@@ -16670,7 +17491,7 @@ var PlParser = class {
16670
17491
  return { kind: "assign", target, expr: this.parseExprUntilSemi() };
16671
17492
  }
16672
17493
  parseSqlTextUntilSemi() {
16673
- const start = this.peek().pos;
17494
+ const start2 = this.peek().pos;
16674
17495
  let depth = 0;
16675
17496
  for (; ; ) {
16676
17497
  const t = this.peek();
@@ -16678,7 +17499,7 @@ var PlParser = class {
16678
17499
  if (t.type === "punct" && t.value === "(") depth++;
16679
17500
  if (t.type === "punct" && t.value === ")") depth--;
16680
17501
  if (t.type === "punct" && t.value === ";" && depth === 0) {
16681
- const text = this.src.slice(start, t.pos).trim();
17502
+ const text = this.src.slice(start2, t.pos).trim();
16682
17503
  this.pos++;
16683
17504
  return text;
16684
17505
  }
@@ -16686,7 +17507,7 @@ var PlParser = class {
16686
17507
  }
16687
17508
  }
16688
17509
  parseExprUntilSemi() {
16689
- const start = this.peek().pos;
17510
+ const start2 = this.peek().pos;
16690
17511
  let depth = 0;
16691
17512
  for (; ; ) {
16692
17513
  const t = this.peek();
@@ -16694,7 +17515,7 @@ var PlParser = class {
16694
17515
  if (t.type === "punct" && t.value === "(") depth++;
16695
17516
  if (t.type === "punct" && t.value === ")") depth--;
16696
17517
  if (t.type === "punct" && t.value === ";" && depth === 0) {
16697
- const text = this.src.slice(start, t.pos);
17518
+ const text = this.src.slice(start2, t.pos);
16698
17519
  this.pos++;
16699
17520
  return parseSqlExpr(text);
16700
17521
  }
@@ -16702,7 +17523,7 @@ var PlParser = class {
16702
17523
  }
16703
17524
  }
16704
17525
  parseExprUntilKw(kw) {
16705
- const start = this.peek().pos;
17526
+ const start2 = this.peek().pos;
16706
17527
  let depth = 0;
16707
17528
  let caseDepth = 0;
16708
17529
  for (; ; ) {
@@ -16713,7 +17534,7 @@ var PlParser = class {
16713
17534
  if (t.type === "ident" && t.value === "case" && depth === 0) caseDepth++;
16714
17535
  if (t.type === "ident" && t.value === "end" && depth === 0 && caseDepth > 0) caseDepth--;
16715
17536
  if (t.type === "ident" && t.value === kw && depth === 0 && caseDepth === 0) {
16716
- const text = this.src.slice(start, t.pos);
17537
+ const text = this.src.slice(start2, t.pos);
16717
17538
  this.pos++;
16718
17539
  return parseSqlExpr(text);
16719
17540
  }
@@ -16721,7 +17542,7 @@ var PlParser = class {
16721
17542
  }
16722
17543
  }
16723
17544
  parseQueryUntilKw(kw) {
16724
- const start = this.peek().pos;
17545
+ const start2 = this.peek().pos;
16725
17546
  let depth = 0;
16726
17547
  for (; ; ) {
16727
17548
  const t = this.peek();
@@ -16729,7 +17550,7 @@ var PlParser = class {
16729
17550
  if (t.type === "punct" && t.value === "(") depth++;
16730
17551
  if (t.type === "punct" && t.value === ")") depth--;
16731
17552
  if (t.type === "ident" && t.value === kw && depth === 0) {
16732
- const text = this.src.slice(start, t.pos).trim();
17553
+ const text = this.src.slice(start2, t.pos).trim();
16733
17554
  this.pos++;
16734
17555
  const stmts = parse(text);
16735
17556
  const q = stmts[0];
@@ -18272,7 +19093,7 @@ function buildFrom(env, items, where = null) {
18272
19093
  }
18273
19094
  }
18274
19095
  if (cols === null) {
18275
- const probe = materializeItem(
19096
+ const probe2 = materializeItem(
18276
19097
  env,
18277
19098
  item,
18278
19099
  new RowScope(
@@ -18282,8 +19103,8 @@ function buildFrom(env, items, where = null) {
18282
19103
  accVars
18283
19104
  )
18284
19105
  );
18285
- cols = [...accRel.columns, ...probe.rel.columns];
18286
- vars = /* @__PURE__ */ new Set([...accVars, ...probe.rangeVars]);
19106
+ cols = [...accRel.columns, ...probe2.rel.columns];
19107
+ vars = /* @__PURE__ */ new Set([...accVars, ...probe2.rangeVars]);
18287
19108
  }
18288
19109
  acc = { rel: { columns: cols, rows }, rangeVars: vars };
18289
19110
  } else {
@@ -19043,13 +19864,13 @@ function expandTargets(core, columns) {
19043
19864
  return items;
19044
19865
  }
19045
19866
  function checkPredicateType(env, columns, rangeVars, e, kind, extras) {
19046
- const probe = new RowScope(
19867
+ const probe2 = new RowScope(
19047
19868
  columns,
19048
19869
  columns.map(() => null),
19049
19870
  env.outer,
19050
19871
  rangeVars
19051
19872
  );
19052
- checkBoolExprType(env.ctx, makeEvalScope(env, probe, extras), e, kind);
19873
+ checkBoolExprType(env.ctx, makeEvalScope(env, probe2, extras), e, kind);
19053
19874
  }
19054
19875
  function executeCore(env0, core, orderBy, opts) {
19055
19876
  const env = env0;
@@ -19139,8 +19960,8 @@ function executeCore(env0, core, orderBy, opts) {
19139
19960
  const groupValues = groupDefs.map((d, di) => {
19140
19961
  if (!activeSet[di]) {
19141
19962
  if (rows.length > 0) {
19142
- const probe = evalScalar(env, repScope, d.expr);
19143
- return tv(probe.t, null);
19963
+ const probe2 = evalScalar(env, repScope, d.expr);
19964
+ return tv(probe2.t, null);
19144
19965
  }
19145
19966
  return tv(UNKNOWN, null);
19146
19967
  }
@@ -20293,8 +21114,8 @@ function executeTransaction(env, stmt) {
20293
21114
  var defaultSettings = null;
20294
21115
  function getDefaultSettings() {
20295
21116
  if (!defaultSettings) {
20296
- const probe = new DatabaseState(new Prng(1), () => /* @__PURE__ */ new Date(0));
20297
- defaultSettings = new Map(probe.settings);
21117
+ const probe2 = new DatabaseState(new Prng(1), () => /* @__PURE__ */ new Date(0));
21118
+ defaultSettings = new Map(probe2.settings);
20298
21119
  }
20299
21120
  return defaultSettings;
20300
21121
  }
@@ -20561,7 +21382,7 @@ function parseCsvLine(line, delim, quote) {
20561
21382
  }
20562
21383
  function splitCsvRecords(data, quote) {
20563
21384
  const records = [];
20564
- let start = 0;
21385
+ let start2 = 0;
20565
21386
  let quoted = false;
20566
21387
  for (let i = 0; i < data.length; i++) {
20567
21388
  const char = data[i];
@@ -20571,13 +21392,13 @@ function splitCsvRecords(data, quote) {
20571
21392
  continue;
20572
21393
  }
20573
21394
  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;
21395
+ const end = i > start2 && data[i - 1] === "\r" ? i - 1 : i;
21396
+ records.push(data.slice(start2, end));
21397
+ start2 = i + 1;
20577
21398
  }
20578
21399
  }
20579
21400
  if (quoted) throw pgError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
20580
- if (start < data.length) records.push(data.slice(start));
21401
+ if (start2 < data.length) records.push(data.slice(start2));
20581
21402
  return records;
20582
21403
  }
20583
21404
  function executeCopyFromData(env, stmt, data) {
@@ -23735,6 +24556,16 @@ var Cluster = class {
23735
24556
  databaseNames() {
23736
24557
  return [...this.databases.keys()].sort();
23737
24558
  }
24559
+ snapshotAll() {
24560
+ return new Map([...this.databases].map(([name, db]) => [name, db.snapshot()]));
24561
+ }
24562
+ restoreAll(points) {
24563
+ if (this.sessions.size > 0) throw new Error("cannot restore a catalog with active sessions");
24564
+ if (!points.has(this.defaultDatabaseName)) throw new Error("snapshot lacks default database");
24565
+ for (const db of this.databases.values()) db.close();
24566
+ this.databases.clear();
24567
+ for (const [name, point] of points) this.installDatabase(name, point.open());
24568
+ }
23738
24569
  getDatabase(name) {
23739
24570
  return this.databases.get(name);
23740
24571
  }
@@ -23747,7 +24578,7 @@ var Cluster = class {
23747
24578
  if (this.databases.has(name)) {
23748
24579
  throw new PostgresError("duplicate_object", `database "${name}" already exists`, "42P04");
23749
24580
  }
23750
- const db = template === void 0 ? new Database() : this.requireTemplate(template).snapshot().open();
24581
+ const db = template === void 0 ? new Database({ now: this.db.now, seed: this.db.seed }) : this.requireTemplate(template).snapshot().open();
23751
24582
  this.installDatabase(name, db);
23752
24583
  }
23753
24584
  dropDatabase(name, requester) {
@@ -23811,8 +24642,8 @@ var Cluster = class {
23811
24642
  if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
23812
24643
  return Promise.reject(deadlock());
23813
24644
  }
23814
- return new Promise((resolve, reject) => {
23815
- const waiter = { session, resolve, reject };
24645
+ return new Promise((resolve2, reject) => {
24646
+ const waiter = { session, resolve: resolve2, reject };
23816
24647
  session.waitingForTurn = waiter;
23817
24648
  this.turnQueue.push(waiter);
23818
24649
  });
@@ -23843,8 +24674,8 @@ var Cluster = class {
23843
24674
  if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
23844
24675
  return Promise.reject(deadlock());
23845
24676
  }
23846
- return new Promise((resolve, reject) => {
23847
- const waiter = { session, resolve, reject };
24677
+ return new Promise((resolve2, reject) => {
24678
+ const waiter = { session, resolve: resolve2, reject };
23848
24679
  session.waitingForLock = key;
23849
24680
  const queue = this.lockQueues.get(key) ?? [];
23850
24681
  queue.push(waiter);
@@ -24103,9 +24934,9 @@ var backend = {
24103
24934
  no: () => new Uint8Array([78])
24104
24935
  };
24105
24936
  var ByteReader = class {
24106
- constructor(bytes, start = 0) {
24937
+ constructor(bytes, start2 = 0) {
24107
24938
  this.bytes = bytes;
24108
- this.pos = start;
24939
+ this.pos = start2;
24109
24940
  this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
24110
24941
  }
24111
24942
  bytes;
@@ -24265,11 +25096,11 @@ var ScramServer = class {
24265
25096
  // src/wire/split.ts
24266
25097
  var splitStatements = (script) => {
24267
25098
  const out = [];
24268
- let start = 0;
25099
+ let start2 = 0;
24269
25100
  let i = 0;
24270
25101
  const n = script.length;
24271
25102
  const push = (end) => {
24272
- const text = script.slice(start, end).trim();
25103
+ const text = script.slice(start2, end).trim();
24273
25104
  if (text.length > 0) out.push(text);
24274
25105
  };
24275
25106
  while (i < n) {
@@ -24325,7 +25156,7 @@ var splitStatements = (script) => {
24325
25156
  }
24326
25157
  if (c === ";") {
24327
25158
  push(i);
24328
- start = i + 1;
25159
+ start2 = i + 1;
24329
25160
  }
24330
25161
  i++;
24331
25162
  }
@@ -24786,8 +25617,8 @@ var Connection = class {
24786
25617
  "25P02"
24787
25618
  );
24788
25619
  }
24789
- const listen = LISTEN.exec(text);
24790
- if (listen) return this.listen(listen[2]);
25620
+ const listen2 = LISTEN.exec(text);
25621
+ if (listen2) return this.listen(listen2[2]);
24791
25622
  const unlisten = UNLISTEN.exec(text);
24792
25623
  if (unlisten) return this.unlisten(unlisten[1].replaceAll('"', ""));
24793
25624
  const notify = NOTIFY.exec(text);
@@ -24804,7 +25635,7 @@ var Connection = class {
24804
25635
  if (faults.delayStatementMs !== void 0) {
24805
25636
  const ms = faults.delayStatementMs;
24806
25637
  faults.delayStatementMs = void 0;
24807
- await new Promise((resolve) => setTimeout(resolve, ms));
25638
+ await new Promise((resolve2) => setTimeout(resolve2, ms));
24808
25639
  }
24809
25640
  if (faults.dropConnection && this.inTransaction) {
24810
25641
  faults.dropConnection = void 0;
@@ -24828,7 +25659,7 @@ var Connection = class {
24828
25659
  }
24829
25660
  }
24830
25661
  waitForLock(key) {
24831
- return new Promise((resolve, reject) => {
25662
+ return new Promise((resolve2, reject) => {
24832
25663
  this.lockWaiter = (error) => {
24833
25664
  this.lockWaiter = null;
24834
25665
  this.waitingForLock = null;
@@ -24837,7 +25668,7 @@ var Connection = class {
24837
25668
  this.cluster.waitLock(this, key).then(
24838
25669
  () => {
24839
25670
  this.lockWaiter = null;
24840
- resolve();
25671
+ resolve2();
24841
25672
  },
24842
25673
  (error) => {
24843
25674
  this.lockWaiter = null;
@@ -25029,10 +25860,10 @@ var Connection = class {
25029
25860
  this.cluster.dropDatabase(name, this);
25030
25861
  return command("DROP DATABASE");
25031
25862
  }
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]);
25863
+ const rename2 = RENAME_DATABASE.exec(text);
25864
+ if (rename2) {
25865
+ const from = identifier(rename2[1], rename2[2]);
25866
+ const to = identifier(rename2[3], rename2[4]);
25036
25867
  this.cluster.renameDatabase(from, to, this);
25037
25868
  return command("ALTER DATABASE");
25038
25869
  }
@@ -25455,7 +26286,7 @@ var normalize = (input) => {
25455
26286
  };
25456
26287
  };
25457
26288
  var isSnapshot = (value) => typeof value === "object" && value !== null && "open" in value && typeof value.open === "function";
25458
- var serve = (input = {}) => {
26289
+ var serve2 = (input = {}) => {
25459
26290
  const options2 = normalize(input);
25460
26291
  const database = isSnapshot(options2.database) ? options2.database.open() : options2.database ?? new Database();
25461
26292
  const initialName = options2.databaseName ?? "postgres";
@@ -25464,20 +26295,20 @@ var serve = (input = {}) => {
25464
26295
  const connections = /* @__PURE__ */ new Set();
25465
26296
  const host = options2.host ?? "127.0.0.1";
25466
26297
  const user = options2.user || "postgres";
25467
- const server2 = createServer((socket) => {
26298
+ const server2 = createServer2((socket) => {
25468
26299
  socket.setNoDelay(true);
25469
26300
  const connection = new Connection(socket, cluster, {
25470
26301
  ...options2.password !== void 0 ? { password: options2.password } : {},
25471
26302
  serverVersion: options2.serverVersion ?? "18.3",
25472
26303
  parameters: options2.parameters ?? {},
25473
26304
  faults,
25474
- ...options2.databaseName !== void 0 ? { database: options2.databaseName } : {},
26305
+ ...options2.databaseName !== void 0 && !options2.allowDatabaseSelection ? { database: options2.databaseName } : {},
25475
26306
  ...options2.onLog ? { onLog: options2.onLog } : {}
25476
26307
  });
25477
26308
  connections.add(connection);
25478
26309
  socket.on("close", () => connections.delete(connection));
25479
26310
  });
25480
- return new Promise((resolve, reject) => {
26311
+ return new Promise((resolve2, reject) => {
25481
26312
  server2.once("error", reject);
25482
26313
  server2.listen(options2.port ?? 0, host, () => {
25483
26314
  server2.removeListener("error", reject);
@@ -25485,11 +26316,13 @@ var serve = (input = {}) => {
25485
26316
  const port = typeof address === "object" && address !== null ? address.port : 0;
25486
26317
  const name = initialName;
25487
26318
  const auth = options2.password ? `${encodeURIComponent(user)}:${encodeURIComponent(options2.password)}@` : `${encodeURIComponent(user)}@`;
25488
- resolve({
26319
+ resolve2({
25489
26320
  port,
25490
26321
  host,
25491
26322
  connectionString: `postgres://${auth}${host}:${port}/${encodeURIComponent(name)}`,
25492
- database,
26323
+ get database() {
26324
+ return cluster.db;
26325
+ },
25493
26326
  databaseNames() {
25494
26327
  return cluster.databaseNames();
25495
26328
  },
@@ -25503,6 +26336,14 @@ var serve = (input = {}) => {
25503
26336
  fault(next) {
25504
26337
  Object.assign(faults, next);
25505
26338
  },
26339
+ clearFaults() {
26340
+ for (const key of Object.keys(faults)) delete faults[key];
26341
+ },
26342
+ faultState() {
26343
+ return structuredClone(faults);
26344
+ },
26345
+ snapshotAll: () => cluster.snapshotAll(),
26346
+ restoreAll: (points) => cluster.restoreAll(points),
25506
26347
  snapshot(databaseName = initialName) {
25507
26348
  return cluster.requireDatabase(databaseName).snapshot();
25508
26349
  },
@@ -25518,9 +26359,251 @@ var serve = (input = {}) => {
25518
26359
  });
25519
26360
  };
25520
26361
 
26362
+ // src/wire/probe.ts
26363
+ import { createHash as createHash2, createHmac as createHmac2, pbkdf2Sync as pbkdf2Sync2, randomBytes as randomBytes2 } from "node:crypto";
26364
+ import { createConnection } from "node:net";
26365
+ function probe(connectionString) {
26366
+ const url = new URL(connectionString);
26367
+ return new Promise((resolve2) => {
26368
+ const socket = createConnection({ host: url.hostname, port: Number(url.port) });
26369
+ let buffer = Buffer.alloc(0);
26370
+ let result = false;
26371
+ let querying = false;
26372
+ let row = false;
26373
+ const nonce = randomBytes2(18).toString("base64");
26374
+ const first = `n=,r=${nonce}`;
26375
+ const finish = (ok) => {
26376
+ result = ok;
26377
+ socket.destroy();
26378
+ };
26379
+ const timeout = setTimeout(() => finish(false), 2e3);
26380
+ const send = (type, body) => {
26381
+ const header = Buffer.alloc(5);
26382
+ header.write(type);
26383
+ header.writeInt32BE(body.length + 4, 1);
26384
+ socket.write(Buffer.concat([header, body]));
26385
+ };
26386
+ socket.once("close", () => {
26387
+ clearTimeout(timeout);
26388
+ resolve2(result);
26389
+ });
26390
+ socket.on("error", () => finish(false));
26391
+ socket.once("connect", () => {
26392
+ const parameters = Buffer.from(
26393
+ `user\0${decodeURIComponent(url.username)}\0database\0${decodeURIComponent(url.pathname.slice(1))}\0\0`
26394
+ );
26395
+ const header = Buffer.alloc(8);
26396
+ header.writeInt32BE(parameters.length + 8);
26397
+ header.writeInt32BE(196608, 4);
26398
+ socket.write(Buffer.concat([header, parameters]));
26399
+ });
26400
+ socket.on("data", (data) => {
26401
+ buffer = Buffer.concat([buffer, data]);
26402
+ while (buffer.length >= 5) {
26403
+ const length = buffer.readInt32BE(1);
26404
+ if (length < 4 || length > 1e6) {
26405
+ finish(false);
26406
+ return;
26407
+ }
26408
+ if (buffer.length < length + 1) return;
26409
+ const type = String.fromCharCode(buffer[0]);
26410
+ const body = buffer.subarray(5, length + 1);
26411
+ buffer = buffer.subarray(length + 1);
26412
+ if (type === "E") {
26413
+ finish(false);
26414
+ return;
26415
+ }
26416
+ if (type === "R") {
26417
+ const auth = body.readInt32BE();
26418
+ if (auth === 10) {
26419
+ const initial = Buffer.from(`n,,${first}`);
26420
+ const size = Buffer.alloc(4);
26421
+ size.writeInt32BE(initial.length);
26422
+ send("p", Buffer.concat([Buffer.from("SCRAM-SHA-256\0"), size, initial]));
26423
+ } else if (auth === 11) {
26424
+ const serverFirst = body.subarray(4).toString();
26425
+ const fields = Object.fromEntries(serverFirst.split(",").map((field) => [field[0], field.slice(2)]));
26426
+ if (!fields.r?.startsWith(nonce) || !fields.s || fields.i !== "4096") {
26427
+ finish(false);
26428
+ return;
26429
+ }
26430
+ const salted = pbkdf2Sync2(
26431
+ decodeURIComponent(url.password),
26432
+ Buffer.from(fields.s, "base64"),
26433
+ 4096,
26434
+ 32,
26435
+ "sha256"
26436
+ );
26437
+ const key = createHmac2("sha256", salted).update("Client Key").digest();
26438
+ const stored = createHash2("sha256").update(key).digest();
26439
+ const final = `c=biws,r=${fields.r}`;
26440
+ const signature = createHmac2("sha256", stored).update(`${first},${serverFirst},${final}`).digest();
26441
+ const proof = Buffer.from(key.map((value, index) => value ^ signature[index]));
26442
+ send("p", Buffer.from(`${final},p=${proof.toString("base64")}`));
26443
+ } else if (auth !== 0 && auth !== 12) {
26444
+ finish(false);
26445
+ return;
26446
+ }
26447
+ }
26448
+ if (type === "D") row = body.readInt16BE() === 1 && body.subarray(6).toString() === "1";
26449
+ if (type === "Z") {
26450
+ if (querying) {
26451
+ finish(row);
26452
+ return;
26453
+ }
26454
+ querying = true;
26455
+ send("Q", Buffer.from("SELECT 1\0"));
26456
+ }
26457
+ }
26458
+ });
26459
+ });
26460
+ }
26461
+
26462
+ // src/wire/fleet.ts
26463
+ async function startProtocol(entry) {
26464
+ const namespaces = /* @__PURE__ */ new Map();
26465
+ const selectors = { default: entry.database ?? "postgres", ...entry.namespaces };
26466
+ try {
26467
+ for (const [name, databaseName] of Object.entries(selectors)) {
26468
+ if (!/^[A-Za-z0-9_.-]{1,64}$/.test(name) || typeof databaseName !== "string")
26469
+ throw new Error("invalid Postgres namespace/database name");
26470
+ const clock = createClock();
26471
+ const database = new Database({
26472
+ now: () => new Date(clock.now()),
26473
+ ...entry.seed !== void 0 ? { seed: Number(entry.seed) } : {}
26474
+ });
26475
+ const server2 = await serve2({
26476
+ database,
26477
+ databaseName,
26478
+ allowDatabaseSelection: true,
26479
+ host: entry.host ?? "127.0.0.1",
26480
+ port: name === "default" ? entry.port ?? 0 : 0,
26481
+ ...entry.password !== void 0 ? { password: entry.password } : {},
26482
+ ...entry.user !== void 0 ? { user: entry.user } : {}
26483
+ });
26484
+ namespaces.set(name, { server: server2, clock });
26485
+ }
26486
+ } catch (error) {
26487
+ await Promise.allSettled([...namespaces.values()].map(({ server: server2 }) => server2.close()));
26488
+ throw error;
26489
+ }
26490
+ const get = (name) => {
26491
+ const found = namespaces.get(name);
26492
+ if (!found) throw new Error(`Postgres namespace ${name} is not configured`);
26493
+ return found;
26494
+ };
26495
+ const publicUrl2 = (name) => {
26496
+ const url = new URL(get(name).server.connectionString);
26497
+ url.password = "";
26498
+ return url.href;
26499
+ };
26500
+ const locked = /* @__PURE__ */ new Set();
26501
+ const snapshots = /* @__PURE__ */ new WeakMap();
26502
+ const origins = new Map([...namespaces].map(([name, { server: server2 }]) => [name, server2.snapshotAll()]));
26503
+ return {
26504
+ protocol: "postgres",
26505
+ url: publicUrl2("default"),
26506
+ connection: get("default").server.connectionString,
26507
+ connections: Object.fromEntries([...namespaces].map(([name, { server: server2 }]) => [name, server2.connectionString])),
26508
+ namespaces: {
26509
+ mechanism: "endpoint",
26510
+ endpoints: Object.fromEntries([...namespaces.keys()].map((name) => [name, publicUrl2(name)]))
26511
+ },
26512
+ async ready() {
26513
+ const results = await Promise.all(
26514
+ [...namespaces.values()].map(
26515
+ ({ server: server2 }) => server2.server.listening ? probe(server2.connectionString) : false
26516
+ )
26517
+ );
26518
+ return results.every(Boolean);
26519
+ },
26520
+ async close() {
26521
+ await Promise.all(
26522
+ [...namespaces.values()].map(async ({ server: server2 }) => {
26523
+ await server2.close();
26524
+ for (const name of server2.databaseNames()) server2.getDatabase(name).close();
26525
+ })
26526
+ );
26527
+ },
26528
+ lock(namespace) {
26529
+ const { server: server2 } = get(namespace);
26530
+ if (locked.has(namespace)) throw new Error("Postgres namespace already locked");
26531
+ locked.add(namespace);
26532
+ const reject = (socket) => socket.destroy();
26533
+ server2.server.prependListener("connection", reject);
26534
+ return () => {
26535
+ locked.delete(namespace);
26536
+ server2.server.off("connection", reject);
26537
+ };
26538
+ },
26539
+ diagnostics(namespace) {
26540
+ const { server: server2 } = get(namespace);
26541
+ return {
26542
+ activeRequests: server2.connections,
26543
+ activeConnections: server2.connections,
26544
+ blockedCommands: 0,
26545
+ pendingJobs: 0,
26546
+ pendingWebhooks: 0,
26547
+ unmatchedRequests: 0
26548
+ };
26549
+ },
26550
+ async checkpoint(namespace) {
26551
+ const { server: server2, clock } = get(namespace);
26552
+ const handle = {};
26553
+ snapshots.set(handle, {
26554
+ namespace,
26555
+ databases: server2.snapshotAll(),
26556
+ clock: clock.state(),
26557
+ faults: server2.faultState()
26558
+ });
26559
+ return handle;
26560
+ },
26561
+ async restore(namespace, handle) {
26562
+ const point = typeof handle === "object" && handle !== null ? snapshots.get(handle) : void 0;
26563
+ if (!point || point.namespace !== namespace) throw new Error("invalid Postgres checkpoint");
26564
+ const { server: server2, clock } = get(namespace);
26565
+ server2.restoreAll(point.databases);
26566
+ for (const name of server2.databaseNames()) server2.getDatabase(name).now = () => new Date(clock.now());
26567
+ server2.clearFaults();
26568
+ server2.fault(point.faults);
26569
+ clock.freeze();
26570
+ clock.set(point.clock.now);
26571
+ if (!point.clock.frozen) clock.unfreeze();
26572
+ },
26573
+ async reset(namespace) {
26574
+ const { server: server2, clock } = get(namespace);
26575
+ server2.restoreAll(origins.get(namespace));
26576
+ for (const name of server2.databaseNames()) server2.getDatabase(name).now = () => new Date(clock.now());
26577
+ server2.clearFaults();
26578
+ clock.reset();
26579
+ },
26580
+ clock: (namespace) => get(namespace).clock
26581
+ };
26582
+ }
26583
+
26584
+ // src/server.ts
26585
+ var serveTarget = {
26586
+ name: "postgres",
26587
+ protocol: "postgres",
26588
+ defaultPort: 0,
26589
+ start: startProtocol
26590
+ };
26591
+
25521
26592
  // src/wire/cli.ts
25522
26593
  var usage = `mockingbird-postgres serve [postgres://URI] [--port <n>] [--host <h>] [--password <p>] [--server-version <v>] [--log]`;
25523
26594
  var args = process.argv.slice(2);
26595
+ if (args[0] === "serve" && args.includes("--config")) {
26596
+ process.exit(
26597
+ await runCli(
26598
+ {
26599
+ bin: "mockingbird-postgres",
26600
+ description: "Postgres and fleet server",
26601
+ commands: { serve: serveCommand(serveTarget) }
26602
+ },
26603
+ args
26604
+ )
26605
+ );
26606
+ }
25524
26607
  if (args[0] !== "serve") {
25525
26608
  console.error(usage);
25526
26609
  process.exit(args[0] === "--help" || args[0] === "-h" ? 0 : 2);
@@ -25569,7 +26652,7 @@ if (log) {
25569
26652
  `[pid ${entry.pid}] ${entry.status} ${entry.durationMs.toFixed(1)}ms ${entry.sql.replace(/\s+/g, " ").slice(0, 200)}`
25570
26653
  );
25571
26654
  }
25572
- var server = await serve(options);
26655
+ var server = await serve2(options);
25573
26656
  console.log(`mockingbird-postgres listening on ${server.connectionString}`);
25574
26657
  var stop = async () => {
25575
26658
  await server.close();