@crvouga/mockingbird-service-postgres 1.5.1 → 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
  }
@@ -3465,7 +4286,13 @@ var DatabaseState = class _DatabaseState {
3465
4286
  this.settings.set("lc_numeric", "C");
3466
4287
  this.settings.set("lc_time", "C");
3467
4288
  this.settings.set("default_transaction_isolation", "read committed");
4289
+ this.settings.set("lock_timeout", "0");
4290
+ this.settings.set("idle_in_transaction_session_timeout", "0");
4291
+ this.settings.set("transaction_timeout", "0");
3468
4292
  this.settings.set("statement_timeout", "0");
4293
+ this.settings.set("check_function_bodies", "on");
4294
+ this.settings.set("xmloption", "content");
4295
+ this.settings.set("row_security", "on");
3469
4296
  this.settings.set("array_nulls", "on");
3470
4297
  this.settings.set("backslash_quote", "safe_encoding");
3471
4298
  }
@@ -4694,18 +5521,18 @@ function isoWeek(days) {
4694
5521
  return jan4 - (isoDw - 1);
4695
5522
  };
4696
5523
  let year = y;
4697
- let start = week1Monday(y);
4698
- if (days < start) {
5524
+ let start2 = week1Monday(y);
5525
+ if (days < start2) {
4699
5526
  year = y - 1;
4700
- start = week1Monday(year);
5527
+ start2 = week1Monday(year);
4701
5528
  } else {
4702
5529
  const nextStart = week1Monday(y + 1);
4703
5530
  if (days >= nextStart) {
4704
5531
  year = y + 1;
4705
- start = nextStart;
5532
+ start2 = nextStart;
4706
5533
  }
4707
5534
  }
4708
- return { week: Math.floor((days - start) / 7) + 1, year };
5535
+ return { week: Math.floor((days - start2) / 7) + 1, year };
4709
5536
  }
4710
5537
  function dateTruncDays(field, days) {
4711
5538
  const { y, m } = daysToCivil(days);
@@ -5275,9 +6102,9 @@ function getArrayFunctions() {
5275
6102
  }
5276
6103
  const elemT = arrayElemType(a.t);
5277
6104
  const target = castTo(ctx, args2[1], elemT, {});
5278
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
6105
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
5279
6106
  const lb = lbsOf(arr)[0] ?? 1;
5280
- 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++) {
5281
6108
  const item = arr.items[i];
5282
6109
  const eq = item === null ? target.v === null : target.v !== null && datumEquals(elemT, item, target.v, ctx);
5283
6110
  if (eq) return tv("int4", i + lb);
@@ -5757,12 +6584,12 @@ function parseByFormat(input, fmt) {
5757
6584
  };
5758
6585
  const readNum = (maxLen) => {
5759
6586
  skipInputSeparators();
5760
- const start = i;
5761
- while (i < input.length && /[0-9]/.test(input[i]) && i - start < maxLen) i++;
5762
- 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) {
5763
6590
  throw pgError("invalid_datetime", `invalid value for field in source string "${input}"`, "22007");
5764
6591
  }
5765
- return Number(input.slice(start, i));
6592
+ return Number(input.slice(start2, i));
5766
6593
  };
