@palbase/backend 33.0.2 → 34.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/bin/palbase-backend.cjs +2020 -350
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +9 -7
  4. package/dist/bin/palbase-backend.js.map +1 -1
  5. package/dist/{chunk-SI4KGEM3.js → chunk-FOWA7PF4.js} +97 -96
  6. package/dist/chunk-FOWA7PF4.js.map +1 -0
  7. package/dist/chunk-GHNC2UHS.js +20 -0
  8. package/dist/chunk-GHNC2UHS.js.map +1 -0
  9. package/dist/{chunk-AAT5G7KY.js → chunk-JJW3L62T.js} +2 -2
  10. package/dist/chunk-JJW3L62T.js.map +1 -0
  11. package/dist/{chunk-TVCCR6SO.js → chunk-LOGL2ZHD.js} +58 -9
  12. package/dist/chunk-LOGL2ZHD.js.map +1 -0
  13. package/dist/{chunk-C6COAB3E.js → chunk-MNGZE2MM.js} +1651 -186
  14. package/dist/chunk-MNGZE2MM.js.map +1 -0
  15. package/dist/{chunk-XOX6RFPZ.js → chunk-PRH45NZU.js} +46 -3
  16. package/dist/chunk-PRH45NZU.js.map +1 -0
  17. package/dist/{chunk-26GH54D2.js → chunk-RZLY4BOY.js} +5 -3
  18. package/dist/{chunk-26GH54D2.js.map → chunk-RZLY4BOY.js.map} +1 -1
  19. package/dist/{chunk-YIQ4RS4F.js → chunk-SEM36BWY.js} +283 -5
  20. package/dist/chunk-SEM36BWY.js.map +1 -0
  21. package/dist/{chunk-KATPXCJ5.js → chunk-UMESJJRS.js} +19 -40
  22. package/dist/chunk-UMESJJRS.js.map +1 -0
  23. package/dist/chunk-VXPNPVAG.js +40 -0
  24. package/dist/chunk-VXPNPVAG.js.map +1 -0
  25. package/dist/{chunk-WWUG2QXF.js → chunk-YX7UA7VC.js} +53 -4
  26. package/dist/chunk-YX7UA7VC.js.map +1 -0
  27. package/dist/db/env.cjs.map +1 -1
  28. package/dist/db/env.d.cts +2 -0
  29. package/dist/db/env.d.ts +2 -0
  30. package/dist/db/index.cjs +261 -2
  31. package/dist/db/index.cjs.map +1 -1
  32. package/dist/db/index.d.cts +46 -3
  33. package/dist/db/index.d.ts +46 -3
  34. package/dist/db/index.js +14 -5
  35. package/dist/{index-DMZG3kpo.d.ts → endpoint-DpNUx-ON.d.ts} +315 -57
  36. package/dist/{index-DB_nW-AV.d.cts → endpoint-kYgEHrrV.d.cts} +315 -57
  37. package/dist/engine/index.cjs +2026 -348
  38. package/dist/engine/index.cjs.map +1 -1
  39. package/dist/engine/index.d.cts +5 -5
  40. package/dist/engine/index.d.ts +5 -5
  41. package/dist/engine/index.js +17 -7
  42. package/dist/{index-D-3duy8Y.d.ts → index-JrHqNsjQ.d.ts} +108 -50
  43. package/dist/{index-DLveQoOf.d.cts → index-pnDsEfxJ.d.cts} +108 -50
  44. package/dist/index.cjs +616 -254
  45. package/dist/index.cjs.map +1 -1
  46. package/dist/index.d.cts +7 -6
  47. package/dist/index.d.ts +7 -6
  48. package/dist/index.js +25 -15
  49. package/dist/index.js.map +1 -1
  50. package/dist/openapi/index.cjs +7 -0
  51. package/dist/openapi/index.cjs.map +1 -1
  52. package/dist/openapi/index.d.cts +3 -3
  53. package/dist/openapi/index.d.ts +3 -3
  54. package/dist/openapi/index.js +4 -3
  55. package/dist/openapi/index.js.map +1 -1
  56. package/dist/{registry-DSTThhKf.d.cts → registry-BcOd8Zhf.d.cts} +3 -1
  57. package/dist/{registry-JjF5lcj4.d.ts → registry-BoX5iNyY.d.ts} +3 -1
  58. package/dist/test/index.cjs +103 -66
  59. package/dist/test/index.cjs.map +1 -1
  60. package/dist/test/index.d.cts +3 -3
  61. package/dist/test/index.d.ts +3 -3
  62. package/dist/test/index.js +33 -7
  63. package/dist/test/index.js.map +1 -1
  64. package/docs/README.md +1 -1
  65. package/docs/database.md +308 -0
  66. package/docs/examples/check-query-contracts.ts +5 -0
  67. package/docs/examples/database-dx.contract.json +65 -0
  68. package/docs/examples/database-dx.ts +63 -0
  69. package/docs/examples/money-transfer.ts +73 -0
  70. package/docs/llms-full.txt +309 -1
  71. package/package.json +1 -2
  72. package/template/package.json +1 -1
  73. package/dist/chunk-AAT5G7KY.js.map +0 -1
  74. package/dist/chunk-C6COAB3E.js.map +0 -1
  75. package/dist/chunk-KATPXCJ5.js.map +0 -1
  76. package/dist/chunk-SI4KGEM3.js.map +0 -1
  77. package/dist/chunk-TVCCR6SO.js.map +0 -1
  78. package/dist/chunk-WWUG2QXF.js.map +0 -1
  79. package/dist/chunk-XOX6RFPZ.js.map +0 -1
  80. package/dist/chunk-YIQ4RS4F.js.map +0 -1
  81. package/stack-images.json +0 -24
@@ -10,9 +10,9 @@ var __export = (target, all) => {
10
10
  };
11
11
  var __copyProps = (to, from, except, desc) => {
12
12
  if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ for (let key2 of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key2) && key2 !== except)
15
+ __defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
16
16
  }
17
17
  return to;
18
18
  };
