@crvouga/mockingbird-service-postgres 3.6.0 → 3.8.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
@@ -161,6 +161,16 @@ var Timeline = class {
161
161
  }
162
162
  };
163
163
 
164
+ // ../core/dist/ids.js
165
+ var normalizeIdBase = (value, label = "idBase", wallNow = Date.now) => {
166
+ if (value === "boot")
167
+ return wallNow();
168
+ const base = typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value.trim()) : typeof value === "number" ? value : Number.NaN;
169
+ if (!Number.isSafeInteger(base) || base < 0)
170
+ throw new RangeError(`${label} must be a non-negative integer or "boot" (got ${JSON.stringify(value)})`);
171
+ return base;
172
+ };
173
+
164
174
  // ../core/dist/clock.js
165
175
  var createClock = (source = Date.now) => {
166
176
  let offsetMs = 0;
@@ -744,11 +754,21 @@ function ownedTransport(policy) {
744
754
 
745
755
  // ../../adapters/node/dist/cli.js
746
756
  import { readFile as readFile5 } from "node:fs/promises";
747
- import { createRequire as createRequire3 } from "node:module";
748
- import { resolve as resolve3 } from "node:path";
749
- import { pathToFileURL } from "node:url";
750
757
  import { parseArgs } from "node:util";
751
758
 
759
+ // ../../adapters/node/dist/boot-settings.js
760
+ var checkSettings = (target, settings, label) => {
761
+ if (!target.parseSettings)
762
+ throw new Error(`${label}: ${target.name} takes no boot settings`);
763
+ try {
764
+ return target.parseSettings(settings);
765
+ } catch (error) {
766
+ throw new Error(`${label}: ${error instanceof Error ? error.message : String(error)}`, {
767
+ cause: error
768
+ });
769
+ }
770
+ };
771
+
752
772
  // ../../adapters/node/dist/fleet.js
753
773
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
754
774
  import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
@@ -760,6 +780,31 @@ import { createHash as createHash3, randomBytes, randomUUID as randomUUID3 } fro
760
780
 
761
781
  // ../../adapters/node/dist/fleet-archive.js
762
782
  import { createHash } from "node:crypto";
783
+ function validateCompatibilityTags(tags) {
784
+ if (!tags || typeof tags !== "object" || Array.isArray(tags))
785
+ throw new Error("invalid archive compatibility tags");
786
+ const entries = Object.entries(tags);
787
+ if (entries.length > 32 || entries.some(([key, value]) => !/^[A-Za-z0-9_.-]{1,64}$/.test(key) || typeof value !== "string" || value.length < 1 || value.length > 256 || [...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)))
788
+ throw new Error("invalid archive compatibility tags");
789
+ }
790
+ function assertCompatibilityTags(tags, policy) {
791
+ if (tags !== void 0)
792
+ validateCompatibilityTags(tags);
793
+ if (!policy)
794
+ return;
795
+ validateCompatibilityTags(policy.tags);
796
+ if (policy.missing !== void 0 && !["allow", "reject"].includes(policy.missing))
797
+ throw new Error("invalid missing compatibility policy");
798
+ if (tags === void 0 && policy.missing === "allow")
799
+ return;
800
+ if (canonical(tags) !== canonical(policy.tags))
801
+ throw new Error("application archive compatibility mismatch");
802
+ }
803
+ function assertArchiveCompatibility(input, policy) {
804
+ const archive = validateArchive(input);
805
+ assertCompatibilityTags(archive.compatibility, policy);
806
+ return archive;
807
+ }
763
808
  var canonical = (value) => {
764
809
  if (Array.isArray(value))
765
810
  return `[${value.map(canonical).join(",")}]`;
@@ -779,6 +824,8 @@ function validateArchive(value) {
779
824
  const { sha256: sha2563, ...content } = doc;
780
825
  if (doc.format !== "mockingbird.fleet/v1" || typeof doc.sourceId !== "string" || typeof doc.createdAt !== "string" || !doc.namespaces || typeof doc.namespaces !== "object" || Array.isArray(doc.namespaces) || sha2563 !== digest(content))
781
826
  throw new Error("unsupported or corrupt fleet archive");
827
+ if (doc.compatibility !== void 0)
828
+ validateCompatibilityTags(doc.compatibility);
782
829
  for (const [namespace, entry] of Object.entries(doc.namespaces)) {
783
830
  if (!/^[A-Za-z0-9_.-]{1,64}$/.test(namespace) || !entry || !Number.isFinite(entry.clock?.now) || typeof entry.clock?.frozen !== "boolean" || !entry.services || !Object.keys(entry.services).length)
784
831
  throw new Error("invalid archive namespace");
@@ -794,6 +841,7 @@ function inspectArchive(input) {
794
841
  format: archive.format,
795
842
  createdAt: archive.createdAt,
796
843
  sha256: archive.sha256,
844
+ ...archive.compatibility ? { compatibility: archive.compatibility } : {},
797
845
  namespaces: Object.fromEntries(Object.entries(archive.namespaces).map(([name2, ns]) => [
798
846
  name2,
799
847
  {
@@ -1436,11 +1484,28 @@ var ingressSupport = {
1436
1484
  streaming: true,
1437
1485
  signing: "vendor request signing is not used; webhook signatures remain payload based"
1438
1486
  },
1487
+ cognito: {
1488
+ publicOrigin: true,
1489
+ pathPrefix: true,
1490
+ namespaces: "path-or-header",
1491
+ streaming: true,
1492
+ signing: "JWT issuer and JWKS use the configured public namespace; SigV4 verification is not modelled"
1493
+ },
1494
+ medplum: {
1495
+ publicOrigin: true,
1496
+ pathPrefix: true,
1497
+ namespaces: "path-or-header",
1498
+ streaming: true,
1499
+ signing: "FHIR, OAuth and storage URLs use the configured public namespace"
1500
+ },
1439
1501
  other: {
1440
1502
  publicOrigin: false,
1441
1503
  reason: "host/path-sensitive signing and hosted URL rewriting are not certified; use a direct endpoint"
1442
1504
  }
1443
1505
  };
1506
+ function supportsPublicIngress(service) {
1507
+ return service === "stripe" || service === "cognito" || service === "medplum";
1508
+ }
1444
1509
  function advertisedHttpBase(entry) {
1445
1510
  if (entry.publicPath !== void 0 && !entry.publicOrigin)
1446
1511
  throw new Error("publicPath requires publicOrigin");
@@ -1979,7 +2044,7 @@ data: ${JSON.stringify(data)}
1979
2044
  {
1980
2045
  protocol: c.protocol,
1981
2046
  coverage: c.coverage,
1982
- ingress: c.targetName === "stripe" ? ingressSupport.stripe : ingressSupport.other,
2047
+ ingress: supportsPublicIngress(c.targetName ?? "") ? ingressSupport[c.targetName] : ingressSupport.other,
1983
2048
  target: c.targetName,
1984
2049
  outboundPolicy: c.outboundPolicy ?? "unsupported",
1985
2050
  version: 1,
@@ -3063,11 +3128,12 @@ data: ${JSON.stringify(data)}
3063
3128
  format: "mockingbird.fleet/v1",
3064
3129
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3065
3130
  sourceId: this.ctx.id,
3131
+ ...this.ctx.options.archiveCompatibility ? { compatibility: structuredClone(this.ctx.options.archiveCompatibility.tags) } : {},
3066
3132
  namespaces: contents
3067
3133
  });
3068
3134
  }
3069
3135
  async restoreArchive(input, selectedNamespace) {
3070
- const archive = validateArchive(input);
3136
+ const archive = assertArchiveCompatibility(input, this.ctx.options.archiveCompatibility);
3071
3137
  const entries = Object.entries(archive.namespaces).filter(([ns]) => selectedNamespace === void 0 || ns === selectedNamespace);
3072
3138
  if (!entries.length)
3073
3139
  throw new Error("selected namespace absent from archive");
@@ -3352,6 +3418,9 @@ var fleet_config_schema_default = {
3352
3418
  adminControls: {
3353
3419
  $ref: "#/$defs/controls"
3354
3420
  },
3421
+ idBase: {
3422
+ $ref: "#/$defs/idBase"
3423
+ },
3355
3424
  namespace: {
3356
3425
  type: "string",
3357
3426
  pattern: "^[A-Za-z0-9_.-]{1,64}$"
@@ -3441,6 +3510,12 @@ var fleet_config_schema_default = {
3441
3510
  adminControls: {
3442
3511
  $ref: "#/$defs/controls"
3443
3512
  },
3513
+ idBase: {
3514
+ $ref: "#/$defs/idBase"
3515
+ },
3516
+ settings: {
3517
+ type: "object"
3518
+ },
3444
3519
  fixtures: {
3445
3520
  type: "object"
3446
3521
  },
@@ -3487,6 +3562,18 @@ var fleet_config_schema_default = {
3487
3562
  }
3488
3563
  }
3489
3564
  },
3565
+ idBase: {
3566
+ anyOf: [
3567
+ {
3568
+ type: "integer",
3569
+ minimum: 0
3570
+ },
3571
+ {
3572
+ type: "string",
3573
+ pattern: "^(boot|[0-9]+)$"
3574
+ }
3575
+ ]
3576
+ },
3490
3577
  controls: {
3491
3578
  type: "object",
3492
3579
  additionalProperties: false,
@@ -3636,6 +3723,8 @@ async function preflightFleet(config, options2) {
3636
3723
  throw new Error("protected archive policy requires a protected repository; configure its protector before startup");
3637
3724
  resolveAdminPrefix(config.adminPrefix);
3638
3725
  normalizeBootControls(config.adminControls);
3726
+ if (config.idBase !== void 0)
3727
+ normalizeIdBase(config.idBase, "idBase");
3639
3728
  const targets = /* @__PURE__ */ new Map();
3640
3729
  const ports = /* @__PURE__ */ new Set();
3641
3730
  for (const [name2, entry] of Object.entries(config.services)) {
@@ -3651,7 +3740,7 @@ async function preflightFleet(config, options2) {
3651
3740
  if (entry.target !== void 0 && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.target))
3652
3741
  throw new Error("target must name a supported service suffix");
3653
3742
  const service = entry.target ?? name2;
3654
- if (advertisedHttpBase(entry) && service !== "stripe")
3743
+ if (advertisedHttpBase(entry) && !supportsPublicIngress(service))
3655
3744
  throw new Error(`public ingress unsupported for ${service}; use a direct endpoint to preserve signed targets`);
3656
3745
  const target = await options2.load(service);
3657
3746
  const protocol = "protocol" in target ? target.protocol : "http";
@@ -3667,6 +3756,13 @@ async function preflightFleet(config, options2) {
3667
3756
  throw new Error("invalid protocol namespace selector");
3668
3757
  }
3669
3758
  }
3759
+ if (entry.idBase !== void 0)
3760
+ normalizeIdBase(entry.idBase, "idBase");
3761
+ if (entry.settings !== void 0) {
3762
+ if (protocol !== "http" || !("create" in target))
3763
+ throw new Error("settings: only HTTP services take boot settings");
3764
+ checkSettings(target, entry.settings, "settings");
3765
+ }
3670
3766
  const port = entry.port ?? target.defaultPort;
3671
3767
  if (port !== 0) {
3672
3768
  const key = `${entry.host ?? "127.0.0.1"}:${port}`;
@@ -3702,7 +3798,7 @@ async function preflightFleet(config, options2) {
3702
3798
  break;
3703
3799
  }
3704
3800
  }
3705
- targets.set(name2, { ...target, packageVersion: packageVersion ?? "3.6.0" });
3801
+ targets.set(name2, { ...target, packageVersion: packageVersion ?? "3.8.0" });
3706
3802
  } catch (error) {
3707
3803
  throw new Error(`service ${name2}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
3708
3804
  }
@@ -3746,6 +3842,11 @@ async function preflightFleet(config, options2) {
3746
3842
  return ordered;
3747
3843
  }
3748
3844
  function validateFleetPolicies(options2) {
3845
+ if (options2.archiveCompatibility) {
3846
+ validateCompatibilityTags(options2.archiveCompatibility.tags);
3847
+ if (options2.archiveCompatibility.missing !== void 0 && !["allow", "reject"].includes(options2.archiveCompatibility.missing))
3848
+ throw new Error("archiveCompatibility.missing must be allow or reject");
3849
+ }
3749
3850
  if (options2.journal && !options2.repository)
3750
3851
  throw new Error("durable operation journal requires an archive repository");
3751
3852
  if (options2.cors) {
@@ -4017,6 +4118,7 @@ var httpChild = async (target, entry, options2, admission, generation, privateAc
4017
4118
  options2.onLog?.(entry2);
4018
4119
  },
4019
4120
  ...entry.adminControls !== void 0 ? { adminControls: entry.adminControls } : {},
4121
+ ...entry.settings !== void 0 ? { settings: checkSettings(target, entry.settings, "settings") } : {},
4020
4122
  ...entry.fixtures !== void 0 ? { fixtures: entry.fixtures } : {},
4021
4123
  ...(advertisedHttpBase(entry) ?? entry.baseUrl) !== void 0 ? { baseUrl: advertisedHttpBase(entry) ?? entry.baseUrl } : {}
4022
4124
  });
@@ -4035,6 +4137,8 @@ var httpChild = async (target, entry, options2, admission, generation, privateAc
4035
4137
  ].some((method) => typeof created[method] !== "function"))
4036
4138
  throw new Error("HTTP target does not implement the fleet runtime protocol; build/install matching supported service versions");
4037
4139
  const runtime = created;
4140
+ if (entry.idBase !== void 0)
4141
+ runtime.setIdBase?.(entry.idBase);
4038
4142
  const unobserve = runtime.webhooks?.observeDeliveries(observer.delivery);
4039
4143
  if (options2.outbound && runtime.webhooks)
4040
4144
  runtime.webhooks.setTransportPolicy(ownedTransport(options2.outbound));
@@ -4633,6 +4737,7 @@ async function startFleet(config, options2) {
4633
4737
  ...entry,
4634
4738
  adminPrefix: resolveAdminPrefix(entry.adminPrefix ?? adminPrefix),
4635
4739
  ...adminKey !== void 0 ? { adminKey } : {},
4740
+ ...(entry.idBase ?? config.idBase) !== void 0 ? { idBase: entry.idBase ?? config.idBase } : {},
4636
4741
  // Fleet-wide controls, with the service's own keys replacing them.
4637
4742
  ...config.adminControls !== void 0 || entry.adminControls !== void 0 ? { adminControls: { ...config.adminControls, ...entry.adminControls } } : {}
4638
4743
  }, options2, (namespace, request) => management?.admit(namespace, request), (namespace) => management?.generation(namespace), (namespace, request) => management?.privateRequest(namespace, request) ?? false, {
@@ -4738,10 +4843,44 @@ async function startFleet(config, options2) {
4738
4843
  }
4739
4844
  }
4740
4845
 
4741
- // ../../adapters/node/dist/fleet-tools.js
4742
- import { readFile as readFile4 } from "node:fs/promises";
4846
+ // ../../adapters/node/dist/fleet-loader.js
4743
4847
  import { createRequire as createRequire2 } from "node:module";
4744
4848
  import { resolve as resolve2 } from "node:path";
4849
+ import { pathToFileURL } from "node:url";
4850
+ var suffix = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4851
+ function createInstalledServiceLoader(options2) {
4852
+ if (!options2.projectRoot)
4853
+ throw new Error("consumer projectRoot required");
4854
+ const allowed = new Set(options2.services);
4855
+ for (const name2 of allowed)
4856
+ if (!suffix.test(name2))
4857
+ throw new Error("invalid service suffix");
4858
+ const require2 = createRequire2(resolve2(options2.projectRoot, "package.json"));
4859
+ const targets = new Map((options2.targets ?? []).map((target) => [target.name, target]));
4860
+ return async (name2) => {
4861
+ if (!suffix.test(name2) || !allowed.has(name2))
4862
+ throw new Error("service is not declared in loader allowlist");
4863
+ const own2 = targets.get(name2);
4864
+ if (own2)
4865
+ return own2;
4866
+ const specifier = `@crvouga/mockingbird-service-${name2}/server`;
4867
+ let location;
4868
+ try {
4869
+ location = require2.resolve(specifier);
4870
+ } catch {
4871
+ throw new Error(`cannot load service "${name2}": install @crvouga/mockingbird-service-${name2} in the consumer project; its /server export is required`);
4872
+ }
4873
+ const mod = await import(pathToFileURL(location).href);
4874
+ if (!mod.serveTarget || mod.serveTarget.name !== name2 || ("protocol" in mod.serveTarget ? typeof mod.serveTarget.start !== "function" : typeof mod.serveTarget.create !== "function"))
4875
+ throw new Error(`${specifier} exports no matching serveTarget`);
4876
+ return mod.serveTarget;
4877
+ };
4878
+ }
4879
+
4880
+ // ../../adapters/node/dist/fleet-tools.js
4881
+ import { readFile as readFile4 } from "node:fs/promises";
4882
+ import { createRequire as createRequire3 } from "node:module";
4883
+ import { resolve as resolve3 } from "node:path";
4745
4884
 
4746
4885
  // ../../adapters/node/dist/fleet-client.js
4747
4886
  import { randomUUID as randomUUID5 } from "node:crypto";
@@ -4755,6 +4894,14 @@ var FleetClientError = class extends Error {
4755
4894
  this.details = details;
4756
4895
  }
4757
4896
  };
4897
+ var FleetClientResponseError = class extends FleetClientError {
4898
+ code;
4899
+ constructor(code, status, origin) {
4900
+ super(status, { code, body: "omitted", ...origin ? { origin } : {} });
4901
+ this.code = code;
4902
+ this.name = "FleetClientResponseError";
4903
+ }
4904
+ };
4758
4905
  var FleetClient = class {
4759
4906
  base;
4760
4907
  options;
@@ -4767,7 +4914,14 @@ var FleetClient = class {
4767
4914
  if (url.username || url.password)
4768
4915
  throw new Error("fleet client URL must not contain credentials");
4769
4916
  url.searchParams.set("namespace", options2.namespace ?? this.options.namespace ?? "default");
4917
+ const limit = options2.maxResponseBytes ?? this.options.maxResponseBytes ?? 64 * 1024 * 1024;
4918
+ if (!Number.isSafeInteger(limit) || limit < 1)
4919
+ throw new Error("invalid response byte limit");
4920
+ const deadline = AbortSignal.timeout(this.options.timeoutMs ?? 3e4);
4921
+ const signal = options2.signal ? AbortSignal.any([options2.signal, deadline]) : deadline;
4922
+ signal.throwIfAborted();
4770
4923
  const response = await (this.options.fetch ?? fetch)(url, {
4924
+ redirect: "manual",
4771
4925
  method: options2.method ?? (options2.body === void 0 ? "GET" : "POST"),
4772
4926
  headers: {
4773
4927
  "content-type": "application/json",
@@ -4775,11 +4929,71 @@ var FleetClient = class {
4775
4929
  ...options2.idempotencyKey ? { "idempotency-key": options2.idempotencyKey } : {}
4776
4930
  },
4777
4931
  ...options2.body !== void 0 ? { body: JSON.stringify(options2.body) } : {},
4778
- signal: options2.signal ?? AbortSignal.timeout(this.options.timeoutMs ?? 3e4)
4932
+ signal
4779
4933
  });
4780
- const data = await response.json();
4781
- if (!response.ok)
4782
- throw new FleetClientError(response.status, data);
4934
+ if (response.status >= 300 && response.status < 400) {
4935
+ await response.body?.cancel();
4936
+ throw new FleetClientResponseError("redirect", response.status, url.origin);
4937
+ }
4938
+ if (Number(response.headers.get("content-length")) > limit) {
4939
+ await response.body?.cancel();
4940
+ throw new FleetClientResponseError("response_too_large", response.status);
4941
+ }
4942
+ const reader = response.body?.getReader();
4943
+ const chunks = [];
4944
+ let size = 0;
4945
+ const abort = () => {
4946
+ void reader?.cancel(signal.reason).catch(() => {
4947
+ });
4948
+ };
4949
+ signal.addEventListener("abort", abort, { once: true });
4950
+ try {
4951
+ if (reader)
4952
+ while (true) {
4953
+ signal.throwIfAborted();
4954
+ const { done, value } = await reader.read();
4955
+ signal.throwIfAborted();
4956
+ if (done)
4957
+ break;
4958
+ size += value.byteLength;
4959
+ if (size > limit) {
4960
+ await reader.cancel();
4961
+ throw new FleetClientResponseError("response_too_large", response.status);
4962
+ }
4963
+ chunks.push(value);
4964
+ }
4965
+ } finally {
4966
+ signal.removeEventListener("abort", abort);
4967
+ if (signal.aborted)
4968
+ await reader?.cancel(signal.reason).catch(() => {
4969
+ });
4970
+ reader?.releaseLock();
4971
+ }
4972
+ const bytes = new Uint8Array(size);
4973
+ let offset = 0;
4974
+ for (const chunk of chunks) {
4975
+ bytes.set(chunk, offset);
4976
+ offset += chunk.byteLength;
4977
+ }
4978
+ const text = new TextDecoder().decode(bytes);
4979
+ let data;
4980
+ try {
4981
+ data = text.length ? JSON.parse(text) : void 0;
4982
+ } catch {
4983
+ if (response.ok)
4984
+ throw new FleetClientResponseError("invalid_json", response.status);
4985
+ }
4986
+ if (!response.ok) {
4987
+ const error = data && typeof data === "object" ? data.error : void 0;
4988
+ const detail = { body: "omitted" };
4989
+ if (error && typeof error === "object")
4990
+ for (const key of ["type", "code"]) {
4991
+ const value = error[key];
4992
+ if (typeof value === "string" && /^[A-Za-z0-9_.-]{1,128}$/.test(value))
4993
+ detail[key] = value;
4994
+ }
4995
+ throw new FleetClientError(response.status, detail);
4996
+ }
4783
4997
  return data;
4784
4998
  }
4785
4999
  discover() {
@@ -4845,17 +5059,52 @@ function renderConnections(manifest, privateManifest, namespace, mapping) {
4845
5059
  const child = manifest.services[entry.service], secret = privateManifest.services[entry.service];
4846
5060
  if (!child || !secret)
4847
5061
  throw new Error(`missing service mapping ${entry.service}`);
5062
+ let endpoint;
4848
5063
  if (child.protocol === "http") {
4849
5064
  const template = child.namespaces.path;
4850
5065
  if (typeof template !== "string")
4851
5066
  throw new Error(`missing HTTP namespace mapping ${entry.service}`);
4852
- result[variable] = new URL(template.replace("{name}", encodeURIComponent(namespace)), secret.url).href;
5067
+ endpoint = new URL(template.replace("{name}", encodeURIComponent(namespace)), secret.url).href;
4853
5068
  } else {
4854
- const endpoint = secret.namespaces?.[namespace];
4855
- if (!endpoint)
5069
+ const selected = secret.namespaces?.[namespace];
5070
+ if (!selected)
4856
5071
  throw new Error(`missing namespace mapping ${namespace}/${entry.service}`);
4857
- result[variable] = endpoint;
5072
+ endpoint = selected;
5073
+ }
5074
+ const url = new URL(endpoint);
5075
+ const field = entry.field ?? "url";
5076
+ let value;
5077
+ switch (field) {
5078
+ case "url":
5079
+ value = endpoint;
5080
+ break;
5081
+ case "hostname":
5082
+ value = url.hostname.replace(/^\[|\]$/g, "");
5083
+ break;
5084
+ case "port":
5085
+ value = url.port;
5086
+ break;
5087
+ case "username":
5088
+ value = decodeURIComponent(url.username);
5089
+ break;
5090
+ case "password":
5091
+ value = decodeURIComponent(url.password);
5092
+ break;
5093
+ case "pathname":
5094
+ value = decodeURIComponent(url.pathname);
5095
+ break;
5096
+ case "database":
5097
+ value = decodeURIComponent(url.pathname.replace(/^\//, ""));
5098
+ break;
5099
+ default:
5100
+ throw new Error("unsupported connection field");
5101
+ }
5102
+ if (!value) {
5103
+ if (typeof entry.fallback !== "string")
5104
+ throw new Error(`missing connection component ${field}`);
5105
+ value = entry.fallback;
4858
5106
  }
5107
+ result[variable] = value;
4859
5108
  }
4860
5109
  return result;
4861
5110
  }
@@ -4879,7 +5128,7 @@ async function doctorFleet(config, options2, probes = []) {
4879
5128
  const plan = [];
4880
5129
  try {
4881
5130
  const targets = await preflightFleet(config, options2);
4882
- const require2 = createRequire2(resolve2(process.cwd(), "package.json"));
5131
+ const require2 = createRequire3(resolve3(process.cwd(), "package.json"));
4883
5132
  for (const [service, target] of targets) {
4884
5133
  let version = "development";
4885
5134
  try {
@@ -4934,7 +5183,7 @@ async function doctorFleet(config, options2, probes = []) {
4934
5183
  };
4935
5184
  }
4936
5185
  async function rehearseFleet(config, options2, input, probes = [], timeoutMs = 3e4) {
4937
- const archive = validateArchive(input);
5186
+ const archive = assertArchiveCompatibility(input, options2.archiveCompatibility);
4938
5187
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
4939
5188
  const isolated = {
4940
5189
  ...config,
@@ -4946,7 +5195,7 @@ async function rehearseFleet(config, options2, input, probes = [], timeoutMs = 3
4946
5195
  };
4947
5196
  let fleet;
4948
5197
  try {
4949
- fleet = await startFleet(isolated, { load: options2.load });
5198
+ fleet = await startFleet(isolated, options2);
4950
5199
  const client = new FleetClient(fleet.manifest.adminBase, {
4951
5200
  ...config.adminKey ? { adminKey: config.adminKey } : {}
4952
5201
  });
@@ -5173,6 +5422,16 @@ var COMMON_SERVE_OPTIONS = {
5173
5422
  value: "<seed>",
5174
5423
  description: "Seed for every random choice (env MOCKINGBIRD_SEED)"
5175
5424
  },
5425
+ "id-base": {
5426
+ type: "string",
5427
+ value: "<n|boot>",
5428
+ description: "Start generated ids at this base so a restart does not reissue the last boot's ids; boot derives one from the clock (env MOCKINGBIRD_ID_BASE; default 0, the same ids every boot)"
5429
+ },
5430
+ settings: {
5431
+ type: "string",
5432
+ value: "<json|file>",
5433
+ description: "Service settings to boot with, as JSON or a JSON file; every namespace starts from them (env MOCKINGBIRD_SETTINGS; only for services that declare settings)"
5434
+ },
5176
5435
  "admin-controls": {
5177
5436
  type: "string",
5178
5437
  value: "<json|file>",
@@ -5267,19 +5526,40 @@ var readJsonFile = async (path, label) => {
5267
5526
  }
5268
5527
  };
5269
5528
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5529
+ var readJsonOption = async (source, label) => {
5530
+ const trimmed = source.trim();
5531
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
5532
+ try {
5533
+ return JSON.parse(trimmed);
5534
+ } catch {
5535
+ throw new Error(`${label}: not valid JSON`);
5536
+ }
5537
+ }
5538
+ return readJsonFile(source, label);
5539
+ };
5540
+ var resolveServeIdBase = (values, env = process.env) => {
5541
+ const source = asString(values["id-base"]) ?? env.MOCKINGBIRD_ID_BASE;
5542
+ if (source === void 0 || source.trim() === "")
5543
+ return void 0;
5544
+ normalizeIdBase(source.trim(), "--id-base");
5545
+ return source.trim();
5546
+ };
5547
+ var resolveServeSettings = async (target, values, env = process.env) => {
5548
+ const flag2 = asString(values.settings);
5549
+ const source = flag2 ?? env.MOCKINGBIRD_SETTINGS;
5550
+ if (source === void 0 || source.trim() === "")
5551
+ return void 0;
5552
+ const label = flag2 !== void 0 ? "--settings" : "MOCKINGBIRD_SETTINGS";
5553
+ const parsed = await readJsonOption(source, label);
5554
+ if (!isObject2(parsed))
5555
+ throw new Error(`${label}: expected a JSON object`);
5556
+ return checkSettings(target, parsed, label);
5557
+ };
5270
5558
  var resolveServeControls = async (values, env = process.env, base) => {
5271
5559
  const source = asString(values["admin-controls"]) ?? env.MOCKINGBIRD_ADMIN_CONTROLS;
5272
5560
  let document = isObject2(base) ? { ...base } : {};
5273
5561
  if (source !== void 0 && source.trim() !== "") {
5274
- let parsed;
5275
- if (source.trim().startsWith("{")) {
5276
- try {
5277
- parsed = JSON.parse(source);
5278
- } catch {
5279
- throw new Error("--admin-controls: not valid JSON");
5280
- }
5281
- } else
5282
- parsed = await readJsonFile(source, "--admin-controls");
5562
+ const parsed = source.trim().startsWith("{") ? await readJsonOption(source, "--admin-controls") : await readJsonFile(source, "--admin-controls");
5283
5563
  if (!isObject2(parsed))
5284
5564
  throw new Error("--admin-controls: expected a JSON object");
5285
5565
  document = { ...document, ...parsed };
@@ -5321,26 +5601,7 @@ var resolveServeControls = async (values, env = process.env, base) => {
5321
5601
  normalizeBootControls(document, "adminControls");
5322
5602
  return document;
5323
5603
  };
5324
- var loadTarget = async (name2, own2) => {
5325
- if (name2 === own2.name)
5326
- return own2;
5327
- const specifier = `@crvouga/mockingbird-service-${name2}/server`;
5328
- try {
5329
- let mod;
5330
- try {
5331
- mod = await import(specifier);
5332
- } catch {
5333
- const fromProject = createRequire3(resolve3(process.cwd(), "package.json"));
5334
- mod = await import(pathToFileURL(fromProject.resolve(specifier)).href);
5335
- }
5336
- if (!mod.serveTarget)
5337
- throw new Error(`${specifier} exports no serveTarget`);
5338
- return mod.serveTarget;
5339
- } catch (error) {
5340
- const reason = error instanceof Error ? error.message : String(error);
5341
- throw new Error(`cannot load service "${name2}": ${reason}. Install @crvouga/mockingbird-service-${name2}.`);
5342
- }
5343
- };
5604
+ var loadTarget = (name2, own2) => createInstalledServiceLoader({ projectRoot: process.cwd(), services: [name2], targets: [own2] })(name2);
5344
5605
  var start = async (target, values, config) => {
5345
5606
  const adminPrefix = resolveAdminPrefix(config.adminPrefix);
5346
5607
  const runtime = await target.create(values, {
@@ -5348,8 +5609,11 @@ var start = async (target, values, config) => {
5348
5609
  adminKey: config.adminKey,
5349
5610
  seed: config.seed,
5350
5611
  onLog: formatLog(config.log),
5612
+ ...config.settings !== void 0 ? { settings: config.settings } : {},
5351
5613
  ...config.adminControls !== void 0 ? { adminControls: config.adminControls } : {}
5352
5614
  });
5615
+ if (config.idBase !== void 0)
5616
+ runtime.setIdBase?.(config.idBase);
5353
5617
  const listening = await listen(runtime, { port: config.port, host: config.host });
5354
5618
  target.listening?.(listening);
5355
5619
  console.log(`${target.name} emulator listening on ${listening.url}`);
@@ -5395,6 +5659,11 @@ var serveCommand = (target) => ({
5395
5659
  const adminControls = await resolveServeControls(values, process.env, config.adminControls);
5396
5660
  if (adminControls !== void 0)
5397
5661
  config.adminControls = adminControls;
5662
+ if (values.settings !== void 0)
5663
+ throw new Error("--settings serves a single service; give each service its own `settings` in the config");
5664
+ const idBase = config.idBase ?? resolveServeIdBase(values);
5665
+ if (idBase !== void 0)
5666
+ config.idBase = idBase;
5398
5667
  const fleet = await startFleet(config, {
5399
5668
  load: (name2) => loadTarget(name2, target),
5400
5669
  onLog: formatLog(config.log ?? log2),
@@ -5424,8 +5693,12 @@ var serveCommand = (target) => ({
5424
5693
  let listening;
5425
5694
  try {
5426
5695
  const adminControls = await resolveServeControls(values);
5696
+ const idBase = resolveServeIdBase(values);
5697
+ const settings = await resolveServeSettings(target, values);
5427
5698
  listening = await start(target, values, {
5428
5699
  ...adminControls !== void 0 ? { adminControls } : {},
5700
+ ...idBase !== void 0 ? { idBase } : {},
5701
+ ...settings !== void 0 ? { settings } : {},
5429
5702
  adminPrefix: resolveAdminPrefix(asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX),
5430
5703
  port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
5431
5704
  host: asString(values.host) ?? "127.0.0.1",
@@ -5786,9 +6059,9 @@ function stemEnglish(input) {
5786
6059
  return false;
5787
6060
  })();
5788
6061
  if (!step1bDone) {
5789
- for (const suffix of ["ingly", "edly", "ing", "ed"]) {
5790
- if (w.endsWith(suffix)) {
5791
- const stem = w.slice(0, -suffix.length);
6062
+ for (const suffix2 of ["ingly", "edly", "ing", "ed"]) {
6063
+ if (w.endsWith(suffix2)) {
6064
+ const stem = w.slice(0, -suffix2.length);
5792
6065
  if ([...stem].some(isVowel)) {
5793
6066
  w = stem;
5794
6067
  if (w.endsWith("at") || w.endsWith("bl") || w.endsWith("iz")) w += "e";
@@ -5826,9 +6099,9 @@ function stemEnglish(input) {
5826
6099
  ["alli", "al"],
5827
6100
  ["bli", "ble"]
5828
6101
  ];
5829
- for (const [suffix, repl] of step2) {
5830
- if (w.endsWith(suffix)) {
5831
- if (w.length - suffix.length >= r1) w = w.slice(0, -suffix.length) + repl;
6102
+ for (const [suffix2, repl] of step2) {
6103
+ if (w.endsWith(suffix2)) {
6104
+ if (w.length - suffix2.length >= r1) w = w.slice(0, -suffix2.length) + repl;
5832
6105
  break;
5833
6106
  }
5834
6107
  }
@@ -5847,9 +6120,9 @@ function stemEnglish(input) {
5847
6120
  ["ness", ""],
5848
6121
  ["ful", ""]
5849
6122
  ];
5850
- for (const [suffix, repl] of step3) {
5851
- if (w.endsWith(suffix)) {
5852
- if (w.length - suffix.length >= r1) w = w.slice(0, -suffix.length) + repl;
6123
+ for (const [suffix2, repl] of step3) {
6124
+ if (w.endsWith(suffix2)) {
6125
+ if (w.length - suffix2.length >= r1) w = w.slice(0, -suffix2.length) + repl;
5853
6126
  break;
5854
6127
  }
5855
6128
  }
@@ -5875,9 +6148,9 @@ function stemEnglish(input) {
5875
6148
  "er",
5876
6149
  "ic"
5877
6150
  ];
5878
- for (const suffix of step4) {
5879
- if (w.endsWith(suffix)) {
5880
- if (w.length - suffix.length >= r2) w = w.slice(0, -suffix.length);
6151
+ for (const suffix2 of step4) {
6152
+ if (w.endsWith(suffix2)) {
6153
+ if (w.length - suffix2.length >= r2) w = w.slice(0, -suffix2.length);
5881
6154
  break;
5882
6155
  }
5883
6156
  }
@@ -8085,6 +8358,12 @@ var invalidName = () => {
8085
8358
  throw pgError("syntax", "invalid name syntax", "42602");
8086
8359
  };
8087
8360
  function parseNamespaceInput(text) {
8361
+ const parsed = parseRelationInput(text);
8362
+ if ("oid" in parsed) return parsed;
8363
+ if (parsed.parts.length !== 1) invalidName();
8364
+ return { name: parsed.parts[0] };
8365
+ }
8366
+ function parseRelationInput(text) {
8088
8367
  if (text === "-") return { oid: 0 };
8089
8368
  if (/^\d+$/.test(text)) return { oid: parseOidText(text) };
8090
8369
  const names = [];
@@ -8120,8 +8399,8 @@ function parseNamespaceInput(text) {
8120
8399
  skipSpace();
8121
8400
  if (at === text.length) invalidName();
8122
8401
  }
8123
- if (names.length !== 1) invalidName();
8124
- return { name: names[0] };
8402
+ if (names.length === 0 || names.length > 3) invalidName();
8403
+ return { parts: names };
8125
8404
  }
8126
8405
  function namespaceText(oid, name2) {
8127
8406
  if (oid === 0) return "-";
@@ -8194,6 +8473,7 @@ var TYPE_ALIASES = {
8194
8473
  regtype: "regtype",
8195
8474
  regproc: "regproc",
8196
8475
  regnamespace: "regnamespace",
8476
+ xid: "xid",
8197
8477
  oid: "oid",
8198
8478
  void: "void",
8199
8479
  record: "record",
@@ -8270,6 +8550,7 @@ var TYPE_OIDS = {
8270
8550
  regproc: 24,
8271
8551
  text: 25,
8272
8552
  oid: 26,
8553
+ xid: 28,
8273
8554
  json: 114,
8274
8555
  float4: 700,
8275
8556
  float8: 701,
@@ -8589,6 +8870,7 @@ function recordText(rec, ctx) {
8589
8870
  return `(${parts.join(",")})`;
8590
8871
  }
8591
8872
  function datumText(t, v, ctx) {
8873
+ if (t === "regclass") return v === 0 ? "-" : ctx.relationName?.(v) ?? String(v);
8592
8874
  if (t === "regnamespace") return namespaceText(v, ctx.namespaceName?.(v));
8593
8875
  if (v === null) throw pgError("internal", "datumText called with null");
8594
8876
  if (isArrayType(t)) return arrayText(v, ctx);
@@ -8599,6 +8881,7 @@ function datumText(t, v, ctx) {
8599
8881
  case "int2":
8600
8882
  case "int4":
8601
8883
  case "oid":
8884
+ case "xid":
8602
8885
  return String(v);
8603
8886
  case "int8":
8604
8887
  return String(v);
@@ -8663,6 +8946,12 @@ function datumText(t, v, ctx) {
8663
8946
  }
8664
8947
  var UTC_INPUT = { zoneOffsetForNaive: () => 0 };
8665
8948
  function datumFromText(t, text, ctx) {
8949
+ if (t === "regclass") {
8950
+ const parsed = parseRelationInput(text);
8951
+ if ("oid" in parsed) return parsed.oid;
8952
+ if (!ctx.relationOid) throw unsupported("regclass input without a catalog");
8953
+ return ctx.relationOid(parsed.parts);
8954
+ }
8666
8955
  if (t === "regnamespace") {
8667
8956
  const parsed = parseNamespaceInput(text);
8668
8957
  if ("oid" in parsed) return parsed.oid;
@@ -8684,6 +8973,7 @@ function datumFromText(t, text, ctx) {
8684
8973
  case "int8":
8685
8974
  return parseIntegerText(text, t);
8686
8975
  case "oid":
8976
+ case "xid":
8687
8977
  return parseOidText(text);
8688
8978
  case "float4":
8689
8979
  return Math.fround(parseFloatText(text, "float4"));
@@ -8840,6 +9130,8 @@ var TableData = class _TableData {
8840
9130
  schema;
8841
9131
  columns;
8842
9132
  rows;
9133
+ rowXmins = /* @__PURE__ */ new WeakMap();
9134
+ slabXmins = null;
8843
9135
  /** Frozen columnar storage after snapshot hydrate; `rows` stays empty until materialized. */
8844
9136
  slab = null;
8845
9137
  constraints;
@@ -8882,6 +9174,11 @@ var TableData = class _TableData {
8882
9174
  materializeSlab() {
8883
9175
  if (!this.slab) return;
8884
9176
  this.rows = this.slab.materialize();
9177
+ if (this.slabXmins)
9178
+ this.rows.forEach((row, index) => {
9179
+ this.rowXmins.set(row, this.slabXmins[index] ?? 2);
9180
+ });
9181
+ this.slabXmins = null;
8885
9182
  this.slab = null;
8886
9183
  this.indexStores = null;
8887
9184
  }
@@ -8915,6 +9212,10 @@ var TableData = class _TableData {
8915
9212
  } else {
8916
9213
  t.rows = this.rows.map((r) => r.slice());
8917
9214
  }
9215
+ t.rows.forEach((row, index) => {
9216
+ const xmin = this.slabXmins?.[index] ?? this.rowXmins.get(this.rows[index]);
9217
+ if (xmin !== void 0) t.rowXmins.set(row, xmin);
9218
+ });
8918
9219
  t.unlogged = this.unlogged;
8919
9220
  t.constraints = this.constraints.map((c) => ({ ...c }));
8920
9221
  t.triggers = this.triggers.map((tr) => ({ ...tr }));
@@ -9000,6 +9301,14 @@ var DatabaseState = class _DatabaseState {
9000
9301
  clock;
9001
9302
  changes = 0;
9002
9303
  oidCounter = 16384;
9304
+ /** Local monotonic xid allocator; 2 denotes a row from a legacy snapshot without metadata. */
9305
+ xidCounter = 3;
9306
+ transactionXid = null;
9307
+ allocateXid() {
9308
+ if (this.xidCounter > 4294967295)
9309
+ throw pgError("program_limit_exceeded", "transaction ID space exhausted; xid wraparound is not modeled", "54000");
9310
+ return this.xidCounter++;
9311
+ }
9003
9312
  inTransaction = false;
9004
9313
  /** Transaction-only events: included in rollback clones, never in exported snapshots. */
9005
9314
  pendingTriggers = [];
@@ -9213,6 +9522,8 @@ var DatabaseState = class _DatabaseState {
9213
9522
  s.sequenceCurrval = new Map(this.sequenceCurrval);
9214
9523
  s.tableStats = new Map(this.tableStats);
9215
9524
  s.oidCounter = this.oidCounter;
9525
+ s.xidCounter = this.xidCounter;
9526
+ s.transactionXid = this.transactionXid;
9216
9527
  for (const [name2, overloads] of this.systemFunctions) s.systemFunctions.set(name2, overloads.slice());
9217
9528
  return s;
9218
9529
  }
@@ -9232,6 +9543,8 @@ var DatabaseState = class _DatabaseState {
9232
9543
  s.sequenceCurrval = new Map(this.sequenceCurrval);
9233
9544
  s.tableStats = new Map(this.tableStats);
9234
9545
  s.oidCounter = this.oidCounter;
9546
+ s.xidCounter = this.xidCounter;
9547
+ s.transactionXid = this.transactionXid;
9235
9548
  for (const [name2, overloads] of this.systemFunctions) s.systemFunctions.set(name2, overloads.slice());
9236
9549
  return s;
9237
9550
  }
@@ -9307,6 +9620,8 @@ var DatabaseState = class _DatabaseState {
9307
9620
  this.sequenceCurrval = other.sequenceCurrval;
9308
9621
  this.tableStats = other.tableStats;
9309
9622
  this.oidCounter = other.oidCounter;
9623
+ this.xidCounter = Math.max(this.xidCounter, other.xidCounter);
9624
+ this.transactionXid = other.transactionXid;
9310
9625
  }
9311
9626
  /** @internal Snapshot codec access to the oid allocator. */
9312
9627
  snapshotOidCounter() {
@@ -9386,6 +9701,11 @@ function validateZone(zone) {
9386
9701
  // src/expressions/context.ts
9387
9702
  var EngineCtx = class {
9388
9703
  state;
9704
+ writeXid = null;
9705
+ rowVersionXid() {
9706
+ if (this.state.inTransaction) return this.state.transactionXid ??= this.state.allocateXid();
9707
+ return this.writeXid ??= this.state.allocateXid();
9708
+ }
9389
9709
  /** transaction timestamp (now(), current_timestamp) — UTC micros since PG epoch */
9390
9710
  txNow;
9391
9711
  /** statement timestamp */
@@ -9400,6 +9720,23 @@ var EngineCtx = class {
9400
9720
  timezone() {
9401
9721
  return this.state.getSetting("timezone") ?? "UTC";
9402
9722
  }
9723
+ relationOid(parts) {
9724
+ const target = this.state.resolveRelationSchema(parts);
9725
+ const schema = target ? this.state.schemas.get(target.schema) : void 0;
9726
+ const relation = target ? schema?.sequences.get(target.name) ?? schema?.tables.get(target.name) ?? schema?.views.get(target.name) : void 0;
9727
+ if (!relation) throw pgError("undefined_table", `relation "${parts.join(".")}" does not exist`, "42P01");
9728
+ return relation.oid;
9729
+ }
9730
+ relationName(oid) {
9731
+ for (const schema of this.state.schemas.values()) {
9732
+ for (const relation of [...schema.tables.values(), ...schema.sequences.values(), ...schema.views.values()]) {
9733
+ if (relation.oid !== oid) continue;
9734
+ const visible = this.state.resolveRelationSchema([relation.name])?.schema === schema.name;
9735
+ return (visible ? [relation.name] : [schema.name, relation.name]).map(quoteIdentifier).join(".");
9736
+ }
9737
+ }
9738
+ return null;
9739
+ }
9403
9740
  namespaceOid(name2) {
9404
9741
  const oid = builtinNamespaceOid(name2) ?? this.state.schemas.get(name2)?.oid;
9405
9742
  if (oid === void 0) throw pgError("undefined_object", `schema "${name2}" does not exist`, "3F000");
@@ -9700,7 +10037,7 @@ function castTo(env, value, target, opts = {}) {
9700
10037
  return tv(target, makeArray(toElem, items, arr.dims.slice(), arr.lbs.slice()));
9701
10038
  }
9702
10039
  if (from === "oid" && target === "regnamespace") return tv(target, v);
9703
- if (from === "regnamespace" && (target === "oid" || target === "int4" || target === "int8")) {
10040
+ if ((from === "regnamespace" || from === "regclass") && (target === "oid" || target === "int4" || target === "int8")) {
9704
10041
  return tv(target, target === "int8" ? BigInt(v) : v);
9705
10042
  }
9706
10043
  if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regnamespace")) {
@@ -10401,6 +10738,8 @@ function datumCompare(t, a, b, ctx = DEFAULT_COMPARE_CTX) {
10401
10738
  case "int2":
10402
10739
  case "int4":
10403
10740
  case "oid":
10741
+ case "regclass":
10742
+ case "xid":
10404
10743
  case "regnamespace":
10405
10744
  case "date":
10406
10745
  return expectNumber(a, t) - expectNumber(b, t);
@@ -10520,6 +10859,8 @@ function datumKey(t, v) {
10520
10859
  case "int2":
10521
10860
  case "int4":
10522
10861
  case "oid":
10862
+ case "regclass":
10863
+ case "xid":
10523
10864
  case "date":
10524
10865
  return `i${v}`;
10525
10866
  case "float4":
@@ -11403,8 +11744,8 @@ function toCharTimestamp(ctx, localMicros, fmt, tzOffsetSec) {
11403
11744
  else if (tryPat("TH") || tryPat("th")) {
11404
11745
  const prev = /(\d+)$/.exec(out);
11405
11746
  const n = prev ? Number.parseInt(prev[1], 10) : 0;
11406
- const suffix = ordinalSuffix(n);
11407
- out += rest.startsWith("TH") ? suffix.toUpperCase() : suffix;
11747
+ const suffix2 = ordinalSuffix(n);
11748
+ out += rest.startsWith("TH") ? suffix2.toUpperCase() : suffix2;
11408
11749
  } else {
11409
11750
  out += fmt[i];
11410
11751
  i++;
@@ -12623,7 +12964,7 @@ function claimConstraintIndexName(schema, constraint, explicit, reserved = /* @_
12623
12964
  if (used(constraint.name)) {
12624
12965
  if (explicit) throw pgError("duplicate_table", `relation "${constraint.name}" already exists`, "42P07");
12625
12966
  const base = constraint.name;
12626
- for (let suffix = 1; used(constraint.name); suffix++) constraint.name = `${base}${suffix}`;
12967
+ for (let suffix2 = 1; used(constraint.name); suffix2++) constraint.name = `${base}${suffix2}`;
12627
12968
  }
12628
12969
  reserved.add(constraint.name);
12629
12970
  }
@@ -13428,7 +13769,7 @@ function setDatabaseCatalogContext(state, context) {
13428
13769
  var PG_VERSION_TEXT = "PostgreSQL 18.3 (postgres-mem) on TypeScript, in-memory engine";
13429
13770
  function findSequenceForCall(ctx, name2) {
13430
13771
  const parts = parseQualifiedName(name2);
13431
- const seq = ctx.state.findSequence(parts);
13772
+ const seq = /^\d+$/.test(name2) ? [...ctx.state.schemas.values()].flatMap((schema) => [...schema.sequences.values()]).find((sequence) => sequence.oid === Number(name2)) : ctx.state.findSequence(parts);
13432
13773
  if (!seq) {
13433
13774
  throw pgError("undefined_table", `relation "${name2}" does not exist`, "42P01");
13434
13775
  }
@@ -13652,14 +13993,16 @@ function getMiscFunctions() {
13652
13993
  "pg_get_serial_sequence",
13653
13994
  strict("text", (ctx, args2) => {
13654
13995
  const tableName2 = argText(ctx, args2[0]);
13655
- const column = argText(ctx, args2[1]).toLowerCase();
13996
+ const column = argText(ctx, args2[1]);
13656
13997
  const table = ctx.state.findTable(parseQualifiedName(tableName2));
13657
13998
  if (!table) throw pgError("undefined_table", `relation "${tableName2}" does not exist`, "42P01");
13658
13999
  const col = table.columns.find((c) => c.name === column);
13659
14000
  if (!col)
13660
14001
  throw pgError("undefined_column", `column "${column}" of relation "${table.name}" does not exist`, "42703");
13661
- if (!col.identity) return tv("text", null);
13662
- return tv("text", col.identity.sequence.split(".").map(quoteIdent).join("."));
14002
+ const seq = [...ctx.state.schemas.get(table.schema)?.sequences.values() ?? []].find(
14003
+ (candidate) => candidate.ownedBy?.table === table.name && candidate.ownedBy.column === column
14004
+ );
14005
+ return tv("text", seq ? [seq.schema, seq.name].map(quoteIdent).join(".") : null);
13663
14006
  })
13664
14007
  );
13665
14008
  m.set("format_type", (ctx, args2) => {
@@ -15177,7 +15520,7 @@ function evalTrgmWordSimilar(ctx, left, right) {
15177
15520
  const score = wordSimilarity(a.v, b.v);
15178
15521
  return tv("bool", score >= WORD_SIMILARITY_THRESHOLD);
15179
15522
  }
15180
- function assertGinTrgmIndex(state, table, stmt) {
15523
+ function assertGinTrgmIndex(state, table, stmt, expressionType) {
15181
15524
  const trgmCols = stmt.columns.filter((col2) => isGinTrgmOpclass(col2.opclass));
15182
15525
  if (trgmCols.length === 0) return;
15183
15526
  if (trgmCols.length !== stmt.columns.length || stmt.columns.length !== 1) {
@@ -15197,11 +15540,7 @@ function assertGinTrgmIndex(state, table, stmt) {
15197
15540
  if (stmt.unique) {
15198
15541
  throw pgError("feature_not_supported", `access method "gin" does not support unique indexes`, "0A000");
15199
15542
  }
15200
- if (col.expr.type !== "colref" || col.expr.parts.length !== 1) {
15201
- throw unsupported("expression gin_trgm_ops");
15202
- }
15203
- const index = table.columnIndex(col.expr.parts[0]);
15204
- const typeId = table.columns[index].type.id;
15543
+ const typeId = col.expr.type === "colref" && col.expr.parts.length === 1 ? table.columns[table.columnIndex(col.expr.parts[0])].type.id : expressionType(col.expr);
15205
15544
  if (!GIN_TRGM_TYPES.has(typeId)) {
15206
15545
  throw pgError(
15207
15546
  "datatype_mismatch",
@@ -18603,7 +18942,7 @@ var RowScope = class {
18603
18942
  let count2 = 0;
18604
18943
  for (let i = 0; i < this.columns.length; i++) {
18605
18944
  const c = this.columns[i];
18606
- if (c.name === name3 && !c.hidden) {
18945
+ if (c.name === name3 && (!c.hidden || c.system)) {
18607
18946
  count2++;
18608
18947
  if (count2 > 1) {
18609
18948
  throw pgError("ambiguous_column", `column reference "${name3}" is ambiguous`);
@@ -18641,7 +18980,7 @@ var RowScope = class {
18641
18980
  const names = [];
18642
18981
  for (let i = 0; i < this.columns.length; i++) {
18643
18982
  const c = this.columns[i];
18644
- if (c.table === rangeVar) {
18983
+ if (c.table === rangeVar && !c.system) {
18645
18984
  types.push(c.type);
18646
18985
  values.push(this.row[i] ?? null);
18647
18986
  names.push(c.name);
@@ -18839,6 +19178,17 @@ function tableScope(table, row) {
18839
19178
  const cols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: table.name }));
18840
19179
  return new RowScope(cols, row, null, /* @__PURE__ */ new Set([table.name]));
18841
19180
  }
19181
+ function expressionTypeOf(env, table, expr) {
19182
+ const scope = makeEvalScope(
19183
+ env,
19184
+ tableScope(
19185
+ table,
19186
+ table.columns.map(() => null)
19187
+ )
19188
+ );
19189
+ const v = evalExpr(env.ctx, scope, expr);
19190
+ return v.t === "unknown" ? "text" : v.t;
19191
+ }
18842
19192
  function checkNotNull(env, table, row) {
18843
19193
  void env;
18844
19194
  for (let i = 0; i < table.columns.length; i++) {
@@ -19084,7 +19434,7 @@ function tryIndexedFromItem(env, item, where) {
19084
19434
  if (!table) return null;
19085
19435
  const alias = item.alias ?? item.name[item.name.length - 1];
19086
19436
  table.materializeSlab();
19087
- return tryIndexedTableRows(env, table, alias, where);
19437
+ return tryIndexedTableRows(env, table, alias, where)?.map((row) => [...row, table.rowXmins.get(row) ?? 2]) ?? null;
19088
19438
  }
19089
19439
  function joinKeyFromRow(row, colIdxs, srcTypes, unifiedTypes, ctx) {
19090
19440
  const parts = [];
@@ -21574,6 +21924,7 @@ var Parser = class {
21574
21924
  let sqlBody = null;
21575
21925
  let volatility = null;
21576
21926
  let strict2 = false;
21927
+ const settings = {};
21577
21928
  for (; ; ) {
21578
21929
  if (this.eatKw("language")) {
21579
21930
  language = this.peek().type === "string" ? this.next().value : this.ident(true);
@@ -21631,8 +21982,23 @@ var Parser = class {
21631
21982
  continue;
21632
21983
  }
21633
21984
  if (this.eatKw("set")) {
21634
- this.ident(true);
21635
- if (this.eatOp("=") || this.eatKw("to")) this.next();
21985
+ const key = this.ident(true).toLowerCase();
21986
+ if (this.eatKw("from")) {
21987
+ this.expectKw("current");
21988
+ settings[key] = "__from_current__";
21989
+ } else {
21990
+ if (!this.eatOp("=")) this.expectKw("to");
21991
+ const values = [];
21992
+ do {
21993
+ const value = this.next();
21994
+ if (!["string", "ident", "quoted_ident", "number"].includes(value.type))
21995
+ this.errorAt(value, "expected setting value");
21996
+ values.push(
21997
+ key === "search_path" && /[^a-z0-9_]/.test(value.value) ? `"${value.value.replaceAll('"', '""')}"` : value.value
21998
+ );
21999
+ } while (this.eatPunct(","));
22000
+ settings[key] = values.join(", ");
22001
+ }
21636
22002
  continue;
21637
22003
  }
21638
22004
  if (this.eatKw("window")) throw unsupported("window functions via CREATE FUNCTION");
@@ -21712,7 +22078,8 @@ var Parser = class {
21712
22078
  body,
21713
22079
  sqlBody,
21714
22080
  volatility,
21715
- strict: strict2
22081
+ strict: strict2,
22082
+ settings
21716
22083
  };
21717
22084
  }
21718
22085
  parseCreateTrigger(orReplace, constraint) {
@@ -21744,7 +22111,6 @@ var Parser = class {
21744
22111
  } while (this.eatKw("or"));
21745
22112
  this.expectKw("on");
21746
22113
  const table = this.qualifiedName();
21747
- if (this.eatKw("referencing")) throw unsupported("trigger transition tables");
21748
22114
  let deferrable = false;
21749
22115
  let initiallyDeferred = false;
21750
22116
  if (this.eatKw("not")) this.expectKw("deferrable");
@@ -21757,6 +22123,23 @@ var Parser = class {
21757
22123
  this.errorAt(this.peek(), "only constraint triggers can be deferred");
21758
22124
  if (initiallyDeferred && !deferrable)
21759
22125
  this.errorAt(this.peek(), "constraint declared INITIALLY DEFERRED must be DEFERRABLE");
22126
+ const transitions = [];
22127
+ if (!constraint && this.eatKw("referencing")) {
22128
+ do {
22129
+ const isNew = this.eatKw("new");
22130
+ if (!isNew) this.expectKw("old");
22131
+ if (this.eatKw("row")) {
22132
+ throw pgError(
22133
+ "feature_not_supported",
22134
+ "ROW variable naming in the REFERENCING clause is not supported",
22135
+ "0A000"
22136
+ );
22137
+ }
22138
+ this.expectKw("table");
22139
+ this.eatKw("as");
22140
+ transitions.push({ isNew, name: this.ident() });
22141
+ } while (this.atKw("new") || this.atKw("old"));
22142
+ }
21760
22143
  let forEachRow = false;
21761
22144
  if (this.eatKw("for")) {
21762
22145
  this.eatKw("each");
@@ -21794,7 +22177,8 @@ var Parser = class {
21794
22177
  forEachRow,
21795
22178
  when,
21796
22179
  funcName,
21797
- funcArgs
22180
+ funcArgs,
22181
+ ...transitions.length > 0 ? { transitions } : {}
21798
22182
  };
21799
22183
  }
21800
22184
  // --- ALTER --------------------------------------------------------------------
@@ -21821,8 +22205,8 @@ var Parser = class {
21821
22205
  type: "alter_sequence",
21822
22206
  name: name2,
21823
22207
  ifExists,
21824
- options: { ownedBy: void 0 },
21825
- ...{ renameTo: to }
22208
+ options: {},
22209
+ renameTo: to
21826
22210
  };
21827
22211
  }
21828
22212
  if (this.atKw("set") && (this.atKw("logged", 1) || this.atKw("unlogged", 1))) {
@@ -24398,7 +24782,20 @@ function runFunctionBody(env, fn, args2) {
24398
24782
  }
24399
24783
  return last;
24400
24784
  }
24785
+ function withFunctionSettings(env, fn, run) {
24786
+ const state = env.ctx.state;
24787
+ const saved = new Map(state.localSettings);
24788
+ for (const [name2, value] of Object.entries(fn.settings ?? {})) state.localSettings.set(name2, value);
24789
+ try {
24790
+ return run();
24791
+ } finally {
24792
+ state.localSettings = saved;
24793
+ }
24794
+ }
24401
24795
  function callSqlFunctionScalar(env, fn, args2) {
24796
+ return withFunctionSettings(env, fn, () => callSqlFunctionScalarBody(env, fn, args2));
24797
+ }
24798
+ function callSqlFunctionScalarBody(env, fn, args2) {
24402
24799
  const retT = fn.returns ?? "text";
24403
24800
  if (fn.language === "plpgsql") {
24404
24801
  return callPlpgsqlScalar(env, fn, args2);
@@ -24428,6 +24825,9 @@ function callSqlFunctionScalar(env, fn, args2) {
24428
24825
  return castTo(env.ctx, tv(rawT, raw), retT, {});
24429
24826
  }
24430
24827
  function callSqlFunctionSet(env, fn, args2) {
24828
+ return withFunctionSettings(env, fn, () => callSqlFunctionSetBody(env, fn, args2));
24829
+ }
24830
+ function callSqlFunctionSetBody(env, fn, args2) {
24431
24831
  if (fn.language === "plpgsql") {
24432
24832
  return callPlpgsqlSet(env, fn, args2);
24433
24833
  }
@@ -24582,15 +24982,17 @@ function executeRecursiveCte(env, cte, query) {
24582
24982
  return rel3;
24583
24983
  }
24584
24984
  function renameWithAliases(rel3, label, colAliases, what) {
24585
- if (colAliases && colAliases.length > rel3.columns.length) {
24985
+ const visibleWidth = rel3.columns.filter((column) => !column.system).length;
24986
+ if (colAliases && colAliases.length > visibleWidth) {
24586
24987
  throw pgError(
24587
24988
  "invalid_column_reference",
24588
- `table "${what}" has ${rel3.columns.length} columns available but ${colAliases.length} columns specified`,
24989
+ `table "${what}" has ${visibleWidth} columns available but ${colAliases.length} columns specified`,
24589
24990
  "42P10"
24590
24991
  );
24591
24992
  }
24592
24993
  const columns = rel3.columns.map((c, i) => ({
24593
- name: colAliases?.[i] ?? c.name,
24994
+ ...c,
24995
+ name: c.system ? c.name : colAliases?.[i] ?? c.name,
24594
24996
  type: c.type === UNKNOWN ? "text" : c.type,
24595
24997
  table: label
24596
24998
  }));
@@ -24611,8 +25013,11 @@ function materializeItem(env, item, scope) {
24611
25013
  if (table) {
24612
25014
  table.materializeSlab();
24613
25015
  const rel3 = {
24614
- columns: table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label })),
24615
- rows: table.rows
25016
+ columns: [
25017
+ ...table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label })),
25018
+ { name: "xmin", type: "xid", table: label, hidden: true, system: true }
25019
+ ],
25020
+ rows: table.rows.map((row) => [...row, table.rowXmins.get(row) ?? 2])
24616
25021
  };
24617
25022
  return { rel: renameWithAliases(rel3, label, item.colAliases, label), rangeVars: /* @__PURE__ */ new Set([label]) };
24618
25023
  }
@@ -25242,7 +25647,7 @@ function resolveColIdx(columns, parts) {
25242
25647
  let found = -1;
25243
25648
  for (let i = 0; i < columns.length; i++) {
25244
25649
  const c = columns[i];
25245
- if (!c.hidden && c.name === name3) {
25650
+ if ((!c.hidden || c.system) && c.name === name3) {
25246
25651
  if (found !== -1) return null;
25247
25652
  found = i;
25248
25653
  }
@@ -25826,7 +26231,7 @@ function expandTargets(core, columns) {
25826
26231
  let any = false;
25827
26232
  for (let i = 0; i < columns.length; i++) {
25828
26233
  const c = columns[i];
25829
- if (c.table === label) {
26234
+ if (c.table === label && !c.system) {
25830
26235
  any = true;
25831
26236
  items.push({ name: c.name, kind: "col", colIdx: i, expr: null, hidden: false });
25832
26237
  }
@@ -26319,6 +26724,29 @@ function fireRowTriggers(env, table, timing, event, oldRow, newRow) {
26319
26724
  }
26320
26725
  return { row };
26321
26726
  }
26727
+ function transitionRelation(table, name2, rows) {
26728
+ return {
26729
+ columns: table.columns.map((c) => ({ name: c.name, type: c.type.id, table: name2 })),
26730
+ rows
26731
+ };
26732
+ }
26733
+ function fireStatementTriggers(env, table, timing, event, transitions) {
26734
+ const fired = table.triggers.filter((t) => !t.forEachRow && t.timing === timing && matches(t, event)).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
26735
+ for (const t of fired) {
26736
+ if (!executor) throw unsupported(`trigger "${t.name}" execution`);
26737
+ const ctes = /* @__PURE__ */ new Map();
26738
+ if (t.oldTable !== void 0)
26739
+ ctes.set(t.oldTable, transitionRelation(table, t.oldTable, transitions?.oldRows ?? []));
26740
+ if (t.newTable !== void 0)
26741
+ ctes.set(t.newTable, transitionRelation(table, t.newTable, transitions?.newRows ?? []));
26742
+ const changes = env.ctx.state.changes;
26743
+ try {
26744
+ executor({ ctx: env.ctx, params: null, ctes, outer: null }, table, t, event, null, null);
26745
+ } finally {
26746
+ env.ctx.state.changes = changes;
26747
+ }
26748
+ }
26749
+ }
26322
26750
  var keyOf = (schema, name2) => JSON.stringify([schema, name2]);
26323
26751
  function flushConstraintTriggers(state, all = false) {
26324
26752
  const env = { ctx: new EngineCtx(state), params: null, ctes: /* @__PURE__ */ new Map(), outer: null };
@@ -26431,6 +26859,7 @@ var TransactionManager = class {
26431
26859
  this.base = null;
26432
26860
  this.savepoints = [];
26433
26861
  this.state.inTransaction = false;
26862
+ this.state.transactionXid = null;
26434
26863
  this.state.localSettings.clear();
26435
26864
  this.state.pendingTriggers = [];
26436
26865
  this.state.constraintModes.clear();
@@ -26443,6 +26872,7 @@ var TransactionManager = class {
26443
26872
  this.base = null;
26444
26873
  this.savepoints = [];
26445
26874
  this.state.inTransaction = false;
26875
+ this.state.transactionXid = null;
26446
26876
  this.state.localSettings.clear();
26447
26877
  this.state.pendingTriggers = [];
26448
26878
  this.state.constraintModes.clear();
@@ -26477,6 +26907,7 @@ var TransactionManager = class {
26477
26907
  this.base = null;
26478
26908
  this.savepoints = [];
26479
26909
  this.state.inTransaction = false;
26910
+ this.state.transactionXid = null;
26480
26911
  this.freezeDepth = 0;
26481
26912
  }
26482
26913
  };
@@ -26680,6 +27111,7 @@ function applyOnConflictUpdate(env, table, label, existingIdx, newRow, sets, whe
26680
27111
  const updated = existing.slice();
26681
27112
  applyUpdateSets(env, table, sets, scope, updated);
26682
27113
  computeGeneratedColumns(env, table, updated);
27114
+ table.rowXmins.set(updated, env.ctx.rowVersionXid());
26683
27115
  rows[existingIdx] = updated;
26684
27116
  indexUpdateRow(env, table, existingIdx, existing, updated);
26685
27117
  checkNotNull(env, table, updated);
@@ -26698,7 +27130,13 @@ function executeInsert(env0, stmt) {
26698
27130
  const sourceRows = insertSourceRows(env, stmt, colIdxs);
26699
27131
  const insertedRows = [];
26700
27132
  let insertedCount = 0;
27133
+ const addedRows = [];
27134
+ const conflictOldRows = [];
27135
+ const conflictNewRows = [];
27136
+ const updatesOnConflict = stmt.onConflict !== null && stmt.onConflict.action !== "nothing";
26701
27137
  return withStatementRollback(env, () => {
27138
+ fireStatementTriggers(env, table, "before", "insert");
27139
+ if (updatesOnConflict) fireStatementTriggers(env, table, "before", "update");
26702
27140
  for (const srcRow of sourceRows) {
26703
27141
  const row = table.columns.map(() => null);
26704
27142
  const provided = /* @__PURE__ */ new Set();
@@ -26744,6 +27182,7 @@ function executeInsert(env0, stmt) {
26744
27182
  }
26745
27183
  if (conflictIdx !== null && conflictWhereOk) {
26746
27184
  if (stmt.onConflict.action === "nothing") continue;
27185
+ const replaced = rows[conflictIdx];
26747
27186
  const did = applyOnConflictUpdate(
26748
27187
  env,
26749
27188
  table,
@@ -26756,6 +27195,8 @@ function executeInsert(env0, stmt) {
26756
27195
  if (did) {
26757
27196
  insertedCount++;
26758
27197
  insertedRows.push(rows[conflictIdx]);
27198
+ conflictOldRows.push(replaced);
27199
+ conflictNewRows.push(rows[conflictIdx]);
26759
27200
  fireRowTriggers(env, table, "after", "update", null, rows[conflictIdx]);
26760
27201
  }
26761
27202
  continue;
@@ -26765,12 +27206,18 @@ function executeInsert(env0, stmt) {
26765
27206
  checkChecks(env, table, newRow);
26766
27207
  checkUnique(env, table, newRow, rows.length);
26767
27208
  checkForeignKeys(env, table, newRow);
27209
+ table.rowXmins.set(newRow, env.ctx.rowVersionXid());
26768
27210
  rows.push(newRow);
26769
27211
  indexInsertRow(env, table, rows.length - 1, newRow);
26770
27212
  insertedCount++;
26771
27213
  insertedRows.push(newRow);
27214
+ addedRows.push(newRow);
26772
27215
  fireRowTriggers(env, table, "after", "insert", null, newRow);
26773
27216
  }
27217
+ fireStatementTriggers(env, table, "after", "insert", { oldRows: [], newRows: addedRows });
27218
+ if (updatesOnConflict) {
27219
+ fireStatementTriggers(env, table, "after", "update", { oldRows: conflictOldRows, newRows: conflictNewRows });
27220
+ }
26774
27221
  env.ctx.state.changes = insertedCount;
26775
27222
  if (stmt.returning) {
26776
27223
  const res = evalReturning(env, table, label, stmt.returning, insertedRows, "INSERT");
@@ -26851,10 +27298,12 @@ function executeUpdate(env0, stmt) {
26851
27298
  const env = applyWith(env0, stmt.with);
26852
27299
  const table = requireTargetTable(env, stmt.table, "update");
26853
27300
  const label = stmt.alias ?? table.name;
27301
+ fireStatementTriggers(env, table, "before", "update");
26854
27302
  const fromSource = stmt.from.length > 0 ? buildFrom(env, stmt.from) : null;
26855
27303
  const rows = table.mutableRows();
26856
27304
  const targetCols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label }));
26857
27305
  const updatedRows = [];
27306
+ const replacedRows = [];
26858
27307
  let updateCount = 0;
26859
27308
  for (let ri = 0; ri < rows.length; ri++) {
26860
27309
  const oldRow = rows[ri];
@@ -26882,6 +27331,7 @@ function executeUpdate(env0, stmt) {
26882
27331
  if (fired.row === null) continue;
26883
27332
  const finalRow = fired.row;
26884
27333
  computeGeneratedColumns(env, table, finalRow);
27334
+ table.rowXmins.set(finalRow, env.ctx.rowVersionXid());
26885
27335
  rows[ri] = finalRow;
26886
27336
  try {
26887
27337
  checkNotNull(env, table, finalRow);
@@ -26896,8 +27346,10 @@ function executeUpdate(env0, stmt) {
26896
27346
  handleReferencedUpdate(env, table, [oldRow], [finalRow]);
26897
27347
  updateCount++;
26898
27348
  updatedRows.push(finalRow);
27349
+ replacedRows.push(oldRow);
26899
27350
  fireRowTriggers(env, table, "after", "update", oldRow, finalRow);
26900
27351
  }
27352
+ fireStatementTriggers(env, table, "after", "update", { oldRows: replacedRows, newRows: updatedRows });
26901
27353
  env.ctx.state.changes = updateCount;
26902
27354
  if (stmt.returning) {
26903
27355
  const res = evalReturning(env, table, label, stmt.returning, updatedRows, "UPDATE");
@@ -26909,6 +27361,7 @@ function executeDelete(env0, stmt) {
26909
27361
  const env = applyWith(env0, stmt.with);
26910
27362
  const table = requireTargetTable(env, stmt.table, "delete from");
26911
27363
  const label = stmt.alias ?? table.name;
27364
+ fireStatementTriggers(env, table, "before", "delete");
26912
27365
  const rows = table.mutableRows();
26913
27366
  const usingSource = stmt.using.length > 0 ? buildFrom(env, stmt.using) : null;
26914
27367
  const targetCols = table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label }));
@@ -26946,6 +27399,7 @@ function executeDelete(env0, stmt) {
26946
27399
  fireRowTriggers(env, table, "after", "delete", row, null);
26947
27400
  }
26948
27401
  if (deletedRows.length > 0) rebuildTableIndexes(env, table);
27402
+ fireStatementTriggers(env, table, "after", "delete", { oldRows: deletedRows, newRows: [] });
26949
27403
  env.ctx.state.changes = deletedRows.length;
26950
27404
  if (stmt.returning) {
26951
27405
  const res = evalReturning(env, table, label, stmt.returning, deletedRows, "DELETE");
@@ -27001,6 +27455,7 @@ function handleReferencedDelete(env, table, row, depth) {
27001
27455
  for (const c of con.columns) {
27002
27456
  newRow[child.columnIndex(c)] = null;
27003
27457
  }
27458
+ child.rowXmins.set(newRow, env.ctx.rowVersionXid());
27004
27459
  childRows[i] = newRow;
27005
27460
  checkNotNull(env, child, newRow);
27006
27461
  checkChecks(env, child, newRow);
@@ -27014,6 +27469,7 @@ function handleReferencedDelete(env, table, row, depth) {
27014
27469
  const ci = child.columnIndex(c);
27015
27470
  newRow[ci] = columnDefault(env, child, child.columns[ci]);
27016
27471
  }
27472
+ child.rowXmins.set(newRow, env.ctx.rowVersionXid());
27017
27473
  childRows[i] = newRow;
27018
27474
  checkNotNull(env, child, newRow);
27019
27475
  checkChecks(env, child, newRow);
@@ -27051,6 +27507,7 @@ function handleReferencedUpdate(env, table, oldRows, newRows) {
27051
27507
  const refIdx = table.columnIndex(con.refColumns[c]);
27052
27508
  newRow[localIdx] = newRows[r][refIdx] ?? null;
27053
27509
  }
27510
+ child.rowXmins.set(newRow, env.ctx.rowVersionXid());
27054
27511
  childRows[i] = newRow;
27055
27512
  checkChecks(env, child, newRow);
27056
27513
  }
@@ -27060,6 +27517,7 @@ function handleReferencedUpdate(env, table, oldRows, newRows) {
27060
27517
  for (const i of idxs) {
27061
27518
  const newRow = childRows[i].slice();
27062
27519
  for (const c of con.columns) newRow[child.columnIndex(c)] = null;
27520
+ child.rowXmins.set(newRow, env.ctx.rowVersionXid());
27063
27521
  childRows[i] = newRow;
27064
27522
  checkNotNull(env, child, newRow);
27065
27523
  }
@@ -27072,6 +27530,7 @@ function handleReferencedUpdate(env, table, oldRows, newRows) {
27072
27530
  const ci = child.columnIndex(c);
27073
27531
  newRow[ci] = columnDefault(env, child, child.columns[ci]);
27074
27532
  }
27533
+ child.rowXmins.set(newRow, env.ctx.rowVersionXid());
27075
27534
  childRows[i] = newRow;
27076
27535
  checkForeignKeys(env, child, newRow);
27077
27536
  }
@@ -29083,7 +29542,7 @@ function pgTrigger(ctx) {
29083
29542
  let oid = 4e4;
29084
29543
  for (const t of allTables(ctx.state)) {
29085
29544
  for (const tr of t.triggers) {
29086
- rows.push([oid++, tr.name, t.oid, !tr.forEachRow]);
29545
+ rows.push([oid++, tr.name, t.oid, !tr.forEachRow, tr.oldTable ?? null, tr.newTable ?? null]);
29087
29546
  }
29088
29547
  }
29089
29548
  return rel2(
@@ -29091,7 +29550,9 @@ function pgTrigger(ctx) {
29091
29550
  ["oid", "oid"],
29092
29551
  ["tgname", "name"],
29093
29552
  ["tgrelid", "oid"],
29094
- ["tgisinternal", "bool"]
29553
+ ["tgisinternal", "bool"],
29554
+ ["tgoldtable", "name"],
29555
+ ["tgnewtable", "name"]
29095
29556
  ],
29096
29557
  rows,
29097
29558
  "pg_trigger"
@@ -30119,13 +30580,63 @@ function executeCreateSequence(env, stmt) {
30119
30580
  schema.sequences.set(name2, seq);
30120
30581
  return commandResult("CREATE SEQUENCE", 0);
30121
30582
  }
30583
+ function renameSequenceReferences(env, seq, newName) {
30584
+ const state = env.ctx.state;
30585
+ const matches2 = (value) => {
30586
+ try {
30587
+ const input = parseRelationInput(value);
30588
+ if ("oid" in input) return input.oid === seq.oid;
30589
+ return state.findSequence(input.parts)?.oid === seq.oid;
30590
+ } catch {
30591
+ return false;
30592
+ }
30593
+ };
30594
+ const rewrite = (value) => {
30595
+ if (Array.isArray(value)) return value.map(rewrite);
30596
+ if (value === null || typeof value !== "object") return value;
30597
+ const node = value;
30598
+ const updated = Object.fromEntries(Object.entries(node).map(([key, child]) => [key, rewrite(child)]));
30599
+ const rewriteLiteral = (literal2) => {
30600
+ if (literal2 && typeof literal2 === "object" && "type" in literal2 && literal2.type === "string_lit" && "value" in literal2 && typeof literal2.value === "string" && matches2(literal2.value)) {
30601
+ return { ...literal2, value: [seq.schema, newName].map(quoteIdent).join(".") };
30602
+ }
30603
+ return literal2;
30604
+ };
30605
+ if (node.type === "func" && Array.isArray(node.name) && ["nextval", "currval", "setval"].includes(String(node.name.at(-1))) && Array.isArray(updated.args))
30606
+ updated.args[0] = rewriteLiteral(updated.args[0]);
30607
+ if (node.type === "cast" && node.target && typeof node.target === "object" && "parts" in node.target && Array.isArray(node.target.parts) && node.target.parts.at(-1) === "regclass")
30608
+ updated.expr = rewriteLiteral(updated.expr);
30609
+ return updated;
30610
+ };
30611
+ for (const schema of state.schemas.values()) {
30612
+ for (const table of [...schema.tables.values()]) {
30613
+ const writable = state.ensureWritableTable(table);
30614
+ for (const column of writable.columns) {
30615
+ if (column.defaultExpr) column.defaultExpr = rewrite(column.defaultExpr);
30616
+ if (column.identity?.sequence === `${seq.schema}.${seq.name}`)
30617
+ column.identity = { ...column.identity, sequence: `${seq.schema}.${newName}` };
30618
+ }
30619
+ }
30620
+ }
30621
+ }
30122
30622
  function executeAlterSequence(env, stmt) {
30123
30623
  const state = env.ctx.state;
30124
- const seq = state.findSequence(stmt.name);
30125
- if (!seq) {
30624
+ const foundSequence = state.findSequence(stmt.name);
30625
+ if (!foundSequence) {
30126
30626
  if (stmt.ifExists) return commandResult("ALTER SEQUENCE", 0);
30127
30627
  throw pgError("undefined_table", `relation "${stmt.name.join(".")}" does not exist`, "42P01");
30128
30628
  }
30629
+ const seq = state.ensureWritableSequence(foundSequence);
30630
+ if (stmt.renameTo) {
30631
+ const schema = state.getSchema(seq.schema);
30632
+ if (schema.hasRelation(stmt.renameTo))
30633
+ throw pgError("duplicate_table", `relation "${stmt.renameTo}" already exists`, "42P07");
30634
+ renameSequenceReferences(env, seq, stmt.renameTo);
30635
+ schema.sequences.delete(seq.name);
30636
+ seq.name = stmt.renameTo;
30637
+ schema.sequences.set(seq.name, seq);
30638
+ return commandResult("ALTER SEQUENCE", 0);
30639
+ }
30129
30640
  const o = stmt.options;
30130
30641
  if (o.logged !== void 0) {
30131
30642
  setSequenceLogged(state, seq, o.logged);
@@ -30183,7 +30694,7 @@ function buildColumn(env, schema, tableName2, def, out, taken) {
30183
30694
  col.defaultExpr = {
30184
30695
  type: "func",
30185
30696
  name: ["nextval"],
30186
- args: [{ type: "string_lit", value: `${schema.name}.${seqName}` }]
30697
+ args: [{ type: "string_lit", value: [schema.name, seqName].map(quoteIdent).join(".") }]
30187
30698
  };
30188
30699
  }
30189
30700
  for (const c of def.constraints) {
@@ -30436,7 +30947,11 @@ function executeCreateTableAs(env, stmt) {
30436
30947
  const table = new TableData(schema.name, name2, columns, state.nextOid(), stmt.temp);
30437
30948
  table.unlogged = stmt.unlogged === true;
30438
30949
  if (stmt.withData) {
30439
- for (const row of rel3.rows) table.mutableRows().push(row.slice());
30950
+ for (const row of rel3.rows) {
30951
+ const stored = row.slice();
30952
+ table.rowXmins.set(stored, env.ctx.rowVersionXid());
30953
+ table.mutableRows().push(stored);
30954
+ }
30440
30955
  }
30441
30956
  schema.tables.set(name2, table);
30442
30957
  const count = stmt.withData ? table.rowCount() : 0;
@@ -30464,7 +30979,7 @@ function executeCreateIndex(env, stmt) {
30464
30979
  }
30465
30980
  }
30466
30981
  }
30467
- assertGinTrgmIndex(state, table, stmt);
30982
+ assertGinTrgmIndex(state, table, stmt, (expr) => expressionTypeOf(env, table, expr));
30468
30983
  const method = stmt.using ?? "btree";
30469
30984
  if (!ACCESS_METHODS.has(method) || method === "heap") {
30470
30985
  throw pgError("undefined_object", `access method "${method}" does not exist`, "42704");
@@ -30705,6 +31220,12 @@ function executeCreateFunction(env, stmt) {
30705
31220
  language: lang,
30706
31221
  body,
30707
31222
  rawBody: stmt.body,
31223
+ settings: Object.fromEntries(
31224
+ Object.entries(stmt.settings ?? {}).map(([key, value]) => [
31225
+ key,
31226
+ value === "__from_current__" ? state.getSetting(key) ?? "" : value
31227
+ ])
31228
+ ),
30708
31229
  strict: stmt.strict,
30709
31230
  oid: state.nextOid()
30710
31231
  };
@@ -30713,6 +31234,50 @@ function executeCreateFunction(env, stmt) {
30713
31234
  schema.functions.set(name2, existing);
30714
31235
  return commandResult("CREATE FUNCTION", 0);
30715
31236
  }
31237
+ function validateTransitionTables(stmt) {
31238
+ const transitions = stmt.transitions ?? [];
31239
+ if (transitions.length === 0) return {};
31240
+ const invalid = (message2) => pgError("invalid_table_definition", message2, "42P17");
31241
+ const unsupportedClause = (message2) => pgError("feature_not_supported", message2, "0A000");
31242
+ if (stmt.timing !== "after") throw invalid("transition table name can only be specified for an AFTER trigger");
31243
+ if (stmt.events.some((e) => e.event === "truncate")) {
31244
+ throw unsupportedClause("TRUNCATE triggers with transition tables are not supported");
31245
+ }
31246
+ if (stmt.events.some((e) => e.columns !== null)) {
31247
+ throw unsupportedClause("transition tables cannot be specified for triggers with column lists");
31248
+ }
31249
+ if (stmt.events.length > 1) {
31250
+ throw unsupportedClause("transition tables cannot be specified for triggers with more than one event");
31251
+ }
31252
+ const event = stmt.events[0].event;
31253
+ let oldTable;
31254
+ let newTable;
31255
+ for (const transition of transitions) {
31256
+ if (transition.isNew) {
31257
+ if (event !== "insert" && event !== "update") {
31258
+ throw invalid("NEW TABLE can only be specified for an INSERT or UPDATE trigger");
31259
+ }
31260
+ if (newTable !== void 0) throw invalid("NEW TABLE cannot be specified multiple times");
31261
+ newTable = transition.name;
31262
+ } else {
31263
+ if (event !== "delete" && event !== "update") {
31264
+ throw invalid("OLD TABLE can only be specified for a DELETE or UPDATE trigger");
31265
+ }
31266
+ if (oldTable !== void 0) throw invalid("OLD TABLE cannot be specified multiple times");
31267
+ oldTable = transition.name;
31268
+ }
31269
+ }
31270
+ if (oldTable !== void 0 && oldTable === newTable) {
31271
+ throw invalid("OLD TABLE name and NEW TABLE name cannot be the same");
31272
+ }
31273
+ if (stmt.forEachRow) {
31274
+ throw pgError("unsupported", "transition tables on FOR EACH ROW triggers are not supported", "0A000");
31275
+ }
31276
+ return {
31277
+ ...oldTable !== void 0 ? { oldTable } : {},
31278
+ ...newTable !== void 0 ? { newTable } : {}
31279
+ };
31280
+ }
30716
31281
  function executeCreateTrigger(env, stmt) {
30717
31282
  if (stmt.constraint && (stmt.timing !== "after" || !stmt.forEachRow))
30718
31283
  throw pgError("syntax", "constraint triggers must be AFTER ROW triggers", "42601");
@@ -30722,6 +31287,7 @@ function executeCreateTrigger(env, stmt) {
30722
31287
  throw pgError("undefined_table", `relation "${stmt.table.join(".")}" does not exist`, "42P01");
30723
31288
  }
30724
31289
  const table = state.ensureWritableTable(found);
31290
+ const transitionTables = validateTransitionTables(stmt);
30725
31291
  const fnSchema = stmt.funcName.length >= 2 ? stmt.funcName[stmt.funcName.length - 2] : null;
30726
31292
  const fnName = stmt.funcName[stmt.funcName.length - 1];
30727
31293
  const fns = state.findFunctions(stmt.funcName);
@@ -30740,6 +31306,7 @@ function executeCreateTrigger(env, stmt) {
30740
31306
  timing: stmt.timing,
30741
31307
  events: stmt.events,
30742
31308
  forEachRow: stmt.forEachRow,
31309
+ ...transitionTables,
30743
31310
  when: stmt.when,
30744
31311
  funcSchema: fnSchema ?? fns[0].schema,
30745
31312
  funcName: fnName,
@@ -30889,6 +31456,7 @@ function executeAlterTable(env, stmt) {
30889
31456
  }
30890
31457
  const next = row.slice();
30891
31458
  next[idx] = nv;
31459
+ table.rowXmins.set(next, table.rowXmins.get(rows[i]) ?? 2);
30892
31460
  rows[i] = next;
30893
31461
  }
30894
31462
  col.type = resolved.column;
@@ -30990,6 +31558,11 @@ function executeAlterTable(env, stmt) {
30990
31558
  );
30991
31559
  }
30992
31560
  table.columns[idx].name = action.to;
31561
+ for (const seq of [...schema.sequences.values()]) {
31562
+ if (seq.ownedBy?.table === table.name && seq.ownedBy.column === action.from) {
31563
+ state.ensureWritableSequence(seq).ownedBy = { table: table.name, column: action.to };
31564
+ }
31565
+ }
30993
31566
  for (const con of table.constraints) {
30994
31567
  if (con.kind === "primary_key" || con.kind === "unique" || con.kind === "foreign_key") {
30995
31568
  con.columns = con.columns.map((c) => c === action.from ? action.to : c);
@@ -31026,6 +31599,11 @@ function executeAlterTable(env, stmt) {
31026
31599
  throw pgError("duplicate_table", `relation "${action.to}" already exists`, "42P07");
31027
31600
  }
31028
31601
  const oldName = table.name;
31602
+ for (const seq of [...schema.sequences.values()]) {
31603
+ if (seq.ownedBy?.table === oldName) {
31604
+ state.ensureWritableSequence(seq).ownedBy = { ...seq.ownedBy, table: action.to };
31605
+ }
31606
+ }
31029
31607
  schema.tables.delete(table.name);
31030
31608
  table.name = action.to;
31031
31609
  schema.tables.set(action.to, table);
@@ -32899,6 +33477,7 @@ function encodeDatabaseState(state, runtime) {
32899
33477
  writeBjv(
32900
33478
  w,
32901
33479
  {
33480
+ xmins: table.allRows().map((row) => table.rowXmins.get(row) ?? 2),
32902
33481
  name: table.name,
32903
33482
  schema: table.schema,
32904
33483
  columns: table.columns,
@@ -32996,8 +33575,14 @@ function decodeInner(snapshot, prng, clock) {
32996
33575
  table.constraints = meta.constraints;
32997
33576
  table.triggers = meta.triggers;
32998
33577
  table.unlogged = meta.unlogged === true;
33578
+ table.slabXmins = meta.xmins ?? null;
33579
+ state.xidCounter = Math.max(
33580
+ state.xidCounter,
33581
+ (meta.xmins ?? []).reduce((max, xid) => Math.max(max, xid + 1), 3)
33582
+ );
32999
33583
  if (meta.stats) state.tableStats.set(table.oid, meta.stats);
33000
33584
  const rowCount = readVarintU32(r);
33585
+ if (meta.xmins && meta.xmins.length !== rowCount) throw snapshotError();
33001
33586
  const slabColumns = [];
33002
33587
  for (let c = 0; c < meta.columns.length; c++)
33003
33588
  slabColumns.push(readPackedColumn(r, rowCount, intern, meta.columns[c].type.id));
@@ -33441,6 +34026,8 @@ function validateTableMetaBjv(value) {
33441
34026
  if (!isRecord2(value) || typeof value.name !== "string" || typeof value.schema !== "string") throw snapshotError();
33442
34027
  if (!Array.isArray(value.columns)) throw snapshotError();
33443
34028
  for (const col of value.columns) validateColumnMeta(col);
34029
+ if (value.xmins !== void 0 && (!Array.isArray(value.xmins) || value.xmins.some((xid) => !Number.isInteger(xid) || xid < 2 || xid > 4294967295)))
34030
+ throw snapshotError();
33444
34031
  }
33445
34032
  function validateViewMetaBjv(value) {
33446
34033
  if (!isRecord2(value) || typeof value.name !== "string" || typeof value.schema !== "string") throw snapshotError();
@@ -33534,6 +34121,7 @@ function forceSchemaIntern(pool, state, schemas) {
33534
34121
  pool.forceId(table.name);
33535
34122
  pool.forceId(table.schema);
33536
34123
  forceBjvStrings(pool, {
34124
+ xmins: table.allRows().map((row) => table.rowXmins.get(row) ?? 2),
33537
34125
  name: table.name,
33538
34126
  schema: table.schema,
33539
34127
  columns: table.columns,
@@ -35164,7 +35752,7 @@ var Connection = class {
35164
35752
  if (stmt?.type !== "transaction") throw pgError2("syntax", "invalid transaction command", "42601");
35165
35753
  switch (stmt.action) {
35166
35754
  case "begin":
35167
- this.transaction ??= { statements: [], savepoints: [] };
35755
+ this.transaction ??= { statements: [], savepoints: [], xid: this.database.state.allocateXid() };
35168
35756
  return command("BEGIN");
35169
35757
  case "commit": {
35170
35758
  const transaction = this.transaction;
@@ -35173,6 +35761,7 @@ var Connection = class {
35173
35761
  this.inSession(
35174
35762
  this.database,
35175
35763
  () => this.database.transaction(() => {
35764
+ this.database.state.transactionXid = transaction.xid;
35176
35765
  for (const logged of transaction.statements) this.replay(this.database, logged);
35177
35766
  })
35178
35767
  );
@@ -35223,6 +35812,7 @@ var Connection = class {
35223
35812
  workspace.state.settings = new Map(this.settings);
35224
35813
  workspace.state.settingDefaults = this.settingDefaults;
35225
35814
  workspace.transactions.begin();
35815
+ workspace.state.transactionXid = this.transaction?.xid ?? null;
35226
35816
  for (const logged of this.transaction?.statements ?? []) {
35227
35817
  this.replay(workspace, logged);
35228
35818
  }