5767
6594
  const readMonthName = () => {
5768
6595
  skipInputSeparators();
@@ -7067,6 +7894,22 @@ function exactDegTan(x) {
7067
7894
  return Math.tan(x * Math.PI / 180);
7068
7895
  }
7069
7896
 
7897
+ // src/sql/deparse.ts
7898
+ function quoteIdent(name) {
7899
+ if (/^[a-z_][a-z0-9_$]*$/.test(name)) return name;
7900
+ return `"${name.replaceAll('"', '""')}"`;
7901
+ }
7902
+
7903
+ // src/runtime/database-context.ts
7904
+ var contexts = /* @__PURE__ */ new WeakMap();
7905
+ var fallback = { name: "postgres", names: () => ["postgres"] };
7906
+ function databaseCatalogContext(state) {
7907
+ return contexts.get(state) ?? fallback;
7908
+ }
7909
+ function setDatabaseCatalogContext(state, context) {
7910
+ contexts.set(state, context);
7911
+ }
7912
+
7070
7913
  // src/functions/misc-fns.ts
7071
7914
  var PG_VERSION_TEXT = "PostgreSQL 18.3 (postgres-mem) on TypeScript, in-memory engine";
7072
7915
  function findSequenceForCall(ctx, name) {
@@ -7150,8 +7993,8 @@ function sequenceNextval(ctx, seq) {
7150
7993
  function getMiscFunctions() {
7151
7994
  const m = /* @__PURE__ */ new Map();
7152
7995
  m.set("version", () => tv("text", PG_VERSION_TEXT));
7153
- m.set("current_database", () => tv("name", "postgres"));
7154
- m.set("current_catalog", () => tv("name", "postgres"));
7996
+ m.set("current_database", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
7997
+ m.set("current_catalog", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
7155
7998
  m.set("current_schema", (ctx) => {
7156
7999
  const path = ctx.state.effectiveSearchPath();
7157
8000
  const first = path.find((s) => ctx.state.schemas.has(s));
@@ -7273,7 +8116,7 @@ function getMiscFunctions() {
7273
8116
  if (!col.identity) return tv("text", null);
7274
8117
  return tv(
7275
8118
  "text",
7276
- col.identity.sequence.split(".").map((p) => p).join(".")
8119
+ col.identity.sequence.split(".").map(quoteIdent).join(".")
7277
8120
  );
7278
8121
  })
7279
8122
  );
@@ -7581,15 +8424,15 @@ var decoderUtf8 = new TextDecoder("utf-8", { fatal: false });
7581
8424
  function chars(s) {
7582
8425
  return [...s];
7583
8426
  }
7584
- function substrImpl(s, start, len) {
8427
+ function substrImpl(s, start2, len) {
7585
8428
  const cs = chars(s);
7586
8429
  if (len === void 0) {
7587
- const from2 = Math.max(start, 1);
8430
+ const from2 = Math.max(start2, 1);
7588
8431
  return cs.slice(from2 - 1).join("");
7589
8432
  }
7590
8433
  if (len < 0) throw pgError("substring_error", "negative substring length not allowed", "22011");
7591
- const end = start + len;
7592
- const from = Math.max(start, 1);
8434
+ const end = start2 + len;
8435
+ const from = Math.max(start2, 1);
7593
8436
  const to = Math.max(end, 1);
7594
8437
  if (to <= from) return "";
7595
8438
  return cs.slice(from - 1, to - 1).join("");
@@ -7653,14 +8496,14 @@ function getStringFunctions() {
7653
8496
  const a = args2[0];
7654
8497
  if (a.t === "bytea") {
7655
8498
  const bytes = a.v;
7656
- const start2 = argInt(ctx, args2[1]);
8499
+ const start3 = argInt(ctx, args2[1]);
7657
8500
  const len2 = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
7658
- return tv("bytea", byteaSubstr(bytes, start2, len2));
8501
+ return tv("bytea", byteaSubstr(bytes, start3, len2));
7659
8502
  }
7660
8503
  const s = argText(ctx, a);
7661
- const start = argInt(ctx, args2[1]);
8504
+ const start2 = argInt(ctx, args2[1]);
7662
8505
  const len = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
7663
- return tv("text", substrImpl(s, start, len));
8506
+ return tv("text", substrImpl(s, start2, len));
7664
8507
  })
7665
8508
  );
7666
8509
  m.set(
@@ -7956,11 +8799,11 @@ function getStringFunctions() {
7956
8799
  strict("int4", (ctx, args2) => {
7957
8800
  const s = argText(ctx, args2[0]);
7958
8801
  const pattern = argText(ctx, args2[1]);
7959
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8802
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
7960
8803
  const flags = args2.length > 3 ? argText(ctx, args2[3]) : "";
7961
- 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}`);
7962
8805
  const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
7963
- const sub = chars(s).slice(start - 1).join("");
8806
+ const sub = chars(s).slice(start2 - 1).join("");
7964
8807
  let count = 0;
7965
8808
  for (const _ of sub.matchAll(js)) count++;
7966
8809
  return tv("int4", count);
@@ -7981,11 +8824,11 @@ function getStringFunctions() {
7981
8824
  strict("text", (ctx, args2) => {
7982
8825
  const s = argText(ctx, args2[0]);
7983
8826
  const pattern = argText(ctx, args2[1]);
7984
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8827
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
7985
8828
  const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
7986
8829
  const flags = args2.length > 4 ? argText(ctx, args2[4]) : "";
7987
8830
  const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
7988
- const sub = chars(s).slice(start - 1).join("");
8831
+ const sub = chars(s).slice(start2 - 1).join("");
7989
8832
  let i = 0;
7990
8833
  for (const match of sub.matchAll(js)) {
7991
8834
  i++;
@@ -7999,21 +8842,21 @@ function getStringFunctions() {
7999
8842
  strict("int4", (ctx, args2) => {
8000
8843
  const s = argText(ctx, args2[0]);
8001
8844
  const pattern = argText(ctx, args2[1]);
8002
- const start = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8845
+ const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
8003
8846
  const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
8004
8847
  const endoption = args2.length > 4 ? argInt(ctx, args2[4]) : 0;
8005
8848
  const flags = args2.length > 5 ? argText(ctx, args2[5]) : "";
8006
- 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}`);
8007
8850
  if (endoption !== 0 && endoption !== 1) {
8008
8851
  throw pgError("invalid_parameter_value", `invalid value for parameter "endoption": ${endoption}`);
8009
8852
  }
8010
8853
  const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
8011
- const sub = chars(s).slice(start - 1).join("");
8854
+ const sub = chars(s).slice(start2 - 1).join("");
8012
8855
  let i = 0;
8013
8856
  for (const match of sub.matchAll(js)) {
8014
8857
  i++;
8015
8858
  if (i === nth) {
8016
- const base = start - 1 + chars(sub.slice(0, match.index)).length;
8859
+ const base = start2 - 1 + chars(sub.slice(0, match.index)).length;
8017
8860
  return tv("int4", endoption === 0 ? base + 1 : base + chars(match[0]).length + 1);
8018
8861
  }
8019
8862
  }
@@ -8127,10 +8970,10 @@ function getStringFunctions() {
8127
8970
  );
8128
8971
  return m;
8129
8972
  }
8130
- function byteaSubstr(bytes, start, len) {
8973
+ function byteaSubstr(bytes, start2, len) {
8131
8974
  if (len !== void 0 && len < 0) throw pgError("substring_error", "negative substring length not allowed", "22011");
8132
- const from = Math.max(start, 1) - 1;
8133
- 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);
8134
8977
  return bytes.slice(from, to);
8135
8978
  }
8136
8979
  function pad(s, len, fill, left) {
@@ -10692,7 +11535,7 @@ function evalOverlaps(ctx, scope, args2) {
10692
11535
  };
10693
11536
  const [s1r, e1r] = pair(args2[0]);
10694
11537
  const [s2r, e2r] = pair(args2[1]);
10695
- 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;
10696
11539
  const e1 = resolveEnd(s1r, e1r);
10697
11540
  const e2 = resolveEnd(s2r, e2r);
10698
11541
  const target = [s1r, e1, s2r, e2].map((x) => x.t).find((t) => t !== "unknown") ?? "timestamp";
@@ -11951,7 +12794,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
11951
12794
  }
11952
12795
  if (a.t === "timestamp" || a.t === "timestamptz" || b.t === "timestamp" || b.t === "timestamptz" || a.t === "date") {
11953
12796
  const t2 = a.t === "timestamptz" || b.t === "timestamptz" ? "timestamptz" : "timestamp";
11954
- const start2 = castTo(ctx, a, t2, { explicit: true }).v;
12797
+ const start3 = castTo(ctx, a, t2, { explicit: true }).v;
11955
12798
  const stop3 = castTo(ctx, b, t2, { explicit: true }).v;
11956
12799
  const stepArg = args2[2];
11957
12800
  if (stepArg === void 0 || !isInterval(stepArg.v)) {
@@ -11966,7 +12809,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
11966
12809
  if (step2.months !== 0) {
11967
12810
  let i = 0;
11968
12811
  for (; ; ) {
11969
- const { days, tod } = splitTs(start2);
12812
+ const { days, tod } = splitTs(start3);
11970
12813
  const c = daysToCivil(days);
11971
12814
  const totalMonths = c.y * 12 + (c.m - 1) + step2.months * i;
11972
12815
  const y = Math.floor(totalMonths / 12);
@@ -11983,19 +12826,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
11983
12826
  const stepMicros = intervalTotalMicros(step2);
11984
12827
  if (stepMicros === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
11985
12828
  if (stepMicros > 0n) {
11986
- for (let cur = start2; cur <= stop3; cur += stepMicros) rows2.push([cur]);
12829
+ for (let cur = start3; cur <= stop3; cur += stepMicros) rows2.push([cur]);
11987
12830
  } else {
11988
- for (let cur = start2; cur >= stop3; cur += stepMicros) rows2.push([cur]);
12831
+ for (let cur = start3; cur >= stop3; cur += stepMicros) rows2.push([cur]);
11989
12832
  }
11990
12833
  return { columns: [{ name: alias, type: t2 }], rows: rows2 };
11991
12834
  }
11992
12835
  if (a.t === "numeric" || b.t === "numeric" || args2[2]?.t === "numeric") {
11993
- const start2 = castTo(ctx, a, "numeric", { explicit: true }).v;
12836
+ const start3 = castTo(ctx, a, "numeric", { explicit: true }).v;
11994
12837
  const stop3 = castTo(ctx, b, "numeric", { explicit: true }).v;
11995
12838
  const step2 = args2[2] ? castTo(ctx, args2[2], "numeric", { explicit: true }).v : castTo(ctx, tv("int4", 1), "numeric", { explicit: true }).v;
11996
12839
  if (numericSign(step2) === 0) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
11997
12840
  const rows2 = [];
11998
- let cur = start2;
12841
+ let cur = start3;
11999
12842
  const ascending = numericSign(step2) > 0;
12000
12843
  while (ascending ? numericCmp(cur, stop3) <= 0 : numericCmp(cur, stop3) >= 0) {
12001
12844
  rows2.push([cur]);
@@ -12007,19 +12850,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
12007
12850
  }
12008
12851
  const useBig = a.t === "int8" || b.t === "int8" || args2[2]?.t === "int8";
12009
12852
  const t = useBig ? "int8" : "int4";
12010
- const start = castTo(ctx, a, "int8", {}).v;
12853
+ const start2 = castTo(ctx, a, "int8", {}).v;
12011
12854
  const stop2 = castTo(ctx, b, "int8", {}).v;
12012
12855
  const step = args2[2] ? castTo(ctx, args2[2], "int8", {}).v : 1n;
12013
12856
  if (step === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
12014
12857
  const rows = [];
12015
12858
  if (step > 0n) {
12016
- for (let cur = start; cur <= stop2; cur += step) {
12859
+ for (let cur = start2; cur <= stop2; cur += step) {
12017
12860
  rows.push([useBig ? cur : Number(cur)]);
12018
12861
  if (rows.length > 1e7)
12019
12862
  throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
12020
12863
  }
12021
12864
  } else {
12022
- for (let cur = start; cur >= stop2; cur += step) {
12865
+ for (let cur = start2; cur >= stop2; cur += step) {
12023
12866
  rows.push([useBig ? cur : Number(cur)]);
12024
12867
  if (rows.length > 1e7)
12025
12868
  throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
@@ -12449,10 +13292,10 @@ function tokenize(sql) {
12449
13292
  const m = /^\$([A-Za-z_\u0080-\uffff][A-Za-z0-9_\u0080-\uffff]*)?\$/.exec(sql.slice(i));
12450
13293
  if (m) {
12451
13294
  const open = m[0];
12452
- const start = i + open.length;
12453
- const end = sql.indexOf(open, start);
13295
+ const start2 = i + open.length;
13296
+ const end = sql.indexOf(open, start2);
12454
13297
  if (end === -1) syntaxError("unterminated dollar-quoted string");
12455
- push("string", sql.slice(start, end), i);
13298
+ push("string", sql.slice(start2, end), i);
12456
13299
  i = end + open.length;
12457
13300
  continue;
12458
13301
  }
@@ -12519,14 +13362,14 @@ function tokenize(sql) {
12519
13362
  continue;
12520
13363
  }
12521
13364
  if (/\d/.test(c) || c === "." && /\d/.test(sql[i + 1] ?? "")) {
12522
- const start = i;
13365
+ const start2 = i;
12523
13366
  const radix = /^0[xX][0-9a-fA-F_]+|^0[oO][0-7_]+|^0[bB][01_]+/.exec(sql.slice(i));
12524
13367
  if (radix) {
12525
13368
  const raw2 = radix[0];
12526
13369
  i += raw2.length;
12527
13370
  const clean = raw2.replaceAll("_", "");
12528
13371
  const v = BigInt(clean);
12529
- push("number", v.toString(), start);
13372
+ push("number", v.toString(), start2);
12530
13373
  continue;
12531
13374
  }
12532
13375
  let j = i;
@@ -12548,7 +13391,7 @@ function tokenize(sql) {
12548
13391
  if (j < n && IDENT_START.test(sql[j])) {
12549
13392
  syntaxError(`trailing junk after numeric literal at or near "${sql.slice(i, j + 1)}"`);
12550
13393
  }
12551
- push("number", raw, start);
13394
+ push("number", raw, start2);
12552
13395
  i = j;
12553
13396
  continue;
12554
13397
  }
@@ -12606,9 +13449,9 @@ function tokenize(sql) {
12606
13449
  push("eof", "", i);
12607
13450
  return tokens;
12608
13451
  }
12609
- function scanStandardString(sql, start) {
13452
+ function scanStandardString(sql, start2) {
12610
13453
  let out = "";
12611
- let i = start + 1;
13454
+ let i = start2 + 1;
12612
13455
  const n = sql.length;
12613
13456
  for (; ; ) {
12614
13457
  if (i >= n) syntaxError("unterminated quoted string");
@@ -12636,9 +13479,9 @@ function scanStandardString(sql, start) {
12636
13479
  i++;
12637
13480
  }
12638
13481
  }
12639
- function scanEscapeString(sql, start) {
13482
+ function scanEscapeString(sql, start2) {
12640
13483
  let out = "";
12641
- let i = start + 1;
13484
+ let i = start2 + 1;
12642
13485
  const n = sql.length;
12643
13486
  for (; ; ) {
12644
13487
  if (i >= n) syntaxError("unterminated quoted string");
@@ -14523,14 +15366,19 @@ var Parser = class {
14523
15366
  parseIdentityOptions() {
14524
15367
  const options2 = {};
14525
15368
  if (this.eatPunct("(")) {
14526
- Object.assign(options2, this.parseSequenceOptions());
15369
+ Object.assign(options2, this.parseSequenceOptions(true));
14527
15370
  this.expectPunct(")");
14528
15371
  }
14529
15372
  return options2;
14530
15373
  }
14531
- parseSequenceOptions() {
15374
+ parseSequenceOptions(identity = false) {
14532
15375
  const options2 = {};
14533
15376
  for (; ; ) {
15377
+ if (identity && this.eatKw("sequence")) {
15378
+ this.expectKw("name");
15379
+ options2.sequenceName = this.qualifiedName();
15380
+ continue;
15381
+ }
14534
15382
  if (this.eatKw("increment")) {
14535
15383
  this.eatKw("by");
14536
15384
  options2.increment = this.parseSignedBigInt();
@@ -16373,14 +17221,14 @@ var Parser = class {
16373
17221
  let frame = null;
16374
17222
  if (this.atKw("range") || this.atKw("rows") || this.atKw("groups")) {
16375
17223
  const mode = this.next().value;
16376
- let start;
17224
+ let start2;
16377
17225
  let end = null;
16378
17226
  if (this.eatKw("between")) {
16379
- start = this.parseFrameBound();
17227
+ start2 = this.parseFrameBound();
16380
17228
  this.expectKw("and");
16381
17229
  end = this.parseFrameBound();
16382
17230
  } else {
16383
- start = this.parseFrameBound();
17231
+ start2 = this.parseFrameBound();
16384
17232
  }
16385
17233
  let exclusion = null;
16386
17234
  if (this.eatKw("exclude")) {
@@ -16397,7 +17245,7 @@ var Parser = class {
16397
17245
  exclusion = "no_others";
16398
17246
  }
16399
17247
  }
16400
- frame = { mode, start, end, exclusion };
17248
+ frame = { mode, start: start2, end, exclusion };
16401
17249
  }
16402
17250
  return { name, partitionBy, orderBy, frame };
16403
17251
  }
@@ -16643,7 +17491,7 @@ var PlParser = class {
16643
17491
  return { kind: "assign", target, expr: this.parseExprUntilSemi() };
16644
17492
  }
16645
17493
  parseSqlTextUntilSemi() {
16646
- const start = this.peek().pos;
17494
+ const start2 = this.peek().pos;
16647
17495
  let depth = 0;
16648
17496
  for (; ; ) {
16649
17497
  const t = this.peek();
@@ -16651,7 +17499,7 @@ var PlParser = class {
16651
17499
  if (t.type === "punct" && t.value === "(") depth++;
16652
17500
  if (t.type === "punct" && t.value === ")") depth--;
16653
17501
  if (t.type === "punct" && t.value === ";" && depth === 0) {
16654
- const text = this.src.slice(start, t.pos).trim();
17502
+ const text = this.src.slice(start2, t.pos).trim();
16655
17503
  this.pos++;
16656
17504
  return text;
16657
17505
  }
@@ -16659,7 +17507,7 @@ var PlParser = class {
16659
17507
  }
16660
17508
  }
16661
17509
  parseExprUntilSemi() {
16662
- const start = this.peek().pos;
17510
+ const start2 = this.peek().pos;
16663
17511
  let depth = 0;
16664
17512
  for (; ; ) {
16665
17513
  const t = this.peek();
@@ -16667,7 +17515,7 @@ var PlParser = class {
16667
17515
  if (t.type === "punct" && t.value === "(") depth++;
16668
17516
  if (t.type === "punct" && t.value === ")") depth--;
16669
17517
  if (t.type === "punct" && t.value === ";" && depth === 0) {
16670
- const text = this.src.slice(start, t.pos);
17518
+ const text = this.src.slice(start2, t.pos);
16671
17519
  this.pos++;
16672
17520
  return parseSqlExpr(text);
16673
17521
  }
@@ -16675,7 +17523,7 @@ var PlParser = class {
16675
17523
  }
16676
17524
  }
16677
17525
  parseExprUntilKw(kw) {
16678
- const start = this.peek().pos;
17526
+ const start2 = this.peek().pos;
16679
17527
  let depth = 0;
16680
17528
  let caseDepth = 0;
16681
17529
  for (; ; ) {
@@ -16686,7 +17534,7 @@ var PlParser = class {
16686
17534
  if (t.type === "ident" && t.value === "case" && depth === 0) caseDepth++;
16687
17535
  if (t.type === "ident" && t.value === "end" && depth === 0 && caseDepth > 0) caseDepth--;
16688
17536
  if (t.type === "ident" && t.value === kw && depth === 0 && caseDepth === 0) {
16689
- const text = this.src.slice(start, t.pos);
17537
+ const text = this.src.slice(start2, t.pos);
16690
17538
  this.pos++;
16691
17539
  return parseSqlExpr(text);
16692
17540
  }
@@ -16694,7 +17542,7 @@ var PlParser = class {
16694
17542
  }
16695
17543
  }
16696
17544
  parseQueryUntilKw(kw) {
16697
- const start = this.peek().pos;
17545
+ const start2 = this.peek().pos;
16698
17546
  let depth = 0;
16699
17547
  for (; ; ) {
16700
17548
  const t = this.peek();
@@ -16702,7 +17550,7 @@ var PlParser = class {
16702
17550
  if (t.type === "punct" && t.value === "(") depth++;
16703
17551
  if (t.type === "punct" && t.value === ")") depth--;
16704
17552
  if (t.type === "ident" && t.value === kw && depth === 0) {
16705
- const text = this.src.slice(start, t.pos).trim();
17553
+ const text = this.src.slice(start2, t.pos).trim();
16706
17554
  this.pos++;
16707
17555
  const stmts = parse(text);
16708
17556
  const q = stmts[0];
@@ -18245,7 +19093,7 @@ function buildFrom(env, items, where = null) {
18245
19093
  }
18246
19094
  }
18247
19095
  if (cols === null) {
18248
- const probe = materializeItem(
19096
+ const probe2 = materializeItem(
18249
19097
  env,
18250
19098
  item,
18251
19099
  new RowScope(
@@ -18255,8 +19103,8 @@ function buildFrom(env, items, where = null) {
18255
19103
  accVars
18256
19104
  )
18257
19105
  );
18258
- cols = [...accRel.columns, ...probe.rel.columns];
18259
- vars = /* @__PURE__ */ new Set([...accVars, ...probe.rangeVars]);
19106
+ cols = [...accRel.columns, ...probe2.rel.columns];
19107
+ vars = /* @__PURE__ */ new Set([...accVars, ...probe2.rangeVars]);
18260
19108
  }
18261
19109
  acc = { rel: { columns: cols, rows }, rangeVars: vars };
18262
19110
  } else {
@@ -19016,13 +19864,13 @@ function expandTargets(core, columns) {
19016
19864
  return items;
19017
19865
  }
19018
19866
  function checkPredicateType(env, columns, rangeVars, e, kind, extras) {
19019
- const probe = new RowScope(
19867
+ const probe2 = new RowScope(
19020
19868
  columns,
19021
19869
  columns.map(() => null),
19022
19870
  env.outer,
19023
19871
  rangeVars
19024
19872
  );
19025
- checkBoolExprType(env.ctx, makeEvalScope(env, probe, extras), e, kind);
19873
+ checkBoolExprType(env.ctx, makeEvalScope(env, probe2, extras), e, kind);
19026
19874
  }
19027
19875
  function executeCore(env0, core, orderBy, opts) {
19028
19876
  const env = env0;
@@ -19112,8 +19960,8 @@ function executeCore(env0, core, orderBy, opts) {
19112
19960
  const groupValues = groupDefs.map((d, di) => {
19113
19961
  if (!activeSet[di]) {
19114
19962
  if (rows.length > 0) {
19115
- const probe = evalScalar(env, repScope, d.expr);
19116
- return tv(probe.t, null);
19963
+ const probe2 = evalScalar(env, repScope, d.expr);
19964
+ return tv(probe2.t, null);
19117
19965
  }
19118
19966
  return tv(UNKNOWN, null);
19119
19967
  }
@@ -20266,8 +21114,8 @@ function executeTransaction(env, stmt) {
20266
21114
  var defaultSettings = null;
20267
21115
  function getDefaultSettings() {
20268
21116
  if (!defaultSettings) {
20269
- const probe = new DatabaseState(new Prng(1), () => /* @__PURE__ */ new Date(0));
20270
- defaultSettings = new Map(probe.settings);
21117
+ const probe2 = new DatabaseState(new Prng(1), () => /* @__PURE__ */ new Date(0));
21118
+ defaultSettings = new Map(probe2.settings);
20271
21119
  }
20272
21120
  return defaultSettings;
20273
21121
  }
@@ -20532,6 +21380,27 @@ function parseCsvLine(line, delim, quote) {
20532
21380
  }
20533
21381
  return fields;
20534
21382
  }
21383
+ function splitCsvRecords(data, quote) {
21384
+ const records = [];
21385
+ let start2 = 0;
21386
+ let quoted = false;
21387
+ for (let i = 0; i < data.length; i++) {
21388
+ const char = data[i];
21389
+ if (char === quote) {
21390
+ if (quoted && data[i + 1] === quote) i++;
21391
+ else quoted = !quoted;
21392
+ continue;
21393
+ }
21394
+ if (char === "\n" && !quoted) {
21395
+ const end = i > start2 && data[i - 1] === "\r" ? i - 1 : i;
21396
+ records.push(data.slice(start2, end));
21397
+ start2 = i + 1;
21398
+ }
21399
+ }
21400
+ if (quoted) throw pgError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
21401
+ if (start2 < data.length) records.push(data.slice(start2));
21402
+ return records;
21403
+ }
20535
21404
  function executeCopyFromData(env, stmt, data) {
20536
21405
  const format = String(stmt.options.format ?? "text").toLowerCase();
20537
21406
  if (format !== "text" && format !== "csv") throw unsupported(`COPY format ${format}`);
@@ -20542,7 +21411,7 @@ function executeCopyFromData(env, stmt, data) {
20542
21411
  const nullStr = String(stmt.options.null ?? (format === "csv" ? "" : "\\N"));
20543
21412
  const quote = String(stmt.options.quote ?? '"');
20544
21413
  const header = stmt.options.header === true || String(stmt.options.header ?? "").toLowerCase() === "true";
20545
- let lines = data.split(/\r?\n/);
21414
+ let lines = format === "csv" ? splitCsvRecords(data, quote) : data.split(/\r?\n/);
20546
21415
  const endMarker = lines.indexOf("\\.");
20547
21416
  if (endMarker !== -1) lines = lines.slice(0, endMarker);
20548
21417
  if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
@@ -21276,7 +22145,8 @@ function pgSettings(ctx) {
21276
22145
  "pg_settings"
21277
22146
  );
21278
22147
  }
21279
- function pgDatabase() {
22148
+ function pgDatabase(ctx) {
22149
+ const names = databaseCatalogContext(ctx.state).names();
21280
22150
  return rel(
21281
22151
  [
21282
22152
  ["oid", "oid"],
@@ -21285,7 +22155,7 @@ function pgDatabase() {
21285
22155
  ["datistemplate", "bool"],
21286
22156
  ["datallowconn", "bool"]
21287
22157
  ],
21288
- [[5, "postgres", 6, false, true]],
22158
+ names.map((name, index) => [5 + index, name, 6, false, true]),
21289
22159
  "pg_database"
21290
22160
  );
21291
22161
  }
@@ -21320,13 +22190,14 @@ function pgTrigger(ctx) {
21320
22190
  "pg_trigger"
21321
22191
  );
21322
22192
  }
21323
- var CATALOG_NAME = "postgres";
22193
+ var catalogName = (state) => databaseCatalogContext(state).name;
21324
22194
  function infoSchemata(state) {
22195
+ const catalog = catalogName(state);
21325
22196
  const rows = [
21326
- [CATALOG_NAME, "pg_catalog", OWNER],
21327
- [CATALOG_NAME, "information_schema", OWNER]
22197
+ [catalog, "pg_catalog", OWNER],
22198
+ [catalog, "information_schema", OWNER]
21328
22199
  ];
21329
- for (const s of state.schemas.values()) rows.push([CATALOG_NAME, s.name, OWNER]);
22200
+ for (const s of state.schemas.values()) rows.push([catalog, s.name, OWNER]);
21330
22201
  return rel(
21331
22202
  [
21332
22203
  ["catalog_name", "name"],
@@ -21338,13 +22209,14 @@ function infoSchemata(state) {
21338
22209
  );
21339
22210
  }
21340
22211
  function infoTables(ctx) {
22212
+ const catalog = catalogName(ctx.state);
21341
22213
  const rows = [];
21342
22214
  for (const schema of ctx.state.schemas.values()) {
21343
22215
  for (const t of schema.tables.values()) {
21344
- rows.push([CATALOG_NAME, t.schema, t.name, t.temp ? "LOCAL TEMPORARY" : "BASE TABLE", "YES"]);
22216
+ rows.push([catalog, t.schema, t.name, t.temp ? "LOCAL TEMPORARY" : "BASE TABLE", "YES"]);
21345
22217
  }
21346
22218
  for (const v of schema.views.values()) {
21347
- if (!v.materialized) rows.push([CATALOG_NAME, v.schema, v.name, "VIEW", "NO"]);
22219
+ if (!v.materialized) rows.push([catalog, v.schema, v.name, "VIEW", "NO"]);
21348
22220
  }
21349
22221
  }
21350
22222
  return rel(
@@ -21360,6 +22232,7 @@ function infoTables(ctx) {
21360
22232
  );
21361
22233
  }
21362
22234
  function infoColumns(ctx) {
22235
+ const catalog = catalogName(ctx.state);
21363
22236
  const rows = [];
21364
22237
  for (const t of allTables(ctx.state)) {
21365
22238
  t.columns.forEach((c, i) => {
@@ -21370,7 +22243,7 @@ function infoColumns(ctx) {
21370
22243
  const dataType = isArrayType(id) ? "ARRAY" : isEnumType(id) ? "USER-DEFINED" : typeDisplayName(id);
21371
22244
  const udt = isArrayType(id) ? `_${arrayUdt(id)}` : isEnumType(id) ? id.slice(5).split(".").pop() : id;
21372
22245
  rows.push([
21373
- CATALOG_NAME,
22246
+ catalog,
21374
22247
  t.schema,
21375
22248
  t.name,
21376
22249
  c.name,
@@ -21415,10 +22288,11 @@ function arrayUdt(t) {
21415
22288
  return isEnumType(elem) ? elem.slice(5).split(".").pop() : elem;
21416
22289
  }
21417
22290
  function infoViews(ctx) {
22291
+ const catalog = catalogName(ctx.state);
21418
22292
  const rows = [];
21419
22293
  for (const schema of ctx.state.schemas.values()) {
21420
22294
  for (const v of schema.views.values()) {
21421
- if (!v.materialized) rows.push([CATALOG_NAME, v.schema, v.name, null]);
22295
+ if (!v.materialized) rows.push([catalog, v.schema, v.name, null]);
21422
22296
  }
21423
22297
  }
21424
22298
  return rel(
@@ -21433,10 +22307,11 @@ function infoViews(ctx) {
21433
22307
  );
21434
22308
  }
21435
22309
  function infoSequences(ctx) {
22310
+ const catalog = catalogName(ctx.state);
21436
22311
  const rows = [];
21437
22312
  for (const s of allSequences(ctx.state)) {
21438
22313
  rows.push([
21439
- CATALOG_NAME,
22314
+ catalog,
21440
22315
  s.schema,
21441
22316
  s.name,
21442
22317
  typeDisplayName(s.dataType),
@@ -21464,11 +22339,12 @@ function infoSequences(ctx) {
21464
22339
  );
21465
22340
  }
21466
22341
  function infoTableConstraints(ctx) {
22342
+ const catalog = catalogName(ctx.state);
21467
22343
  const rows = [];
21468
22344
  for (const t of allTables(ctx.state)) {
21469
22345
  for (const c of t.constraints) {
21470
22346
  const type = c.kind === "primary_key" ? "PRIMARY KEY" : c.kind === "unique" ? "UNIQUE" : c.kind === "check" ? "CHECK" : "FOREIGN KEY";
21471
- rows.push([CATALOG_NAME, t.schema, c.name, CATALOG_NAME, t.schema, t.name, type]);
22347
+ rows.push([catalog, t.schema, c.name, catalog, t.schema, t.name, type]);
21472
22348
  }
21473
22349
  }
21474
22350
  return rel(
@@ -21486,12 +22362,13 @@ function infoTableConstraints(ctx) {
21486
22362
  );
21487
22363
  }
21488
22364
  function infoKeyColumnUsage(ctx) {
22365
+ const catalog = catalogName(ctx.state);
21489
22366
  const rows = [];
21490
22367
  for (const t of allTables(ctx.state)) {
21491
22368
  for (const c of t.constraints) {
21492
22369
  if (c.kind !== "primary_key" && c.kind !== "unique" && c.kind !== "foreign_key") continue;
21493
22370
  c.columns.forEach((col, i) => {
21494
- rows.push([CATALOG_NAME, t.schema, c.name, CATALOG_NAME, t.schema, t.name, col, i + 1]);
22371
+ rows.push([catalog, t.schema, c.name, catalog, t.schema, t.name, col, i + 1]);
21495
22372
  });
21496
22373
  }
21497
22374
  }
@@ -21511,12 +22388,13 @@ function infoKeyColumnUsage(ctx) {
21511
22388
  );
21512
22389
  }
21513
22390
  function infoRoutines(ctx) {
22391
+ const catalog = catalogName(ctx.state);
21514
22392
  const rows = [];
21515
22393
  for (const schema of ctx.state.schemas.values()) {
21516
22394
  for (const fns of schema.functions.values()) {
21517
22395
  for (const f of fns) {
21518
22396
  rows.push([
21519
- CATALOG_NAME,
22397
+ catalog,
21520
22398
  f.schema,
21521
22399
  f.name,
21522
22400
  "FUNCTION",
@@ -21573,7 +22451,7 @@ function buildCatalogRelation(ctx, schema, name) {
21573
22451
  case "pg_settings":
21574
22452
  return pgSettings(ctx);
21575
22453
  case "pg_database":
21576
- return pgDatabase();
22454
+ return pgDatabase(ctx);
21577
22455
  case "pg_roles":
21578
22456
  case "pg_user":
21579
22457
  return pgRoles();
@@ -21754,6 +22632,11 @@ var MAX_IDENT = 63;
21754
22632
  function truncIdent(s) {
21755
22633
  return s.length > MAX_IDENT ? s.slice(0, MAX_IDENT) : s;
21756
22634
  }
22635
+ function identitySequenceTarget(env, tableSchema, defaultName, options2) {
22636
+ const parts = options2.sequenceName;
22637
+ if (!parts || parts.length === 0) return { schema: tableSchema, name: defaultName };
22638
+ return targetSchema(env, parts);
22639
+ }
21757
22640
  var SEQ_LIMITS = {
21758
22641
  int2: { min: -32768n, max: 32767n },
21759
22642
  int4: { min: -2147483648n, max: 2147483647n },
@@ -21930,8 +22813,8 @@ function buildColumn(env, schema, tableName, def, out, taken) {
21930
22813
  break;
21931
22814
  }
21932
22815
  case "generated_identity": {
21933
- const seqName = truncIdent(`${tableName}_${def.name}_seq`);
21934
- const seq = buildSequence(env, schema, seqName, c.options, false);
22816
+ const target = identitySequenceTarget(env, schema, truncIdent(`${tableName}_${def.name}_seq`), c.options);
22817
+ const seq = buildSequence(env, target.schema, target.name, c.options, false);
21935
22818
  if (!c.options.as) {
21936
22819
  seq.dataType = col.type.id === "int2" || col.type.id === "int4" || col.type.id === "int8" ? col.type.id : "int8";
21937
22820
  const limits = SEQ_LIMITS[seq.dataType];
@@ -21940,7 +22823,7 @@ function buildColumn(env, schema, tableName, def, out, taken) {
21940
22823
  }
21941
22824
  seq.ownedBy = { table: tableName, column: def.name };
21942
22825
  out.sequences.push(seq);
21943
- col.identity = { always: c.always, sequence: `${schema.name}.${seqName}` };
22826
+ col.identity = { always: c.always, sequence: `${target.schema.name}.${target.name}` };
21944
22827
  col.notNull = true;
21945
22828
  break;
21946
22829
  }
@@ -22076,10 +22959,11 @@ function executeCreateTable(env, stmt) {
22076
22959
  throw pgError("invalid_table_definition", `multiple primary keys for table "${name}" are not allowed`, "42P16");
22077
22960
  }
22078
22961
  for (const seq of built.sequences) {
22079
- if (schema.hasRelation(seq.name)) {
22962
+ const seqSchema = state.getSchema(seq.schema);
22963
+ if (seqSchema.hasRelation(seq.name)) {
22080
22964
  throw pgError("duplicate_table", `relation "${seq.name}" already exists`, "42P07");
22081
22965
  }
22082
- schema.sequences.set(seq.name, seq);
22966
+ seqSchema.sequences.set(seq.name, seq);
22083
22967
  }
22084
22968
  const table = new TableData(schema.name, name, built.columns, state.nextOid(), stmt.temp);
22085
22969
  table.constraints = built.constraints;
@@ -22431,7 +23315,13 @@ function executeAlterTable(env, stmt) {
22431
23315
  const built = { columns: [], constraints: [], sequences: [] };
22432
23316
  const taken = constraintNames(table);
22433
23317
  const col = buildColumn(env, schema, table.name, action.column, built, taken);
22434
- for (const seq of built.sequences) schema.sequences.set(seq.name, seq);
23318
+ for (const seq of built.sequences) {
23319
+ const seqSchema = state.getSchema(seq.schema);
23320
+ if (seqSchema.hasRelation(seq.name)) {
23321
+ throw pgError("duplicate_table", `relation "${seq.name}" already exists`, "42P07");
23322
+ }
23323
+ seqSchema.sequences.set(seq.name, seq);
23324
+ }
22435
23325
  table.columns.push(col);
22436
23326
  table.constraints.push(...built.constraints);
22437
23327
  for (let i = 0; i < rows.length; i++) {
@@ -22751,12 +23641,32 @@ function executeAlterTable(env, stmt) {
22751
23641
  "42P16"
22752
23642
  );
22753
23643
  }
22754
- const seqName = truncIdent(`${table.name}_${action.column}_seq`);
22755
- const seq = buildSequence(env, schema, seqName, action.options, false);
23644
+ if (!col.notNull) {
23645
+ throw pgError(
23646
+ "object_not_in_prerequisite_state",
23647
+ `column "${action.column}" of relation "${table.name}" must be declared NOT NULL before identity can be added`,
23648
+ "55000"
23649
+ );
23650
+ }
23651
+ const target = identitySequenceTarget(
23652
+ env,
23653
+ schema,
23654
+ truncIdent(`${table.name}_${action.column}_seq`),
23655
+ action.options
23656
+ );
23657
+ if (target.schema.hasRelation(target.name)) {
23658
+ throw pgError("duplicate_table", `relation "${target.name}" already exists`, "42P07");
23659
+ }
23660
+ const seq = buildSequence(env, target.schema, target.name, action.options, false);
23661
+ if (!action.options.as) {
23662
+ seq.dataType = col.type.id === "int2" || col.type.id === "int4" || col.type.id === "int8" ? col.type.id : "int8";
23663
+ const limits = SEQ_LIMITS[seq.dataType];
23664
+ if (action.options.maxValue === void 0) seq.maxValue = seq.increment > 0n ? limits.max : -1n;
23665
+ if (action.options.minValue === void 0) seq.minValue = seq.increment > 0n ? 1n : limits.min;
23666
+ }
22756
23667
  seq.ownedBy = { table: table.name, column: action.column };
22757
- schema.sequences.set(seqName, seq);
22758
- col.identity = { always: true, sequence: `${schema.name}.${seqName}` };
22759
- col.notNull = true;
23668
+ target.schema.sequences.set(target.name, seq);
23669
+ col.identity = { always: action.always, sequence: `${target.schema.name}.${target.name}` };
22760
23670
  break;
22761
23671
  }
22762
23672
  case "drop_identity": {
@@ -23625,11 +24535,11 @@ if (typeof disposeKey === "symbol") {
23625
24535
 
23626
24536
  // src/wire/cluster.ts
23627
24537
  var Cluster = class {
23628
- constructor(db) {
23629
- this.db = db;
23630
- registerSessionFunctions(this);
24538
+ constructor(db, defaultDatabaseName = "postgres") {
24539
+ this.defaultDatabaseName = defaultDatabaseName;
24540
+ this.installDatabase(defaultDatabaseName, db);
23631
24541
  }
23632
- db;
24542
+ defaultDatabaseName;
23633
24543
  turnHolder = null;
23634
24544
  turnQueue = [];
23635
24545
  locks = /* @__PURE__ */ new Map();
@@ -23638,6 +24548,87 @@ var Cluster = class {
23638
24548
  sessions = /* @__PURE__ */ new Map();
23639
24549
  current = null;
23640
24550
  nextPid = 1e3;
24551
+ databases = /* @__PURE__ */ new Map();
24552
+ /** The initial database, retained for the server's backwards-compatible public handle. */
24553
+ get db() {
24554
+ return this.requireDatabase(this.defaultDatabaseName);
24555
+ }
24556
+ databaseNames() {
24557
+ return [...this.databases.keys()].sort();
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
+ }
24569
+ getDatabase(name) {
24570
+ return this.databases.get(name);
24571
+ }
24572
+ requireDatabase(name) {
24573
+ const db = this.databases.get(name);
24574
+ if (!db) throw new PostgresError("undefined_object", `database "${name}" does not exist`, "3D000");
24575
+ return db;
24576
+ }
24577
+ createDatabase(name, template) {
24578
+ if (this.databases.has(name)) {
24579
+ throw new PostgresError("duplicate_object", `database "${name}" already exists`, "42P04");
24580
+ }
24581
+ const db = template === void 0 ? new Database({ now: this.db.now, seed: this.db.seed }) : this.requireTemplate(template).snapshot().open();
24582
+ this.installDatabase(name, db);
24583
+ }
24584
+ dropDatabase(name, requester) {
24585
+ if (name === requester.databaseName || this.activeConnections(name) > 0) {
24586
+ throw new PostgresError(
24587
+ "object_not_in_prerequisite_state",
24588
+ `database "${name}" is being accessed by other users`,
24589
+ "55006"
24590
+ );
24591
+ }
24592
+ if (!this.databases.delete(name)) {
24593
+ throw new PostgresError("undefined_object", `database "${name}" does not exist`, "3D000");
24594
+ }
24595
+ }
24596
+ renameDatabase(from, to, requester) {
24597
+ if (this.databases.has(to)) {
24598
+ throw new PostgresError("duplicate_object", `database "${to}" already exists`, "42P04");
24599
+ }
24600
+ const db = this.requireDatabase(from);
24601
+ if (from === requester.databaseName || this.activeConnections(from) > 0) {
24602
+ throw new PostgresError(
24603
+ "object_not_in_prerequisite_state",
24604
+ `database "${from}" is being accessed by other users`,
24605
+ "55006"
24606
+ );
24607
+ }
24608
+ this.databases.delete(from);
24609
+ this.installDatabase(to, db);
24610
+ }
24611
+ requireTemplate(name) {
24612
+ if (this.activeConnections(name) > 0) {
24613
+ throw new PostgresError(
24614
+ "object_not_in_prerequisite_state",
24615
+ `source database "${name}" is being accessed by other users`,
24616
+ "55006"
24617
+ );
24618
+ }
24619
+ return this.requireDatabase(name);
24620
+ }
24621
+ activeConnections(name) {
24622
+ let count = 0;
24623
+ for (const session of this.sessions.values()) if (session.databaseName === name) count++;
24624
+ return count;
24625
+ }
24626
+ installDatabase(name, db) {
24627
+ const context = { name, names: () => this.databaseNames() };
24628
+ setDatabaseCatalogContext(db.state, context);
24629
+ this.databases.set(name, db);
24630
+ registerSessionFunctions(this, db);
24631
+ }
23641
24632
  newPid() {
23642
24633
  return this.nextPid++;
23643
24634
  }
@@ -23651,8 +24642,8 @@ var Cluster = class {
23651
24642
  if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
23652
24643
  return Promise.reject(deadlock());
23653
24644
  }
23654
- return new Promise((resolve, reject) => {
23655
- const waiter = { session, resolve, reject };
24645
+ return new Promise((resolve2, reject) => {
24646
+ const waiter = { session, resolve: resolve2, reject };
23656
24647
  session.waitingForTurn = waiter;
23657
24648
  this.turnQueue.push(waiter);
23658
24649
  });
@@ -23683,8 +24674,8 @@ var Cluster = class {
23683
24674
  if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
23684
24675
  return Promise.reject(deadlock());
23685
24676
  }
23686
- return new Promise((resolve, reject) => {
23687
- const waiter = { session, resolve, reject };
24677
+ return new Promise((resolve2, reject) => {
24678
+ const waiter = { session, resolve: resolve2, reject };
23688
24679
  session.waitingForLock = key;
23689
24680
  const queue = this.lockQueues.get(key) ?? [];
23690
24681
  queue.push(waiter);
@@ -23729,7 +24720,7 @@ var Cluster = class {
23729
24720
  }
23730
24721
  this.unlockAll(session);
23731
24722
  if (this.turnHolder === session) {
23732
- if (this.db.transactions.inTransaction) this.db.transactions.rollback();
24723
+ if (session.database.transactions.inTransaction) session.database.transactions.rollback();
23733
24724
  this.releaseTurn(session);
23734
24725
  }
23735
24726
  }
@@ -23761,8 +24752,7 @@ var LockWait = class extends Error {
23761
24752
  key;
23762
24753
  };
23763
24754
  var lockKey = (args2) => args2.map((a) => String(a)).join(":");
23764
- var registerSessionFunctions = (cluster) => {
23765
- const db = cluster.db;
24755
+ var registerSessionFunctions = (cluster, db) => {
23766
24756
  const me = () => {
23767
24757
  if (!cluster.current) throw new PostgresError("internal", "no session is executing", "XX000");
23768
24758
  return cluster.current;
@@ -23929,13 +24919,24 @@ var backend = {
23929
24919
  for (const oid of oids) w.i32(oid);
23930
24920
  }),
23931
24921
  notificationResponse: (pid, channel, payload) => message("A", (w) => w.i32(pid).cstring(channel).cstring(payload)),
24922
+ copyInResponse: (columns) => message("G", (w) => {
24923
+ w.u8(0).i16(columns);
24924
+ for (let i = 0; i < columns; i++) w.i16(0);
24925
+ }),
24926
+ copyOutResponse: (columns) => message("H", (w) => {
24927
+ w.u8(0).i16(columns);
24928
+ for (let i = 0; i < columns; i++) w.i16(0);
24929
+ }),
24930
+ copyData: (data) => message("d", (w) => w.bytes(data)),
24931
+ copyDone: () => message("c", () => {
24932
+ }),
23932
24933
  /** The one-byte answers to SSLRequest / GSSENCRequest: no encryption here. */
23933
24934
  no: () => new Uint8Array([78])
23934
24935
  };
23935
24936
  var ByteReader = class {
23936
- constructor(bytes, start = 0) {
24937
+ constructor(bytes, start2 = 0) {
23937
24938
  this.bytes = bytes;
23938
- this.pos = start;
24939
+ this.pos = start2;
23939
24940
  this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
23940
24941
  }
23941
24942
  bytes;
@@ -24095,11 +25096,11 @@ var ScramServer = class {
24095
25096
  // src/wire/split.ts
24096
25097
  var splitStatements = (script) => {
24097
25098
  const out = [];
24098
- let start = 0;
25099
+ let start2 = 0;
24099
25100
  let i = 0;
24100
25101
  const n = script.length;
24101
25102
  const push = (end) => {
24102
- const text = script.slice(start, end).trim();
25103
+ const text = script.slice(start2, end).trim();
24103
25104
  if (text.length > 0) out.push(text);
24104
25105
  };
24105
25106
  while (i < n) {
@@ -24155,7 +25156,7 @@ var splitStatements = (script) => {
24155
25156
  }
24156
25157
  if (c === ";") {
24157
25158
  push(i);
24158
- start = i + 1;
25159
+ start2 = i + 1;
24159
25160
  }
24160
25161
  i++;
24161
25162
  }
@@ -24170,6 +25171,23 @@ var LISTEN = /^listen\s+("?)([A-Za-z_][\w$]*)\1\s*$/i;
24170
25171
  var UNLISTEN = /^unlisten\s+(\*|"?[A-Za-z_][\w$]*"?)\s*$/i;
24171
25172
  var NOTIFY = /^notify\s+("?)([A-Za-z_][\w$]*)\1\s*(?:,\s*'((?:[^']|'')*)')?\s*$/i;
24172
25173
  var RETURNS_ROWS = /^(select|with|values|show|table|explain)\b/i;
25174
+ var CLUSTER_DDL = /^(create|drop|alter)\s+database\b/i;
25175
+ var IDENT = '(?:"((?:[^"]|"")+)"|([A-Za-z_][A-Za-z0-9_$]*))';
25176
+ var CREATE_DATABASE = new RegExp(
25177
+ `^create\\s+database\\s+${IDENT}(?:\\s+with)?(?:\\s+template\\s*(?:=\\s*)?${IDENT})?\\s*$`,
25178
+ "i"
25179
+ );
25180
+ var DROP_DATABASE = new RegExp(
25181
+ `^drop\\s+database\\s+(if\\s+exists\\s+)?${IDENT}(?:\\s+with\\s*\\(\\s*force\\s*\\))?\\s*$`,
25182
+ "i"
25183
+ );
25184
+ var RENAME_DATABASE = new RegExp(`^alter\\s+database\\s+${IDENT}\\s+rename\\s+to\\s+${IDENT}\\s*$`, "i");
25185
+ var identifier = (quoted, plain) => quoted === void 0 ? plain.toLowerCase() : quoted.replaceAll('""', '"');
25186
+ var ROW_LOCK = /\s+for\s+(?:no\s+key\s+update|key\s+share|update|share)(?:\s+of\s+(?:(?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s*,\s*(?:(?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?))*)?(?:\s+(nowait|skip\s+locked))?\s*$/i;
25187
+ var LIMIT = /\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$/i;
25188
+ var FROM_TABLE = /\bfrom\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)/i;
25189
+ var UPDATE_TARGET = /^update\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?(?:"[^"]+"|[A-Za-z_][\w$]*))?\s+set\s+[\s\S]*?(?:\s+where\s+([\s\S]*?))?(?:\s+returning\s+[\s\S]*)?$/i;
25190
+ var DELETE_TARGET = /^delete\s+from\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?(?:"[^"]+"|[A-Za-z_][\w$]*))?(?:\s+where\s+([\s\S]*?))?(?:\s+returning\s+[\s\S]*)?$/i;
24173
25191
  var TYPLEN = {
24174
25192
  bool: 1,
24175
25193
  int2: 2,
@@ -24262,6 +25280,7 @@ var binaryValue = (type, text) => {
24262
25280
  }
24263
25281
  };
24264
25282
  var binaryEncodable = (type) => TEXT_TYPES2.has(type) || ["bool", "int2", "int4", "oid", "int8", "float4", "float8", "bytea", "uuid"].includes(type);
25283
+ var mutatesDatabase = (sql) => parse(sql).some((stmt) => !["select", "show", "explain", "transaction", "comment", "no_op"].includes(stmt.type));
24265
25284
  var decodeParam = (oid, format, bytes) => {
24266
25285
  if (bytes === null) return null;
24267
25286
  if (format === 0) return utf83.decode(bytes);
@@ -24310,6 +25329,8 @@ var Connection = class {
24310
25329
  this.socket = socket;
24311
25330
  this.cluster = cluster;
24312
25331
  this.options = options2;
25332
+ this.database = cluster.db;
25333
+ this.databaseName = cluster.defaultDatabaseName;
24313
25334
  this.pid = cluster.newPid();
24314
25335
  this.secret = Math.floor(Math.random() * 2147483647);
24315
25336
  cluster.sessions.set(this.pid, this);
@@ -24325,6 +25346,8 @@ var Connection = class {
24325
25346
  waitingForTurn = null;
24326
25347
  waitingForLock = null;
24327
25348
  pendingNotifies = [];
25349
+ database;
25350
+ databaseName;
24328
25351
  frames = new FrameParser();
24329
25352
  inbox = [];
24330
25353
  pumping = false;
@@ -24341,6 +25364,10 @@ var Connection = class {
24341
25364
  prepared = /* @__PURE__ */ new Map();
24342
25365
  portals = /* @__PURE__ */ new Map();
24343
25366
  notifications = [];
25367
+ copyIn = null;
25368
+ copyDrain = false;
25369
+ /** Successful writes in this session's READ COMMITTED transaction workspace. */
25370
+ transaction = null;
24344
25371
  closed = false;
24345
25372
  receive(chunk) {
24346
25373
  let messages;
@@ -24418,6 +25445,20 @@ var Connection = class {
24418
25445
  this.fatal("08P01", "expected startup or password message");
24419
25446
  return;
24420
25447
  }
25448
+ if (this.copyIn && message2.kind === "typed") {
25449
+ await this.handleCopyIn(message2.type, message2.body);
25450
+ return;
25451
+ }
25452
+ if (this.copyDrain && message2.kind === "typed") {
25453
+ if (message2.type === "c" || message2.type === "f") {
25454
+ this.copyDrain = false;
25455
+ this.readyForQuery();
25456
+ } else if (message2.type === "X") {
25457
+ this.socket.end();
25458
+ this.dispose();
25459
+ }
25460
+ return;
25461
+ }
24421
25462
  if (this.skipUntilSync && message2.type !== "S" && message2.type !== "X") return;
24422
25463
  try {
24423
25464
  switch (message2.type) {
@@ -24468,10 +25509,13 @@ var Connection = class {
24468
25509
  }
24469
25510
  this.user = parameters.user ?? "postgres";
24470
25511
  const requested = parameters.database && parameters.database.length > 0 ? parameters.database : this.user;
24471
- if (this.options.database !== void 0 && requested !== this.options.database) {
25512
+ const database = this.cluster.getDatabase(requested);
25513
+ if (!database || this.options.database !== void 0 && requested !== this.options.database) {
24472
25514
  this.fatal("3D000", `database "${requested}" does not exist`);
24473
25515
  return;
24474
25516
  }
25517
+ this.database = database;
25518
+ this.databaseName = requested;
24475
25519
  if (this.options.password !== void 0) {
24476
25520
  this.scram = new ScramServer(this.options.password);
24477
25521
  this.write(backend.authenticationSASL(["SCRAM-SHA-256"]));
@@ -24535,7 +25579,7 @@ var Connection = class {
24535
25579
  }
24536
25580
  // --- execution ----------------------------------------------------------------------------
24537
25581
  get inTransaction() {
24538
- return this.cluster.turnHolder === this && this.cluster.db.transactions.inTransaction;
25582
+ return this.transaction !== null;
24539
25583
  }
24540
25584
  status() {
24541
25585
  if (this.aborted) return "E";
@@ -24558,6 +25602,7 @@ var Connection = class {
24558
25602
  return outcome;
24559
25603
  } catch (error) {
24560
25604
  if (this.inTransaction) this.aborted = true;
25605
+ if (error instanceof PostgresError && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
24561
25606
  const status = error instanceof PostgresError ? error.sqlState : "XX000";
24562
25607
  this.options.onLog?.({ pid: this.pid, sql: text, durationMs: performance.now() - started, status });
24563
25608
  throw error;
@@ -24572,8 +25617,8 @@ var Connection = class {
24572
25617
  "25P02"
24573
25618
  );
24574
25619
  }
24575
- const listen = LISTEN.exec(text);
24576
- if (listen) return this.listen(listen[2]);
25620
+ const listen2 = LISTEN.exec(text);
25621
+ if (listen2) return this.listen(listen2[2]);
24577
25622
  const unlisten = UNLISTEN.exec(text);
24578
25623
  if (unlisten) return this.unlisten(unlisten[1].replaceAll('"', ""));
24579
25624
  const notify = NOTIFY.exec(text);
@@ -24582,7 +25627,7 @@ var Connection = class {
24582
25627
  if (!this.inTransaction) this.flushNotifies();
24583
25628
  return command("NOTIFY");
24584
25629
  }
24585
- const db = this.cluster.db;
25630
+ if (CLUSTER_DDL.test(text)) return this.executeClusterDdl(text);
24586
25631
  for (; ; ) {
24587
25632
  if (this.cancelled) throw pgError2("internal", "canceling statement due to user request", "57014");
24588
25633
  await this.cluster.acquireTurn(this);
@@ -24590,9 +25635,9 @@ var Connection = class {
24590
25635
  if (faults.delayStatementMs !== void 0) {
24591
25636
  const ms = faults.delayStatementMs;
24592
25637
  faults.delayStatementMs = void 0;
24593
- await new Promise((resolve) => setTimeout(resolve, ms));
25638
+ await new Promise((resolve2) => setTimeout(resolve2, ms));
24594
25639
  }
24595
- if (faults.dropConnection && db.transactions.inTransaction) {
25640
+ if (faults.dropConnection && this.inTransaction) {
24596
25641
  faults.dropConnection = void 0;
24597
25642
  this.destroy();
24598
25643
  throw new ConnectionDropped();
@@ -24604,7 +25649,7 @@ var Connection = class {
24604
25649
  } catch (error) {
24605
25650
  if (error instanceof LockWait) {
24606
25651
  const key = error.key;
24607
- if (!db.transactions.inTransaction) this.cluster.releaseTurn(this);
25652
+ this.cluster.releaseTurn(this);
24608
25653
  await this.waitForLock(key);
24609
25654
  continue;
24610
25655
  }
@@ -24614,7 +25659,7 @@ var Connection = class {
24614
25659
  }
24615
25660
  }
24616
25661
  waitForLock(key) {
24617
- return new Promise((resolve, reject) => {
25662
+ return new Promise((resolve2, reject) => {
24618
25663
  this.lockWaiter = (error) => {
24619
25664
  this.lockWaiter = null;
24620
25665
  this.waitingForLock = null;
@@ -24623,7 +25668,7 @@ var Connection = class {
24623
25668
  this.cluster.waitLock(this, key).then(
24624
25669
  () => {
24625
25670
  this.lockWaiter = null;
24626
- resolve();
25671
+ resolve2();
24627
25672
  },
24628
25673
  (error) => {
24629
25674
  this.lockWaiter = null;
@@ -24634,57 +25679,196 @@ var Connection = class {
24634
25679
  }
24635
25680
  /** The engine call itself: statement-atomic, and transaction control handled as the server sees it. */
24636
25681
  runStatement(text, params, control) {
24637
- const db = this.cluster.db;
24638
- const tx = db.transactions;
24639
25682
  const faults = this.options.faults;
24640
25683
  if (control && COMMIT.test(text)) {
24641
25684
  if (this.aborted) {
24642
- tx.rollback();
25685
+ this.transaction = null;
24643
25686
  this.aborted = false;
24644
25687
  return command("ROLLBACK");
24645
25688
  }
24646
25689
  if (faults.failCommit !== void 0) {
24647
25690
  const code = faults.failCommit;
24648
25691
  faults.failCommit = void 0;
24649
- tx.rollback();
25692
+ this.transaction = null;
24650
25693
  throw pgError2("internal", "could not serialize access due to concurrent update", code);
24651
25694
  }
24652
25695
  }
24653
25696
  if (control && this.aborted && /^(rollback|abort)\b/i.test(text) && !/\bto\b/i.test(text)) {
24654
- tx.rollback();
25697
+ this.transaction = null;
24655
25698
  this.aborted = false;
24656
25699
  return command("ROLLBACK");
24657
25700
  }
24658
- const run = () => {
24659
- const result = this.cluster.as(this, () => db.prepare(text).textResult(...params));
24660
- return { empty: false, result, tag: commandTag(result) };
24661
- };
24662
- if (control) return run();
24663
- if (tx.inTransaction) {
24664
- const name = `__wire_${this.pid}`;
24665
- tx.savepoint(name);
24666
- try {
24667
- const outcome = run();
24668
- tx.releaseSavepoint(name);
24669
- return outcome;
24670
- } catch (error) {
24671
- tx.rollbackToSavepoint(name);
24672
- tx.releaseSavepoint(name);
24673
- throw error;
25701
+ if (control) return this.transactionControl(text);
25702
+ const workspace = this.transaction === null ? null : this.openWorkspace();
25703
+ const db = workspace ?? this.database;
25704
+ try {
25705
+ const run = () => {
25706
+ const result = this.cluster.as(this, () => this.runQuery(db, text, params));
25707
+ return { empty: false, result, tag: commandTag(result) };
25708
+ };
25709
+ const outcome = db.transaction(run);
25710
+ if (this.transaction && mutatesDatabase(text)) {
25711
+ this.transaction.statements.push({ kind: "sql", sql: text, params: params.slice() });
24674
25712
  }
25713
+ return outcome;
25714
+ } finally {
25715
+ workspace?.close();
24675
25716
  }
24676
- return db.transaction(run);
24677
25717
  }
24678
- /** Release the turn and transaction-scoped locks once no block is open; deliver NOTIFYs at commit. */
25718
+ /** Release the statement turn; transaction-scoped locks live until COMMIT / ROLLBACK. */
24679
25719
  afterStatement() {
24680
- if (this.cluster.db.transactions.inTransaction) return;
24681
- this.cluster.unlockAll(this, true);
24682
25720
  this.cluster.releaseTurn(this);
24683
- this.flushNotifies();
25721
+ if (!this.inTransaction) {
25722
+ this.cluster.unlockAll(this, true);
25723
+ this.flushNotifies();
25724
+ }
25725
+ }
25726
+ transactionControl(text) {
25727
+ const stmt = parse(text)[0];
25728
+ if (stmt?.type !== "transaction") throw pgError2("syntax", "invalid transaction command", "42601");
25729
+ switch (stmt.action) {
25730
+ case "begin":
25731
+ this.transaction ??= { statements: [], savepoints: [] };
25732
+ return command("BEGIN");
25733
+ case "commit": {
25734
+ const transaction = this.transaction;
25735
+ this.transaction = null;
25736
+ if (transaction) {
25737
+ this.database.transaction(() => {
25738
+ for (const logged of transaction.statements) this.replay(this.database, logged);
25739
+ });
25740
+ }
25741
+ return command("COMMIT");
25742
+ }
25743
+ case "rollback":
25744
+ this.transaction = null;
25745
+ return command("ROLLBACK");
25746
+ case "savepoint": {
25747
+ if (!this.transaction) throw pgError2("internal", "SAVEPOINT can only be used in transaction blocks", "25P01");
25748
+ this.transaction.savepoints.push({
25749
+ name: stmt.savepointName,
25750
+ statementCount: this.transaction.statements.length
25751
+ });
25752
+ return command("SAVEPOINT");
25753
+ }
25754
+ case "release": {
25755
+ const index = this.findSavepoint(stmt.savepointName);
25756
+ this.transaction?.savepoints.splice(index);
25757
+ return command("RELEASE");
25758
+ }
25759
+ case "rollback_to": {
25760
+ const index = this.findSavepoint(stmt.savepointName);
25761
+ const savepoint = this.transaction?.savepoints[index];
25762
+ if (!this.transaction || !savepoint) throw pgError2("internal", "savepoint does not exist", "3B001");
25763
+ this.transaction.statements.splice(savepoint.statementCount);
25764
+ this.transaction.savepoints.splice(index + 1);
25765
+ this.aborted = false;
25766
+ return command("ROLLBACK");
25767
+ }
25768
+ }
25769
+ }
25770
+ findSavepoint(name) {
25771
+ if (!this.transaction) throw pgError2("internal", "SAVEPOINT can only be used in transaction blocks", "25P01");
25772
+ for (let i = this.transaction.savepoints.length - 1; i >= 0; i--) {
25773
+ if (this.transaction.savepoints[i]?.name === name) return i;
25774
+ }
25775
+ throw pgError2("internal", `savepoint "${name}" does not exist`, "3B001");
25776
+ }
25777
+ /** Rebase this transaction's successful writes onto the latest committed state. */
25778
+ openWorkspace() {
25779
+ const workspace = this.database.branch();
25780
+ setDatabaseCatalogContext(workspace.state, {
25781
+ name: this.databaseName,
25782
+ names: () => this.cluster.databaseNames()
25783
+ });
25784
+ for (const logged of this.transaction?.statements ?? []) {
25785
+ workspace.transaction(() => this.replay(workspace, logged));
25786
+ }
25787
+ return workspace;
25788
+ }
25789
+ replay(db, logged) {
25790
+ this.cluster.as(this, () => {
25791
+ if (logged.kind === "sql") db.prepare(logged.sql).textResult(...logged.params);
25792
+ else {
25793
+ const env = { ctx: new EngineCtx(db.state), params: null, ctes: /* @__PURE__ */ new Map(), outer: null };
25794
+ executeCopyFromData(env, logged.stmt, logged.data);
25795
+ }
25796
+ });
25797
+ }
25798
+ runQuery(db, text, params) {
25799
+ const lock = ROW_LOCK.exec(text);
25800
+ if (!lock) {
25801
+ const mutation = UPDATE_TARGET.exec(text) ?? DELETE_TARGET.exec(text);
25802
+ if (mutation) {
25803
+ const lockQuery = `SELECT * FROM ${mutation[1]}${mutation[2] ? ` WHERE ${mutation[2]}` : ""}`;
25804
+ this.lockRows(db, lockQuery, params, "wait");
25805
+ }
25806
+ return db.prepare(text).textResult(...params);
25807
+ }
25808
+ let query = text.slice(0, lock.index).trimEnd();
25809
+ const limitMatch = LIMIT.exec(query);
25810
+ const limit = limitMatch ? Number(limitMatch[1]) : Number.POSITIVE_INFINITY;
25811
+ const offset = limitMatch ? Number(limitMatch[2] ?? 0) : 0;
25812
+ if (limitMatch) query = query.slice(0, limitMatch.index).trimEnd();
25813
+ return this.lockRows(db, query, params, lock[1]?.toLowerCase().replace(/\s+/g, "_") ?? "wait", limit, offset);
25814
+ }
25815
+ lockRows(db, query, params, policy, limit = Number.POSITIVE_INFINITY, offset = 0) {
25816
+ const result = db.prepare(query).textResult(...params);
25817
+ const rows = [];
25818
+ for (const row of result.rows.slice(offset)) {
25819
+ const key = this.rowLockKey(db, query, result.columns, row);
25820
+ if (this.cluster.tryLock(this, key, true)) {
25821
+ rows.push(row);
25822
+ if (rows.length >= limit) break;
25823
+ continue;
25824
+ }
25825
+ if (policy === "skip_locked") continue;
25826
+ if (policy === "nowait") {
25827
+ throw pgError2("internal", "could not obtain lock on row in relation", "55P03");
25828
+ }
25829
+ throw new LockWait(key);
25830
+ }
25831
+ return { ...result, rows, rowCount: rows.length };
25832
+ }
25833
+ rowLockKey(db, query, columns, row) {
25834
+ const rawName = FROM_TABLE.exec(query)?.[1] ?? "query";
25835
+ const parts = rawName.split(".").map((part) => part.startsWith('"') ? part.slice(1, -1).replaceAll('""', '"') : part.toLowerCase());
25836
+ const table = db.state.findTable(parts);
25837
+ const primary = table?.constraints.find((constraint) => constraint.kind === "primary_key");
25838
+ const indexes = primary?.columns.map((column) => columns.indexOf(column)) ?? [];
25839
+ const identity = indexes.length > 0 && indexes.every((index) => index >= 0) ? indexes.map((i) => row[i]) : row;
25840
+ return `row:${this.databaseName}:${parts.join(".")}:${JSON.stringify(identity)}`;
24684
25841
  }
24685
25842
  flushNotifies() {
24686
25843
  for (const { channel, payload } of this.pendingNotifies.splice(0)) this.cluster.notify(this, channel, payload);
24687
25844
  }
25845
+ executeClusterDdl(text) {
25846
+ if (this.inTransaction) {
25847
+ throw pgError2("internal", "CREATE/DROP/ALTER DATABASE cannot run inside a transaction block", "25001");
25848
+ }
25849
+ const create = CREATE_DATABASE.exec(text);
25850
+ if (create) {
25851
+ const name = identifier(create[1], create[2]);
25852
+ const template = create[3] === void 0 && create[4] === void 0 ? void 0 : identifier(create[3], create[4]);
25853
+ this.cluster.createDatabase(name, template);
25854
+ return command("CREATE DATABASE");
25855
+ }
25856
+ const drop = DROP_DATABASE.exec(text);
25857
+ if (drop) {
25858
+ const name = identifier(drop[2], drop[3]);
25859
+ if (drop[1] && !this.cluster.getDatabase(name)) return command("DROP DATABASE");
25860
+ this.cluster.dropDatabase(name, this);
25861
+ return command("DROP DATABASE");
25862
+ }
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]);
25867
+ this.cluster.renameDatabase(from, to, this);
25868
+ return command("ALTER DATABASE");
25869
+ }
25870
+ throw pgError2("syntax", "unsupported CREATE/DROP/ALTER DATABASE option", "0A000");
25871
+ }
24688
25872
  listen(channel) {
24689
25873
  const set = this.cluster.listeners.get(channel) ?? /* @__PURE__ */ new Set();
24690
25874
  set.add(this);
@@ -24705,6 +25889,24 @@ var Connection = class {
24705
25889
  this.readyForQuery();
24706
25890
  return;
24707
25891
  }
25892
+ if (statements.length === 1) {
25893
+ const copy = this.copyStatement(statements[0]);
25894
+ if (copy?.direction === "from") {
25895
+ await this.beginCopyIn(copy);
25896
+ return;
25897
+ }
25898
+ if (copy?.direction === "to") {
25899
+ try {
25900
+ const outcome = await this.execute(statements[0]);
25901
+ this.sendCopyOut(outcome, this.copyColumnCount(copy));
25902
+ } catch (error) {
25903
+ this.write(backend.errorResponse(errorFields(error)));
25904
+ if (this.inTransaction) this.aborted = true;
25905
+ }
25906
+ this.readyForQuery();
25907
+ return;
25908
+ }
25909
+ }
24708
25910
  const implicit = statements.length > 1 && !this.inTransaction && !statements.some((s) => TX_CONTROL.test(s));
24709
25911
  let failed = false;
24710
25912
  try {
@@ -24725,6 +25927,138 @@ var Connection = class {
24725
25927
  if (failed && this.inTransaction) this.aborted = true;
24726
25928
  this.readyForQuery();
24727
25929
  }
25930
+ copyStatement(sql) {
25931
+ try {
25932
+ const statements = parse(sql);
25933
+ const stmt = statements.length === 1 ? statements[0] : void 0;
25934
+ return stmt?.type === "copy" ? stmt : null;
25935
+ } catch {
25936
+ return null;
25937
+ }
25938
+ }
25939
+ copyColumnCount(stmt) {
25940
+ if (stmt.columns) return stmt.columns.length;
25941
+ if (stmt.table) return this.database.state.findTable(stmt.table)?.columns.length ?? 0;
25942
+ return 0;
25943
+ }
25944
+ async handleCopyIn(type, body) {
25945
+ const copy = this.copyIn;
25946
+ if (type === "d") {
25947
+ try {
25948
+ copy.buffer += copy.decoder.decode(body.rest(), { stream: true });
25949
+ this.flushCopyRecords(copy, false);
25950
+ } catch (error) {
25951
+ this.abortCopyIn(copy, error, true);
25952
+ }
25953
+ return;
25954
+ }
25955
+ if (type === "f") {
25956
+ const detail = body.cstring();
25957
+ this.abortCopyIn(copy, pgError2("internal", `COPY from stdin failed: ${detail}`, "57014"), false);
25958
+ return;
25959
+ }
25960
+ if (type === "c") {
25961
+ this.finishCopyIn(copy);
25962
+ return;
25963
+ }
25964
+ if (type === "H") return;
25965
+ if (type === "X") {
25966
+ this.socket.end();
25967
+ this.dispose();
25968
+ return;
25969
+ }
25970
+ this.fatal("08P01", `unexpected frontend message type ${JSON.stringify(type)} during COPY`);
25971
+ }
25972
+ async beginCopyIn(stmt) {
25973
+ await this.cluster.acquireTurn(this);
25974
+ const workspace = this.transaction ? this.openWorkspace() : null;
25975
+ const database = workspace ?? this.database;
25976
+ database.transactions.begin();
25977
+ this.copyIn = {
25978
+ stmt,
25979
+ database,
25980
+ workspace: workspace !== null,
25981
+ buffer: "",
25982
+ decoder: new TextDecoder(),
25983
+ rowCount: 0,
25984
+ headerPending: stmt.options.header === true || String(stmt.options.header ?? "").toLowerCase() === "true",
25985
+ loggedData: ""
25986
+ };
25987
+ this.write(backend.copyInResponse(this.copyColumnCount(stmt)));
25988
+ }
25989
+ finishCopyIn(copy) {
25990
+ const tx = copy.database.transactions;
25991
+ try {
25992
+ copy.buffer += copy.decoder.decode();
25993
+ this.flushCopyRecords(copy, true);
25994
+ tx.commit();
25995
+ if (copy.workspace) {
25996
+ this.transaction?.statements.push({ kind: "copy", stmt: copy.stmt, data: copy.loggedData });
25997
+ copy.database.close();
25998
+ }
25999
+ this.copyIn = null;
26000
+ this.write(backend.commandComplete(`COPY ${copy.rowCount}`));
26001
+ this.cluster.releaseTurn(this);
26002
+ this.readyForQuery();
26003
+ } catch (error) {
26004
+ this.abortCopyIn(copy, error, false);
26005
+ }
26006
+ }
26007
+ flushCopyRecords(copy, final) {
26008
+ const format = String(copy.stmt.options.format ?? "text").toLowerCase();
26009
+ const quote = String(copy.stmt.options.quote ?? '"');
26010
+ const boundaries = [];
26011
+ let quoted = false;
26012
+ for (let i = 0; i < copy.buffer.length; i++) {
26013
+ const char = copy.buffer[i];
26014
+ if (format === "csv" && char === quote) {
26015
+ if (quoted && copy.buffer[i + 1] === quote) i++;
26016
+ else quoted = !quoted;
26017
+ } else if (char === "\n" && !quoted) {
26018
+ boundaries.push(i + 1);
26019
+ }
26020
+ }
26021
+ if (final && quoted) {
26022
+ throw new PostgresError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
26023
+ }
26024
+ const end = boundaries.at(-1) ?? 0;
26025
+ const data = final ? copy.buffer : end > 0 ? copy.buffer.slice(0, end) : "";
26026
+ if (data === "") return;
26027
+ copy.buffer = final ? "" : copy.buffer.slice(end);
26028
+ const stmt = copy.headerPending ? copy.stmt : { ...copy.stmt, options: { ...copy.stmt.options, header: false } };
26029
+ const env = {
26030
+ ctx: new EngineCtx(copy.database.state),
26031
+ params: null,
26032
+ ctes: /* @__PURE__ */ new Map(),
26033
+ outer: null
26034
+ };
26035
+ const result = this.cluster.as(this, () => executeCopyFromData(env, stmt, data));
26036
+ copy.rowCount += result.rowCount;
26037
+ copy.loggedData += data;
26038
+ copy.headerPending = false;
26039
+ }
26040
+ abortCopyIn(copy, error, drain) {
26041
+ const tx = copy.database.transactions;
26042
+ if (tx.inTransaction) tx.rollback();
26043
+ if (copy.workspace) copy.database.close();
26044
+ if (this.inTransaction) this.aborted = true;
26045
+ this.cluster.releaseTurn(this);
26046
+ this.copyIn = null;
26047
+ this.copyDrain = drain;
26048
+ this.write(backend.errorResponse(errorFields(error)));
26049
+ if (!drain) this.readyForQuery();
26050
+ }
26051
+ sendCopyOut(outcome, columns) {
26052
+ if (outcome.empty) throw pgError2("internal", "COPY TO produced no result", "XX000");
26053
+ this.write(backend.copyOutResponse(columns));
26054
+ for (const row of outcome.result.rows) {
26055
+ const line = row[0] ?? "";
26056
+ this.write(backend.copyData(utf83.encode(`${line}
26057
+ `)));
26058
+ }
26059
+ this.write(backend.copyDone());
26060
+ this.write(backend.commandComplete(outcome.tag));
26061
+ }
24728
26062
  sendOutcome(outcome, resultFormats, opts = { rowDescription: false }) {
24729
26063
  const from = opts.from ?? 0;
24730
26064
  const limit = opts.limit ?? 0;
@@ -24779,7 +26113,7 @@ var Connection = class {
24779
26113
  for (let i = 0; i < count; i++) paramOids.push(body.i32());
24780
26114
  const text = sql.trim();
24781
26115
  if (text !== "" && !LISTEN.test(text) && !UNLISTEN.test(text) && !NOTIFY.test(text) && !TX_CONTROL.test(text)) {
24782
- this.cluster.db.prepare(text);
26116
+ if (!CLUSTER_DDL.test(text)) this.database.prepare(text);
24783
26117
  }
24784
26118
  if (name !== "" && this.prepared.has(name)) {
24785
26119
  throw pgError2("internal", `prepared statement "${name}" already exists`, "42P05");
@@ -24838,7 +26172,7 @@ var Connection = class {
24838
26172
  */
24839
26173
  async shapeOf(prepared) {
24840
26174
  if (!RETURNS_ROWS.test(prepared.sql)) return null;
24841
- const db = this.cluster.db;
26175
+ const db = this.database;
24842
26176
  await this.cluster.acquireTurn(this);
24843
26177
  try {
24844
26178
  const params = prepared.paramOids.map(() => null);
@@ -24952,40 +26286,49 @@ var normalize = (input) => {
24952
26286
  };
24953
26287
  };
24954
26288
  var isSnapshot = (value) => typeof value === "object" && value !== null && "open" in value && typeof value.open === "function";
24955
- var serve = (input = {}) => {
26289
+ var serve2 = (input = {}) => {
24956
26290
  const options2 = normalize(input);
24957
26291
  const database = isSnapshot(options2.database) ? options2.database.open() : options2.database ?? new Database();
24958
- const cluster = new Cluster(database);
26292
+ const initialName = options2.databaseName ?? "postgres";
26293
+ const cluster = new Cluster(database, initialName);
24959
26294
  const faults = {};
24960
26295
  const connections = /* @__PURE__ */ new Set();
24961
26296
  const host = options2.host ?? "127.0.0.1";
24962
26297
  const user = options2.user || "postgres";
24963
- const server2 = createServer((socket) => {
26298
+ const server2 = createServer2((socket) => {
24964
26299
  socket.setNoDelay(true);
24965
26300
  const connection = new Connection(socket, cluster, {
24966
26301
  ...options2.password !== void 0 ? { password: options2.password } : {},
24967
26302
  serverVersion: options2.serverVersion ?? "18.3",
24968
26303
  parameters: options2.parameters ?? {},
24969
26304
  faults,
24970
- ...options2.databaseName !== void 0 ? { database: options2.databaseName } : {},
26305
+ ...options2.databaseName !== void 0 && !options2.allowDatabaseSelection ? { database: options2.databaseName } : {},
24971
26306
  ...options2.onLog ? { onLog: options2.onLog } : {}
24972
26307
  });
24973
26308
  connections.add(connection);
24974
26309
  socket.on("close", () => connections.delete(connection));
24975
26310
  });
24976
- return new Promise((resolve, reject) => {
26311
+ return new Promise((resolve2, reject) => {
24977
26312
  server2.once("error", reject);
24978
26313
  server2.listen(options2.port ?? 0, host, () => {
24979
26314
  server2.removeListener("error", reject);
24980
26315
  const address = server2.address();
24981
26316
  const port = typeof address === "object" && address !== null ? address.port : 0;
24982
- const name = options2.databaseName ?? "postgres";
26317
+ const name = initialName;
24983
26318
  const auth = options2.password ? `${encodeURIComponent(user)}:${encodeURIComponent(options2.password)}@` : `${encodeURIComponent(user)}@`;
24984
- resolve({
26319
+ resolve2({
24985
26320
  port,
24986
26321
  host,
24987
26322
  connectionString: `postgres://${auth}${host}:${port}/${encodeURIComponent(name)}`,
24988
- database,
26323
+ get database() {
26324
+ return cluster.db;
26325
+ },
26326
+ databaseNames() {
26327
+ return cluster.databaseNames();
26328
+ },
26329
+ getDatabase(databaseName) {
26330
+ return cluster.requireDatabase(databaseName);
26331
+ },
24989
26332
  server: server2,
24990
26333
  get connections() {
24991
26334
  return connections.size;
@@ -24993,8 +26336,16 @@ var serve = (input = {}) => {
24993
26336
  fault(next) {
24994
26337
  Object.assign(faults, next);
24995
26338
  },
24996
- snapshot() {
24997
- return database.snapshot();
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),
26347
+ snapshot(databaseName = initialName) {
26348
+ return cluster.requireDatabase(databaseName).snapshot();
24998
26349
  },
24999
26350
  close() {
25000
26351
  for (const connection of connections) connection.destroy();
@@ -25008,9 +26359,251 @@ var serve = (input = {}) => {
25008
26359
  });
25009
26360
  };
25010
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
+
25011
26592
  // src/wire/cli.ts
25012
26593
  var usage = `mockingbird-postgres serve [postgres://URI] [--port <n>] [--host <h>] [--password <p>] [--server-version <v>] [--log]`;
25013
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
+ }
25014
26607
  if (args[0] !== "serve") {
25015
26608
  console.error(usage);
25016
26609
  process.exit(args[0] === "--help" || args[0] === "-h" ? 0 : 2);
@@ -25059,7 +26652,7 @@ if (log) {
25059
26652
  `[pid ${entry.pid}] ${entry.status} ${entry.durationMs.toFixed(1)}ms ${entry.sql.replace(/\s+/g, " ").slice(0, 200)}`
25060
26653
  );
25061
26654
  }
25062
- var server = await serve(options);
26655
+ var server = await serve2(options);
25063
26656
  console.log(`mockingbird-postgres listening on ${server.connectionString}`);
25064
26657
  var stop = async () => {
25065
26658
  await server.close();