@@ -23,6 +23,7 @@ var engine_exports = {};
23
23
  __export(engine_exports, {
24
24
  AuthVerifier: () => AuthVerifier,
25
25
  BootRefused: () => BootRefused,
26
+ COMMAND_EXECUTOR_DDL: () => COMMAND_EXECUTOR_DDL,
26
27
  DECLARATION_REFUSAL: () => DECLARATION_REFUSAL,
27
28
  DeclarationRefused: () => DeclarationRefused,
28
29
  RateLimiter: () => RateLimiter,
@@ -33,6 +34,7 @@ __export(engine_exports, {
33
34
  createRequestDatabase: () => createRequestDatabase,
34
35
  effectiveAuth: () => effectiveAuth,
35
36
  hostAllowed: () => hostAllowed,
37
+ installCommandExecutor: () => installCommandExecutor,
36
38
  installEgressFence: () => installEgressFence,
37
39
  isDeclarationRefused: () => isDeclarationRefused,
38
40
  loadConfig: () => loadConfig,
@@ -40,6 +42,8 @@ __export(engine_exports, {
40
42
  matchRoute: () => matchRoute,
41
43
  quoteIdent: () => quoteIdent,
42
44
  scrubSecrets: () => scrubSecrets,
45
+ setCursorKey: () => setCursorKey,
46
+ withQueryCancellation: () => withQueryCancellation,
43
47
  withTables: () => withTables
44
48
  });
45
49
  module.exports = __toCommonJS(engine_exports);
@@ -388,10 +392,10 @@ function encodeFilterValue(value, column) {
388
392
  __name(encodeFilterValue, "encodeFilterValue");
389
393
  function encodeFilterMap(map) {
390
394
  const out = {};
391
- for (const key of Object.keys(map).sort()) {
392
- const value = map[key];
395
+ for (const key2 of Object.keys(map).sort()) {
396
+ const value = map[key2];
393
397
  if (value === void 0) continue;
394
- out[key] = encodeFilterValue(value, key);
398
+ out[key2] = encodeFilterValue(value, key2);
395
399
  }
396
400
  return out;
397
401
  }
@@ -413,12 +417,12 @@ function assertNoNestedHandles(value, column) {
413
417
  __name(assertNoNestedHandles, "assertNoNestedHandles");
414
418
  function encodeMap(map, allowColumnExpr) {
415
419
  const out = {};
416
- for (const key of Object.keys(map).sort()) {
417
- const value = map[key];
420
+ for (const key2 of Object.keys(map).sort()) {
421
+ const value = map[key2];
418
422
  if (value === void 0) {
419
- throw new TxPlanError(`${key} de\u011Feri undefined \u2014 bu bir yazma de\u011Feri de\u011Fil. Anahtar duruyor ama de\u011Feri yok, yani kolon sessizce YAZILMAZDI (istek g\xF6vdesinden gelen bir alan\u0131n eksik olmas\u0131 bu \u015Fekilde g\xF6r\xFCn\xFCr). Kolon varsay\u0131lan\u0131n\u0131 istiyorsan\u0131z anahtar\u0131 hi\xE7 koymay\u0131n; NULL yazmak istiyorsan\u0131z a\xE7\u0131k\xE7a null yaz\u0131n.`);
423
+ throw new TxPlanError(`${key2} de\u011Feri undefined \u2014 bu bir yazma de\u011Feri de\u011Fil. Anahtar duruyor ama de\u011Feri yok, yani kolon sessizce YAZILMAZDI (istek g\xF6vdesinden gelen bir alan\u0131n eksik olmas\u0131 bu \u015Fekilde g\xF6r\xFCn\xFCr). Kolon varsay\u0131lan\u0131n\u0131 istiyorsan\u0131z anahtar\u0131 hi\xE7 koymay\u0131n; NULL yazmak istiyorsan\u0131z a\xE7\u0131k\xE7a null yaz\u0131n.`);
420
424
  }
421
- out[key] = encodeValue(value, key, allowColumnExpr);
425
+ out[key2] = encodeValue(value, key2, allowColumnExpr);
422
426
  }
423
427
  return out;
424
428
  }
@@ -650,7 +654,7 @@ function materializeResult(value, results) {
650
654
  if (Array.isArray(value)) return value.map((item) => materializeResult(item, results));
651
655
  if (isPlainObject(value)) {
652
656
  const out = {};
653
- for (const [key, item] of Object.entries(value)) out[key] = materializeResult(item, results);
657
+ for (const [key2, item] of Object.entries(value)) out[key2] = materializeResult(item, results);
654
658
  return out;
655
659
  }
656
660
  return value;
@@ -699,162 +703,71 @@ function translateRejection(err, builder) {
699
703
  }
700
704
  __name(translateRejection, "translateRejection");
701
705
 
702
- // src/errors.ts
703
- var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
704
- var ENGINE_RAISED = /* @__PURE__ */ Symbol.for("palbase.backend.engineRaised");
705
- function markEngineRaised(e) {
706
- e[ENGINE_RAISED] = true;
707
- return e;
708
- }
709
- __name(markEngineRaised, "markEngineRaised");
710
- function isEngineRaised(e) {
711
- return typeof e === "object" && e !== null && e[ENGINE_RAISED] === true;
712
- }
713
- __name(isEngineRaised, "isEngineRaised");
714
- function isHttpError(err) {
715
- if (typeof err !== "object" || err === null) return false;
716
- const e = err;
717
- return e[HTTP_ERROR_BRAND] === true && typeof e.status === "number" && typeof e.error === "string" && typeof e.errorDescription === "string";
718
- }
719
- __name(isHttpError, "isHttpError");
720
- var HttpError = class extends Error {
721
- static {
722
- __name(this, "HttpError");
723
- }
724
- status;
725
- error;
726
- errorDescription;
727
- data;
728
- /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
729
- [HTTP_ERROR_BRAND] = true;
730
- constructor(status, error, errorDescription, data) {
731
- super(errorDescription);
732
- this.name = "HttpError";
733
- this.status = status;
734
- this.error = error;
735
- this.errorDescription = errorDescription;
736
- if (data !== void 0) {
737
- this.data = data;
738
- }
739
- }
740
- /**
741
- * Serialize to the standard Palbase error response format.
742
- * The `requestId` is injected by the runtime layer from the request context.
743
- * When called without arguments (e.g. JSON.stringify), request_id is omitted.
744
- * When `data` is set, it is appended as a strict-superset field.
745
- */
746
- toJSON(requestId) {
747
- const result = {
748
- error: this.error,
749
- error_description: this.errorDescription,
750
- status: this.status
751
- };
752
- if (requestId) {
753
- result.request_id = requestId;
754
- }
755
- if (this.data !== void 0) {
756
- result.data = this.data;
757
- }
758
- return result;
759
- }
760
- };
761
- var NamedHttpError = class NamedHttpError2 extends HttpError {
762
- static {
763
- __name(this, "NamedHttpError");
764
- }
765
- constructor(status, defaultCode, name, message, code, data) {
766
- super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
767
- this.name = name;
706
+ // src/db/workload.ts
707
+ function validateWorkload(value) {
708
+ if (typeof value.name !== "string" || !/^[a-zA-Z][a-zA-Z0-9_.-]{0,63}$/.test(value.name)) throw new Error("workload name must be 1\u201364 letters/digits/dots/dashes");
709
+ for (const name of [
710
+ "concurrency",
711
+ "tenantConcurrency",
712
+ "queueLimit",
713
+ "tenantQueueLimit",
714
+ "queueTimeoutMs"
715
+ ]) {
716
+ const n = value[name], min = name.endsWith("Limit") ? 0 : 1;
717
+ const max = name === "queueTimeoutMs" ? 6e4 : name.endsWith("Limit") ? 1e4 : 1e3;
718
+ if (!Number.isInteger(n) || n < min || n > max) throw new Error(`workload ${name} must be an integer between ${min} and ${max}`);
719
+ }
720
+ if (value.tenantConcurrency > value.concurrency || value.tenantQueueLimit > value.queueLimit) throw new Error("workload tenant limits cannot exceed global limits");
721
+ if (typeof value.tenantClaim !== "string" || !/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(value.tenantClaim)) throw new Error("workload tenantClaim must be a top-level claim name");
722
+ }
723
+ __name(validateWorkload, "validateWorkload");
724
+
725
+ // src/db/transaction-options.ts
726
+ function retryBudget(value) {
727
+ const n = value ?? 0;
728
+ if (!Number.isInteger(n) || n < 0 || n > 10) {
729
+ throw new Error("transaction retry must be an integer between 0 and 10");
768
730
  }
769
- };
770
- function defaultMessage(name) {
771
- const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
772
- return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
731
+ return n;
773
732
  }
774
- __name(defaultMessage, "defaultMessage");
775
- var Conflict = class extends NamedHttpError {
776
- static {
777
- __name(this, "Conflict");
733
+ __name(retryBudget, "retryBudget");
734
+ function validateAtomicOptions(options) {
735
+ retryBudget(options.retry);
736
+ if (options.isolation !== void 0 && ![
737
+ "read committed",
738
+ "repeatable read",
739
+ "serializable"
740
+ ].includes(options.isolation)) {
741
+ throw new Error("transaction isolation must be read committed, repeatable read or serializable");
778
742
  }
779
- constructor(message, code, data) {
780
- super(409, "conflict", "Conflict", message, code, data);
743
+ if (options.readOnly !== void 0 && typeof options.readOnly !== "boolean") {
744
+ throw new Error("transaction readOnly must be a boolean");
781
745
  }
782
- };
783
- var UniqueViolation = class extends Conflict {
784
- static {
785
- __name(this, "UniqueViolation");
746
+ for (const name of [
747
+ "lockTimeoutMs",
748
+ "statementTimeoutMs",
749
+ "timeoutMs"
750
+ ]) {
751
+ const n = options[name];
752
+ if (n !== void 0 && (!Number.isInteger(n) || n < 1 || n > 2147483647)) {
753
+ throw new Error(`transaction ${name} must be a positive integer in milliseconds`);
754
+ }
786
755
  }
787
- /**
788
- * Whether `e` is a unique violation REGARDLESS of which copy of this SDK
789
- * constructed it.
790
- *
791
- * Use this instead of `instanceof`. Measured on a live stack: a controller
792
- * bundle INLINES its own copy of `@palbase/backend`, and the engine that
793
- * raises this error is the runtime's copy. Two copies, two class identities,
794
- * and `e instanceof UniqueViolation` is false in the one place a caller
795
- * writes it — a check that reads as correct and silently never matches.
796
- */
797
- static is(e) {
798
- return typeof e === "object" && e !== null && e.name === "UniqueViolation" && typeof e.constraint === "string";
756
+ if (options.workload !== void 0) validateWorkload(options.workload);
757
+ if (options.conflictKeys !== void 0 && (!options.workload || !Array.isArray(options.conflictKeys) || options.conflictKeys.length > 64 || options.conflictKeys.some((k) => typeof k !== "string" || k.length < 1 || k.length > 256))) {
758
+ throw new Error("conflictKeys requires a workload and at most 64 nonempty keys of at most 256 characters");
799
759
  }
800
- /** The unique constraint the statement violated, as Postgres named it.
801
- * `""` when the driver did not say which — see `engine/db.ts`. */
802
- constraint;
803
- constructor(constraint, message, code, data) {
804
- super(message ?? "Unique constraint violated", code ?? "unique_violation", data);
805
- this.name = "UniqueViolation";
806
- this.constraint = constraint;
760
+ if (options.signal !== void 0 && (typeof options.signal.aborted !== "boolean" || typeof options.signal.addEventListener !== "function")) {
761
+ throw new Error("transaction signal must be an AbortSignal");
807
762
  }
808
- };
809
- function isRetryable(e) {
810
- return typeof e === "object" && e !== null && e.retryable === true && typeof e.sqlstate === "string";
811
763
  }
812
- __name(isRetryable, "isRetryable");
813
- var SerializationFailure = class extends Conflict {
814
- static {
815
- __name(this, "SerializationFailure");
816
- }
817
- retryable = true;
818
- sqlstate;
819
- static is(e) {
820
- return typeof e === "object" && e !== null && e.name === "SerializationFailure";
821
- }
822
- constructor(sqlstate = "40001", message, data) {
823
- super(message ?? "i\u015Flem e\u015Fzamanl\u0131 bir i\u015Flemle serile\u015Ftirilemedi \u2014 ayn\u0131 i\u015Flemi ba\u015Ftan \xE7al\u0131\u015Ft\u0131r\u0131n ($transaction'a { retry: n } verebilirsiniz)", "serialization_failure", data);
824
- this.name = "SerializationFailure";
825
- this.sqlstate = sqlstate;
826
- }
827
- };
828
- var DeadlockDetected = class extends Conflict {
829
- static {
830
- __name(this, "DeadlockDetected");
831
- }
832
- retryable = true;
833
- sqlstate;
834
- static is(e) {
835
- return typeof e === "object" && e !== null && e.name === "DeadlockDetected";
836
- }
837
- constructor(sqlstate = "40P01", message, data) {
838
- super(message ?? "deadlock: iki i\u015Flem birbirinin kilidini bekledi ve bu i\u015Flem iptal edildi \u2014 kilit s\u0131ras\u0131n\u0131 sabitlemek i\xE7in Database.$lockRows(tablo, id'ler) kullan\u0131n", "deadlock_detected", data);
839
- this.name = "DeadlockDetected";
840
- this.sqlstate = sqlstate;
841
- }
842
- };
843
-
844
- // src/db/typed-db.ts
845
- async function withRetry(fn, opts = {}) {
846
- const budget = opts.retry ?? 0;
847
- let attempt = 0;
848
- for (; ; ) {
849
- try {
850
- return await fn();
851
- } catch (e) {
852
- if (attempt >= budget || !isRetryable(e)) throw e;
853
- attempt += 1;
854
- }
764
+ __name(validateAtomicOptions, "validateAtomicOptions");
765
+ function assertPlanRetry(options) {
766
+ if (retryBudget(options?.retry) !== 0) {
767
+ throw new Error("Database.$transaction is a savepoint plan on the request transaction and cannot retry its snapshot or COMMIT. Use Database.$atomic(async tx => { ... }, { retry, isolation }) and put all decision reads and writes inside that callback.");
855
768
  }
856
769
  }
857
- __name(withRetry, "withRetry");
770
+ __name(assertPlanRetry, "assertPlanRetry");
858
771
 
859
772
  // src/db/schema-json.ts
860
773
  function qualifiedTableKey(schemaName, tableName) {
@@ -951,10 +864,10 @@ async function __runStartHooks() {
951
864
  };
952
865
  }
953
866
  __name(__runStartHooks, "__runStartHooks");
954
- function makeServiceProxy(key) {
867
+ function makeServiceProxy(key2) {
955
868
  const handler = {
956
869
  get(_target, prop, receiver) {
957
- const client = __getRuntime()[key];
870
+ const client = __getRuntime()[key2];
958
871
  const value = Reflect.get(client, prop, receiver);
959
872
  return typeof value === "function" ? value.bind(client) : value;
960
873
  }
@@ -977,6 +890,14 @@ function makeTableProxy(ops, prefix) {
977
890
  update: /* @__PURE__ */ __name((q) => ops().update(name, q.where.id, q.set), "update"),
978
891
  delete: /* @__PURE__ */ __name((id) => ops().delete(name, id), "delete"),
979
892
  findById: /* @__PURE__ */ __name((id) => ops().findById(name, id), "findById"),
893
+ findUnique: /* @__PURE__ */ __name((q) => ops().findUnique(name, q.where, {
894
+ select: q.select,
895
+ with: q.with
896
+ }), "findUnique"),
897
+ page: /* @__PURE__ */ __name((q) => {
898
+ const { where, ...opts } = q ?? {};
899
+ return ops().page(name, where, opts);
900
+ }, "page"),
980
901
  findMany: /* @__PURE__ */ __name((q) => {
981
902
  const { where, ...opts } = q ?? {};
982
903
  return ops().findMany(name, where, opts);
@@ -1014,10 +935,16 @@ function makeTypedSurface(raw) {
1014
935
  const reco = raw;
1015
936
  const ops = {
1016
937
  $query: /* @__PURE__ */ __name((sql, params) => raw.query(sql, params), "$query"),
938
+ $diagnostics: /* @__PURE__ */ __name(() => raw.diagnostics(), "$diagnostics"),
939
+ $readQuery: /* @__PURE__ */ __name((query) => raw.readQuery(query), "$readQuery"),
940
+ $compileQuery: /* @__PURE__ */ __name((query) => raw.compileQuery(query), "$compileQuery"),
941
+ $explainQuery: /* @__PURE__ */ __name((query) => raw.explainQuery(query), "$explainQuery"),
1017
942
  $insert: /* @__PURE__ */ __name((table, data) => raw.insert(table, data), "$insert"),
1018
943
  $update: /* @__PURE__ */ __name((table, id, data) => raw.update(table, id, data), "$update"),
1019
944
  $delete: /* @__PURE__ */ __name((table, id) => raw.delete(table, id), "$delete"),
1020
945
  $findById: /* @__PURE__ */ __name((table, id) => raw.findById(table, id), "$findById"),
946
+ $findUnique: /* @__PURE__ */ __name((table, where, opts) => raw.findUnique(table, where, opts), "$findUnique"),
947
+ $page: /* @__PURE__ */ __name((table, query, opts) => raw.page(table, query, opts), "$page"),
1021
948
  $findMany: /* @__PURE__ */ __name((table, query, opts) => raw.findMany(table, query, opts), "$findMany"),
1022
949
  $put: /* @__PURE__ */ __name((table, data, opts) => raw.put(table, data, opts), "$put"),
1023
950
  // `opts` İLETİLİR. Düşürüldüğü sürece `Database.$updateMany(t, w, s,
@@ -1034,20 +961,55 @@ function makeTypedSurface(raw) {
1034
961
  $facets: /* @__PURE__ */ __name((table, params) => reco.facets(table, params), "$facets"),
1035
962
  $claim: /* @__PURE__ */ __name((table, unique, extra) => reco.claim(table, unique, extra), "$claim"),
1036
963
  $lockRows: /* @__PURE__ */ __name((table, ids) => reco.lockRows(table, ids), "$lockRows"),
1037
- $advisoryXactLock: /* @__PURE__ */ __name((key) => reco.advisoryXactLock(key), "$advisoryXactLock"),
964
+ $advisoryXactLock: /* @__PURE__ */ __name((key2) => reco.advisoryXactLock(key2), "$advisoryXactLock"),
1038
965
  $aggregate: /* @__PURE__ */ __name((table, q) => raw.aggregate(table, q), "$aggregate"),
1039
966
  $insertMany: /* @__PURE__ */ __name((table, rows, opts) => raw.insertMany(table, rows, opts), "$insertMany"),
1040
967
  $supersede: /* @__PURE__ */ __name((table, id, row) => raw.supersede(table, id, row), "$supersede")
1041
968
  };
1042
969
  const base = Object.assign(ops, {
970
+ $atomic(fn, options) {
971
+ if (typeof raw.atomic !== "function") {
972
+ throw new Error("This Database handle cannot open a root transaction; nested $atomic is not supported");
973
+ }
974
+ const parent = __requestALS.getStore();
975
+ let runtime2;
976
+ try {
977
+ runtime2 = __getRuntime();
978
+ } catch {
979
+ }
980
+ return raw.atomic(async (tx) => {
981
+ const typed = makeTypedSurface(tx);
982
+ const invoke = /* @__PURE__ */ __name(() => fn(typed), "invoke");
983
+ if (!runtime2) return invoke();
984
+ const refuse = /* @__PURE__ */ __name(() => {
985
+ throw new Error("Database.$atomic owns one transaction and identity; nested roots and $asService are not allowed");
986
+ }, "refuse");
987
+ const ambient = Object.assign({}, tx, {
988
+ atomic: refuse,
989
+ asService: refuse
990
+ });
991
+ return __requestALS.run({
992
+ ...parent,
993
+ runtime: {
994
+ ...runtime2,
995
+ Database: ambient
996
+ }
997
+ }, invoke);
998
+ }, options);
999
+ },
1043
1000
  // Both surfaces get it: a savepoint on the service transaction is as useful
1044
1001
  // as one on the request's, and each is bound to its own connection.
1045
1002
  $attempt: /* @__PURE__ */ __name((fn) => raw.attempt(fn), "$attempt"),
1046
1003
  $transaction(fn, opts) {
1047
- return withRetry(() => {
1048
- const builder = new TxPlanBuilder();
1049
- return runTxPlan(raw, makeTxPlanHandle(builder), builder, fn);
1050
- }, opts ?? {});
1004
+ assertPlanRetry(opts);
1005
+ const builder = new TxPlanBuilder();
1006
+ return runTxPlan(raw, makeTxPlanHandle(builder), builder, fn);
1007
+ },
1008
+ $command(fn, options) {
1009
+ const builder = new TxPlanBuilder();
1010
+ return runTxPlan({
1011
+ txPlan: /* @__PURE__ */ __name((plan) => raw.command(plan, options), "txPlan")
1012
+ }, makeTxPlanHandle(builder), builder, fn);
1051
1013
  }
1052
1014
  });
1053
1015
  return new Proxy(base, {
@@ -1148,8 +1110,8 @@ var Flags = Object.assign({
1148
1110
  get(flagName, defaultOrContext, maybeContext) {
1149
1111
  return rawFlags.get(flagName, defaultOrContext, maybeContext);
1150
1112
  },
1151
- setOverride(key, value) {
1152
- return rawFlags.setOverride(key, value);
1113
+ setOverride(key2, value) {
1114
+ return rawFlags.setOverride(key2, value);
1153
1115
  }
1154
1116
  }, {
1155
1117
  /**
@@ -1177,6 +1139,175 @@ var Flags = Object.assign({
1177
1139
  });
1178
1140
  var Realtime = makeServiceProxy("Realtime");
1179
1141
 
1142
+ // src/db/sqlstate.ts
1143
+ var SQLSTATE = Object.freeze({
1144
+ serializationFailure: "40001",
1145
+ deadlockDetected: "40P01",
1146
+ commandGuardFailed: "PBC01"
1147
+ });
1148
+ function isTransactionRollbackState(state) {
1149
+ return state === SQLSTATE.serializationFailure || state === SQLSTATE.deadlockDetected;
1150
+ }
1151
+ __name(isTransactionRollbackState, "isTransactionRollbackState");
1152
+
1153
+ // src/errors.ts
1154
+ var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
1155
+ var ENGINE_RAISED = /* @__PURE__ */ Symbol.for("palbase.backend.engineRaised");
1156
+ function markEngineRaised(e) {
1157
+ e[ENGINE_RAISED] = true;
1158
+ return e;
1159
+ }
1160
+ __name(markEngineRaised, "markEngineRaised");
1161
+ function isEngineRaised(e) {
1162
+ return typeof e === "object" && e !== null && e[ENGINE_RAISED] === true;
1163
+ }
1164
+ __name(isEngineRaised, "isEngineRaised");
1165
+ function isHttpError(err) {
1166
+ if (typeof err !== "object" || err === null) return false;
1167
+ const e = err;
1168
+ return e[HTTP_ERROR_BRAND] === true && typeof e.status === "number" && typeof e.error === "string" && typeof e.errorDescription === "string";
1169
+ }
1170
+ __name(isHttpError, "isHttpError");
1171
+ var HttpError = class extends Error {
1172
+ static {
1173
+ __name(this, "HttpError");
1174
+ }
1175
+ status;
1176
+ error;
1177
+ errorDescription;
1178
+ data;
1179
+ /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
1180
+ [HTTP_ERROR_BRAND] = true;
1181
+ constructor(status, error, errorDescription, data) {
1182
+ super(errorDescription);
1183
+ this.name = "HttpError";
1184
+ this.status = status;
1185
+ this.error = error;
1186
+ this.errorDescription = errorDescription;
1187
+ if (data !== void 0) {
1188
+ this.data = data;
1189
+ }
1190
+ }
1191
+ /**
1192
+ * Serialize to the standard Palbase error response format.
1193
+ * The `requestId` is injected by the runtime layer from the request context.
1194
+ * When called without arguments (e.g. JSON.stringify), request_id is omitted.
1195
+ * When `data` is set, it is appended as a strict-superset field.
1196
+ */
1197
+ toJSON(requestId) {
1198
+ const result = {
1199
+ error: this.error,
1200
+ error_description: this.errorDescription,
1201
+ status: this.status
1202
+ };
1203
+ if (requestId) {
1204
+ result.request_id = requestId;
1205
+ }
1206
+ if (this.data !== void 0) {
1207
+ result.data = this.data;
1208
+ }
1209
+ return result;
1210
+ }
1211
+ };
1212
+ var NamedHttpError = class NamedHttpError2 extends HttpError {
1213
+ static {
1214
+ __name(this, "NamedHttpError");
1215
+ }
1216
+ constructor(status, defaultCode, name, message, code, data) {
1217
+ super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
1218
+ this.name = name;
1219
+ }
1220
+ };
1221
+ function defaultMessage(name) {
1222
+ const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
1223
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
1224
+ }
1225
+ __name(defaultMessage, "defaultMessage");
1226
+ var BadRequest = class extends NamedHttpError {
1227
+ static {
1228
+ __name(this, "BadRequest");
1229
+ }
1230
+ constructor(data, message) {
1231
+ super(400, "bad_request", "BadRequest", message, void 0, data);
1232
+ }
1233
+ };
1234
+ var Conflict = class extends NamedHttpError {
1235
+ static {
1236
+ __name(this, "Conflict");
1237
+ }
1238
+ constructor(message, code, data) {
1239
+ super(409, "conflict", "Conflict", message, code, data);
1240
+ }
1241
+ };
1242
+ var UniqueViolation = class extends Conflict {
1243
+ static {
1244
+ __name(this, "UniqueViolation");
1245
+ }
1246
+ /**
1247
+ * Whether `e` is a unique violation — REGARDLESS of which copy of this SDK
1248
+ * constructed it.
1249
+ *
1250
+ * Use this instead of `instanceof`. Measured on a live stack: a controller
1251
+ * bundle INLINES its own copy of `@palbase/backend`, and the engine that
1252
+ * raises this error is the runtime's copy. Two copies, two class identities,
1253
+ * and `e instanceof UniqueViolation` is false in the one place a caller
1254
+ * writes it — a check that reads as correct and silently never matches.
1255
+ */
1256
+ static is(e) {
1257
+ return typeof e === "object" && e !== null && e.name === "UniqueViolation" && typeof e.constraint === "string";
1258
+ }
1259
+ /** The unique constraint the statement violated, as Postgres named it.
1260
+ * `""` when the driver did not say which — see `engine/db.ts`. */
1261
+ constraint;
1262
+ constructor(constraint, message, code, data) {
1263
+ super(message ?? "Unique constraint violated", code ?? "unique_violation", data);
1264
+ this.name = "UniqueViolation";
1265
+ this.constraint = constraint;
1266
+ }
1267
+ };
1268
+ function isRetryable(e) {
1269
+ return typeof e === "object" && e !== null && e.retryable === true && isTransactionRollbackState(e.sqlstate);
1270
+ }
1271
+ __name(isRetryable, "isRetryable");
1272
+ var SerializationFailure = class extends Conflict {
1273
+ static {
1274
+ __name(this, "SerializationFailure");
1275
+ }
1276
+ retryable = true;
1277
+ sqlstate;
1278
+ static is(e) {
1279
+ return typeof e === "object" && e !== null && e.name === "SerializationFailure";
1280
+ }
1281
+ constructor(sqlstate = SQLSTATE.serializationFailure, message, data) {
1282
+ super(message ?? "i\u015Flem e\u015Fzamanl\u0131 bir i\u015Flemle serile\u015Ftirilemedi \u2014 ayn\u0131 i\u015Flemi ba\u015Ftan \xE7al\u0131\u015Ft\u0131r\u0131n (karar okumalar\u0131 ve yazmalar\u0131 $atomic i\xE7ine al\u0131p { retry: n } verin)", "serialization_failure", data);
1283
+ this.name = "SerializationFailure";
1284
+ this.sqlstate = sqlstate;
1285
+ }
1286
+ };
1287
+ var DeadlockDetected = class extends Conflict {
1288
+ static {
1289
+ __name(this, "DeadlockDetected");
1290
+ }
1291
+ retryable = true;
1292
+ sqlstate;
1293
+ static is(e) {
1294
+ return typeof e === "object" && e !== null && e.name === "DeadlockDetected";
1295
+ }
1296
+ constructor(sqlstate = SQLSTATE.deadlockDetected, message, data) {
1297
+ super(message ?? "deadlock: iki i\u015Flem birbirinin kilidini bekledi ve bu i\u015Flem iptal edildi \u2014 kilit s\u0131ras\u0131n\u0131 sabitlemek i\xE7in Database.$lockRows(tablo, id'ler) kullan\u0131n", "deadlock_detected", data);
1298
+ this.name = "DeadlockDetected";
1299
+ this.sqlstate = sqlstate;
1300
+ }
1301
+ };
1302
+ var TooManyRequests = class extends NamedHttpError {
1303
+ static {
1304
+ __name(this, "TooManyRequests");
1305
+ }
1306
+ constructor(data, message) {
1307
+ super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
1308
+ }
1309
+ };
1310
+
1180
1311
  // src/engine/config.ts
1181
1312
  var BootRefused = class extends Error {
1182
1313
  static {
@@ -1227,6 +1358,8 @@ ${detail}`);
1227
1358
  uploadSecret: env.PALBASE_UPLOAD_SECRET ?? "",
1228
1359
  anonKey: env.PALBASE_ANON_KEY ?? "",
1229
1360
  serviceRoleKey: env.PALBASE_SERVICE_ROLE_KEY ?? "",
1361
+ cursorSecret: env.PALBASE_CURSOR_SECRET,
1362
+ enableCommands: env.PALBASE_DB_COMMANDS === "true",
1230
1363
  realtimeSecret: env.REALTIME_INGESTION_SECRET ?? "",
1231
1364
  port,
1232
1365
  dbRole: env.DB_ROLE ?? "backend_authenticated",
@@ -1235,7 +1368,14 @@ ${detail}`);
1235
1368
  // (`roleBackendServiceRole = "backend_service_role"`, NOLOGIN BYPASSRLS),
1236
1369
  // and the live database agrees (pg_roles.rolbypassrls = true).
1237
1370
  dbServiceRole: env.DB_SERVICE_ROLE ?? "backend_service_role",
1238
- poolMax
1371
+ poolMax,
1372
+ ...env.PALBASE_DB_DIAGNOSTICS === "true" ? {
1373
+ databaseDiagnostics: {
1374
+ onComplete: /* @__PURE__ */ __name((diagnostics) => {
1375
+ console.log("palbase.database", JSON.stringify(diagnostics));
1376
+ }, "onComplete")
1377
+ }
1378
+ } : {}
1239
1379
  };
1240
1380
  }
1241
1381
  __name(loadConfig, "loadConfig");
@@ -1351,14 +1491,14 @@ var AuthVerifier = class {
1351
1491
  return null;
1352
1492
  }
1353
1493
  if (header.alg !== "ES256" || !header.kid) return null;
1354
- const key = await this.key(header.kid);
1355
- if (!key) return null;
1494
+ const key2 = await this.key(header.kid);
1495
+ if (!key2) return null;
1356
1496
  let ok = false;
1357
1497
  try {
1358
1498
  ok = await crypto.subtle.verify({
1359
1499
  name: "ECDSA",
1360
1500
  hash: "SHA-256"
1361
- }, key, b64urlToBytes(sig), new TextEncoder().encode(`${h}.${p}`));
1501
+ }, key2, b64urlToBytes(sig), new TextEncoder().encode(`${h}.${p}`));
1362
1502
  } catch {
1363
1503
  return null;
1364
1504
  }
@@ -1424,12 +1564,12 @@ var RateLimiter = class {
1424
1564
  * wait (never 0 — a caller told to wait 0 comes straight back to the same
1425
1565
  * refusal).
1426
1566
  */
1427
- check(rule, key, now) {
1567
+ check(rule, key2, now) {
1428
1568
  if (!rule || !(rule.max > 0) || !(rule.window > 0)) return null;
1429
- const bucket = this.buckets.get(key);
1569
+ const bucket = this.buckets.get(key2);
1430
1570
  if (!bucket || now >= bucket.resetAt) {
1431
1571
  if (this.buckets.size >= this.maxKeys) this.evict(now);
1432
- this.buckets.set(key, {
1572
+ this.buckets.set(key2, {
1433
1573
  count: 1,
1434
1574
  resetAt: now + rule.window * 1e3
1435
1575
  });
@@ -1472,11 +1612,11 @@ function makeMemoryCache(opts = {}) {
1472
1612
  const now = opts.now ?? (() => Date.now());
1473
1613
  const store = /* @__PURE__ */ new Map();
1474
1614
  const inflight = /* @__PURE__ */ new Map();
1475
- const live = /* @__PURE__ */ __name((key) => {
1476
- const e = store.get(key);
1615
+ const live = /* @__PURE__ */ __name((key2) => {
1616
+ const e = store.get(key2);
1477
1617
  if (!e) return void 0;
1478
1618
  if (e.expiresAt !== 0 && e.expiresAt <= now()) {
1479
- store.delete(key);
1619
+ store.delete(key2);
1480
1620
  return void 0;
1481
1621
  }
1482
1622
  return e;
@@ -1499,51 +1639,830 @@ function makeMemoryCache(opts = {}) {
1499
1639
  if (victim) store.delete(victim[0]);
1500
1640
  }
1501
1641
  }, "evict");
1502
- const set = /* @__PURE__ */ __name(async (key, value, ttl) => {
1503
- if (store.size >= maxEntries && !store.has(key)) evict();
1504
- store.set(key, {
1642
+ const set = /* @__PURE__ */ __name(async (key2, value, ttl) => {
1643
+ if (store.size >= maxEntries && !store.has(key2)) evict();
1644
+ store.set(key2, {
1505
1645
  value,
1506
1646
  expiresAt: ttl && ttl > 0 ? now() + ttl * 1e3 : 0
1507
1647
  });
1508
1648
  }, "set");
1509
1649
  return {
1510
- async get(key) {
1511
- const e = live(key);
1650
+ async get(key2) {
1651
+ const e = live(key2);
1512
1652
  return e ? e.value : null;
1513
1653
  },
1514
1654
  set,
1515
- async del(key) {
1516
- store.delete(key);
1655
+ async del(key2) {
1656
+ store.delete(key2);
1517
1657
  },
1518
- async incr(key) {
1519
- const e = live(key);
1658
+ async incr(key2) {
1659
+ const e = live(key2);
1520
1660
  const next = (typeof e?.value === "number" ? e.value : 0) + 1;
1521
- store.set(key, {
1661
+ store.set(key2, {
1522
1662
  value: next,
1523
1663
  expiresAt: e?.expiresAt ?? 0
1524
1664
  });
1525
1665
  return next;
1526
1666
  },
1527
- async getOrSet(key, ttl, fn) {
1528
- const hit = live(key);
1667
+ async getOrSet(key2, ttl, fn) {
1668
+ const hit = live(key2);
1529
1669
  if (hit) return hit.value;
1530
- const running = inflight.get(key);
1670
+ const running = inflight.get(key2);
1531
1671
  if (running) return running;
1532
1672
  const fill = (async () => {
1533
1673
  try {
1534
1674
  const value = await fn();
1535
- await set(key, value, ttl);
1675
+ await set(key2, value, ttl);
1536
1676
  return value;
1537
1677
  } finally {
1538
- inflight.delete(key);
1678
+ inflight.delete(key2);
1679
+ }
1680
+ })();
1681
+ inflight.set(key2, fill);
1682
+ return fill;
1683
+ }
1684
+ };
1685
+ }
1686
+ __name(makeMemoryCache, "makeMemoryCache");
1687
+
1688
+ // src/engine/db.ts
1689
+ var import_node_async_hooks2 = require("async_hooks");
1690
+
1691
+ // src/engine/schema-cache.ts
1692
+ function memoTable(cache, schema, table, compute) {
1693
+ if (!schema.tables || !Object.hasOwn(schema.tables, table)) return compute();
1694
+ let tables = cache.get(schema);
1695
+ if (!tables) {
1696
+ tables = /* @__PURE__ */ new Map();
1697
+ cache.set(schema, tables);
1698
+ }
1699
+ if (!tables.has(table)) tables.set(table, compute());
1700
+ return tables.get(table);
1701
+ }
1702
+ __name(memoTable, "memoTable");
1703
+ function snapshotDeclaration(value, seen = /* @__PURE__ */ new WeakMap()) {
1704
+ if (value === null || typeof value !== "object") return value;
1705
+ const previous = seen.get(value);
1706
+ if (previous) return previous;
1707
+ if (value instanceof Date) return new Date(value.getTime());
1708
+ const result = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
1709
+ seen.set(value, result);
1710
+ for (const key2 of Reflect.ownKeys(value)) {
1711
+ if (Array.isArray(value) && key2 === "length") continue;
1712
+ const descriptor = Object.getOwnPropertyDescriptor(value, key2);
1713
+ if ("value" in descriptor) descriptor.value = snapshotDeclaration(descriptor.value, seen);
1714
+ Object.defineProperty(result, key2, descriptor);
1715
+ }
1716
+ return Object.freeze(result);
1717
+ }
1718
+ __name(snapshotDeclaration, "snapshotDeclaration");
1719
+
1720
+ // src/engine/cursor.ts
1721
+ var import_node_crypto = require("crypto");
1722
+ var key = null;
1723
+ var invalidPage = /* @__PURE__ */ __name((message, field = "page") => new BadRequest({
1724
+ fields: [
1725
+ {
1726
+ field,
1727
+ message
1728
+ }
1729
+ ]
1730
+ }, message), "invalidPage");
1731
+ function setCursorKey(secret) {
1732
+ key = secret ? (0, import_node_crypto.createHash)("sha256").update("palbase:cursor:v1\0").update(secret).digest() : null;
1733
+ }
1734
+ __name(setCursorKey, "setCursorKey");
1735
+ function cursorKey() {
1736
+ if (!key) throw new Error("Database.page needs PALBASE_CURSOR_SECRET or PALBASE_SERVICE_ROLE_KEY shared by all replicas");
1737
+ return key;
1738
+ }
1739
+ __name(cursorKey, "cursorKey");
1740
+ function fingerprint(value) {
1741
+ const stable = /* @__PURE__ */ __name((v) => {
1742
+ if (typeof v === "bigint") return {
1743
+ $bigint: v.toString()
1744
+ };
1745
+ if (v instanceof Date) return {
1746
+ $date: v.toISOString()
1747
+ };
1748
+ if (Array.isArray(v)) return v.map(stable);
1749
+ if (v && typeof v === "object") {
1750
+ return Object.fromEntries(Object.keys(v).sort().map((k) => [
1751
+ k,
1752
+ stable(v[k])
1753
+ ]));
1754
+ }
1755
+ return v;
1756
+ }, "stable");
1757
+ return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(stable(value))).digest("base64url");
1758
+ }
1759
+ __name(fingerprint, "fingerprint");
1760
+ function cursorIdentity(role, claimsJson) {
1761
+ const claims = JSON.parse(claimsJson);
1762
+ const { iat, exp, nbf, jti, ...authorization } = claims;
1763
+ return fingerprint({
1764
+ role,
1765
+ authorization
1766
+ });
1767
+ }
1768
+ __name(cursorIdentity, "cursorIdentity");
1769
+ function encodeCursor(secret, scope, values) {
1770
+ const nonce = (0, import_node_crypto.randomBytes)(12);
1771
+ const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", secret, nonce);
1772
+ cipher.setAAD(Buffer.from(scope));
1773
+ const body = Buffer.concat([
1774
+ cipher.update(JSON.stringify(values), "utf8"),
1775
+ cipher.final()
1776
+ ]);
1777
+ const token = `p1.${Buffer.concat([
1778
+ nonce,
1779
+ cipher.getAuthTag(),
1780
+ body
1781
+ ]).toString("base64url")}`;
1782
+ if (token.length > 16384) throw invalidPage("page cursor values are too large; choose shorter ordering columns");
1783
+ return token;
1784
+ }
1785
+ __name(encodeCursor, "encodeCursor");
1786
+ function decodeCursor(secret, scope, token, count) {
1787
+ try {
1788
+ if (typeof token !== "string" || token.length > 16384 || !/^p1\.[\w-]+$/.test(token)) throw new Error();
1789
+ const data = Buffer.from(token.slice(3), "base64url");
1790
+ if (data.length < 30 || data.toString("base64url") !== token.slice(3)) throw new Error();
1791
+ const cipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", secret, data.subarray(0, 12));
1792
+ cipher.setAAD(Buffer.from(scope));
1793
+ cipher.setAuthTag(data.subarray(12, 28));
1794
+ const values = JSON.parse(Buffer.concat([
1795
+ cipher.update(data.subarray(28)),
1796
+ cipher.final()
1797
+ ]).toString("utf8"));
1798
+ if (!Array.isArray(values) || values.length !== count || values.some((v) => v !== null && typeof v !== "string")) throw new Error();
1799
+ return values;
1800
+ } catch {
1801
+ throw invalidPage("Invalid cursor for this query or identity; restart from the first page", "cursor");
1802
+ }
1803
+ }
1804
+ __name(decodeCursor, "decodeCursor");
1805
+ function pageOrder(columns, primaryKey, opts) {
1806
+ const raw = opts;
1807
+ if ("limit" in raw || "offset" in raw) throw invalidPage("page uses first/after or last/before, not limit/offset");
1808
+ const backward = opts.last !== void 0 || opts.before !== void 0;
1809
+ if (backward && (opts.first !== void 0 || opts.after !== void 0)) throw invalidPage("Use first/after or last/before in one page request");
1810
+ const size = (backward ? opts.last : opts.first) ?? 50;
1811
+ if (!Number.isInteger(size) || size < 1 || size > 1e3) throw invalidPage("page size must be an integer between 1 and 1000");
1812
+ if (primaryKey.length === 0) throw new Error("page requires a declared primary key for a unique, stable ordering");
1813
+ const input = opts.orderBy === void 0 ? [] : Array.isArray(opts.orderBy) ? opts.orderBy : [
1814
+ opts.orderBy
1815
+ ];
1816
+ if (input.length > 16) throw invalidPage("page supports at most 16 ordering columns");
1817
+ const seen = /* @__PURE__ */ new Set();
1818
+ const order = [];
1819
+ const add = /* @__PURE__ */ __name((o) => {
1820
+ const def = columns[o?.column];
1821
+ if (!Object.hasOwn(columns, o?.column) || !def) throw invalidPage(`page order column ${o?.column} is not declared`);
1822
+ if (!(/* @__PURE__ */ new Set([
1823
+ "uuid",
1824
+ "text",
1825
+ "integer",
1826
+ "bigint",
1827
+ "boolean",
1828
+ "timestamp",
1829
+ "numeric",
1830
+ "enum"
1831
+ ])).has(def.type ?? "")) {
1832
+ throw invalidPage(`page cannot order ${o.column}: ${def.type ?? "unknown"} is not a supported scalar column`);
1833
+ }
1834
+ if (seen.has(o.column)) throw invalidPage(`page order column ${o.column} is repeated`);
1835
+ if (o.direction !== void 0 && o.direction !== "asc" && o.direction !== "desc") throw invalidPage("page direction must be asc or desc");
1836
+ if (o.nulls !== void 0 && o.nulls !== "first" && o.nulls !== "last") throw invalidPage("page nulls must be first or last");
1837
+ const direction = o.direction ?? "asc";
1838
+ seen.add(o.column);
1839
+ order.push({
1840
+ column: o.column,
1841
+ direction,
1842
+ nulls: o.nulls ?? (direction === "asc" ? "last" : "first"),
1843
+ nullable: def.nullable !== false
1844
+ });
1845
+ }, "add");
1846
+ for (const o of input) add(o);
1847
+ for (const column of primaryKey) if (!seen.has(column)) add({
1848
+ column,
1849
+ direction: order.at(-1)?.direction ?? "asc"
1850
+ });
1851
+ return {
1852
+ order,
1853
+ size,
1854
+ backward,
1855
+ token: backward ? opts.before : opts.after
1856
+ };
1857
+ }
1858
+ __name(pageOrder, "pageOrder");
1859
+ function pageBoundary(order, values, backward, bind, column, inclusive = false) {
1860
+ const refs = values.map((v) => bind(v));
1861
+ if (order.every((o) => !o.nullable && o.direction === order[0].direction) && values.every((v) => v !== null)) {
1862
+ const greater = order[0].direction === "asc" !== backward;
1863
+ return `(${order.map((o) => column(o.column)).join(", ")}) ${greater ? ">" : "<"}${inclusive ? "=" : ""} (${refs.join(", ")})`;
1864
+ }
1865
+ const parts = [];
1866
+ const prefix = [];
1867
+ for (let i = 0; i < order.length; i++) {
1868
+ const o = order[i], c = column(o.column), r = refs[i];
1869
+ const nullsLast = o.nulls === "last" !== backward;
1870
+ const cmp = o.direction === "asc" !== backward ? ">" : "<";
1871
+ const tail = values[i] === null ? nullsLast ? "false" : `${c} IS NOT NULL` : `(${c} ${cmp} ${r}${nullsLast ? ` OR ${c} IS NULL` : ""})`;
1872
+ parts.push(`(${[
1873
+ ...prefix,
1874
+ tail
1875
+ ].join(" AND ")})`);
1876
+ prefix.push(`${c} IS NOT DISTINCT FROM ${r}`);
1877
+ }
1878
+ if (inclusive) parts.push(`(${prefix.join(" AND ")})`);
1879
+ return `(${parts.join(" OR ")})`;
1880
+ }
1881
+ __name(pageBoundary, "pageBoundary");
1882
+
1883
+ // src/db/bulk.ts
1884
+ function validateInsertManyOptions(opts) {
1885
+ if (!opts) return;
1886
+ if (opts.returning !== void 0 && typeof opts.returning !== "boolean") throw new Error("insertMany returning must be a boolean");
1887
+ if (opts.action !== void 0 && opts.action !== "ignore" && opts.action !== "update") throw new Error("insertMany action must be ignore or update");
1888
+ if (opts.onConflict !== void 0 && (!Array.isArray(opts.onConflict) || opts.onConflict.some((c) => typeof c !== "string" || c.length === 0))) {
1889
+ throw new Error("insertMany onConflict must be an array of column names");
1890
+ }
1891
+ if (opts.action !== void 0 && !opts.onConflict?.length) throw new Error("insertMany action requires nonempty onConflict columns");
1892
+ }
1893
+ __name(validateInsertManyOptions, "validateInsertManyOptions");
1894
+
1895
+ // src/db/query-contract.ts
1896
+ function queryColumnContract(column) {
1897
+ const def = column && typeof column === "object" && "_def" in column ? column._def : column;
1898
+ if (!def?.type) throw new Error("Query contract requires declared column types");
1899
+ if (def.transform !== void 0 && def.codec === void 0) throw new Error("Query contracts require a named codec instead of a free-form column transform");
1900
+ return {
1901
+ type: def.type,
1902
+ nullable: def.nullable === true,
1903
+ ...def.codec === void 0 ? {} : {
1904
+ codec: def.codec
1905
+ },
1906
+ ...def.enumValues === void 0 ? {} : {
1907
+ enumValues: [
1908
+ ...def.enumValues
1909
+ ]
1910
+ },
1911
+ ...def.dimensions === void 0 ? {} : {
1912
+ dimensions: def.dimensions
1913
+ }
1914
+ };
1915
+ }
1916
+ __name(queryColumnContract, "queryColumnContract");
1917
+ function stableContract(value) {
1918
+ if (Array.isArray(value)) return `[${value.map(stableContract).join(",")}]`;
1919
+ if (value && typeof value === "object") return `{${Object.keys(value).sort().map((key2) => `${JSON.stringify(key2)}:${stableContract(value[key2])}`).join(",")}}`;
1920
+ return JSON.stringify(value) ?? "null";
1921
+ }
1922
+ __name(stableContract, "stableContract");
1923
+ function assertReadContract(query, columns) {
1924
+ if (!query.options.select.length || new Set(query.options.select).size !== query.options.select.length) {
1925
+ throw new Error(`Query ${query.name}: select must contain distinct, explicit columns`);
1926
+ }
1927
+ for (const name of query.options.select) {
1928
+ if (!columns || !Object.hasOwn(columns, name) || !Object.hasOwn(query.columns, name) || stableContract(queryColumnContract(columns[name])) !== stableContract(query.columns[name])) {
1929
+ throw new Error(`Query ${query.name}: schema drift at ${query.table}.${name}; regenerate and review its response contract`);
1930
+ }
1931
+ }
1932
+ }
1933
+ __name(assertReadContract, "assertReadContract");
1934
+
1935
+ // src/db/unique.ts
1936
+ function uniqueKeysOf(table) {
1937
+ const columns = table.columns ?? {};
1938
+ const inlinePrimary = [], keys = [];
1939
+ for (const [name, value] of Object.entries(columns)) {
1940
+ const col = value && typeof value === "object" && "_def" in value ? value._def : value;
1941
+ if (col?.primaryKey) inlinePrimary.push(name);
1942
+ if (col?.unique) keys.push([
1943
+ name
1944
+ ]);
1945
+ }
1946
+ const primary = table.primaryKey ?? inlinePrimary;
1947
+ if (primary.length) keys.unshift([
1948
+ ...primary
1949
+ ]);
1950
+ for (const key2 of table.unique ?? []) keys.push([
1951
+ ...key2.columns
1952
+ ]);
1953
+ const seen = /* @__PURE__ */ new Set();
1954
+ return keys.filter((key2) => {
1955
+ if (!key2.length || new Set(key2).size !== key2.length || key2.some((col) => !Object.hasOwn(columns, col))) {
1956
+ throw new Error("Unique lookup requires a nonempty declared key of distinct, known columns");
1957
+ }
1958
+ const identity = JSON.stringify([
1959
+ ...key2
1960
+ ].sort());
1961
+ if (seen.has(identity)) return false;
1962
+ seen.add(identity);
1963
+ return true;
1964
+ });
1965
+ }
1966
+ __name(uniqueKeysOf, "uniqueKeysOf");
1967
+ function assertUniqueLookup(table, keys, where) {
1968
+ if (!where || typeof where !== "object" || Array.isArray(where)) throw new Error(`findUnique(${table}): where must contain one declared unique key`);
1969
+ const columns = Object.keys(where);
1970
+ if (!keys.some((key2) => key2.length === columns.length && key2.every((col) => Object.hasOwn(where, col)))) {
1971
+ throw new Error(`findUnique(${table}): supply exactly one declared unique key (${keys.map((key2) => key2.join(" + ")).join("; ") || "none declared"}); use findMany for other filters`);
1972
+ }
1973
+ for (const [name, value] of Object.entries(where)) {
1974
+ if (value === null || value === void 0 || typeof value === "object" && !(value instanceof Date) || typeof value === "function" || typeof value === "symbol") {
1975
+ throw new Error(`findUnique(${table}): ${name} requires a non-null scalar equality value`);
1976
+ }
1977
+ }
1978
+ }
1979
+ __name(assertUniqueLookup, "assertUniqueLookup");
1980
+
1981
+ // src/engine/commands.ts
1982
+ var installed = /* @__PURE__ */ new WeakSet();
1983
+ var hasCommandExecutor = /* @__PURE__ */ __name((sql) => installed.has(sql), "hasCommandExecutor");
1984
+ var COMMAND_EXECUTOR_DDL = `
1985
+ CREATE SCHEMA IF NOT EXISTS palbase_runtime;
1986
+ CREATE OR REPLACE FUNCTION palbase_runtime.command_v1(steps jsonb) RETURNS jsonb
1987
+ LANGUAGE plpgsql VOLATILE SECURITY INVOKER SET search_path = pg_catalog, public AS $pb_command$
1988
+ DECLARE
1989
+ results jsonb := '[]'::jsonb;
1990
+ step jsonb; chunk jsonb; binding jsonb; source jsonb; rows jsonb;
1991
+ statement text; rendered text; detail text; guard jsonb;
1992
+ index integer := 0; affected integer; passed boolean;
1993
+ BEGIN
1994
+ FOR step IN SELECT value FROM jsonb_array_elements(steps) LOOP
1995
+ IF step->>'empty' = 'true' THEN rows := '[]'::jsonb;
1996
+ ELSE
1997
+ statement := '';
1998
+ FOR chunk IN SELECT value FROM jsonb_array_elements(step->'chunks') LOOP
1999
+ IF jsonb_typeof(chunk) = 'string' THEN statement := statement || (chunk #>> '{}');
2000
+ ELSE
2001
+ binding := step->'bindings'->((chunk->>'bind')::integer);
2002
+ IF binding ? 'ref' THEN
2003
+ source := results->((binding->'ref'->>'op')::integer)->'rows'->0;
2004
+ IF source IS NULL OR NOT source ? (binding->'ref'->>'field') THEN
2005
+ RAISE EXCEPTION 'Command reference has no source row/column; use expectOne before reading it'
2006
+ USING ERRCODE = 'PBC02', DETAIL = jsonb_build_object('error_code','tx_ref_unresolved','op',index)::text;
2007
+ END IF;
2008
+ source := source->(binding->'ref'->>'field');
2009
+ ELSE source := binding->'value'; END IF;
2010
+ rendered := CASE WHEN source IS NULL OR source = 'null'::jsonb THEN NULL
2011
+ WHEN binding->>'json' = 'true' THEN source::text
2012
+ WHEN jsonb_typeof(source) = 'string' THEN source #>> '{}' ELSE source::text END;
2013
+ -- PostgreSQL quotes the value itself. Values reach this function as
2014
+ -- one bound JSON parameter, never as application-generated SQL text.
2015
+ statement := statement || quote_nullable(rendered);
2016
+ END IF;
2017
+ END LOOP;
2018
+ EXECUTE statement INTO rows;
2019
+ END IF;
2020
+ affected := jsonb_array_length(rows);
2021
+ guard := step->'guard';
2022
+ IF guard IS NOT NULL AND guard <> 'null'::jsonb THEN
2023
+ passed := CASE guard->>'kind'
2024
+ WHEN 'one' THEN affected = 1 WHEN 'none' THEN affected = 0
2025
+ WHEN 'atLeast' THEN affected >= (guard->>'n')::integer
2026
+ WHEN 'atMost' THEN affected <= (guard->>'n')::integer ELSE false END;
2027
+ IF NOT passed THEN
2028
+ RAISE EXCEPTION 'Command expectation failed'
2029
+ USING ERRCODE = 'PBC01', DETAIL = jsonb_build_object('error_code','tx_guard_failed','slot',guard->'slot','op',index)::text;
2030
+ END IF;
2031
+ END IF;
2032
+ results := results || jsonb_build_array(jsonb_build_object('rows',rows,'rows_affected',affected));
2033
+ index := index + 1;
2034
+ END LOOP;
2035
+ RETURN jsonb_build_object('results',results);
2036
+ EXCEPTION WHEN SQLSTATE 'PBC01' OR SQLSTATE 'PBC02' THEN
2037
+ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL;
2038
+ RETURN jsonb_build_object('error',detail::jsonb);
2039
+ END
2040
+ $pb_command$;
2041
+ REVOKE ALL ON FUNCTION palbase_runtime.command_v1(jsonb) FROM PUBLIC;
2042
+ CREATE OR REPLACE FUNCTION palbase_runtime.command_rows_v1(rows jsonb, guard jsonb) RETURNS jsonb
2043
+ LANGUAGE plpgsql VOLATILE SECURITY INVOKER SET search_path = pg_catalog, public AS $pb_rows$
2044
+ DECLARE affected integer := jsonb_array_length(rows); passed boolean;
2045
+ BEGIN
2046
+ IF guard IS NOT NULL AND guard <> 'null'::jsonb THEN
2047
+ passed := CASE guard->>'kind'
2048
+ WHEN 'one' THEN affected = 1 WHEN 'none' THEN affected = 0
2049
+ WHEN 'atLeast' THEN affected >= (guard->>'n')::integer
2050
+ WHEN 'atMost' THEN affected <= (guard->>'n')::integer ELSE false END;
2051
+ IF NOT passed THEN
2052
+ RAISE EXCEPTION 'Command expectation failed'
2053
+ USING ERRCODE = 'PBC01', DETAIL = jsonb_build_object('error_code','tx_guard_failed','slot',guard->'slot')::text;
2054
+ END IF;
2055
+ END IF;
2056
+ RETURN rows;
2057
+ END
2058
+ $pb_rows$;
2059
+ REVOKE ALL ON FUNCTION palbase_runtime.command_rows_v1(jsonb,jsonb) FROM PUBLIC;
2060
+ `;
2061
+ async function installCommandExecutor(sql, roles) {
2062
+ const quote = /* @__PURE__ */ __name((s) => `"${s.replace(/"/g, '""')}"`, "quote");
2063
+ if (roles.length === 0) throw new Error("Command executor needs explicit runtime roles");
2064
+ await sql.begin(async (tx) => {
2065
+ await tx.unsafe("SELECT pg_advisory_xact_lock(196754721, 1)");
2066
+ await tx.unsafe(COMMAND_EXECUTOR_DDL);
2067
+ const list = [
2068
+ ...new Set(roles)
2069
+ ].map(quote).join(", ");
2070
+ await tx.unsafe(`GRANT USAGE ON SCHEMA palbase_runtime TO ${list}`);
2071
+ await tx.unsafe(`GRANT EXECUTE ON FUNCTION palbase_runtime.command_v1(jsonb) TO ${list}`);
2072
+ await tx.unsafe(`GRANT EXECUTE ON FUNCTION palbase_runtime.command_rows_v1(jsonb,jsonb) TO ${list}`);
2073
+ });
2074
+ installed.add(sql);
2075
+ }
2076
+ __name(installCommandExecutor, "installCommandExecutor");
2077
+ function parameterChunks(sql) {
2078
+ const chunks = [];
2079
+ let start = 0, quote = null;
2080
+ for (let i = 0; i < sql.length; i++) {
2081
+ const c = sql[i];
2082
+ if (quote) {
2083
+ if (c === quote) {
2084
+ if (sql[i + 1] === quote) i++;
2085
+ else quote = null;
2086
+ }
2087
+ continue;
2088
+ }
2089
+ if (c === "'" || c === '"') {
2090
+ quote = c;
2091
+ continue;
2092
+ }
2093
+ if (c !== "$") continue;
2094
+ const parameter = /^\$(\d+)\b/.exec(sql.slice(i));
2095
+ if (!parameter) continue;
2096
+ chunks.push(sql.slice(start, i), {
2097
+ bind: Number(parameter[1]) - 1
2098
+ });
2099
+ i += parameter[0].length - 1;
2100
+ start = i + 1;
2101
+ }
2102
+ chunks.push(sql.slice(start));
2103
+ return chunks;
2104
+ }
2105
+ __name(parameterChunks, "parameterChunks");
2106
+
2107
+ // src/engine/admission.ts
2108
+ var WorkloadAdmission = class {
2109
+ static {
2110
+ __name(this, "WorkloadAdmission");
2111
+ }
2112
+ definition;
2113
+ pending = [];
2114
+ active = 0;
2115
+ activeKeys = /* @__PURE__ */ new Set();
2116
+ tenants = /* @__PURE__ */ new Map();
2117
+ draining = false;
2118
+ constructor(definition) {
2119
+ this.definition = definition;
2120
+ validateWorkload(definition);
2121
+ }
2122
+ get snapshot() {
2123
+ return {
2124
+ active: this.active,
2125
+ queued: this.pending.length,
2126
+ tenants: this.tenants.size,
2127
+ keys: this.activeKeys.size
2128
+ };
2129
+ }
2130
+ acquire(tenant, keys, signal) {
2131
+ if (signal?.aborted) return Promise.reject(signal.reason ?? new Error("Database admission cancelled"));
2132
+ const normalized = [
2133
+ ...new Set(keys)
2134
+ ].sort();
2135
+ if (normalized.length > 64 || normalized.some((k) => typeof k !== "string" || k.length === 0 || k.length > 256)) return Promise.reject(new Error("conflictKeys must contain at most 64 nonempty strings of at most 256 characters"));
2136
+ const scoped = normalized;
2137
+ const globalFull = this.pending.length >= this.definition.queueLimit;
2138
+ const tenantFull = this.pending.filter((w) => w.tenant === tenant).length >= this.definition.tenantQueueLimit;
2139
+ const canStart = this.active < this.definition.concurrency && (this.tenants.get(tenant) ?? 0) < this.definition.tenantConcurrency && scoped.every((k) => !this.activeKeys.has(k) && !this.pending.some((w) => w.keys.includes(k)));
2140
+ if (!canStart && (globalFull || tenantFull)) {
2141
+ return Promise.reject(new TooManyRequests({
2142
+ retryAfter: 1
2143
+ }, `Database workload ${this.definition.name}: ${tenantFull ? "tenant" : "global"} queue is full`));
2144
+ }
2145
+ return new Promise((grant, reject) => {
2146
+ const waiter = {
2147
+ tenant,
2148
+ keys: scoped,
2149
+ entered: performance.now(),
2150
+ grant,
2151
+ reject,
2152
+ cleanup: /* @__PURE__ */ __name(() => {
2153
+ }, "cleanup")
2154
+ };
2155
+ const remove = /* @__PURE__ */ __name((reason2) => {
2156
+ const index = this.pending.indexOf(waiter);
2157
+ if (index === -1) return;
2158
+ this.pending.splice(index, 1);
2159
+ waiter.cleanup();
2160
+ reject(reason2);
2161
+ this.drain();
2162
+ }, "remove");
2163
+ const abort = /* @__PURE__ */ __name(() => remove(signal?.reason ?? new Error("Database admission cancelled")), "abort");
2164
+ const timer = setTimeout(() => remove(new TooManyRequests({
2165
+ retryAfter: 1
2166
+ }, `Database workload ${this.definition.name}: queue deadline exceeded`)), this.definition.queueTimeoutMs);
2167
+ waiter.cleanup = () => {
2168
+ clearTimeout(timer);
2169
+ signal?.removeEventListener("abort", abort);
2170
+ };
2171
+ signal?.addEventListener("abort", abort, {
2172
+ once: true
2173
+ });
2174
+ this.pending.push(waiter);
2175
+ if (signal?.aborted) abort();
2176
+ else this.drain();
2177
+ });
2178
+ }
2179
+ drain() {
2180
+ if (this.draining) return;
2181
+ this.draining = true;
2182
+ try {
2183
+ const blockedKeys = /* @__PURE__ */ new Set();
2184
+ for (let index = 0; index < this.pending.length && this.active < this.definition.concurrency; ) {
2185
+ const waiter = this.pending[index];
2186
+ if ((this.tenants.get(waiter.tenant) ?? 0) >= this.definition.tenantConcurrency || waiter.keys.some((key2) => this.activeKeys.has(key2) || blockedKeys.has(key2))) {
2187
+ for (const key2 of waiter.keys) blockedKeys.add(key2);
2188
+ index++;
2189
+ continue;
1539
2190
  }
1540
- })();
1541
- inflight.set(key, fill);
1542
- return fill;
2191
+ this.pending.splice(index, 1);
2192
+ waiter.cleanup();
2193
+ this.active++;
2194
+ this.tenants.set(waiter.tenant, (this.tenants.get(waiter.tenant) ?? 0) + 1);
2195
+ for (const key2 of waiter.keys) this.activeKeys.add(key2);
2196
+ let released = false;
2197
+ waiter.grant({
2198
+ queueMs: performance.now() - waiter.entered,
2199
+ release: /* @__PURE__ */ __name(() => {
2200
+ if (released) return;
2201
+ released = true;
2202
+ this.active--;
2203
+ const left = this.tenants.get(waiter.tenant) - 1;
2204
+ if (left === 0) this.tenants.delete(waiter.tenant);
2205
+ else this.tenants.set(waiter.tenant, left);
2206
+ for (const key2 of waiter.keys) this.activeKeys.delete(key2);
2207
+ this.drain();
2208
+ }, "release")
2209
+ });
2210
+ }
2211
+ } finally {
2212
+ this.draining = false;
1543
2213
  }
1544
- };
2214
+ }
2215
+ };
2216
+ var registries = /* @__PURE__ */ new WeakMap();
2217
+ function workloadAdmission(owner, definition) {
2218
+ validateWorkload(definition);
2219
+ let registry = registries.get(owner);
2220
+ if (!registry) {
2221
+ registry = /* @__PURE__ */ new Map();
2222
+ registries.set(owner, registry);
2223
+ }
2224
+ const existing = registry.get(definition.name);
2225
+ if (existing) {
2226
+ const d = existing.definition;
2227
+ if (Object.keys(d).some((k) => d[k] !== definition[k])) throw new Error(`Workload ${definition.name} was already registered with different limits`);
2228
+ return existing;
2229
+ }
2230
+ if (registry.size >= 64) throw new Error("At most 64 named database workloads may be registered per engine; declare and reuse them at module scope");
2231
+ const admission = new WorkloadAdmission(Object.freeze({
2232
+ ...definition
2233
+ }));
2234
+ registry.set(definition.name, admission);
2235
+ return admission;
1545
2236
  }
1546
- __name(makeMemoryCache, "makeMemoryCache");
2237
+ __name(workloadAdmission, "workloadAdmission");
2238
+ function workloadTenant(definition, claimsJson) {
2239
+ const claims = JSON.parse(claimsJson);
2240
+ const value = claims[definition.tenantClaim] ?? claims.sub;
2241
+ return typeof value === "string" && value.length > 0 ? value : "__anonymous__";
2242
+ }
2243
+ __name(workloadTenant, "workloadTenant");
2244
+
2245
+ // src/engine/transaction-control.ts
2246
+ var TransactionControl = class {
2247
+ static {
2248
+ __name(this, "TransactionControl");
2249
+ }
2250
+ controller = new AbortController();
2251
+ active = /* @__PURE__ */ new Set();
2252
+ cancelBackend;
2253
+ cancellation = Promise.resolve();
2254
+ timer;
2255
+ unlink;
2256
+ rejection;
2257
+ deadline;
2258
+ get signal() {
2259
+ return this.controller.signal;
2260
+ }
2261
+ constructor(timeoutMs, external) {
2262
+ this.deadline = timeoutMs === void 0 ? void 0 : performance.now() + timeoutMs;
2263
+ this.rejection = new Promise((_, reject) => {
2264
+ this.signal.addEventListener("abort", () => {
2265
+ reject(this.signal.reason);
2266
+ if (this.active.size && this.cancelBackend) {
2267
+ this.cancellation = this.cancelBackend().catch(() => void 0);
2268
+ } else {
2269
+ for (const query of this.active) {
2270
+ try {
2271
+ query.cancel?.();
2272
+ } catch {
2273
+ }
2274
+ }
2275
+ }
2276
+ }, {
2277
+ once: true
2278
+ });
2279
+ });
2280
+ void this.rejection.catch(() => void 0);
2281
+ const abort = /* @__PURE__ */ __name(() => this.controller.abort(external?.reason ?? new DOMException("Database transaction cancelled", "AbortError")), "abort");
2282
+ external?.addEventListener("abort", abort, {
2283
+ once: true
2284
+ });
2285
+ this.unlink = () => external?.removeEventListener("abort", abort);
2286
+ if (external?.aborted) abort();
2287
+ if (timeoutMs !== void 0) this.timer = setTimeout(() => this.controller.abort(new HttpError(503, "database_deadline_exceeded", "Database transaction exceeded its deadline")), timeoutMs);
2288
+ }
2289
+ check() {
2290
+ if (this.deadline !== void 0 && performance.now() >= this.deadline && !this.signal.aborted) {
2291
+ this.controller.abort(new HttpError(503, "database_deadline_exceeded", "Database transaction exceeded its deadline"));
2292
+ }
2293
+ this.signal.throwIfAborted();
2294
+ }
2295
+ remainingMs() {
2296
+ this.check();
2297
+ return this.deadline === void 0 ? void 0 : Math.max(1, Math.ceil(this.deadline - performance.now()));
2298
+ }
2299
+ race(work) {
2300
+ return Promise.race([
2301
+ work,
2302
+ this.rejection
2303
+ ]);
2304
+ }
2305
+ bindBackend(cancel) {
2306
+ this.cancelBackend = cancel;
2307
+ }
2308
+ /** Keep the application's connection parked until the cancellation request
2309
+ * and all statements settle; a late cancel must not hit its next borrower. */
2310
+ async drainCancellation() {
2311
+ await this.cancellation;
2312
+ await Promise.allSettled([
2313
+ ...this.active
2314
+ ]);
2315
+ }
2316
+ async watch(query) {
2317
+ this.active.add(query);
2318
+ try {
2319
+ if (this.signal.aborted) query.cancel?.();
2320
+ return await query;
2321
+ } finally {
2322
+ this.active.delete(query);
2323
+ }
2324
+ }
2325
+ dispose() {
2326
+ if (this.timer !== void 0) clearTimeout(this.timer);
2327
+ this.unlink();
2328
+ }
2329
+ };
2330
+
2331
+ // src/engine/diagnostics.ts
2332
+ var import_node_crypto2 = require("crypto");
2333
+ var DatabaseTrace = class {
2334
+ static {
2335
+ __name(this, "DatabaseTrace");
2336
+ }
2337
+ options;
2338
+ start = performance.now();
2339
+ ended;
2340
+ failure;
2341
+ state;
2342
+ constructor(options) {
2343
+ this.options = options;
2344
+ for (const key2 of [
2345
+ "maxQueries",
2346
+ "maxRows",
2347
+ "maxBytes"
2348
+ ]) {
2349
+ const value = options[key2];
2350
+ if (value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) throw new Error(`database ${key2} must be a nonnegative safe integer`);
2351
+ }
2352
+ this.state = {
2353
+ endpoint: options.endpoint,
2354
+ requestId: options.requestId,
2355
+ outcome: "active",
2356
+ elapsedMs: 0,
2357
+ queries: 0,
2358
+ rows: 0,
2359
+ bytes: 0,
2360
+ queueMs: 0,
2361
+ poolMs: 0,
2362
+ setupMs: 0,
2363
+ sqlMs: 0,
2364
+ commitMs: 0,
2365
+ rollbackMs: 0,
2366
+ commits: 0,
2367
+ commitFailures: 0,
2368
+ rollbackRequests: 0,
2369
+ retries: 0,
2370
+ statements: []
2371
+ };
2372
+ }
2373
+ check() {
2374
+ if (this.failure) throw this.failure;
2375
+ }
2376
+ time(key2, started) {
2377
+ this.state[key2] += performance.now() - started;
2378
+ }
2379
+ retry() {
2380
+ this.state.retries++;
2381
+ }
2382
+ committed() {
2383
+ this.state.commits++;
2384
+ }
2385
+ commitFailed() {
2386
+ this.state.commitFailures++;
2387
+ }
2388
+ failed() {
2389
+ this.state.outcome = "failed";
2390
+ }
2391
+ rolledBack() {
2392
+ this.state.rollbackRequests++;
2393
+ }
2394
+ refuse(kind) {
2395
+ this.failure ??= new HttpError(500, "database_budget_exceeded", `Database ${kind} budget exceeded${this.options.endpoint ? ` in ${this.options.endpoint}` : ""}`);
2396
+ throw this.failure;
2397
+ }
2398
+ emit(callback, event) {
2399
+ try {
2400
+ const result = callback?.(event);
2401
+ if (result) void Promise.resolve(result).catch(() => void 0);
2402
+ } catch {
2403
+ }
2404
+ }
2405
+ async query(sql, name, run) {
2406
+ this.check();
2407
+ if (this.options.maxQueries !== void 0 && this.state.queries >= this.options.maxQueries) this.refuse("query count");
2408
+ const ordinal = ++this.state.queries;
2409
+ const event = {
2410
+ ordinal,
2411
+ fingerprint: (0, import_node_crypto2.createHash)("sha256").update(sql).digest("hex").slice(0, 24),
2412
+ ...name === void 0 ? {} : {
2413
+ name: name.slice(0, 128)
2414
+ },
2415
+ ...this.options.includeSql ? {
2416
+ sql: sql.slice(0, 4096)
2417
+ } : {},
2418
+ durationMs: 0,
2419
+ rows: 0,
2420
+ bytes: 0
2421
+ };
2422
+ if (this.options.captureSource) event.source = new Error().stack?.split("\n").slice(1, 9).join("\n").slice(0, 2500);
2423
+ const started = performance.now();
2424
+ let measured = false;
2425
+ try {
2426
+ const result = await run();
2427
+ event.durationMs = performance.now() - started;
2428
+ measured = true;
2429
+ if (Array.isArray(result)) {
2430
+ event.rows = result.length;
2431
+ event.bytes = Buffer.byteLength(JSON.stringify(result, (_, value) => typeof value === "bigint" ? value.toString() : value));
2432
+ }
2433
+ this.state.rows += event.rows;
2434
+ this.state.bytes += event.bytes;
2435
+ if (this.options.maxRows !== void 0 && this.state.rows > this.options.maxRows) this.refuse("returned rows");
2436
+ if (this.options.maxBytes !== void 0 && this.state.bytes > this.options.maxBytes) this.refuse("result bytes");
2437
+ return result;
2438
+ } catch (error) {
2439
+ const e = error;
2440
+ event.errorCode = String(e?.sqlstate ?? e?.errno ?? e?.code ?? "query_failed").slice(0, 80);
2441
+ throw error;
2442
+ } finally {
2443
+ if (!measured) event.durationMs = performance.now() - started;
2444
+ this.state.sqlMs += event.durationMs;
2445
+ const frozen = Object.freeze(event);
2446
+ if (this.state.statements.length < 200) this.state.statements.push(frozen);
2447
+ this.emit(this.options.onQuery, frozen);
2448
+ }
2449
+ }
2450
+ get snapshot() {
2451
+ return Object.freeze({
2452
+ ...this.state,
2453
+ elapsedMs: (this.ended ?? performance.now()) - this.start,
2454
+ statements: Object.freeze([
2455
+ ...this.state.statements
2456
+ ].sort((a, b) => a.ordinal - b.ordinal))
2457
+ });
2458
+ }
2459
+ complete(outcome) {
2460
+ if (this.ended !== void 0) return;
2461
+ this.ended = performance.now();
2462
+ this.state.outcome = outcome;
2463
+ this.emit(this.options.onComplete, this.snapshot);
2464
+ }
2465
+ };
1547
2466
 
1548
2467
  // src/db/columns.ts
1549
2468
  var CODECS = {
@@ -1602,9 +2521,9 @@ function buildRelations(schemas) {
1602
2521
  const taken = /* @__PURE__ */ new Map();
1603
2522
  for (const schema of schemas) {
1604
2523
  for (const t of Object.values(schema.tables)) {
1605
- const key = qualifiedTableKey(schema.name, t.name);
1606
- out.set(key, []);
1607
- taken.set(key, /* @__PURE__ */ new Map());
2524
+ const key2 = qualifiedTableKey(schema.name, t.name);
2525
+ out.set(key2, []);
2526
+ taken.set(key2, /* @__PURE__ */ new Map());
1608
2527
  }
1609
2528
  }
1610
2529
  const claim = /* @__PURE__ */ __name((tableKey, name, origin, edge) => {
@@ -1668,10 +2587,10 @@ function quoteIdent(name) {
1668
2587
  return `"${name.replace(/"/g, '""')}"`;
1669
2588
  }
1670
2589
  __name(quoteIdent, "quoteIdent");
1671
- function quoteTable(key) {
1672
- const dot = key.indexOf(".");
1673
- if (dot === -1) return quoteIdent(key);
1674
- return `${quoteIdent(key.slice(0, dot))}.${quoteIdent(key.slice(dot + 1))}`;
2590
+ function quoteTable(key2) {
2591
+ const dot = key2.indexOf(".");
2592
+ if (dot === -1) return quoteIdent(key2);
2593
+ return `${quoteIdent(key2.slice(0, dot))}.${quoteIdent(key2.slice(dot + 1))}`;
1675
2594
  }
1676
2595
  __name(quoteTable, "quoteTable");
1677
2596
  function encodePgArray(value) {
@@ -1684,9 +2603,10 @@ function encodePgArray(value) {
1684
2603
  }
1685
2604
  __name(encodePgArray, "encodePgArray");
1686
2605
  var BIND_SQL = "select set_config('role',$1,true), set_config('search_path','public',true), set_config('request.jwt.claims',$2,true)";
2606
+ var atomicScope = new import_node_async_hooks2.AsyncLocalStorage();
1687
2607
  function createLazyTransaction(sql, role, claimsJson, options = {}) {
1688
2608
  const { lockTimeout } = options;
1689
- const bindSql = lockTimeout ? `${BIND_SQL}, set_config('lock_timeout',$3,true)` : BIND_SQL;
2609
+ let bindSql = lockTimeout ? `${BIND_SQL}, set_config('lock_timeout',$3,true)` : BIND_SQL;
1690
2610
  const bindParams = lockTimeout ? [
1691
2611
  role,
1692
2612
  claimsJson,
@@ -1695,25 +2615,118 @@ function createLazyTransaction(sql, role, claimsJson, options = {}) {
1695
2615
  role,
1696
2616
  claimsJson
1697
2617
  ];
2618
+ if (options.statementTimeout !== void 0) {
2619
+ bindParams.push(options.statementTimeout);
2620
+ bindSql += `, set_config('statement_timeout',$${bindParams.length},true)`;
2621
+ }
1698
2622
  let opening = null;
1699
2623
  let release = null;
1700
2624
  let fail = null;
1701
2625
  let settled = null;
1702
- const ensure = /* @__PURE__ */ __name(() => {
2626
+ let closed = false;
2627
+ const assertAccess = /* @__PURE__ */ __name(() => {
2628
+ if (closed) throw new Error("Database transaction has settled; its handle cannot be used again");
2629
+ options.control?.check();
2630
+ options.trace?.check();
2631
+ if (atomicScope.getStore() !== options.scope) {
2632
+ throw new Error("Database.$atomic cannot use a handle from another transaction or identity; use its tx argument");
2633
+ }
2634
+ }, "assertAccess");
2635
+ let savepointALS;
2636
+ let activeSavepoint;
2637
+ const pendingSavepoints = /* @__PURE__ */ new Set();
2638
+ const scoped = /* @__PURE__ */ __name((tx, checkParent = assertAccess, owner, children = /* @__PURE__ */ new Set()) => {
2639
+ const access = /* @__PURE__ */ __name(() => {
2640
+ checkParent();
2641
+ if (activeSavepoint !== owner || savepointALS?.getStore() !== owner) {
2642
+ throw new Error("Database connection belongs to another active savepoint; use its tx argument and await it before using the parent handle");
2643
+ }
2644
+ }, "access");
2645
+ return {
2646
+ async unsafe(text, params, metadata) {
2647
+ access();
2648
+ const run = /* @__PURE__ */ __name(() => {
2649
+ const query = tx.unsafe(text, params);
2650
+ return options.control ? options.control.watch(query) : query;
2651
+ }, "run");
2652
+ return options.trace ? options.trace.query(text, metadata?.name, run) : run();
2653
+ },
2654
+ async savepoint(fn) {
2655
+ access();
2656
+ const token = /* @__PURE__ */ Symbol("savepoint"), descendants = /* @__PURE__ */ new Set();
2657
+ savepointALS ??= new import_node_async_hooks2.AsyncLocalStorage();
2658
+ activeSavepoint = token;
2659
+ const work = (async () => {
2660
+ try {
2661
+ return await tx.savepoint(async (sp) => {
2662
+ let ended = false;
2663
+ const check = /* @__PURE__ */ __name(() => {
2664
+ checkParent();
2665
+ if (ended) throw new Error("Database savepoint has settled; its handle cannot be used again");
2666
+ }, "check");
2667
+ try {
2668
+ const callback = savepointALS.run(token, () => fn(scoped(sp, check, token, descendants)));
2669
+ return await (options.control ? options.control.race(callback) : callback);
2670
+ } finally {
2671
+ if (descendants.size) await Promise.allSettled([
2672
+ ...descendants
2673
+ ]);
2674
+ ended = true;
2675
+ }
2676
+ });
2677
+ } finally {
2678
+ activeSavepoint = owner;
2679
+ }
2680
+ })();
2681
+ pendingSavepoints.add(work);
2682
+ children.add(work);
2683
+ try {
2684
+ return await work;
2685
+ } finally {
2686
+ pendingSavepoints.delete(work);
2687
+ children.delete(work);
2688
+ }
2689
+ }
2690
+ };
2691
+ }, "scoped");
2692
+ const ensure = /* @__PURE__ */ __name(async () => {
2693
+ assertAccess();
1703
2694
  if (opening) return opening;
1704
2695
  opening = new Promise((resolveTx2, rejectTx) => {
1705
2696
  const parked = new Promise((res, rej) => {
1706
2697
  release = res;
1707
2698
  fail = rej;
1708
2699
  });
2700
+ void parked.catch(() => void 0);
2701
+ const openingAt = options.trace ? performance.now() : 0;
1709
2702
  settled = sql.begin(async (tx) => {
1710
- await tx.unsafe(bindSql, bindParams);
1711
- resolveTx2(tx);
2703
+ options.trace?.time("poolMs", openingAt);
2704
+ const setupAt = options.trace ? performance.now() : 0;
2705
+ options.control?.check();
2706
+ if (options.isolation !== void 0 || options.readOnly !== void 0) {
2707
+ const isolation = options.isolation ?? "read committed";
2708
+ validateAtomicOptions({
2709
+ isolation,
2710
+ readOnly: options.readOnly
2711
+ });
2712
+ await tx.unsafe(`SET TRANSACTION ISOLATION LEVEL ${isolation.toUpperCase()}${options.readOnly === void 0 ? "" : options.readOnly ? " READ ONLY" : " READ WRITE"}`);
2713
+ }
2714
+ if (options.control && sql.cancelBackend) {
2715
+ const ticket = await tx.unsafe("SELECT pg_backend_pid() AS pid, backend_start::text AS started FROM pg_stat_activity WHERE pid=pg_backend_pid()");
2716
+ if (!Number.isInteger(ticket[0]?.pid) || typeof ticket[0]?.started !== "string") throw new Error("Database cancellation cannot identify its own backend");
2717
+ options.control.bindBackend(() => sql.cancelBackend(ticket[0].pid, ticket[0].started));
2718
+ options.control.check();
2719
+ }
2720
+ const binding = tx.unsafe(bindSql, bindParams);
2721
+ await (options.control ? options.control.watch(binding) : binding);
2722
+ options.trace?.time("setupMs", setupAt);
2723
+ resolveTx2(scoped(tx));
1712
2724
  await parked;
1713
2725
  }).catch((e) => {
1714
2726
  rejectTx(e);
1715
2727
  throw e;
1716
2728
  });
2729
+ void settled.catch(() => void 0);
1717
2730
  });
1718
2731
  return opening;
1719
2732
  }, "ensure");
@@ -1723,14 +2736,37 @@ function createLazyTransaction(sql, role, claimsJson, options = {}) {
1723
2736
  return opening !== null;
1724
2737
  },
1725
2738
  async commit() {
2739
+ if (pendingSavepoints.size) await Promise.allSettled([
2740
+ ...pendingSavepoints
2741
+ ]);
2742
+ options.control?.check();
2743
+ options.trace?.check();
2744
+ closed = true;
1726
2745
  if (!opening) return;
2746
+ const started = options.trace ? performance.now() : 0;
1727
2747
  release();
1728
- await settled;
2748
+ try {
2749
+ await settled;
2750
+ options.trace?.committed();
2751
+ } catch (error) {
2752
+ options.trace?.commitFailed();
2753
+ throw error;
2754
+ } finally {
2755
+ options.trace?.time("commitMs", started);
2756
+ }
1729
2757
  },
1730
2758
  async rollback(reason2) {
2759
+ closed = true;
1731
2760
  if (!opening) return;
2761
+ await options.control?.drainCancellation();
2762
+ if (pendingSavepoints.size) await Promise.allSettled([
2763
+ ...pendingSavepoints
2764
+ ]);
2765
+ const started = options.trace ? performance.now() : 0;
1732
2766
  fail(reason2);
1733
2767
  await settled?.catch(() => void 0);
2768
+ options.trace?.time("rollbackMs", started);
2769
+ options.trace?.rolledBack();
1734
2770
  }
1735
2771
  };
1736
2772
  }
@@ -1745,7 +2781,7 @@ __name(asWireValue, "asWireValue");
1745
2781
  function asWireRow(row) {
1746
2782
  if (row === null || typeof row !== "object") return row;
1747
2783
  const out = {};
1748
- for (const [key, value] of Object.entries(row)) out[key] = asWireValue(value);
2784
+ for (const [key2, value] of Object.entries(row)) out[key2] = asWireValue(value);
1749
2785
  return out;
1750
2786
  }
1751
2787
  __name(asWireRow, "asWireRow");
@@ -1757,7 +2793,7 @@ function toVectorLiteral(v) {
1757
2793
  return `[${v.join(",")}]`;
1758
2794
  }
1759
2795
  __name(toVectorLiteral, "toVectorLiteral");
1760
- function vectorColumnsOf(schema, table) {
2796
+ function computeVectorColumns(schema, table) {
1761
2797
  const out = /* @__PURE__ */ new Set();
1762
2798
  const def = tableDefOf(table, schema);
1763
2799
  if (def) {
@@ -1768,9 +2804,9 @@ function vectorColumnsOf(schema, table) {
1768
2804
  }
1769
2805
  return out;
1770
2806
  }
1771
- __name(vectorColumnsOf, "vectorColumnsOf");
1772
- function tableDefOf(key, schema = currentSchema) {
1773
- return schema.tables?.[key] ?? null;
2807
+ __name(computeVectorColumns, "computeVectorColumns");
2808
+ function tableDefOf(key2, schema = currentSchema) {
2809
+ return schema.tables?.[key2] ?? null;
1774
2810
  }
1775
2811
  __name(tableDefOf, "tableDefOf");
1776
2812
  function reviveVectors(row, vectorCols) {
@@ -1783,7 +2819,7 @@ function reviveVectors(row, vectorCols) {
1783
2819
  return row;
1784
2820
  }
1785
2821
  __name(reviveVectors, "reviveVectors");
1786
- function transformsOf(schema, table) {
2822
+ function computeTransforms(schema, table) {
1787
2823
  const out = /* @__PURE__ */ new Map();
1788
2824
  const def = tableDefOf(table, schema);
1789
2825
  if (def) {
@@ -1806,7 +2842,7 @@ function transformsOf(schema, table) {
1806
2842
  }
1807
2843
  return out;
1808
2844
  }
1809
- __name(transformsOf, "transformsOf");
2845
+ __name(computeTransforms, "computeTransforms");
1810
2846
  function applyFromDb(row, transforms) {
1811
2847
  if (transforms.size === 0) return row;
1812
2848
  for (const [col, t] of transforms) {
@@ -1823,10 +2859,23 @@ function asTableRow(table, row) {
1823
2859
  return applyFromDb(revived, transformsOf(currentSchema, table));
1824
2860
  }
1825
2861
  __name(asTableRow, "asTableRow");
1826
- function asTableRows(table, rows) {
2862
+ function asTableRows(table, rows, withSpec) {
1827
2863
  const vectorCols = vectorColumnsOf(currentSchema, table);
1828
2864
  const transforms = transformsOf(currentSchema, table);
1829
- return rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
2865
+ const result = rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
2866
+ if (withSpec) for (const rel of currentRelations.get(table) ?? []) {
2867
+ const spec = withSpec[rel.name];
2868
+ if (!spec) continue;
2869
+ const nested = typeof spec === "object" ? spec.with : void 0;
2870
+ for (const row of result) {
2871
+ const value = row[rel.name];
2872
+ if (Array.isArray(value)) row[rel.name] = asTableRows(rel.to, value, nested);
2873
+ else if (value && typeof value === "object") row[rel.name] = asTableRows(rel.to, [
2874
+ value
2875
+ ], nested)[0];
2876
+ }
2877
+ }
2878
+ return result;
1830
2879
  }
1831
2880
  __name(asTableRows, "asTableRows");
1832
2881
  function asBindParams(table, cols, data, caller) {
@@ -2069,7 +3118,7 @@ function excludeSql(pk, ids, add) {
2069
3118
  return ` AND t.${quoteIdent(pk)}::text <> ALL(ARRAY[${ids.map((x) => add(x)).join(", ")}])`;
2070
3119
  }
2071
3120
  __name(excludeSql, "excludeSql");
2072
- function primaryKeyOf(table) {
3121
+ function computePrimaryKey(table) {
2073
3122
  const t = tableDefOf(table);
2074
3123
  const columns = t?.columns ?? {};
2075
3124
  const cols = Object.keys(columns);
@@ -2089,7 +3138,7 @@ function primaryKeyOf(table) {
2089
3138
  const pks = cols.filter((c) => pkOf(columns[c]));
2090
3139
  return pks.length === 1 ? pks[0] : null;
2091
3140
  }
2092
- __name(primaryKeyOf, "primaryKeyOf");
3141
+ __name(computePrimaryKey, "computePrimaryKey");
2093
3142
  function isSinglePkEquality(where, pk) {
2094
3143
  if (pk === null) return false;
2095
3144
  const keys = Object.keys(where);
@@ -2104,12 +3153,43 @@ function assertNotAppendOnly(caller, table) {
2104
3153
  throw new Error(`${caller}(${table}): "${table}" append-only \u2014 sat\u0131r g\xFCncellenemez ya da silinemez. D\xFCzeltme i\xE7in TELAF\u0130 KAYDI ekleyin: insert("${table}", { \u2026 }) ile ters i\u015Fareti ta\u015F\u0131yan yeni bir sat\u0131r yaz\u0131n; ge\xE7mi\u015F oldu\u011Fu gibi kals\u0131n diye tablo b\xF6yle bildirilmi\u015F (appendOnly: true).`);
2105
3154
  }
2106
3155
  __name(assertNotAppendOnly, "assertNotAppendOnly");
2107
- function schemaColumns(table) {
3156
+ function computeSchemaColumns(table) {
2108
3157
  const t = tableDefOf(table);
2109
3158
  const cols = t?.columns ? Object.keys(t.columns) : [];
2110
3159
  return cols.length > 0 ? new Set(cols) : null;
2111
3160
  }
2112
- __name(schemaColumns, "schemaColumns");
3161
+ __name(computeSchemaColumns, "computeSchemaColumns");
3162
+ function timestampResult(expr) {
3163
+ return `CASE WHEN isfinite(${expr}) AND extract(year FROM ${expr} AT TIME ZONE 'UTC') BETWEEN 1 AND 9999 THEN to_char(${expr} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') ELSE ${expr}::text END`;
3164
+ }
3165
+ __name(timestampResult, "timestampResult");
3166
+ function columnResult(table, column, alias) {
3167
+ const expr = `${alias ? `${quoteIdent(alias)}.` : ""}${quoteIdent(column)}`;
3168
+ return columnTypeOf(table, column) === "timestamp" ? timestampResult(expr) : expr;
3169
+ }
3170
+ __name(columnResult, "columnResult");
3171
+ function projectedColumn(table, column, alias) {
3172
+ const expr = columnResult(table, column, alias);
3173
+ return columnTypeOf(table, column) === "timestamp" ? `${expr} AS ${quoteIdent(column)}` : expr;
3174
+ }
3175
+ __name(projectedColumn, "projectedColumn");
3176
+ function rowProjection(table, alias, wildcard = alias ? `${alias}.*` : "*") {
3177
+ const cols = schemaColumns(table);
3178
+ if (!cols || !memoTable(timestampMemo, currentSchema, table, () => [
3179
+ ...cols
3180
+ ].some((c) => columnTypeOf(table, c) === "timestamp"))) return wildcard;
3181
+ const projections = memoTable(projectionMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
3182
+ const key2 = alias ?? "";
3183
+ let sql = projections.get(key2);
3184
+ if (sql === void 0) {
3185
+ sql = [
3186
+ ...cols
3187
+ ].map((c) => projectedColumn(table, c, alias)).join(", ");
3188
+ projections.set(key2, sql);
3189
+ }
3190
+ return sql;
3191
+ }
3192
+ __name(rowProjection, "rowProjection");
2113
3193
  function colRefSql(table, known, ref, caller, alias = "t") {
2114
3194
  const name = ref.$col;
2115
3195
  const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
@@ -2123,13 +3203,13 @@ function selectClause(table, known, select) {
2123
3203
  if (select !== void 0 && select.length === 0) {
2124
3204
  throw new Error(`findMany(${table}): select bo\u015F bir liste ald\u0131 \u2014 bu "hepsini ver" DE\u011E\u0130L, "hi\xE7birini saymad\u0131m" demek. Projeksiyon istemiyorsan\u0131z anahtar\u0131 hi\xE7 koymay\u0131n; istiyorsan\u0131z en az bir kolon ad\u0131 verin.`);
2125
3205
  }
2126
- if (select === void 0) return "*";
3206
+ if (select === void 0) return rowProjection(table, "t", "*");
2127
3207
  return select.map((col) => {
2128
3208
  const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(col);
2129
3209
  if (known !== null ? !known.has(col) : !shapeOK) {
2130
3210
  throw new Error(`findMany(${table}): select kolonu "${col}" tabloda yok`);
2131
3211
  }
2132
- return `t.${quoteIdent(col)}`;
3212
+ return columnTypeOf(table, col) === "timestamp" ? projectedColumn(table, col, "t") : `t.${quoteIdent(col)}`;
2133
3213
  }).join(", ");
2134
3214
  }
2135
3215
  __name(selectClause, "selectClause");
@@ -2199,7 +3279,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
2199
3279
  const transforms = transformsOf(currentSchema, table);
2200
3280
  const bindFor = /* @__PURE__ */ __name((col) => {
2201
3281
  const t = transforms.get(col);
2202
- return t?.toDb === void 0 ? add : (v) => add(v === null || v === void 0 ? v : t.toDb(v));
3282
+ return (v) => add(t?.toDb === void 0 || v === null || v === void 0 ? v : t.toDb(v), col, table);
2203
3283
  }, "bindFor");
2204
3284
  assertUsableFilter(caller, table, where);
2205
3285
  if (isSqlFragment(where)) {
@@ -2230,8 +3310,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
2230
3310
  const known = rels.map((r) => r.name).sort();
2231
3311
  throw new Error(`${caller}(${table}): has.${relName} diye bir ili\u015Fki yok` + (known.length > 0 ? ` (bilinenler: ${known.join(", ")}). \u0130li\u015Fkiler yabanc\u0131 anahtarlardan T\xDCRET\u0130L\u0130R; ad\u0131n\u0131 de\u011Fi\u015Ftirmek i\xE7in references(..., { as, reverseAs }).` : `. Bu tablo H\u0130\xC7 yabanc\u0131 anahtar bildirmiyor, ve ili\u015Fkiler onlardan T\xDCRET\u0130L\u0130R. Var olan bir veritaban\u0131ndan introspect edilmi\u015F \u015Femalarda bu s\u0131k olur: kolon \`text()\`/\`uuid()\` olarak durur, \`references(() => <tablo>.<kolon>)\` yoktur. Bildirdi\u011Finiz anda ili\u015Fki iki tarafta da adland\u0131r\u0131l\u0131r \u2014 ama bu CANLI veritaban\u0131na giden bir k\u0131s\u0131tt\u0131r ve mevcut yetim sat\u0131rlar onu d\xFC\u015F\xFCr\xFCr; \xF6nce onlar\u0131 say\u0131n.`));
2232
3312
  }
2233
- const ourCol = rel.kind === "many" ? primaryKeyOf(table) : rel.via;
2234
- const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
3313
+ const [ourCol, theirCol] = relationColumns(table, rel);
2235
3314
  if (ourCol === null || theirCol === null) {
2236
3315
  const missing = ourCol === null ? table : rel.to;
2237
3316
  throw new Error(`${caller}(${table}): has.${relName} kurulam\u0131yor \u2014 "${missing}" tablosunun tek kolonlu bir primary key'i yok. Korelasyon bir anahtar ister; \xE7ok kolonlu anahtarda ili\u015Fki \xFCzerinden s\xFCzmek yerine sqlFragment ile yaz\u0131n.`);
@@ -2369,8 +3448,8 @@ async function embedQuery(embed, text) {
2369
3448
  if (secretReader === null) {
2370
3449
  throw new Error(`query embed: secret reader ba\u011Flanmam\u0131\u015F \u2014 ${embed.apiKeyName} okunam\u0131yor`);
2371
3450
  }
2372
- const key = await secretReader(embed.apiKeyName);
2373
- if (key === null || key === "") {
3451
+ const key2 = await secretReader(embed.apiKeyName);
3452
+ if (key2 === null || key2 === "") {
2374
3453
  throw new Error(`query embed: vault'ta ${embed.apiKeyName} yok (FR-021/FR-025)`);
2375
3454
  }
2376
3455
  const url = (embed.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "") + "/embeddings";
@@ -2381,7 +3460,7 @@ async function embedQuery(embed, text) {
2381
3460
  method: "POST",
2382
3461
  headers: {
2383
3462
  "Content-Type": "application/json",
2384
- Authorization: `Bearer ${key}`
3463
+ Authorization: `Bearer ${key2}`
2385
3464
  },
2386
3465
  body: JSON.stringify({
2387
3466
  model: embed.model,
@@ -2439,14 +3518,14 @@ function buildValueTuple(table, cols, data, add, caller) {
2439
3518
  }).join(", ");
2440
3519
  }
2441
3520
  __name(buildValueTuple, "buildValueTuple");
2442
- function columnTypeOf(table, col) {
3521
+ function computeColumnType(table, col) {
2443
3522
  const def = tableDefOf(table);
2444
3523
  const c = def?.columns?.[col];
2445
3524
  if (c === void 0 || c === null) return null;
2446
3525
  const d = typeof c === "object" && "_def" in c ? c._def : c;
2447
3526
  return d !== null && typeof d === "object" && typeof d.type === "string" ? d.type : null;
2448
3527
  }
2449
- __name(columnTypeOf, "columnTypeOf");
3528
+ __name(computeColumnType, "computeColumnType");
2450
3529
  var NUMERIC_PG_TYPES = /* @__PURE__ */ new Set([
2451
3530
  "integer",
2452
3531
  "bigint",
@@ -2456,23 +3535,44 @@ function aggregateExpr(table, fn, col, alias) {
2456
3535
  const q = `t.${quoteIdent(col)}`;
2457
3536
  const t = columnTypeOf(table, col);
2458
3537
  if (fn === "sum" || fn === "avg") return `${fn}(${q})::text AS ${quoteIdent(alias)}`;
3538
+ if (t === "timestamp") return `${timestampResult(`${fn}(${q})`)} AS ${quoteIdent(alias)}`;
2459
3539
  return t !== null && NUMERIC_PG_TYPES.has(t) && t !== "integer" ? `${fn}(${q})::text AS ${quoteIdent(alias)}` : `${fn}(${q}) AS ${quoteIdent(alias)}`;
2460
3540
  }
2461
3541
  __name(aggregateExpr, "aggregateExpr");
2462
- function jsonObjectFor(table, cols, alias) {
3542
+ function jsonObjectFor(table, cols, alias, extra = []) {
2463
3543
  const pairs = cols.map((c) => {
2464
3544
  const t = columnTypeOf(table, c);
2465
- const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : `${alias}.${quoteIdent(c)}`;
3545
+ const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : t === "timestamp" ? timestampResult(`${alias}.${quoteIdent(c)}`) : `${alias}.${quoteIdent(c)}`;
2466
3546
  return `${quoteLiteral(c)}, ${expr}`;
2467
3547
  });
2468
- return `json_build_object(${pairs.join(", ")})`;
3548
+ return `json_build_object(${[
3549
+ ...pairs,
3550
+ ...extra
3551
+ ].join(", ")})`;
2469
3552
  }
2470
3553
  __name(jsonObjectFor, "jsonObjectFor");
2471
- function withClauses(table, spec, add, caller) {
3554
+ function relationColumns(table, relation) {
3555
+ const child = relation.kind === "many" ? relation.to : table;
3556
+ const value = tableDefOf(child)?.columns?.[relation.via];
3557
+ const column = value && typeof value === "object" && "_def" in value ? value._def : value;
3558
+ const target = column?.references?.column ?? primaryKeyOf(relation.kind === "many" ? table : relation.to);
3559
+ return relation.kind === "many" ? [
3560
+ target,
3561
+ relation.via
3562
+ ] : [
3563
+ relation.via,
3564
+ target
3565
+ ];
3566
+ }
3567
+ __name(relationColumns, "relationColumns");
3568
+ function withClauses(table, spec, add, caller, parentAlias = "t", depth = 0, counter = {
3569
+ value: 0
3570
+ }) {
3571
+ if (depth >= 3 && Object.values(spec).some(Boolean)) throw new Error(`${caller}(${table}): with supports at most three relation levels`);
2472
3572
  const rels = currentRelations.get(table) ?? [];
2473
3573
  const select = [];
2474
3574
  const joins = [];
2475
- let i = 0;
3575
+ const jsonPairs = [];
2476
3576
  for (const [name, opt] of Object.entries(spec)) {
2477
3577
  if (opt === false || opt === void 0) continue;
2478
3578
  const rel = rels.find((r) => r.name === name);
@@ -2480,13 +3580,13 @@ function withClauses(table, spec, add, caller) {
2480
3580
  const known = rels.map((r) => r.name).sort();
2481
3581
  throw new Error(`${caller}(${table}): with.${name} diye bir ili\u015Fki yok` + (known.length > 0 ? ` (bilinenler: ${known.join(", ")})` : `. Bu tablo hi\xE7 yabanc\u0131 anahtar bildirmiyor ve ili\u015Fkiler onlardan T\xDCRET\u0130L\u0130R`) + `. \u0130li\u015Fkiler references(() => <tablo>.<kolon>) ile bildirilir.`);
2482
3582
  }
2483
- const ourCol = rel.kind === "many" ? primaryKeyOf(table) : rel.via;
2484
- const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
3583
+ const [ourCol, theirCol] = relationColumns(table, rel);
2485
3584
  if (ourCol === null || theirCol === null) {
2486
3585
  throw new Error(`${caller}(${table}): with.${name} birincil anahtar bilmeden kurulamaz \u2014 ili\u015Fkinin bir ucu tablonun PK's\u0131 ve bu tablo tek kolonlu bir PK bildirmiyor.`);
2487
3586
  }
2488
3587
  const o = typeof opt === "object" && opt !== null ? opt : {};
2489
- const childAlias = `w${++i}`;
3588
+ const i = ++counter.value;
3589
+ const childAlias = `w${i}`;
2490
3590
  const childKnown = schemaColumns(rel.to);
2491
3591
  const childCols = Array.isArray(o["select"]) && o["select"].length > 0 ? o["select"] : childKnown !== null ? [
2492
3592
  ...childKnown
@@ -2499,28 +3599,100 @@ function withClauses(table, spec, add, caller) {
2499
3599
  throw new Error(`${caller}(${table}): with.${name}.select kolonu "${c}" "${rel.to}" tablosunda yok`);
2500
3600
  }
2501
3601
  }
2502
- const obj = jsonObjectFor(rel.to, childCols, childAlias);
3602
+ const nestedSpec = o["with"];
3603
+ if (nestedSpec !== void 0 && (nestedSpec === null || typeof nestedSpec !== "object" || Array.isArray(nestedSpec))) {
3604
+ throw new Error(`${caller}(${table}): with.${name}.with must be a relation object`);
3605
+ }
3606
+ const nested = nestedSpec === void 0 ? {
3607
+ joins: [],
3608
+ jsonPairs: []
3609
+ } : withClauses(rel.to, nestedSpec, add, caller, childAlias, depth + 1, counter);
3610
+ const nestedJoins = nested.joins.length ? ` ${nested.joins.join(" ")}` : "";
3611
+ const obj = jsonObjectFor(rel.to, childCols, childAlias, nested.jsonPairs);
2503
3612
  const innerWhere = o["where"] !== void 0 && o["where"] !== null ? compileWhereBare(rel.to, childKnown, o["where"], add, caller, childAlias, i) : "";
2504
- const link = `${childAlias}.${quoteIdent(theirCol)} = t.${quoteIdent(ourCol)}`;
3613
+ const link = `${childAlias}.${quoteIdent(theirCol)} = ${parentAlias}.${quoteIdent(ourCol)}`;
2505
3614
  const cond = innerWhere === "" ? link : `${link} AND ${innerWhere}`;
2506
- const inner = orderClauseFor(rel.to, childKnown, o["orderBy"], childAlias) + limitClause(o["limit"]);
3615
+ const requestedLimit = o["limit"];
3616
+ limitClause(requestedLimit);
3617
+ const relationLimit = rel.kind === "many" ? requestedLimit : Math.min(1, requestedLimit ?? 1);
3618
+ const inner = orderClauseFor(rel.to, childKnown, o["orderBy"], childAlias) + limitClause(relationLimit);
2507
3619
  const outAlias = `wj${i}`;
2508
3620
  if (rel.kind === "many") {
2509
- joins.push(`LEFT JOIN LATERAL (SELECT coalesce(json_agg(x.o), '[]'::json) AS d FROM (SELECT ${obj} AS o FROM ${quoteTable(rel.to)} ${childAlias} WHERE ${cond}${inner}) x) ${quoteIdent(outAlias)} ON true`);
3621
+ joins.push(`LEFT JOIN LATERAL (SELECT coalesce(json_agg(x.o), '[]'::json) AS d FROM (SELECT ${obj} AS o FROM ${quoteTable(rel.to)} ${childAlias}${nestedJoins} WHERE ${cond}${inner}) x) ${quoteIdent(outAlias)} ON true`);
2510
3622
  } else {
2511
- joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias} WHERE ${cond}${inner} LIMIT 1) ${quoteIdent(outAlias)} ON true`);
3623
+ joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias}${nestedJoins} WHERE ${cond}${inner}) ${quoteIdent(outAlias)} ON true`);
2512
3624
  }
2513
3625
  select.push(`${quoteIdent(outAlias)}."d" AS ${quoteIdent(name)}`);
3626
+ jsonPairs.push(`${quoteLiteral(name)}, ${quoteIdent(outAlias)}."d"`);
2514
3627
  }
2515
3628
  return {
2516
3629
  select,
2517
- joins
3630
+ joins,
3631
+ jsonPairs
2518
3632
  };
2519
3633
  }
2520
3634
  __name(withClauses, "withClauses");
2521
- function createOps(tx) {
3635
+ function compileFindMany(table, query = {}, opts = {}) {
3636
+ const params = [];
3637
+ const add = /* @__PURE__ */ __name((v) => {
3638
+ params.push(v);
3639
+ return `$${params.length}`;
3640
+ }, "add");
3641
+ const known = schemaColumns(table);
3642
+ const whereSql = compileWhere(table, known, query, add, "findMany");
3643
+ const order = orderClause(table, known, opts.orderBy);
3644
+ const limit = limitClause(opts.limit);
3645
+ const offset = offsetClause(opts.offset, opts.limit);
3646
+ const cols = selectClause(table, known, opts.select);
3647
+ const w = opts.with !== void 0 && Object.keys(opts.with).length > 0 ? withClauses(table, opts.with, add, "findMany") : {
3648
+ select: [],
3649
+ joins: []
3650
+ };
3651
+ const selectList = w.select.length > 0 ? `${cols === "*" ? "t.*" : cols}, ${w.select.join(", ")}` : cols;
3652
+ const joinSql = w.joins.length > 0 ? ` ${w.joins.join(" ")}` : "";
3653
+ const sql = `SELECT ${selectList} FROM ${quoteTable(table)} t${joinSql} WHERE true${whereSql}${order}${limit}${offset}`;
3654
+ return {
3655
+ sql,
3656
+ params
3657
+ };
3658
+ }
3659
+ __name(compileFindMany, "compileFindMany");
3660
+ var uniqueKeysCache = /* @__PURE__ */ new WeakMap();
3661
+ var pageSchemaCache = /* @__PURE__ */ new WeakMap();
3662
+ function pageSchema(table) {
3663
+ return memoTable(pageSchemaCache, currentSchema, table, () => {
3664
+ const def = tableDefOf(table);
3665
+ if (!def?.columns) throw new Error(`page(${table}) requires an installed table declaration`);
3666
+ const columns = /* @__PURE__ */ Object.create(null);
3667
+ const inlineKeys = [];
3668
+ for (const [name, column] of Object.entries(def.columns)) {
3669
+ const c = column;
3670
+ const d = c._def ?? c;
3671
+ columns[name] = {
3672
+ type: d.type,
3673
+ nullable: d.nullable
3674
+ };
3675
+ if (d.primaryKey) inlineKeys.push(name);
3676
+ }
3677
+ const primaryKey = def.primaryKey ?? inlineKeys;
3678
+ for (const name of primaryKey) if (columns[name]) columns[name].nullable = false;
3679
+ return {
3680
+ columns,
3681
+ primaryKey,
3682
+ stamp: fingerprint({
3683
+ columns,
3684
+ primaryKey
3685
+ })
3686
+ };
3687
+ });
3688
+ }
3689
+ __name(pageSchema, "pageSchema");
3690
+ function createOps(tx, context = {}) {
2522
3691
  const at = /* @__PURE__ */ __name(() => resolveTx(tx), "at");
2523
3692
  const ops = {
3693
+ diagnostics() {
3694
+ return context.trace?.snapshot ?? null;
3695
+ },
2524
3696
  // Generic dönüş, çağıranın İDDİASIDIR (FR-021). Motor her zaman satır objeleri
2525
3697
  // üretir; `T` onlara bir ad vermekten ibarettir ve verilmezse `unknown` kalır —
2526
3698
  // yani "bilmiyorum", ki ham SQL için doğru cevap odur.
@@ -2534,7 +3706,7 @@ function createOps(tx) {
2534
3706
  const params = [];
2535
3707
  const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
2536
3708
  const tuple = buildValueTuple(table, cols, data, add, "insert");
2537
- const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING *`;
3709
+ const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`;
2538
3710
  const rows = await (await at()).unsafe(sql, params);
2539
3711
  const inserted = rows[0];
2540
3712
  if (!inserted) {
@@ -2556,8 +3728,9 @@ function createOps(tx) {
2556
3728
  * bir satırın fazla kolonu SESSİZCE YAZILMAZDI. Eksik kolon için `null`
2557
3729
  * yazın ya da varsayılanı istiyorsanız o satırları ayrı çağrıda ekleyin.
2558
3730
  */
2559
- async insertMany(table, rows, opts) {
2560
- if (rows.length === 0) return [];
3731
+ insertMany: /* @__PURE__ */ __name(async function(table, rows, opts) {
3732
+ validateInsertManyOptions(opts);
3733
+ if (rows.length === 0) return opts?.returning === false ? 0 : [];
2561
3734
  const first = rows[0];
2562
3735
  const cols = Object.keys(first);
2563
3736
  if (cols.length === 0) throw new Error(`insertMany into ${table}: no columns given`);
@@ -2572,11 +3745,15 @@ function createOps(tx) {
2572
3745
  const params = [];
2573
3746
  const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
2574
3747
  const tuples = rows.map((r) => `(${buildValueTuple(table, cols, r, add, "insertMany")})`);
2575
- const tail = opts !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
2576
- const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING *`;
3748
+ const tail = opts?.onConflict !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
3749
+ const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail}${opts?.returning === false ? "" : ` RETURNING ${rowProjection(table)}`}`;
2577
3750
  const out = await (await at()).unsafe(sql, params);
3751
+ if (opts?.returning === false) {
3752
+ if (!Number.isSafeInteger(out.count) || out.count < 0) throw new Error("insertMany returning:false requires a driver with an affected row count");
3753
+ return out.count;
3754
+ }
2578
3755
  return out.map((r) => asTableRow(table, r));
2579
- },
3756
+ }, "insertMany"),
2580
3757
  /**
2581
3758
  * Bir ADI kilitle — satırı değil (FR-054).
2582
3759
  *
@@ -2590,9 +3767,9 @@ function createOps(tx) {
2590
3767
  * Transaction kapsamlısı commit/rollback'te Postgres tarafından bırakılır,
2591
3768
  * yani unutulması imkânsız.
2592
3769
  */
2593
- async advisoryXactLock(key) {
3770
+ async advisoryXactLock(key2) {
2594
3771
  await (await at()).unsafe("select pg_advisory_xact_lock(hashtextextended($1, 0))", [
2595
- key
3772
+ key2
2596
3773
  ]);
2597
3774
  },
2598
3775
  /**
@@ -2666,7 +3843,7 @@ function createOps(tx) {
2666
3843
  const params = [];
2667
3844
  const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
2668
3845
  const tuple = buildValueTuple(table, cols, data, add, "claim");
2669
- const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING *`, params);
3846
+ const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
2670
3847
  if (!rows[0]) {
2671
3848
  throw new Error(`claim(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si).`);
2672
3849
  }
@@ -2683,7 +3860,7 @@ function createOps(tx) {
2683
3860
  return `$${params.length}`;
2684
3861
  }, "add");
2685
3862
  const where = keyCols.map((c) => `t.${quoteIdent(c)} = ${add(unique[c])}`).join(" AND ");
2686
- const found = await (await at()).unsafe(`SELECT * FROM ${quoteTable(table)} t WHERE ${where} LIMIT 1`, params);
3863
+ const found = await (await at()).unsafe(`SELECT ${rowProjection(table, "t", "*")} FROM ${quoteTable(table)} t WHERE ${where} LIMIT 1`, params);
2687
3864
  if (!found[0]) {
2688
3865
  const named = constraintOf(e);
2689
3866
  throw new Error(`claim(${table}): benzersizlik ihlali${named ? ` (${named})` : ""} yakaland\u0131 ama ${keyCols.join(", ")} ile sat\u0131r bulunamad\u0131. Ya ihlal BA\u015EKA bir unique k\u0131s\u0131ttan geliyor \u2014 o zaman claim() bu \xE7a\u011Fr\u0131 i\xE7in yanl\u0131\u015F ara\xE7 \u2014 ya da sat\u0131r var ve bir RLS policy'si onu bu \xE7a\u011F\u0131randan gizliyor.`);
@@ -2720,13 +3897,13 @@ function createOps(tx) {
2720
3897
  const bindData = [];
2721
3898
  const addPut = /* @__PURE__ */ __name((v) => `$${bindData.push(v)}`, "addPut");
2722
3899
  const ph = buildValueTuple(table, cols, data, addPut, "put");
2723
- const insertSql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${ph}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) WHERE "valid_to" IS NULL DO NOTHING RETURNING *`;
3900
+ const insertSql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${ph}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) WHERE "valid_to" IS NULL DO NOTHING RETURNING ${rowProjection(table)}`;
2724
3901
  const live = await at();
2725
3902
  const first = await live.unsafe(insertSql, bindData);
2726
3903
  if (first[0]) return asTableRow(table, first[0]);
2727
3904
  const condSql = conflict.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ");
2728
3905
  const condBind = conflict.map((c) => data[c]);
2729
- const cur = await live.unsafe(`SELECT * FROM ${quoteTable(table)} WHERE ${condSql} AND "valid_to" IS NULL`, condBind);
3906
+ const cur = await live.unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE ${condSql} AND "valid_to" IS NULL`, condBind);
2730
3907
  const old = cur[0];
2731
3908
  if (old === void 0) {
2732
3909
  throw new Error(`upsert into ${table}: e\u015Fzamanl\u0131 supersede yar\u0131\u015F\u0131 \u2014 ge\xE7erli sat\u0131r bulunamad\u0131, iste\u011Fi yineleyin (D-020)`);
@@ -2752,7 +3929,7 @@ function createOps(tx) {
2752
3929
  const conflictSet = new Set(conflict);
2753
3930
  const assignments = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
2754
3931
  const action = assignments.length ? `DO UPDATE SET ${assignments.join(", ")}` : `DO UPDATE SET ${quoteIdent(conflict[0])} = EXCLUDED.${quoteIdent(conflict[0])}`;
2755
- const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING *`;
3932
+ const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING ${rowProjection(table)}`;
2756
3933
  const rows = await (await at()).unsafe(sql, params);
2757
3934
  const row = rows[0];
2758
3935
  if (!row) {
@@ -2770,7 +3947,7 @@ function createOps(tx) {
2770
3947
  return `$${params.length}`;
2771
3948
  }, "add");
2772
3949
  const assignments = buildSetAssignments(table, cols, data, add, "update");
2773
- const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING *`;
3950
+ const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING ${rowProjection(table)}`;
2774
3951
  const rows = await (await at()).unsafe(sql, params);
2775
3952
  return rows[0] ? asTableRow(table, rows[0]) : null;
2776
3953
  },
@@ -2822,9 +3999,9 @@ function createOps(tx) {
2822
3999
  assertHasPredicate(whereSql, "updateMany", table);
2823
4000
  const pk = primaryKeyOf(table);
2824
4001
  const wantRows = opts?.returning !== false;
2825
- const tail = wantRows ? "RETURNING *" : "RETURNING 1";
4002
+ const tail = wantRows ? `RETURNING ${rowProjection(table, "t", "*")}` : "RETURNING 1";
2826
4003
  const simple = isSinglePkEquality(where, pk) || pk === null;
2827
- const body = simple ? `UPDATE ${quoteTable(table)} AS t SET ${assignments} WHERE true${whereSql} ${tail}` : `UPDATE ${quoteTable(table)} AS t SET ${assignments} FROM locked WHERE t.${quoteIdent(pk)} = locked.${quoteIdent(pk)} ` + (wantRows ? "RETURNING t.*" : "RETURNING 1");
4004
+ const body = simple ? `UPDATE ${quoteTable(table)} AS t SET ${assignments} WHERE true${whereSql} ${tail}` : `UPDATE ${quoteTable(table)} AS t SET ${assignments} FROM locked WHERE t.${quoteIdent(pk)} = locked.${quoteIdent(pk)} ` + (wantRows ? `RETURNING ${rowProjection(table, "t")}` : "RETURNING 1");
2828
4005
  const lockedCte = simple ? "" : `WITH locked AS (SELECT t.${quoteIdent(pk)} FROM ${quoteTable(table)} t WHERE true${whereSql} ORDER BY t.${quoteIdent(pk)} FOR NO KEY UPDATE) `;
2829
4006
  const sql = wantRows ? `${lockedCte}${body}` : simple ? `WITH upd AS (${body}) SELECT count(*)::int AS n FROM upd` : `${lockedCte.replace(/ $/, "")}, upd AS (${body}) SELECT count(*)::int AS n FROM upd`;
2830
4007
  const out = await (await at()).unsafe(sql, params);
@@ -2909,7 +4086,7 @@ function createOps(tx) {
2909
4086
  }
2910
4087
  const groups = q.groupBy ?? [];
2911
4088
  for (const col of groups) check(col, "groupBy");
2912
- const groupSel = groups.map((c) => `t.${quoteIdent(c)} AS ${quoteIdent(`g__${c}`)}`);
4089
+ const groupSel = groups.map((c) => `${columnTypeOf(table, c) === "timestamp" ? columnResult(table, c, "t") : `t.${quoteIdent(c)}`} AS ${quoteIdent(`g__${c}`)}`);
2913
4090
  const params = [];
2914
4091
  const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
2915
4092
  const whereSql = compileWhere(table, known, q.where ?? {}, add, "aggregate");
@@ -2943,32 +4120,114 @@ function createOps(tx) {
2943
4120
  const rows = await (await at()).unsafe(sql, params);
2944
4121
  return Number(rows[0]?.n ?? 0);
2945
4122
  },
4123
+ async findUnique(table, where, opts = {}) {
4124
+ const keys = memoTable(uniqueKeysCache, currentSchema, table, () => uniqueKeysOf(tableDefOf(table) ?? {}));
4125
+ assertUniqueLookup(table, keys, where);
4126
+ const rows = await ops.findMany(table, where, {
4127
+ ...opts,
4128
+ select: opts.select === void 0 ? void 0 : [
4129
+ ...opts.select
4130
+ ],
4131
+ limit: 2
4132
+ });
4133
+ if (rows.length > 1) throw new Error(`findUnique(${table}): deployed constraints disagree with the schema; multiple rows matched`);
4134
+ return rows[0] ?? null;
4135
+ },
2946
4136
  async findById(table, id) {
2947
- const rows = await (await at()).unsafe(`SELECT * FROM ${quoteTable(table)} WHERE id = $1`, [
4137
+ const rows = await (await at()).unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE id = $1`, [
2948
4138
  id
2949
4139
  ]);
2950
4140
  return rows[0] ? asTableRow(table, rows[0]) : null;
2951
4141
  },
4142
+ compileQuery(query) {
4143
+ assertReadContract(query, tableDefOf(query.table)?.columns);
4144
+ return compileFindMany(query.table, query.where, query.options);
4145
+ },
4146
+ async readQuery(query) {
4147
+ const compiled = ops.compileQuery(query);
4148
+ return asTableRows(query.table, await (await at()).unsafe(compiled.sql, compiled.params, {
4149
+ name: query.name
4150
+ }));
4151
+ },
4152
+ async explainQuery(query) {
4153
+ const compiled = ops.compileQuery(query);
4154
+ return await (await at()).unsafe(`EXPLAIN (FORMAT JSON) ${compiled.sql}`, compiled.params, {
4155
+ name: `${query.name}.explain`
4156
+ });
4157
+ },
2952
4158
  async findMany(table, query = {}, opts = {}) {
4159
+ const compiled = compileFindMany(table, query, opts);
4160
+ return asTableRows(table, await (await at()).unsafe(compiled.sql, compiled.params), opts.with);
4161
+ },
4162
+ async page(table, query = {}, opts = {}) {
4163
+ const secret = cursorKey();
4164
+ const schema = pageSchema(table);
4165
+ const { order, size, backward, token } = pageOrder(schema.columns, schema.primaryKey, opts);
2953
4166
  const params = [];
2954
- const add = /* @__PURE__ */ __name((v) => {
2955
- params.push(v);
2956
- return `$${params.length}`;
2957
- }, "add");
4167
+ const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
2958
4168
  const known = schemaColumns(table);
2959
- const whereSql = compileWhere(table, known, query, add, "findMany");
2960
- const order = orderClause(table, known, opts.orderBy);
2961
- const limit = limitClause(opts.limit);
2962
- const offset = offsetClause(opts.offset, opts.limit);
2963
- const cols = selectClause(table, known, opts.select);
2964
- const w = opts.with !== void 0 && Object.keys(opts.with).length > 0 ? withClauses(table, opts.with, add, "findMany") : {
4169
+ const where = compileWhere(table, known, query, add, "page");
4170
+ const cols = selectClause(table, known, opts.select === void 0 ? void 0 : [
4171
+ ...opts.select
4172
+ ]);
4173
+ const scope = fingerprint({
4174
+ table,
4175
+ schema: schema.stamp,
4176
+ order,
4177
+ where,
4178
+ params,
4179
+ select: opts.select,
4180
+ with: opts.with,
4181
+ identity: context.cursorScope ?? "direct"
4182
+ });
4183
+ const values = token === void 0 ? void 0 : decodeCursor(secret, scope, token, order.length);
4184
+ const column = /* @__PURE__ */ __name((name) => `t.${quoteIdent(name)}`, "column");
4185
+ const boundary = values ? ` AND ${pageBoundary(order, values, backward, add, column)}` : "";
4186
+ const opposite = values ? `EXISTS (SELECT 1 FROM ${quoteTable(table)} t WHERE true${where} AND ${pageBoundary(order, values, !backward, add, column, true)})` : "false";
4187
+ let prefix = "__pb_page_";
4188
+ const names = [
4189
+ ...Object.keys(schema.columns),
4190
+ ...Object.keys(opts.with ?? {})
4191
+ ];
4192
+ while (names.some((n) => n.startsWith(prefix))) prefix += "_";
4193
+ const ordinal = `${prefix}ordinal`, other = `${prefix}opposite`;
4194
+ const hidden = order.map((o, i) => `${prefix}${i}`);
4195
+ const cursorCols = order.map((o, i) => `${columnTypeOf(table, o.column) === "timestamp" ? timestampResult(column(o.column)) : `${column(o.column)}::text`} AS ${quoteIdent(hidden[i])}`);
4196
+ const w = opts.with && Object.keys(opts.with).length ? withClauses(table, opts.with, add, "page") : {
2965
4197
  select: [],
2966
4198
  joins: []
2967
4199
  };
2968
- const selectList = w.select.length > 0 ? `${cols === "*" ? "t.*" : cols}, ${w.select.join(", ")}` : cols;
2969
- const joinSql = w.joins.length > 0 ? ` ${w.joins.join(" ")}` : "";
2970
- const sql = `SELECT ${selectList} FROM ${quoteTable(table)} t${joinSql} WHERE true${whereSql}${order}${limit}${offset}`;
2971
- return asTableRows(table, await (await at()).unsafe(sql, params));
4200
+ const nativeOrder = order.map((o) => `${column(o.column)} ${o.direction === "asc" !== backward ? "ASC" : "DESC"} NULLS ${o.nulls === "first" !== backward ? "FIRST" : "LAST"}`).join(", ");
4201
+ const visible = cols === "*" ? "t.*" : cols;
4202
+ const inner = `SELECT ${[
4203
+ visible,
4204
+ ...w.select,
4205
+ ...cursorCols,
4206
+ `row_number() OVER (ORDER BY ${nativeOrder}) AS ${quoteIdent(ordinal)}`
4207
+ ].join(", ")} FROM ${quoteTable(table)} t ${w.joins.join(" ")} WHERE true${where}${boundary} ORDER BY ${nativeOrder} LIMIT ${size + 1}`;
4208
+ const sql = `SELECT p.*, b.${quoteIdent(other)} FROM (SELECT ${opposite} AS ${quoteIdent(other)}) b LEFT JOIN LATERAL (${inner}) p ON true ORDER BY p.${quoteIdent(ordinal)}`;
4209
+ const result = await (await at()).unsafe(sql, params);
4210
+ const hasOpposite = result[0]?.[other] === true;
4211
+ let rows = result.filter((row) => row[ordinal] !== null && row[ordinal] !== void 0);
4212
+ const hasMore = rows.length > size;
4213
+ rows = rows.slice(0, size);
4214
+ if (backward) rows.reverse();
4215
+ const cursor = /* @__PURE__ */ __name((row) => row ? encodeCursor(secret, scope, hidden.map((name) => row[name] === null ? null : String(row[name]))) : null, "cursor");
4216
+ const pageInfo = {
4217
+ startCursor: cursor(rows[0]),
4218
+ endCursor: cursor(rows.at(-1)),
4219
+ hasNextPage: backward ? hasOpposite : hasMore,
4220
+ hasPreviousPage: backward ? hasMore : hasOpposite
4221
+ };
4222
+ for (const row of rows) for (const name of [
4223
+ ...hidden,
4224
+ ordinal,
4225
+ other
4226
+ ]) delete row[name];
4227
+ return {
4228
+ rows: asTableRows(table, rows, opts.with),
4229
+ pageInfo
4230
+ };
2972
4231
  },
2973
4232
  /**
2974
4233
  * Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
@@ -3073,7 +4332,7 @@ function createOps(tx) {
3073
4332
  kwC = `SELECT c.${pidQ} AS pid, c.chunk_seq, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(c.palbase_fts, websearch_to_tsquery('simple', ${qpC})) DESC) AS r FROM ${quoteTable(ck.table)} c${parentJoinC}c.palbase_fts @@ websearch_to_tsquery('simple', ${qpC}) ORDER BY r LIMIT ${cpool}`;
3074
4333
  }
3075
4334
  const msC = params.minScore === void 0 ? "" : addC(params.minScore);
3076
- const colListC = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
4335
+ const colListC = cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`).join(", ");
3077
4336
  const scoreC = scoreMul === "" ? "g._score" : `(g._score)${scoreMul}`;
3078
4337
  const assembleC = /* @__PURE__ */ __name((kwIn, withKwn) => {
3079
4338
  const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
@@ -3155,7 +4414,7 @@ function createOps(tx) {
3155
4414
  qpRef = qp;
3156
4415
  kwSql = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(t.palbase_fts, websearch_to_tsquery('simple', ${qp})) DESC) AS r FROM ${quoteTable(table)} t WHERE t.palbase_fts @@ websearch_to_tsquery('simple', ${qp})${whereSql} ORDER BY r LIMIT ${pool}`;
3157
4416
  }
3158
- const colList = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
4417
+ const colList = cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`).join(", ");
3159
4418
  const msP = params.minScore === void 0 ? "" : add(params.minScore);
3160
4419
  const hlCol = params.highlight === true && wantText ? `, ts_headline('simple', ${ftsExprOf(cfg.ftsCols)}, websearch_to_tsquery('simple', ${qpRef})) AS _highlight` : "";
3161
4420
  const assemble = /* @__PURE__ */ __name((kwIn, withKwn) => {
@@ -3363,7 +4622,7 @@ function createOps(tx) {
3363
4622
  const params = [];
3364
4623
  const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
3365
4624
  const tuple = buildValueTuple(table, cols, data, add, "supersede");
3366
- const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING *`, params);
4625
+ const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
3367
4626
  if (!rows[0]) {
3368
4627
  throw new Error(`supersede(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si)`);
3369
4628
  }
@@ -3373,7 +4632,7 @@ function createOps(tx) {
3373
4632
  /** A real SAVEPOINT inside the request's transaction. */
3374
4633
  async transaction(cb) {
3375
4634
  const live = await at();
3376
- return live.savepoint(async (sp) => cb(withTables(createOps(sp), currentSchema)));
4635
+ return live.savepoint(async (sp) => cb(withTables(createOps(sp, context), currentSchema)));
3377
4636
  },
3378
4637
  /**
3379
4638
  * Run `fn` against a handle bound to a SAVEPOINT, so a failure inside it
@@ -3394,7 +4653,7 @@ function createOps(tx) {
3394
4653
  */
3395
4654
  async attempt(fn) {
3396
4655
  const live = await at();
3397
- return live.savepoint(async (sp) => fn(withTables(createOps(sp), currentSchema)));
4656
+ return live.savepoint(async (sp) => fn(withTables(createOps(sp, context), currentSchema)));
3398
4657
  },
3399
4658
  /**
3400
4659
  * Execute a whole transaction plan — what `Database.$transaction(fn)` builds.
@@ -3414,6 +4673,62 @@ function createOps(tx) {
3414
4673
  * "transport.txPlan is not a function" while every test that covered
3415
4674
  * transactions passed against a mock that did implement it.
3416
4675
  */
4676
+ async command(plan, options = {}) {
4677
+ if (options.mode !== void 0 && ![
4678
+ "auto",
4679
+ "compiled",
4680
+ "sequential"
4681
+ ].includes(options.mode)) throw new Error("command mode must be auto, compiled or sequential");
4682
+ if (options.mode === "sequential") return ops.txPlan(plan);
4683
+ const compiled = context.commands ? compileCommand(plan) : {
4684
+ reason: "the PostgreSQL command executor is not installed"
4685
+ };
4686
+ if ("reason" in compiled) {
4687
+ if (options.mode === "compiled") throw new Error(`Command cannot compile: ${compiled.reason}. Use auto/sequential mode or installCommandExecutor at boot.`);
4688
+ return ops.txPlan(plan);
4689
+ }
4690
+ if (compiled.steps.length === 0) return {
4691
+ results: []
4692
+ };
4693
+ if (compiled.pipeline) {
4694
+ return (await at()).savepoint(async (sp) => {
4695
+ const settled = await Promise.allSettled(compiled.pipeline.map((statement) => sp.unsafe(statement.sql, statement.params)));
4696
+ const failed = settled.find((result) => result.status === "rejected");
4697
+ if (failed?.status === "rejected") {
4698
+ const error = failed.reason;
4699
+ if (sqlstateOf(error) === SQLSTATE.commandGuardFailed && typeof error.detail === "string") {
4700
+ const detail = JSON.parse(error.detail);
4701
+ if (detail.error_code === "tx_guard_failed" && Number.isInteger(detail.slot)) {
4702
+ throw Object.assign(new Error("Command expectation failed"), detail);
4703
+ }
4704
+ }
4705
+ throw error;
4706
+ }
4707
+ return {
4708
+ results: settled.map((result, i) => {
4709
+ const rows2 = result.value[0].rows;
4710
+ return {
4711
+ rows: asTableRows(plan.ops[i].table, rows2),
4712
+ rows_affected: rows2.length
4713
+ };
4714
+ })
4715
+ };
4716
+ });
4717
+ }
4718
+ const rows = await (await at()).savepoint((sp) => sp.unsafe("SELECT palbase_runtime.command_v1($1::jsonb -> 'steps') AS result", [
4719
+ {
4720
+ steps: compiled.steps
4721
+ }
4722
+ ]));
4723
+ const response = rows[0].result;
4724
+ if (response.error) throw Object.assign(new Error(response.error.error_code === "tx_ref_unresolved" ? "Command reference has no source row/column; use expectOne before reading it" : "Command expectation failed"), response.error);
4725
+ return {
4726
+ results: response.results.map((result, i) => ({
4727
+ ...result,
4728
+ rows: asTableRows(plan.ops[i].table, result.rows)
4729
+ }))
4730
+ };
4731
+ },
3417
4732
  async txPlan(plan) {
3418
4733
  const live = await at();
3419
4734
  return live.savepoint(async (sp) => {
@@ -3437,27 +4752,55 @@ function createOps(tx) {
3437
4752
  }
3438
4753
  __name(createOps, "createOps");
3439
4754
  var currentSchema = {};
4755
+ var columnsMemo = /* @__PURE__ */ new WeakMap();
4756
+ var primaryMemo = /* @__PURE__ */ new WeakMap();
4757
+ var vectorMemo = /* @__PURE__ */ new WeakMap();
4758
+ var transformMemo = /* @__PURE__ */ new WeakMap();
4759
+ var typeMemo = /* @__PURE__ */ new WeakMap();
4760
+ var timestampMemo = /* @__PURE__ */ new WeakMap();
4761
+ var projectionMemo = /* @__PURE__ */ new WeakMap();
4762
+ function schemaColumns(table) {
4763
+ return memoTable(columnsMemo, currentSchema, table, () => computeSchemaColumns(table));
4764
+ }
4765
+ __name(schemaColumns, "schemaColumns");
4766
+ function primaryKeyOf(table) {
4767
+ return memoTable(primaryMemo, currentSchema, table, () => computePrimaryKey(table));
4768
+ }
4769
+ __name(primaryKeyOf, "primaryKeyOf");
4770
+ function vectorColumnsOf(schema, table) {
4771
+ return memoTable(vectorMemo, schema, table, () => computeVectorColumns(schema, table));
4772
+ }
4773
+ __name(vectorColumnsOf, "vectorColumnsOf");
4774
+ function transformsOf(schema, table) {
4775
+ return memoTable(transformMemo, schema, table, () => computeTransforms(schema, table));
4776
+ }
4777
+ __name(transformsOf, "transformsOf");
4778
+ function columnTypeOf(table, col) {
4779
+ const types = memoTable(typeMemo, currentSchema, table, () => new Map([
4780
+ ...schemaColumns(table) ?? []
4781
+ ].map((c) => [
4782
+ c,
4783
+ computeColumnType(table, c)
4784
+ ])));
4785
+ return types.get(col) ?? null;
4786
+ }
4787
+ __name(columnTypeOf, "columnTypeOf");
3440
4788
  var currentRelations = /* @__PURE__ */ new Map();
3441
4789
  function setSchema(schemas) {
3442
- cachedVectorSchema = null;
3443
- cachedTrgmSchema = null;
3444
- const tables = {};
4790
+ const tables = /* @__PURE__ */ Object.create(null);
3445
4791
  for (const entry of schemas) {
3446
4792
  const mod = entry;
3447
4793
  const def = (mod && "default" in mod ? mod.default : mod) ?? {};
3448
4794
  const schemaName = def.name ?? "public";
3449
- for (const [key, table] of Object.entries(def.tables ?? {})) {
3450
- tables[qualifiedTableKey(schemaName, table.name ?? key)] = table;
4795
+ for (const [key2, table] of Object.entries(def.tables ?? {})) {
4796
+ tables[qualifiedTableKey(schemaName, table.name ?? key2)] = table;
3451
4797
  }
3452
4798
  }
3453
- currentSchema = {
3454
- tables
3455
- };
3456
- currentRelations = buildRelations(schemas.map((entry) => {
4799
+ const relations = buildRelations(schemas.map((entry) => {
3457
4800
  const mod = entry;
3458
4801
  const def = (mod && "default" in mod ? mod.default : mod) ?? {};
3459
- const tables2 = {};
3460
- for (const [key, t] of Object.entries(def.tables ?? {})) {
4802
+ const tables2 = /* @__PURE__ */ Object.create(null);
4803
+ for (const [key2, t] of Object.entries(def.tables ?? {})) {
3461
4804
  const table = t ?? {};
3462
4805
  const columns = {};
3463
4806
  for (const [cname, c] of Object.entries(table.columns ?? {})) {
@@ -3467,9 +4810,9 @@ function setSchema(schemas) {
3467
4810
  _def: cd
3468
4811
  };
3469
4812
  }
3470
- tables2[key] = {
4813
+ tables2[key2] = {
3471
4814
  ...table,
3472
- name: table.name ?? key,
4815
+ name: table.name ?? key2,
3473
4816
  columns
3474
4817
  };
3475
4818
  }
@@ -3478,40 +4821,86 @@ function setSchema(schemas) {
3478
4821
  tables: tables2
3479
4822
  };
3480
4823
  }));
4824
+ for (const key2 of Object.keys(tables)) tables[key2] = snapshotDeclaration(tables[key2]);
4825
+ currentSchema = Object.freeze({
4826
+ tables: Object.freeze(tables)
4827
+ });
4828
+ currentRelations = relations;
4829
+ cachedVectorSchema = null;
4830
+ cachedTrgmSchema = null;
3481
4831
  }
3482
4832
  __name(setSchema, "setSchema");
4833
+ var tableNamespaces = /* @__PURE__ */ new WeakMap();
3483
4834
  function withTables(ops, schema = currentSchema) {
3484
- const bind = /* @__PURE__ */ __name((key) => ({
3485
- insert: /* @__PURE__ */ __name((data) => ops.insert(key, data), "insert"),
3486
- update: /* @__PURE__ */ __name((id, data) => ops.update(key, id, data), "update"),
3487
- delete: /* @__PURE__ */ __name((id) => ops.delete(key, id), "delete"),
3488
- findById: /* @__PURE__ */ __name((id) => ops.findById(key, id), "findById"),
3489
- findMany: /* @__PURE__ */ __name((query, opts) => ops.findMany(key, query ?? {}, opts), "findMany"),
3490
- put: /* @__PURE__ */ __name((q) => ops.put(key, q.data, {
4835
+ let namespaces = tableNamespaces.get(schema);
4836
+ if (!namespaces) {
4837
+ namespaces = /* @__PURE__ */ new Map();
4838
+ for (const key2 of Object.keys(schema.tables ?? {})) {
4839
+ const dot = key2.indexOf(".");
4840
+ const name = dot === -1 ? "public" : key2.slice(0, dot);
4841
+ const short = dot === -1 ? key2 : key2.slice(dot + 1);
4842
+ if (!namespaces.has(name)) namespaces.set(name, /* @__PURE__ */ new Map());
4843
+ namespaces.get(name).set(short, key2);
4844
+ }
4845
+ if (Object.isFrozen(schema) && Object.isFrozen(schema.tables)) tableNamespaces.set(schema, namespaces);
4846
+ }
4847
+ const bind = /* @__PURE__ */ __name((key2) => ({
4848
+ insert: /* @__PURE__ */ __name((data) => ops.insert(key2, data), "insert"),
4849
+ update: /* @__PURE__ */ __name((id, data) => ops.update(key2, id, data), "update"),
4850
+ delete: /* @__PURE__ */ __name((id) => ops.delete(key2, id), "delete"),
4851
+ findById: /* @__PURE__ */ __name((id) => ops.findById(key2, id), "findById"),
4852
+ findUnique: /* @__PURE__ */ __name((where, opts) => ops.findUnique(key2, where, opts), "findUnique"),
4853
+ findMany: /* @__PURE__ */ __name((query, opts) => ops.findMany(key2, query ?? {}, opts), "findMany"),
4854
+ page: /* @__PURE__ */ __name((query, opts) => ops.page(key2, query ?? {}, opts), "page"),
4855
+ put: /* @__PURE__ */ __name((q) => ops.put(key2, q.data, {
3491
4856
  onConflict: q.onConflict
3492
4857
  }), "put")
3493
4858
  }), "bind");
3494
- const tables = {};
3495
- for (const key of Object.keys(schema.tables ?? {})) {
3496
- if (!key.includes(".")) tables[key] = bind(key);
3497
- }
4859
+ const surfaces = /* @__PURE__ */ new Map();
3498
4860
  const schemaOf = /* @__PURE__ */ __name((name) => {
3499
- const out = {};
3500
- const prefix = `${name}.`;
3501
- for (const key of Object.keys(schema.tables ?? {})) {
3502
- if (name === "public") {
3503
- if (!key.includes(".")) out[key] = bind(key);
3504
- } else if (key.startsWith(prefix)) {
3505
- out[key.slice(prefix.length)] = bind(key);
4861
+ const cached = surfaces.get(name);
4862
+ if (cached) return cached;
4863
+ const names = namespaces.get(name) ?? /* @__PURE__ */ new Map();
4864
+ const target = /* @__PURE__ */ Object.create(null);
4865
+ const get = /* @__PURE__ */ __name((key2) => {
4866
+ if (!Object.hasOwn(target, key2) && names.has(key2)) {
4867
+ Object.defineProperty(target, key2, {
4868
+ value: bind(names.get(key2)),
4869
+ configurable: true,
4870
+ enumerable: true,
4871
+ writable: true
4872
+ });
3506
4873
  }
3507
- }
3508
- return {
3509
- tables: out
4874
+ return Reflect.get(target, key2);
4875
+ }, "get");
4876
+ const tables = new Proxy(target, {
4877
+ get: /* @__PURE__ */ __name((t, key2, receiver) => typeof key2 === "string" ? get(key2) : Reflect.get(t, key2, receiver), "get"),
4878
+ has: /* @__PURE__ */ __name((t, key2) => typeof key2 === "string" && names.has(key2) || Reflect.has(t, key2), "has"),
4879
+ ownKeys: /* @__PURE__ */ __name((t) => [
4880
+ .../* @__PURE__ */ new Set([
4881
+ ...names.keys(),
4882
+ ...Reflect.ownKeys(t)
4883
+ ])
4884
+ ], "ownKeys"),
4885
+ getOwnPropertyDescriptor: /* @__PURE__ */ __name((t, key2) => Reflect.getOwnPropertyDescriptor(t, key2) ?? (typeof key2 === "string" && names.has(key2) ? {
4886
+ configurable: true,
4887
+ enumerable: true,
4888
+ get: /* @__PURE__ */ __name(() => get(key2), "get")
4889
+ } : void 0), "getOwnPropertyDescriptor"),
4890
+ preventExtensions: /* @__PURE__ */ __name((t) => {
4891
+ for (const key2 of names.keys()) get(key2);
4892
+ return Reflect.preventExtensions(t);
4893
+ }, "preventExtensions")
4894
+ });
4895
+ const surface = {
4896
+ tables
3510
4897
  };
4898
+ if (namespaces.has(name) || name === "public") surfaces.set(name, surface);
4899
+ return surface;
3511
4900
  }, "schemaOf");
3512
4901
  const base = /* @__PURE__ */ Object.create(null);
3513
4902
  return Object.assign(base, ops, {
3514
- tables,
4903
+ tables: schemaOf("public").tables,
3515
4904
  schema: schemaOf
3516
4905
  });
3517
4906
  }
@@ -3569,8 +4958,8 @@ function diagnosingDriver(sql, surface = "service") {
3569
4958
  return markEngineRaised(new UniqueViolation(constraintOf(e)));
3570
4959
  }
3571
4960
  const state = sqlstateOf(e);
3572
- if (state === "40001") return markEngineRaised(new SerializationFailure(state));
3573
- if (state === "40P01") return markEngineRaised(new DeadlockDetected(state));
4961
+ if (state === SQLSTATE.serializationFailure) return markEngineRaised(new SerializationFailure(state));
4962
+ if (state === SQLSTATE.deadlockDetected) return markEngineRaised(new DeadlockDetected(state));
3574
4963
  if (surface === "service" && isForeignKeyViolation(e)) {
3575
4964
  return new Error(`Database.$asService() hit a foreign key with nothing to point at. It runs in its OWN transaction on its OWN connection, so a row this request wrote through \`Database.*\` is not visible to it until the request commits \u2014 and the request cannot commit until the handler returns. If the row it needs is one this request just created, do both writes on ONE surface. (${original(e)})`);
3576
4965
  }
@@ -3580,11 +4969,16 @@ function diagnosingDriver(sql, surface = "service") {
3580
4969
  return e;
3581
4970
  }, "explain");
3582
4971
  const wrapTx = /* @__PURE__ */ __name((tx) => ({
3583
- async unsafe(text, params) {
4972
+ unsafe(text, params) {
3584
4973
  try {
3585
- return await tx.unsafe(text, params);
4974
+ const query = tx.unsafe(text, params);
4975
+ const mapped = query.catch((e) => {
4976
+ throw explain(e);
4977
+ });
4978
+ if (query.cancel) mapped.cancel = () => query.cancel();
4979
+ return mapped;
3586
4980
  } catch (e) {
3587
- throw explain(e);
4981
+ return Promise.reject(explain(e));
3588
4982
  }
3589
4983
  },
3590
4984
  savepoint(cb) {
@@ -3593,34 +4987,120 @@ function diagnosingDriver(sql, surface = "service") {
3593
4987
  }), "wrapTx");
3594
4988
  return {
3595
4989
  unsafe: /* @__PURE__ */ __name((text, params) => sql.unsafe(text, params), "unsafe"),
3596
- begin: /* @__PURE__ */ __name((cb) => sql.begin((tx) => cb(wrapTx(tx))), "begin")
4990
+ cancelBackend: sql.cancelBackend?.bind(sql),
4991
+ begin: /* @__PURE__ */ __name((cb) => sql.begin((tx) => cb(wrapTx(tx))).catch((e) => {
4992
+ throw explain(e);
4993
+ }), "begin")
3597
4994
  };
3598
4995
  }
3599
4996
  __name(diagnosingDriver, "diagnosingDriver");
3600
- function createRequestDatabase(sql, identity) {
3601
- const tx = createLazyTransaction(diagnosingDriver(sql, "user"), identity.role, identity.claimsJson);
4997
+ function createRequestDatabase(sql, identity, diagnostics) {
4998
+ const trace = diagnostics === void 0 ? void 0 : new DatabaseTrace(diagnostics);
4999
+ let requestClosed = false;
5000
+ const contextFor = /* @__PURE__ */ __name((role) => {
5001
+ let scope;
5002
+ return {
5003
+ trace,
5004
+ get cursorScope() {
5005
+ return scope ??= cursorIdentity(role, identity.claimsJson);
5006
+ },
5007
+ get commands() {
5008
+ return hasCommandExecutor(sql);
5009
+ }
5010
+ };
5011
+ }, "contextFor");
5012
+ const userContext = contextFor(identity.role);
5013
+ const serviceContext = contextFor(identity.serviceRole);
5014
+ const atomic = /* @__PURE__ */ __name((role, surface) => async (fn, options = {}) => {
5015
+ validateAtomicOptions(options);
5016
+ if (requestClosed) throw new Error("Database request has settled; it cannot open another transaction");
5017
+ if (atomicScope.getStore() !== void 0) throw new Error("Nested Database.$atomic is not supported; use tx.$attempt for a savepoint");
5018
+ const retries = retryBudget(options.retry);
5019
+ const control = options.timeoutMs !== void 0 || options.signal !== void 0 ? new TransactionControl(options.timeoutMs, options.signal) : void 0;
5020
+ let lease;
5021
+ try {
5022
+ control?.check();
5023
+ if (options.workload) {
5024
+ const queuedAt = trace ? performance.now() : 0;
5025
+ try {
5026
+ lease = await workloadAdmission(sql, options.workload).acquire(workloadTenant(options.workload, identity.claimsJson), options.conflictKeys ?? [], control?.signal);
5027
+ } finally {
5028
+ trace?.time("queueMs", queuedAt);
5029
+ }
5030
+ }
5031
+ for (let attempt = 0; ; attempt++) {
5032
+ control?.check();
5033
+ const timeouts = [
5034
+ options.statementTimeoutMs,
5035
+ control?.remainingMs()
5036
+ ].filter((n) => n !== void 0);
5037
+ const scope = /* @__PURE__ */ Symbol("atomic transaction");
5038
+ const root = createLazyTransaction(diagnosingDriver(sql, surface), role, identity.claimsJson, {
5039
+ scope,
5040
+ isolation: options.isolation,
5041
+ readOnly: options.readOnly,
5042
+ control,
5043
+ trace,
5044
+ lockTimeout: `${options.lockTimeoutMs ?? 5e3}ms`,
5045
+ statementTimeout: timeouts.length === 0 ? void 0 : `${Math.min(...timeouts)}ms`
5046
+ });
5047
+ try {
5048
+ const callback = atomicScope.run(scope, () => fn(withTables(createOps(root, surface === "user" ? userContext : serviceContext))));
5049
+ const value = await (control ? control.race(callback) : callback);
5050
+ control?.check();
5051
+ await root.commit();
5052
+ return value;
5053
+ } catch (error) {
5054
+ await root.rollback(error);
5055
+ if (attempt >= retries || !isRetryable(error)) throw error;
5056
+ trace?.retry();
5057
+ const delay = new Promise((resolve) => setTimeout(resolve, Math.min(100, 2 ** attempt)));
5058
+ await (control ? control.race(delay) : delay);
5059
+ }
5060
+ }
5061
+ } finally {
5062
+ control?.dispose();
5063
+ lease?.release();
5064
+ }
5065
+ }, "atomic");
5066
+ const tx = createLazyTransaction(diagnosingDriver(sql, "user"), identity.role, identity.claimsJson, {
5067
+ trace
5068
+ });
3602
5069
  let serviceTx = null;
3603
5070
  let serviceClient = null;
3604
5071
  const asService = /* @__PURE__ */ __name(() => {
3605
5072
  if (serviceClient === null) {
3606
5073
  serviceTx = createLazyTransaction(diagnosingDriver(sql, "service"), identity.serviceRole, identity.claimsJson, {
3607
- lockTimeout: SERVICE_LOCK_TIMEOUT
5074
+ lockTimeout: SERVICE_LOCK_TIMEOUT,
5075
+ trace
5076
+ });
5077
+ serviceClient = Object.assign(withTables(createOps(serviceTx, serviceContext)), {
5078
+ atomic: atomic(identity.serviceRole, "service")
3608
5079
  });
3609
- serviceClient = withTables(createOps(serviceTx));
3610
5080
  }
3611
5081
  return serviceClient;
3612
5082
  }, "asService");
3613
5083
  return {
3614
- client: Object.assign(withTables(createOps(tx)), {
3615
- asService
5084
+ client: Object.assign(withTables(createOps(tx, userContext)), {
5085
+ asService,
5086
+ atomic: atomic(identity.role, "user")
3616
5087
  }),
3617
5088
  async commit() {
3618
- await tx.commit();
3619
- await serviceTx?.commit();
5089
+ requestClosed = true;
5090
+ try {
5091
+ await tx.commit();
5092
+ await serviceTx?.commit();
5093
+ trace?.complete("committed");
5094
+ } catch (error) {
5095
+ trace?.failed();
5096
+ throw error;
5097
+ }
3620
5098
  },
3621
5099
  async rollback(reason2) {
5100
+ requestClosed = true;
3622
5101
  await tx.rollback(reason2);
3623
5102
  await serviceTx?.rollback(reason2);
5103
+ trace?.complete("failed");
3624
5104
  }
3625
5105
  };
3626
5106
  }
@@ -3630,8 +5110,13 @@ var Args = class Args2 {
3630
5110
  __name(this, "Args");
3631
5111
  }
3632
5112
  values = [];
3633
- bind(value) {
5113
+ columns = [];
5114
+ bind(value, column, table) {
3634
5115
  this.values.push(value);
5116
+ this.columns.push({
5117
+ column,
5118
+ table
5119
+ });
3635
5120
  return `$${this.values.length}`;
3636
5121
  }
3637
5122
  };
@@ -3717,7 +5202,7 @@ function planWhere(table, where, args, results) {
3717
5202
  }
3718
5203
  const resolved = resolveRefsDeep(where, results);
3719
5204
  const known = schemaColumns(table);
3720
- const bare = compileWhereBare(table, known, resolved, (v) => args.bind(v), "transaction plan");
5205
+ const bare = compileWhereBare(table, known, resolved, (v, column, from) => args.bind(v, column, from), "transaction plan");
3721
5206
  return {
3722
5207
  sql: bare === "" ? "" : ` AND ${bare}`,
3723
5208
  resolved
@@ -3726,16 +5211,16 @@ function planWhere(table, where, args, results) {
3726
5211
  __name(planWhere, "planWhere");
3727
5212
  function bindMaybeVector(args, column, vectorCols, value, transforms) {
3728
5213
  if (column !== void 0 && vectorCols?.has(column) && Array.isArray(value)) {
3729
- return args.bind(toVectorLiteral(value));
5214
+ return args.bind(toVectorLiteral(value), column);
3730
5215
  }
3731
5216
  const t = column !== void 0 ? transforms?.get(column) : void 0;
3732
5217
  if (t?.toDb !== void 0 && value !== null && value !== void 0) {
3733
- return args.bind(t.toDb(value));
5218
+ return args.bind(t.toDb(value), column);
3734
5219
  }
3735
- return args.bind(value);
5220
+ return args.bind(value, column);
3736
5221
  }
3737
5222
  __name(bindMaybeVector, "bindMaybeVector");
3738
- async function runPlanOp(sp, op, results) {
5223
+ function compilePlanOp(op, results) {
3739
5224
  const opVectorCols = vectorColumnsOf(currentSchema, op.table);
3740
5225
  const opTransforms = transformsOf(currentSchema, op.table);
3741
5226
  const args = new Args();
@@ -3745,7 +5230,7 @@ async function runPlanOp(sp, op, results) {
3745
5230
  case "insert": {
3746
5231
  const cols = Object.keys(op.values ?? {});
3747
5232
  const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols, opTransforms));
3748
- sql = cols.length ? `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) RETURNING *` : `INSERT INTO ${table} DEFAULT VALUES RETURNING *`;
5233
+ sql = cols.length ? `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) RETURNING ${rowProjection(op.table)}` : `INSERT INTO ${table} DEFAULT VALUES RETURNING ${rowProjection(op.table)}`;
3749
5234
  break;
3750
5235
  }
3751
5236
  case "upsert": {
@@ -3758,17 +5243,17 @@ async function runPlanOp(sp, op, results) {
3758
5243
  });
3759
5244
  }
3760
5245
  const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols, opTransforms));
3761
- sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING *`;
5246
+ sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING ${rowProjection(op.table)}`;
3762
5247
  break;
3763
5248
  }
3764
5249
  case "insertMany": {
3765
5250
  const rows = op.rows ?? [];
3766
- if (rows.length === 0 || !rows[0]) return [];
5251
+ if (rows.length === 0 || !rows[0]) return null;
3767
5252
  const cols = Object.keys(rows[0]);
3768
5253
  const tuples = rows.map((r) => `(${cols.map((c) => renderValue(r[c], c, args, results, opVectorCols, opTransforms)).join(", ")})`);
3769
5254
  const conflictCols = op.onConflict ?? [];
3770
5255
  const tail = op.action !== void 0 && conflictCols.length > 0 ? ` ${onConflictTail(cols, conflictCols, op.action)}` : "";
3771
- sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING *`;
5256
+ sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING ${rowProjection(op.table)}`;
3772
5257
  break;
3773
5258
  }
3774
5259
  case "update": {
@@ -3784,12 +5269,12 @@ async function runPlanOp(sp, op, results) {
3784
5269
  const assignments = cols.map((c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols, opTransforms)}`);
3785
5270
  const planned = planWhere(op.table, op.where, args, results);
3786
5271
  const planPk = primaryKeyOf(op.table);
3787
- sql = isSinglePkEquality(planned.resolved, planPk) || planPk === null ? `UPDATE ${table} AS t SET ${assignments.join(", ")} WHERE true${planned.sql} RETURNING t.*` : `WITH locked AS (SELECT t.${quoteIdent(planPk)} FROM ${table} t WHERE true${planned.sql} ORDER BY t.${quoteIdent(planPk)} FOR NO KEY UPDATE) UPDATE ${table} AS t SET ${assignments.join(", ")} FROM locked WHERE t.${quoteIdent(planPk)} = locked.${quoteIdent(planPk)} RETURNING t.*`;
5272
+ sql = isSinglePkEquality(planned.resolved, planPk) || planPk === null ? `UPDATE ${table} AS t SET ${assignments.join(", ")} WHERE true${planned.sql} RETURNING ${rowProjection(op.table, "t")}` : `WITH locked AS (SELECT t.${quoteIdent(planPk)} FROM ${table} t WHERE true${planned.sql} ORDER BY t.${quoteIdent(planPk)} FOR NO KEY UPDATE) UPDATE ${table} AS t SET ${assignments.join(", ")} FROM locked WHERE t.${quoteIdent(planPk)} = locked.${quoteIdent(planPk)} RETURNING ${rowProjection(op.table, "t")}`;
3788
5273
  break;
3789
5274
  }
3790
5275
  case "delete": {
3791
5276
  assertNotAppendOnly("deleteWhere", op.table);
3792
- sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING t.*`;
5277
+ sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING ${rowProjection(op.table, "t")}`;
3793
5278
  break;
3794
5279
  }
3795
5280
  case "select": {
@@ -3802,15 +5287,157 @@ async function runPlanOp(sp, op, results) {
3802
5287
  limit = ` LIMIT ${n}`;
3803
5288
  }
3804
5289
  const lock = op.lock === "update" ? " FOR UPDATE" : "";
3805
- sql = `SELECT t.* FROM ${table} t WHERE true${renderWhere(op.table, op.where, args, results)}${limit}${lock}`;
5290
+ sql = `SELECT ${rowProjection(op.table, "t")} FROM ${table} t WHERE true${renderWhere(op.table, op.where, args, results)}${limit}${lock}`;
3806
5291
  break;
3807
5292
  }
3808
5293
  default:
3809
5294
  throw new Error(`unknown operation "${String(op.op)}" in a transaction plan`);
3810
5295
  }
3811
- return await sp.unsafe(sql, args.values);
5296
+ return {
5297
+ sql,
5298
+ params: args.values,
5299
+ columns: args.columns
5300
+ };
5301
+ }
5302
+ __name(compilePlanOp, "compilePlanOp");
5303
+ async function runPlanOp(sp, op, results) {
5304
+ const statement = compilePlanOp(op, results);
5305
+ return statement ? await sp.unsafe(statement.sql, statement.params) : [];
3812
5306
  }
3813
5307
  __name(runPlanOp, "runPlanOp");
5308
+ function compileCommand(plan) {
5309
+ const symbols = /* @__PURE__ */ new Map();
5310
+ const results = [];
5311
+ const steps = [];
5312
+ const pipeline = [];
5313
+ let hasRefs = false;
5314
+ const prefix = `command:${crypto.randomUUID()}:`;
5315
+ const containsFragment = /* @__PURE__ */ __name((v) => isSqlFragment(v) || v !== null && typeof v === "object" && Object.values(v).some(containsFragment), "containsFragment");
5316
+ const hasNullableFilterRef = /* @__PURE__ */ __name((v) => {
5317
+ if (isRef(v)) {
5318
+ const source = plan.ops[v.$ref.op];
5319
+ return source !== void 0 && pageSchema(source.table).columns[v.$ref.field]?.nullable !== false;
5320
+ }
5321
+ return v !== null && typeof v === "object" && Object.values(v).some(hasNullableFilterRef);
5322
+ }, "hasNullableFilterRef");
5323
+ for (const op of plan.ops) {
5324
+ if (!schemaColumns(op.table)?.size) return {
5325
+ reason: `table ${op.table} has no installed declaration`
5326
+ };
5327
+ if (transformsOf(currentSchema, op.table).size || vectorColumnsOf(currentSchema, op.table).size) return {
5328
+ reason: `${op.table} uses JavaScript codecs/transforms or vector values`
5329
+ };
5330
+ if (containsFragment(op)) return {
5331
+ reason: "SQL fragments require the sequential path"
5332
+ };
5333
+ if (hasNullableFilterRef(op.where)) return {
5334
+ reason: "nullable references in filters require the sequential path"
5335
+ };
5336
+ }
5337
+ for (const [index, op] of plan.ops.entries()) {
5338
+ const statement = compilePlanOp(op, results);
5339
+ if (statement === null) {
5340
+ steps.push({
5341
+ empty: true,
5342
+ guard: op.guard
5343
+ });
5344
+ pipeline.push({
5345
+ sql: "SELECT palbase_runtime.command_rows_v1('[]'::jsonb,$1::jsonb) AS rows",
5346
+ params: [
5347
+ op.guard ?? null
5348
+ ]
5349
+ });
5350
+ } else {
5351
+ if (statement.params.some((v) => v === void 0 || typeof v === "symbol" || typeof v === "function" || typeof v === "number" && (!Number.isInteger(v) || v < -2147483648 || v > 2147483647))) {
5352
+ return {
5353
+ reason: "non-integer/wide numeric or non-JSON parameters require the sequential driver path; use exact decimal strings"
5354
+ };
5355
+ }
5356
+ for (const [i, value] of statement.params.entries()) {
5357
+ const target = statement.columns[i];
5358
+ if (target.column === void 0 || columnTypeOf(target.table ?? op.table, target.column) !== "jsonb") continue;
5359
+ const source = typeof value === "string" ? symbols.get(value) : void 0;
5360
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || source && "ref" in source && ![
5361
+ "text",
5362
+ "uuid",
5363
+ "timestamp",
5364
+ "enum"
5365
+ ].includes(columnTypeOf(plan.ops[source.ref.op].table, source.ref.field) ?? "")) {
5366
+ return {
5367
+ reason: "JSONB scalar/ref binding requires the sequential driver path"
5368
+ };
5369
+ }
5370
+ }
5371
+ const wide = [
5372
+ ...schemaColumns(op.table)
5373
+ ].filter((c) => [
5374
+ "bigint",
5375
+ "numeric"
5376
+ ].includes(columnTypeOf(op.table, c) ?? ""));
5377
+ const exact = wide.length ? ` || jsonb_build_object(${wide.map((c) => `${quoteLiteral(c)}, r.${quoteIdent(c)}::text`).join(", ")})` : "";
5378
+ const sql = `WITH command_rows AS (${statement.sql}) SELECT coalesce(jsonb_agg(to_jsonb(r)${exact}), '[]'::jsonb) FROM command_rows r`;
5379
+ pipeline.push({
5380
+ sql: `WITH command_rows AS (${statement.sql}) SELECT palbase_runtime.command_rows_v1(coalesce(jsonb_agg(to_jsonb(r)${exact}), '[]'::jsonb),$${statement.params.length + 1}::jsonb) AS rows FROM command_rows r`,
5381
+ params: [
5382
+ ...statement.params,
5383
+ op.guard ?? null
5384
+ ]
5385
+ });
5386
+ const bindings = statement.params.map((value, i) => {
5387
+ const target = statement.columns[i];
5388
+ const json = target.column !== void 0 && columnTypeOf(target.table ?? op.table, target.column) === "jsonb";
5389
+ if (typeof value === "string" && symbols.has(value)) {
5390
+ hasRefs = true;
5391
+ return {
5392
+ ...symbols.get(value),
5393
+ json
5394
+ };
5395
+ }
5396
+ if (typeof value === "bigint") return {
5397
+ value: value.toString(),
5398
+ json
5399
+ };
5400
+ if (value instanceof Date) return {
5401
+ value: value.toISOString(),
5402
+ json
5403
+ };
5404
+ return {
5405
+ value,
5406
+ json
5407
+ };
5408
+ });
5409
+ steps.push({
5410
+ chunks: parameterChunks(sql),
5411
+ bindings,
5412
+ guard: op.guard
5413
+ });
5414
+ }
5415
+ const row = {};
5416
+ for (const field of schemaColumns(op.table)) {
5417
+ const token = `${prefix}${index}:${field}`;
5418
+ symbols.set(token, {
5419
+ ref: {
5420
+ op: index,
5421
+ field
5422
+ }
5423
+ });
5424
+ row[field] = token;
5425
+ }
5426
+ results.push({
5427
+ rows: [
5428
+ row
5429
+ ],
5430
+ rows_affected: 1
5431
+ });
5432
+ }
5433
+ return {
5434
+ steps,
5435
+ ...hasRefs ? {} : {
5436
+ pipeline
5437
+ }
5438
+ };
5439
+ }
5440
+ __name(compileCommand, "compileCommand");
3814
5441
  function assertGuard(op, result) {
3815
5442
  const guard = op.guard;
3816
5443
  if (!guard) return;
@@ -3824,6 +5451,33 @@ function assertGuard(op, result) {
3824
5451
  }
3825
5452
  __name(assertGuard, "assertGuard");
3826
5453
 
5454
+ // src/engine/cancellation.ts
5455
+ function withQueryCancellation(application, control) {
5456
+ if (application === control) throw new Error("Query cancellation requires a separate control pool");
5457
+ const close = /* @__PURE__ */ __name(async (driver) => {
5458
+ const d = driver;
5459
+ if (d.close) await d.close();
5460
+ else if (d.end) await d.end();
5461
+ }, "close");
5462
+ return {
5463
+ unsafe: /* @__PURE__ */ __name((sql, params) => application.unsafe(sql, params), "unsafe"),
5464
+ begin: /* @__PURE__ */ __name((fn) => application.begin(fn), "begin"),
5465
+ async cancelBackend(pid, startedAt) {
5466
+ await control.unsafe(`SELECT pg_cancel_backend(pid) FROM pg_stat_activity
5467
+ WHERE pid=$1::integer AND backend_start=$2::timestamptz
5468
+ AND usename=session_user AND datname=current_database() AND backend_type='client backend'`, [
5469
+ pid,
5470
+ startedAt
5471
+ ]);
5472
+ },
5473
+ async close() {
5474
+ await close(application);
5475
+ await close(control);
5476
+ }
5477
+ };
5478
+ }
5479
+ __name(withQueryCancellation, "withQueryCancellation");
5480
+
3827
5481
  // src/decorators/registry.ts
3828
5482
  var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
3829
5483
  var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
@@ -4765,10 +6419,13 @@ async function defaultSqlDriver(config) {
4765
6419
  if (!g.Bun?.SQL) {
4766
6420
  throw new BootRefused([], "boot refused: no SQL driver. Running outside Bun means the driver must be supplied \u2014 pass `sql` to createApp().");
4767
6421
  }
4768
- return new g.Bun.SQL({
6422
+ return withQueryCancellation(new g.Bun.SQL({
4769
6423
  url: config.databaseUrl,
4770
6424
  max: config.poolMax
4771
- });
6425
+ }), new g.Bun.SQL({
6426
+ url: config.databaseUrl,
6427
+ max: 2
6428
+ }));
4772
6429
  }
4773
6430
  __name(defaultSqlDriver, "defaultSqlDriver");
4774
6431
  var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([
@@ -4827,6 +6484,11 @@ async function createApp(opts) {
4827
6484
  const sql = opts.sql ?? await defaultSqlDriver(config);
4828
6485
  await sql.unsafe("select 1");
4829
6486
  setSchema(opts.schemas ?? []);
6487
+ setCursorKey(config.cursorSecret || config.serviceRoleKey || null);
6488
+ if (config.enableCommands) await installCommandExecutor(sql, [
6489
+ config.dbRole,
6490
+ config.dbServiceRole
6491
+ ]);
4830
6492
  setSecretReader(opts.secretReader ?? null);
4831
6493
  const auth = new AuthVerifier({
4832
6494
  jwksUrl: config.authJwksUrl,
@@ -4872,6 +6534,9 @@ async function createApp(opts) {
4872
6534
  role: config.dbRole,
4873
6535
  serviceRole: config.dbServiceRole,
4874
6536
  claimsJson: "{}"
6537
+ }, config.databaseDiagnostics === void 0 ? void 0 : {
6538
+ ...config.databaseDiagnostics,
6539
+ endpoint: "scheduled job"
4875
6540
  });
4876
6541
  try {
4877
6542
  const out = await runWithRuntime(buildServices(db), fn);
@@ -4901,6 +6566,10 @@ async function createApp(opts) {
4901
6566
  role: config.dbRole,
4902
6567
  serviceRole: config.dbServiceRole,
4903
6568
  claimsJson: JSON.stringify(callerClaims ?? {})
6569
+ }, config.databaseDiagnostics === void 0 ? void 0 : {
6570
+ ...config.databaseDiagnostics,
6571
+ endpoint: "upload authorization",
6572
+ requestId
4904
6573
  });
4905
6574
  try {
4906
6575
  const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId, makeRoleResolver(uploadDb.client));
@@ -4948,6 +6617,11 @@ async function createApp(opts) {
4948
6617
  role: config.dbRole,
4949
6618
  serviceRole: config.dbServiceRole,
4950
6619
  claimsJson: JSON.stringify(claims ?? {})
6620
+ }, config.databaseDiagnostics === void 0 && meta.options.databaseBudget === void 0 ? void 0 : {
6621
+ ...config.databaseDiagnostics,
6622
+ ...meta.options.databaseBudget,
6623
+ endpoint: hit.entry.id,
6624
+ requestId
4951
6625
  });
4952
6626
  const rolesOfCaller = makeRoleResolver(db.client);
4953
6627
  let dispatched = false;
@@ -5373,6 +7047,7 @@ __name(closeDriver, "closeDriver");
5373
7047
  0 && (module.exports = {
5374
7048
  AuthVerifier,
5375
7049
  BootRefused,
7050
+ COMMAND_EXECUTOR_DDL,
5376
7051
  DECLARATION_REFUSAL,
5377
7052
  DeclarationRefused,
5378
7053
  RateLimiter,
@@ -5383,6 +7058,7 @@ __name(closeDriver, "closeDriver");
5383
7058
  createRequestDatabase,
5384
7059
  effectiveAuth,
5385
7060
  hostAllowed,
7061
+ installCommandExecutor,
5386
7062
  installEgressFence,
5387
7063
  isDeclarationRefused,
5388
7064
  loadConfig,
@@ -5390,6 +7066,8 @@ __name(closeDriver, "closeDriver");
5390
7066
  matchRoute,
5391
7067
  quoteIdent,
5392
7068
  scrubSecrets,
7069
+ setCursorKey,
7070
+ withQueryCancellation,
5393
7071
  withTables
5394
7072
  });
5395
7073
  //# sourceMappingURL=index.cjs.map