@palbase/backend 33.0.1 → 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 (85) 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-BRLJOXWS.js → chunk-JJW3L62T.js} +3 -3
  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-IKDONZ5D.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 +8 -1
  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/AGENTS.md +7 -7
  73. package/template/modules/health/health.controller.ts +7 -4
  74. package/template/modules/notes/dto/create.ts +4 -3
  75. package/template/modules/notes/notes.controller.ts +8 -6
  76. package/template/package.json +1 -1
  77. package/dist/chunk-BRLJOXWS.js.map +0 -1
  78. package/dist/chunk-IKDONZ5D.js.map +0 -1
  79. package/dist/chunk-KATPXCJ5.js.map +0 -1
  80. package/dist/chunk-SI4KGEM3.js.map +0 -1
  81. package/dist/chunk-TVCCR6SO.js.map +0 -1
  82. package/dist/chunk-WWUG2QXF.js.map +0 -1
  83. package/dist/chunk-XOX6RFPZ.js.map +0 -1
  84. package/dist/chunk-YIQ4RS4F.js.map +0 -1
  85. package/stack-images.json +0 -24
@@ -352,10 +352,10 @@ function encodeFilterValue(value, column) {
352
352
  __name(encodeFilterValue, "encodeFilterValue");
353
353
  function encodeFilterMap(map) {
354
354
  const out = {};
355
- for (const key of Object.keys(map).sort()) {
356
- const value = map[key];
355
+ for (const key2 of Object.keys(map).sort()) {
356
+ const value = map[key2];
357
357
  if (value === void 0) continue;
358
- out[key] = encodeFilterValue(value, key);
358
+ out[key2] = encodeFilterValue(value, key2);
359
359
  }
360
360
  return out;
361
361
  }
@@ -377,12 +377,12 @@ function assertNoNestedHandles(value, column) {
377
377
  __name(assertNoNestedHandles, "assertNoNestedHandles");
378
378
  function encodeMap(map, allowColumnExpr) {
379
379
  const out = {};
380
- for (const key of Object.keys(map).sort()) {
381
- const value = map[key];
380
+ for (const key2 of Object.keys(map).sort()) {
381
+ const value = map[key2];
382
382
  if (value === void 0) {
383
- 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.`);
383
+ 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.`);
384
384
  }
385
- out[key] = encodeValue(value, key, allowColumnExpr);
385
+ out[key2] = encodeValue(value, key2, allowColumnExpr);
386
386
  }
387
387
  return out;
388
388
  }
@@ -614,7 +614,7 @@ function materializeResult(value, results) {
614
614
  if (Array.isArray(value)) return value.map((item) => materializeResult(item, results));
615
615
  if (isPlainObject(value)) {
616
616
  const out = {};
617
- for (const [key, item] of Object.entries(value)) out[key] = materializeResult(item, results);
617
+ for (const [key2, item] of Object.entries(value)) out[key2] = materializeResult(item, results);
618
618
  return out;
619
619
  }
620
620
  return value;
@@ -663,162 +663,71 @@ function translateRejection(err, builder) {
663
663
  }
664
664
  __name(translateRejection, "translateRejection");
665
665
 
666
- // src/errors.ts
667
- var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
668
- var ENGINE_RAISED = /* @__PURE__ */ Symbol.for("palbase.backend.engineRaised");
669
- function markEngineRaised(e) {
670
- e[ENGINE_RAISED] = true;
671
- return e;
672
- }
673
- __name(markEngineRaised, "markEngineRaised");
674
- function isEngineRaised(e) {
675
- return typeof e === "object" && e !== null && e[ENGINE_RAISED] === true;
676
- }
677
- __name(isEngineRaised, "isEngineRaised");
678
- function isHttpError(err) {
679
- if (typeof err !== "object" || err === null) return false;
680
- const e = err;
681
- return e[HTTP_ERROR_BRAND] === true && typeof e.status === "number" && typeof e.error === "string" && typeof e.errorDescription === "string";
682
- }
683
- __name(isHttpError, "isHttpError");
684
- var HttpError = class extends Error {
685
- static {
686
- __name(this, "HttpError");
687
- }
688
- status;
689
- error;
690
- errorDescription;
691
- data;
692
- /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
693
- [HTTP_ERROR_BRAND] = true;
694
- constructor(status, error, errorDescription, data) {
695
- super(errorDescription);
696
- this.name = "HttpError";
697
- this.status = status;
698
- this.error = error;
699
- this.errorDescription = errorDescription;
700
- if (data !== void 0) {
701
- this.data = data;
702
- }
703
- }
704
- /**
705
- * Serialize to the standard Palbase error response format.
706
- * The `requestId` is injected by the runtime layer from the request context.
707
- * When called without arguments (e.g. JSON.stringify), request_id is omitted.
708
- * When `data` is set, it is appended as a strict-superset field.
709
- */
710
- toJSON(requestId) {
711
- const result = {
712
- error: this.error,
713
- error_description: this.errorDescription,
714
- status: this.status
715
- };
716
- if (requestId) {
717
- result.request_id = requestId;
718
- }
719
- if (this.data !== void 0) {
720
- result.data = this.data;
721
- }
722
- return result;
723
- }
724
- };
725
- var NamedHttpError = class NamedHttpError2 extends HttpError {
726
- static {
727
- __name(this, "NamedHttpError");
728
- }
729
- constructor(status, defaultCode, name, message, code, data) {
730
- super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
731
- this.name = name;
666
+ // src/db/workload.ts
667
+ function validateWorkload(value) {
668
+ 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");
669
+ for (const name of [
670
+ "concurrency",
671
+ "tenantConcurrency",
672
+ "queueLimit",
673
+ "tenantQueueLimit",
674
+ "queueTimeoutMs"
675
+ ]) {
676
+ const n = value[name], min = name.endsWith("Limit") ? 0 : 1;
677
+ const max = name === "queueTimeoutMs" ? 6e4 : name.endsWith("Limit") ? 1e4 : 1e3;
678
+ if (!Number.isInteger(n) || n < min || n > max) throw new Error(`workload ${name} must be an integer between ${min} and ${max}`);
732
679
  }
733
- };
734
- function defaultMessage(name) {
735
- const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
736
- return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
680
+ if (value.tenantConcurrency > value.concurrency || value.tenantQueueLimit > value.queueLimit) throw new Error("workload tenant limits cannot exceed global limits");
681
+ 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");
737
682
  }
738
- __name(defaultMessage, "defaultMessage");
739
- var Conflict = class extends NamedHttpError {
740
- static {
741
- __name(this, "Conflict");
742
- }
743
- constructor(message, code, data) {
744
- super(409, "conflict", "Conflict", message, code, data);
745
- }
746
- };
747
- var UniqueViolation = class extends Conflict {
748
- static {
749
- __name(this, "UniqueViolation");
683
+ __name(validateWorkload, "validateWorkload");
684
+
685
+ // src/db/transaction-options.ts
686
+ function retryBudget(value) {
687
+ const n = value ?? 0;
688
+ if (!Number.isInteger(n) || n < 0 || n > 10) {
689
+ throw new Error("transaction retry must be an integer between 0 and 10");
690
+ }
691
+ return n;
692
+ }
693
+ __name(retryBudget, "retryBudget");
694
+ function validateAtomicOptions(options) {
695
+ retryBudget(options.retry);
696
+ if (options.isolation !== void 0 && ![
697
+ "read committed",
698
+ "repeatable read",
699
+ "serializable"
700
+ ].includes(options.isolation)) {
701
+ throw new Error("transaction isolation must be read committed, repeatable read or serializable");
702
+ }
703
+ if (options.readOnly !== void 0 && typeof options.readOnly !== "boolean") {
704
+ throw new Error("transaction readOnly must be a boolean");
705
+ }
706
+ for (const name of [
707
+ "lockTimeoutMs",
708
+ "statementTimeoutMs",
709
+ "timeoutMs"
710
+ ]) {
711
+ const n = options[name];
712
+ if (n !== void 0 && (!Number.isInteger(n) || n < 1 || n > 2147483647)) {
713
+ throw new Error(`transaction ${name} must be a positive integer in milliseconds`);
714
+ }
750
715
  }
751
- /**
752
- * Whether `e` is a unique violation REGARDLESS of which copy of this SDK
753
- * constructed it.
754
- *
755
- * Use this instead of `instanceof`. Measured on a live stack: a controller
756
- * bundle INLINES its own copy of `@palbase/backend`, and the engine that
757
- * raises this error is the runtime's copy. Two copies, two class identities,
758
- * and `e instanceof UniqueViolation` is false in the one place a caller
759
- * writes it — a check that reads as correct and silently never matches.
760
- */
761
- static is(e) {
762
- return typeof e === "object" && e !== null && e.name === "UniqueViolation" && typeof e.constraint === "string";
716
+ if (options.workload !== void 0) validateWorkload(options.workload);
717
+ 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))) {
718
+ throw new Error("conflictKeys requires a workload and at most 64 nonempty keys of at most 256 characters");
763
719
  }
764
- /** The unique constraint the statement violated, as Postgres named it.
765
- * `""` when the driver did not say which — see `engine/db.ts`. */
766
- constraint;
767
- constructor(constraint, message, code, data) {
768
- super(message ?? "Unique constraint violated", code ?? "unique_violation", data);
769
- this.name = "UniqueViolation";
770
- this.constraint = constraint;
720
+ if (options.signal !== void 0 && (typeof options.signal.aborted !== "boolean" || typeof options.signal.addEventListener !== "function")) {
721
+ throw new Error("transaction signal must be an AbortSignal");
771
722
  }
772
- };
773
- function isRetryable(e) {
774
- return typeof e === "object" && e !== null && e.retryable === true && typeof e.sqlstate === "string";
775
723
  }
776
- __name(isRetryable, "isRetryable");
777
- var SerializationFailure = class extends Conflict {
778
- static {
779
- __name(this, "SerializationFailure");
780
- }
781
- retryable = true;
782
- sqlstate;
783
- static is(e) {
784
- return typeof e === "object" && e !== null && e.name === "SerializationFailure";
785
- }
786
- constructor(sqlstate = "40001", message, data) {
787
- 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);
788
- this.name = "SerializationFailure";
789
- this.sqlstate = sqlstate;
790
- }
791
- };
792
- var DeadlockDetected = class extends Conflict {
793
- static {
794
- __name(this, "DeadlockDetected");
795
- }
796
- retryable = true;
797
- sqlstate;
798
- static is(e) {
799
- return typeof e === "object" && e !== null && e.name === "DeadlockDetected";
800
- }
801
- constructor(sqlstate = "40P01", message, data) {
802
- 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);
803
- this.name = "DeadlockDetected";
804
- this.sqlstate = sqlstate;
805
- }
806
- };
807
-
808
- // src/db/typed-db.ts
809
- async function withRetry(fn, opts = {}) {
810
- const budget = opts.retry ?? 0;
811
- let attempt = 0;
812
- for (; ; ) {
813
- try {
814
- return await fn();
815
- } catch (e) {
816
- if (attempt >= budget || !isRetryable(e)) throw e;
817
- attempt += 1;
818
- }
724
+ __name(validateAtomicOptions, "validateAtomicOptions");
725
+ function assertPlanRetry(options) {
726
+ if (retryBudget(options?.retry) !== 0) {
727
+ 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.");
819
728
  }
820
729
  }
821
- __name(withRetry, "withRetry");
730
+ __name(assertPlanRetry, "assertPlanRetry");
822
731
 
823
732
  // src/db/schema-json.ts
824
733
  function qualifiedTableKey(schemaName, tableName) {
@@ -915,10 +824,10 @@ async function __runStartHooks() {
915
824
  };
916
825
  }
917
826
  __name(__runStartHooks, "__runStartHooks");
918
- function makeServiceProxy(key) {
827
+ function makeServiceProxy(key2) {
919
828
  const handler = {
920
829
  get(_target, prop, receiver) {
921
- const client = __getRuntime()[key];
830
+ const client = __getRuntime()[key2];
922
831
  const value = Reflect.get(client, prop, receiver);
923
832
  return typeof value === "function" ? value.bind(client) : value;
924
833
  }
@@ -941,6 +850,14 @@ function makeTableProxy(ops, prefix) {
941
850
  update: /* @__PURE__ */ __name((q) => ops().update(name, q.where.id, q.set), "update"),
942
851
  delete: /* @__PURE__ */ __name((id) => ops().delete(name, id), "delete"),
943
852
  findById: /* @__PURE__ */ __name((id) => ops().findById(name, id), "findById"),
853
+ findUnique: /* @__PURE__ */ __name((q) => ops().findUnique(name, q.where, {
854
+ select: q.select,
855
+ with: q.with
856
+ }), "findUnique"),
857
+ page: /* @__PURE__ */ __name((q) => {
858
+ const { where, ...opts } = q ?? {};
859
+ return ops().page(name, where, opts);
860
+ }, "page"),
944
861
  findMany: /* @__PURE__ */ __name((q) => {
945
862
  const { where, ...opts } = q ?? {};
946
863
  return ops().findMany(name, where, opts);
@@ -978,10 +895,16 @@ function makeTypedSurface(raw) {
978
895
  const reco = raw;
979
896
  const ops = {
980
897
  $query: /* @__PURE__ */ __name((sql, params) => raw.query(sql, params), "$query"),
898
+ $diagnostics: /* @__PURE__ */ __name(() => raw.diagnostics(), "$diagnostics"),
899
+ $readQuery: /* @__PURE__ */ __name((query) => raw.readQuery(query), "$readQuery"),
900
+ $compileQuery: /* @__PURE__ */ __name((query) => raw.compileQuery(query), "$compileQuery"),
901
+ $explainQuery: /* @__PURE__ */ __name((query) => raw.explainQuery(query), "$explainQuery"),
981
902
  $insert: /* @__PURE__ */ __name((table, data) => raw.insert(table, data), "$insert"),
982
903
  $update: /* @__PURE__ */ __name((table, id, data) => raw.update(table, id, data), "$update"),
983
904
  $delete: /* @__PURE__ */ __name((table, id) => raw.delete(table, id), "$delete"),
984
905
  $findById: /* @__PURE__ */ __name((table, id) => raw.findById(table, id), "$findById"),
906
+ $findUnique: /* @__PURE__ */ __name((table, where, opts) => raw.findUnique(table, where, opts), "$findUnique"),
907
+ $page: /* @__PURE__ */ __name((table, query, opts) => raw.page(table, query, opts), "$page"),
985
908
  $findMany: /* @__PURE__ */ __name((table, query, opts) => raw.findMany(table, query, opts), "$findMany"),
986
909
  $put: /* @__PURE__ */ __name((table, data, opts) => raw.put(table, data, opts), "$put"),
987
910
  // `opts` İLETİLİR. Düşürüldüğü sürece `Database.$updateMany(t, w, s,
@@ -998,20 +921,55 @@ function makeTypedSurface(raw) {
998
921
  $facets: /* @__PURE__ */ __name((table, params) => reco.facets(table, params), "$facets"),
999
922
  $claim: /* @__PURE__ */ __name((table, unique, extra) => reco.claim(table, unique, extra), "$claim"),
1000
923
  $lockRows: /* @__PURE__ */ __name((table, ids) => reco.lockRows(table, ids), "$lockRows"),
1001
- $advisoryXactLock: /* @__PURE__ */ __name((key) => reco.advisoryXactLock(key), "$advisoryXactLock"),
924
+ $advisoryXactLock: /* @__PURE__ */ __name((key2) => reco.advisoryXactLock(key2), "$advisoryXactLock"),
1002
925
  $aggregate: /* @__PURE__ */ __name((table, q) => raw.aggregate(table, q), "$aggregate"),
1003
926
  $insertMany: /* @__PURE__ */ __name((table, rows, opts) => raw.insertMany(table, rows, opts), "$insertMany"),
1004
927
  $supersede: /* @__PURE__ */ __name((table, id, row) => raw.supersede(table, id, row), "$supersede")
1005
928
  };
1006
929
  const base = Object.assign(ops, {
930
+ $atomic(fn, options) {
931
+ if (typeof raw.atomic !== "function") {
932
+ throw new Error("This Database handle cannot open a root transaction; nested $atomic is not supported");
933
+ }
934
+ const parent = __requestALS.getStore();
935
+ let runtime2;
936
+ try {
937
+ runtime2 = __getRuntime();
938
+ } catch {
939
+ }
940
+ return raw.atomic(async (tx) => {
941
+ const typed = makeTypedSurface(tx);
942
+ const invoke = /* @__PURE__ */ __name(() => fn(typed), "invoke");
943
+ if (!runtime2) return invoke();
944
+ const refuse = /* @__PURE__ */ __name(() => {
945
+ throw new Error("Database.$atomic owns one transaction and identity; nested roots and $asService are not allowed");
946
+ }, "refuse");
947
+ const ambient = Object.assign({}, tx, {
948
+ atomic: refuse,
949
+ asService: refuse
950
+ });
951
+ return __requestALS.run({
952
+ ...parent,
953
+ runtime: {
954
+ ...runtime2,
955
+ Database: ambient
956
+ }
957
+ }, invoke);
958
+ }, options);
959
+ },
1007
960
  // Both surfaces get it: a savepoint on the service transaction is as useful
1008
961
  // as one on the request's, and each is bound to its own connection.
1009
962
  $attempt: /* @__PURE__ */ __name((fn) => raw.attempt(fn), "$attempt"),
1010
963
  $transaction(fn, opts) {
1011
- return withRetry(() => {
1012
- const builder = new TxPlanBuilder();
1013
- return runTxPlan(raw, makeTxPlanHandle(builder), builder, fn);
1014
- }, opts ?? {});
964
+ assertPlanRetry(opts);
965
+ const builder = new TxPlanBuilder();
966
+ return runTxPlan(raw, makeTxPlanHandle(builder), builder, fn);
967
+ },
968
+ $command(fn, options) {
969
+ const builder = new TxPlanBuilder();
970
+ return runTxPlan({
971
+ txPlan: /* @__PURE__ */ __name((plan) => raw.command(plan, options), "txPlan")
972
+ }, makeTxPlanHandle(builder), builder, fn);
1015
973
  }
1016
974
  });
1017
975
  return new Proxy(base, {
@@ -1112,8 +1070,8 @@ var Flags = Object.assign({
1112
1070
  get(flagName, defaultOrContext, maybeContext) {
1113
1071
  return rawFlags.get(flagName, defaultOrContext, maybeContext);
1114
1072
  },
1115
- setOverride(key, value) {
1116
- return rawFlags.setOverride(key, value);
1073
+ setOverride(key2, value) {
1074
+ return rawFlags.setOverride(key2, value);
1117
1075
  }
1118
1076
  }, {
1119
1077
  /**
@@ -1141,6 +1099,175 @@ var Flags = Object.assign({
1141
1099
  });
1142
1100
  var Realtime = makeServiceProxy("Realtime");
1143
1101
 
1102
+ // src/db/sqlstate.ts
1103
+ var SQLSTATE = Object.freeze({
1104
+ serializationFailure: "40001",
1105
+ deadlockDetected: "40P01",
1106
+ commandGuardFailed: "PBC01"
1107
+ });
1108
+ function isTransactionRollbackState(state) {
1109
+ return state === SQLSTATE.serializationFailure || state === SQLSTATE.deadlockDetected;
1110
+ }
1111
+ __name(isTransactionRollbackState, "isTransactionRollbackState");
1112
+
1113
+ // src/errors.ts
1114
+ var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
1115
+ var ENGINE_RAISED = /* @__PURE__ */ Symbol.for("palbase.backend.engineRaised");
1116
+ function markEngineRaised(e) {
1117
+ e[ENGINE_RAISED] = true;
1118
+ return e;
1119
+ }
1120
+ __name(markEngineRaised, "markEngineRaised");
1121
+ function isEngineRaised(e) {
1122
+ return typeof e === "object" && e !== null && e[ENGINE_RAISED] === true;
1123
+ }
1124
+ __name(isEngineRaised, "isEngineRaised");
1125
+ function isHttpError(err) {
1126
+ if (typeof err !== "object" || err === null) return false;
1127
+ const e = err;
1128
+ return e[HTTP_ERROR_BRAND] === true && typeof e.status === "number" && typeof e.error === "string" && typeof e.errorDescription === "string";
1129
+ }
1130
+ __name(isHttpError, "isHttpError");
1131
+ var HttpError = class extends Error {
1132
+ static {
1133
+ __name(this, "HttpError");
1134
+ }
1135
+ status;
1136
+ error;
1137
+ errorDescription;
1138
+ data;
1139
+ /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
1140
+ [HTTP_ERROR_BRAND] = true;
1141
+ constructor(status, error, errorDescription, data) {
1142
+ super(errorDescription);
1143
+ this.name = "HttpError";
1144
+ this.status = status;
1145
+ this.error = error;
1146
+ this.errorDescription = errorDescription;
1147
+ if (data !== void 0) {
1148
+ this.data = data;
1149
+ }
1150
+ }
1151
+ /**
1152
+ * Serialize to the standard Palbase error response format.
1153
+ * The `requestId` is injected by the runtime layer from the request context.
1154
+ * When called without arguments (e.g. JSON.stringify), request_id is omitted.
1155
+ * When `data` is set, it is appended as a strict-superset field.
1156
+ */
1157
+ toJSON(requestId) {
1158
+ const result = {
1159
+ error: this.error,
1160
+ error_description: this.errorDescription,
1161
+ status: this.status
1162
+ };
1163
+ if (requestId) {
1164
+ result.request_id = requestId;
1165
+ }
1166
+ if (this.data !== void 0) {
1167
+ result.data = this.data;
1168
+ }
1169
+ return result;
1170
+ }
1171
+ };
1172
+ var NamedHttpError = class NamedHttpError2 extends HttpError {
1173
+ static {
1174
+ __name(this, "NamedHttpError");
1175
+ }
1176
+ constructor(status, defaultCode, name, message, code, data) {
1177
+ super(status, code ?? defaultCode, message ?? defaultMessage(name), data);
1178
+ this.name = name;
1179
+ }
1180
+ };
1181
+ function defaultMessage(name) {
1182
+ const spaced = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
1183
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
1184
+ }
1185
+ __name(defaultMessage, "defaultMessage");
1186
+ var BadRequest = class extends NamedHttpError {
1187
+ static {
1188
+ __name(this, "BadRequest");
1189
+ }
1190
+ constructor(data, message) {
1191
+ super(400, "bad_request", "BadRequest", message, void 0, data);
1192
+ }
1193
+ };
1194
+ var Conflict = class extends NamedHttpError {
1195
+ static {
1196
+ __name(this, "Conflict");
1197
+ }
1198
+ constructor(message, code, data) {
1199
+ super(409, "conflict", "Conflict", message, code, data);
1200
+ }
1201
+ };
1202
+ var UniqueViolation = class extends Conflict {
1203
+ static {
1204
+ __name(this, "UniqueViolation");
1205
+ }
1206
+ /**
1207
+ * Whether `e` is a unique violation — REGARDLESS of which copy of this SDK
1208
+ * constructed it.
1209
+ *
1210
+ * Use this instead of `instanceof`. Measured on a live stack: a controller
1211
+ * bundle INLINES its own copy of `@palbase/backend`, and the engine that
1212
+ * raises this error is the runtime's copy. Two copies, two class identities,
1213
+ * and `e instanceof UniqueViolation` is false in the one place a caller
1214
+ * writes it — a check that reads as correct and silently never matches.
1215
+ */
1216
+ static is(e) {
1217
+ return typeof e === "object" && e !== null && e.name === "UniqueViolation" && typeof e.constraint === "string";
1218
+ }
1219
+ /** The unique constraint the statement violated, as Postgres named it.
1220
+ * `""` when the driver did not say which — see `engine/db.ts`. */
1221
+ constraint;
1222
+ constructor(constraint, message, code, data) {
1223
+ super(message ?? "Unique constraint violated", code ?? "unique_violation", data);
1224
+ this.name = "UniqueViolation";
1225
+ this.constraint = constraint;
1226
+ }
1227
+ };
1228
+ function isRetryable(e) {
1229
+ return typeof e === "object" && e !== null && e.retryable === true && isTransactionRollbackState(e.sqlstate);
1230
+ }
1231
+ __name(isRetryable, "isRetryable");
1232
+ var SerializationFailure = class extends Conflict {
1233
+ static {
1234
+ __name(this, "SerializationFailure");
1235
+ }
1236
+ retryable = true;
1237
+ sqlstate;
1238
+ static is(e) {
1239
+ return typeof e === "object" && e !== null && e.name === "SerializationFailure";
1240
+ }
1241
+ constructor(sqlstate = SQLSTATE.serializationFailure, message, data) {
1242
+ 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);
1243
+ this.name = "SerializationFailure";
1244
+ this.sqlstate = sqlstate;
1245
+ }
1246
+ };
1247
+ var DeadlockDetected = class extends Conflict {
1248
+ static {
1249
+ __name(this, "DeadlockDetected");
1250
+ }
1251
+ retryable = true;
1252
+ sqlstate;
1253
+ static is(e) {
1254
+ return typeof e === "object" && e !== null && e.name === "DeadlockDetected";
1255
+ }
1256
+ constructor(sqlstate = SQLSTATE.deadlockDetected, message, data) {
1257
+ 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);
1258
+ this.name = "DeadlockDetected";
1259
+ this.sqlstate = sqlstate;
1260
+ }
1261
+ };
1262
+ var TooManyRequests = class extends NamedHttpError {
1263
+ static {
1264
+ __name(this, "TooManyRequests");
1265
+ }
1266
+ constructor(data, message) {
1267
+ super(429, "too_many_requests", "TooManyRequests", message, void 0, data);
1268
+ }
1269
+ };
1270
+
1144
1271
  // src/engine/config.ts
1145
1272
  var BootRefused = class extends Error {
1146
1273
  static {
@@ -1191,6 +1318,8 @@ ${detail}`);
1191
1318
  uploadSecret: env.PALBASE_UPLOAD_SECRET ?? "",
1192
1319
  anonKey: env.PALBASE_ANON_KEY ?? "",
1193
1320
  serviceRoleKey: env.PALBASE_SERVICE_ROLE_KEY ?? "",
1321
+ cursorSecret: env.PALBASE_CURSOR_SECRET,
1322
+ enableCommands: env.PALBASE_DB_COMMANDS === "true",
1194
1323
  realtimeSecret: env.REALTIME_INGESTION_SECRET ?? "",
1195
1324
  port,
1196
1325
  dbRole: env.DB_ROLE ?? "backend_authenticated",
@@ -1199,7 +1328,14 @@ ${detail}`);
1199
1328
  // (`roleBackendServiceRole = "backend_service_role"`, NOLOGIN BYPASSRLS),
1200
1329
  // and the live database agrees (pg_roles.rolbypassrls = true).
1201
1330
  dbServiceRole: env.DB_SERVICE_ROLE ?? "backend_service_role",
1202
- poolMax
1331
+ poolMax,
1332
+ ...env.PALBASE_DB_DIAGNOSTICS === "true" ? {
1333
+ databaseDiagnostics: {
1334
+ onComplete: /* @__PURE__ */ __name((diagnostics) => {
1335
+ console.log("palbase.database", JSON.stringify(diagnostics));
1336
+ }, "onComplete")
1337
+ }
1338
+ } : {}
1203
1339
  };
1204
1340
  }
1205
1341
  __name(loadConfig, "loadConfig");
@@ -1311,14 +1447,14 @@ var AuthVerifier = class {
1311
1447
  return null;
1312
1448
  }
1313
1449
  if (header.alg !== "ES256" || !header.kid) return null;
1314
- const key = await this.key(header.kid);
1315
- if (!key) return null;
1450
+ const key2 = await this.key(header.kid);
1451
+ if (!key2) return null;
1316
1452
  let ok = false;
1317
1453
  try {
1318
1454
  ok = await crypto.subtle.verify({
1319
1455
  name: "ECDSA",
1320
1456
  hash: "SHA-256"
1321
- }, key, b64urlToBytes(sig), new TextEncoder().encode(`${h}.${p}`));
1457
+ }, key2, b64urlToBytes(sig), new TextEncoder().encode(`${h}.${p}`));
1322
1458
  } catch {
1323
1459
  return null;
1324
1460
  }
@@ -1384,12 +1520,12 @@ var RateLimiter = class {
1384
1520
  * wait (never 0 — a caller told to wait 0 comes straight back to the same
1385
1521
  * refusal).
1386
1522
  */
1387
- check(rule, key, now) {
1523
+ check(rule, key2, now) {
1388
1524
  if (!rule || !(rule.max > 0) || !(rule.window > 0)) return null;
1389
- const bucket = this.buckets.get(key);
1525
+ const bucket = this.buckets.get(key2);
1390
1526
  if (!bucket || now >= bucket.resetAt) {
1391
1527
  if (this.buckets.size >= this.maxKeys) this.evict(now);
1392
- this.buckets.set(key, {
1528
+ this.buckets.set(key2, {
1393
1529
  count: 1,
1394
1530
  resetAt: now + rule.window * 1e3
1395
1531
  });
@@ -1432,11 +1568,11 @@ function makeMemoryCache(opts = {}) {
1432
1568
  const now = opts.now ?? (() => Date.now());
1433
1569
  const store = /* @__PURE__ */ new Map();
1434
1570
  const inflight = /* @__PURE__ */ new Map();
1435
- const live = /* @__PURE__ */ __name((key) => {
1436
- const e = store.get(key);
1571
+ const live = /* @__PURE__ */ __name((key2) => {
1572
+ const e = store.get(key2);
1437
1573
  if (!e) return void 0;
1438
1574
  if (e.expiresAt !== 0 && e.expiresAt <= now()) {
1439
- store.delete(key);
1575
+ store.delete(key2);
1440
1576
  return void 0;
1441
1577
  }
1442
1578
  return e;
@@ -1459,51 +1595,830 @@ function makeMemoryCache(opts = {}) {
1459
1595
  if (victim) store.delete(victim[0]);
1460
1596
  }
1461
1597
  }, "evict");
1462
- const set = /* @__PURE__ */ __name(async (key, value, ttl) => {
1463
- if (store.size >= maxEntries && !store.has(key)) evict();
1464
- store.set(key, {
1598
+ const set = /* @__PURE__ */ __name(async (key2, value, ttl) => {
1599
+ if (store.size >= maxEntries && !store.has(key2)) evict();
1600
+ store.set(key2, {
1465
1601
  value,
1466
1602
  expiresAt: ttl && ttl > 0 ? now() + ttl * 1e3 : 0
1467
1603
  });
1468
1604
  }, "set");
1469
1605
  return {
1470
- async get(key) {
1471
- const e = live(key);
1606
+ async get(key2) {
1607
+ const e = live(key2);
1472
1608
  return e ? e.value : null;
1473
1609
  },
1474
1610
  set,
1475
- async del(key) {
1476
- store.delete(key);
1611
+ async del(key2) {
1612
+ store.delete(key2);
1477
1613
  },
1478
- async incr(key) {
1479
- const e = live(key);
1614
+ async incr(key2) {
1615
+ const e = live(key2);
1480
1616
  const next = (typeof e?.value === "number" ? e.value : 0) + 1;
1481
- store.set(key, {
1617
+ store.set(key2, {
1482
1618
  value: next,
1483
1619
  expiresAt: e?.expiresAt ?? 0
1484
1620
  });
1485
1621
  return next;
1486
1622
  },
1487
- async getOrSet(key, ttl, fn) {
1488
- const hit = live(key);
1623
+ async getOrSet(key2, ttl, fn) {
1624
+ const hit = live(key2);
1489
1625
  if (hit) return hit.value;
1490
- const running = inflight.get(key);
1626
+ const running = inflight.get(key2);
1491
1627
  if (running) return running;
1492
1628
  const fill = (async () => {
1493
1629
  try {
1494
1630
  const value = await fn();
1495
- await set(key, value, ttl);
1631
+ await set(key2, value, ttl);
1496
1632
  return value;
1497
1633
  } finally {
1498
- inflight.delete(key);
1634
+ inflight.delete(key2);
1635
+ }
1636
+ })();
1637
+ inflight.set(key2, fill);
1638
+ return fill;
1639
+ }
1640
+ };
1641
+ }
1642
+ __name(makeMemoryCache, "makeMemoryCache");
1643
+
1644
+ // src/engine/db.ts
1645
+ var import_node_async_hooks2 = require("async_hooks");
1646
+
1647
+ // src/engine/schema-cache.ts
1648
+ function memoTable(cache, schema, table, compute) {
1649
+ if (!schema.tables || !Object.hasOwn(schema.tables, table)) return compute();
1650
+ let tables = cache.get(schema);
1651
+ if (!tables) {
1652
+ tables = /* @__PURE__ */ new Map();
1653
+ cache.set(schema, tables);
1654
+ }
1655
+ if (!tables.has(table)) tables.set(table, compute());
1656
+ return tables.get(table);
1657
+ }
1658
+ __name(memoTable, "memoTable");
1659
+ function snapshotDeclaration(value, seen = /* @__PURE__ */ new WeakMap()) {
1660
+ if (value === null || typeof value !== "object") return value;
1661
+ const previous = seen.get(value);
1662
+ if (previous) return previous;
1663
+ if (value instanceof Date) return new Date(value.getTime());
1664
+ const result = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
1665
+ seen.set(value, result);
1666
+ for (const key2 of Reflect.ownKeys(value)) {
1667
+ if (Array.isArray(value) && key2 === "length") continue;
1668
+ const descriptor = Object.getOwnPropertyDescriptor(value, key2);
1669
+ if ("value" in descriptor) descriptor.value = snapshotDeclaration(descriptor.value, seen);
1670
+ Object.defineProperty(result, key2, descriptor);
1671
+ }
1672
+ return Object.freeze(result);
1673
+ }
1674
+ __name(snapshotDeclaration, "snapshotDeclaration");
1675
+
1676
+ // src/engine/cursor.ts
1677
+ var import_node_crypto = require("crypto");
1678
+ var key = null;
1679
+ var invalidPage = /* @__PURE__ */ __name((message, field = "page") => new BadRequest({
1680
+ fields: [
1681
+ {
1682
+ field,
1683
+ message
1684
+ }
1685
+ ]
1686
+ }, message), "invalidPage");
1687
+ function setCursorKey(secret) {
1688
+ key = secret ? (0, import_node_crypto.createHash)("sha256").update("palbase:cursor:v1\0").update(secret).digest() : null;
1689
+ }
1690
+ __name(setCursorKey, "setCursorKey");
1691
+ function cursorKey() {
1692
+ if (!key) throw new Error("Database.page needs PALBASE_CURSOR_SECRET or PALBASE_SERVICE_ROLE_KEY shared by all replicas");
1693
+ return key;
1694
+ }
1695
+ __name(cursorKey, "cursorKey");
1696
+ function fingerprint(value) {
1697
+ const stable = /* @__PURE__ */ __name((v) => {
1698
+ if (typeof v === "bigint") return {
1699
+ $bigint: v.toString()
1700
+ };
1701
+ if (v instanceof Date) return {
1702
+ $date: v.toISOString()
1703
+ };
1704
+ if (Array.isArray(v)) return v.map(stable);
1705
+ if (v && typeof v === "object") {
1706
+ return Object.fromEntries(Object.keys(v).sort().map((k) => [
1707
+ k,
1708
+ stable(v[k])
1709
+ ]));
1710
+ }
1711
+ return v;
1712
+ }, "stable");
1713
+ return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(stable(value))).digest("base64url");
1714
+ }
1715
+ __name(fingerprint, "fingerprint");
1716
+ function cursorIdentity(role, claimsJson) {
1717
+ const claims = JSON.parse(claimsJson);
1718
+ const { iat, exp, nbf, jti, ...authorization } = claims;
1719
+ return fingerprint({
1720
+ role,
1721
+ authorization
1722
+ });
1723
+ }
1724
+ __name(cursorIdentity, "cursorIdentity");
1725
+ function encodeCursor(secret, scope, values) {
1726
+ const nonce = (0, import_node_crypto.randomBytes)(12);
1727
+ const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", secret, nonce);
1728
+ cipher.setAAD(Buffer.from(scope));
1729
+ const body = Buffer.concat([
1730
+ cipher.update(JSON.stringify(values), "utf8"),
1731
+ cipher.final()
1732
+ ]);
1733
+ const token = `p1.${Buffer.concat([
1734
+ nonce,
1735
+ cipher.getAuthTag(),
1736
+ body
1737
+ ]).toString("base64url")}`;
1738
+ if (token.length > 16384) throw invalidPage("page cursor values are too large; choose shorter ordering columns");
1739
+ return token;
1740
+ }
1741
+ __name(encodeCursor, "encodeCursor");
1742
+ function decodeCursor(secret, scope, token, count) {
1743
+ try {
1744
+ if (typeof token !== "string" || token.length > 16384 || !/^p1\.[\w-]+$/.test(token)) throw new Error();
1745
+ const data = Buffer.from(token.slice(3), "base64url");
1746
+ if (data.length < 30 || data.toString("base64url") !== token.slice(3)) throw new Error();
1747
+ const cipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", secret, data.subarray(0, 12));
1748
+ cipher.setAAD(Buffer.from(scope));
1749
+ cipher.setAuthTag(data.subarray(12, 28));
1750
+ const values = JSON.parse(Buffer.concat([
1751
+ cipher.update(data.subarray(28)),
1752
+ cipher.final()
1753
+ ]).toString("utf8"));
1754
+ if (!Array.isArray(values) || values.length !== count || values.some((v) => v !== null && typeof v !== "string")) throw new Error();
1755
+ return values;
1756
+ } catch {
1757
+ throw invalidPage("Invalid cursor for this query or identity; restart from the first page", "cursor");
1758
+ }
1759
+ }
1760
+ __name(decodeCursor, "decodeCursor");
1761
+ function pageOrder(columns, primaryKey, opts) {
1762
+ const raw = opts;
1763
+ if ("limit" in raw || "offset" in raw) throw invalidPage("page uses first/after or last/before, not limit/offset");
1764
+ const backward = opts.last !== void 0 || opts.before !== void 0;
1765
+ if (backward && (opts.first !== void 0 || opts.after !== void 0)) throw invalidPage("Use first/after or last/before in one page request");
1766
+ const size = (backward ? opts.last : opts.first) ?? 50;
1767
+ if (!Number.isInteger(size) || size < 1 || size > 1e3) throw invalidPage("page size must be an integer between 1 and 1000");
1768
+ if (primaryKey.length === 0) throw new Error("page requires a declared primary key for a unique, stable ordering");
1769
+ const input = opts.orderBy === void 0 ? [] : Array.isArray(opts.orderBy) ? opts.orderBy : [
1770
+ opts.orderBy
1771
+ ];
1772
+ if (input.length > 16) throw invalidPage("page supports at most 16 ordering columns");
1773
+ const seen = /* @__PURE__ */ new Set();
1774
+ const order = [];
1775
+ const add = /* @__PURE__ */ __name((o) => {
1776
+ const def = columns[o?.column];
1777
+ if (!Object.hasOwn(columns, o?.column) || !def) throw invalidPage(`page order column ${o?.column} is not declared`);
1778
+ if (!(/* @__PURE__ */ new Set([
1779
+ "uuid",
1780
+ "text",
1781
+ "integer",
1782
+ "bigint",
1783
+ "boolean",
1784
+ "timestamp",
1785
+ "numeric",
1786
+ "enum"
1787
+ ])).has(def.type ?? "")) {
1788
+ throw invalidPage(`page cannot order ${o.column}: ${def.type ?? "unknown"} is not a supported scalar column`);
1789
+ }
1790
+ if (seen.has(o.column)) throw invalidPage(`page order column ${o.column} is repeated`);
1791
+ if (o.direction !== void 0 && o.direction !== "asc" && o.direction !== "desc") throw invalidPage("page direction must be asc or desc");
1792
+ if (o.nulls !== void 0 && o.nulls !== "first" && o.nulls !== "last") throw invalidPage("page nulls must be first or last");
1793
+ const direction = o.direction ?? "asc";
1794
+ seen.add(o.column);
1795
+ order.push({
1796
+ column: o.column,
1797
+ direction,
1798
+ nulls: o.nulls ?? (direction === "asc" ? "last" : "first"),
1799
+ nullable: def.nullable !== false
1800
+ });
1801
+ }, "add");
1802
+ for (const o of input) add(o);
1803
+ for (const column of primaryKey) if (!seen.has(column)) add({
1804
+ column,
1805
+ direction: order.at(-1)?.direction ?? "asc"
1806
+ });
1807
+ return {
1808
+ order,
1809
+ size,
1810
+ backward,
1811
+ token: backward ? opts.before : opts.after
1812
+ };
1813
+ }
1814
+ __name(pageOrder, "pageOrder");
1815
+ function pageBoundary(order, values, backward, bind, column, inclusive = false) {
1816
+ const refs = values.map((v) => bind(v));
1817
+ if (order.every((o) => !o.nullable && o.direction === order[0].direction) && values.every((v) => v !== null)) {
1818
+ const greater = order[0].direction === "asc" !== backward;
1819
+ return `(${order.map((o) => column(o.column)).join(", ")}) ${greater ? ">" : "<"}${inclusive ? "=" : ""} (${refs.join(", ")})`;
1820
+ }
1821
+ const parts = [];
1822
+ const prefix = [];
1823
+ for (let i = 0; i < order.length; i++) {
1824
+ const o = order[i], c = column(o.column), r = refs[i];
1825
+ const nullsLast = o.nulls === "last" !== backward;
1826
+ const cmp = o.direction === "asc" !== backward ? ">" : "<";
1827
+ const tail = values[i] === null ? nullsLast ? "false" : `${c} IS NOT NULL` : `(${c} ${cmp} ${r}${nullsLast ? ` OR ${c} IS NULL` : ""})`;
1828
+ parts.push(`(${[
1829
+ ...prefix,
1830
+ tail
1831
+ ].join(" AND ")})`);
1832
+ prefix.push(`${c} IS NOT DISTINCT FROM ${r}`);
1833
+ }
1834
+ if (inclusive) parts.push(`(${prefix.join(" AND ")})`);
1835
+ return `(${parts.join(" OR ")})`;
1836
+ }
1837
+ __name(pageBoundary, "pageBoundary");
1838
+
1839
+ // src/db/bulk.ts
1840
+ function validateInsertManyOptions(opts) {
1841
+ if (!opts) return;
1842
+ if (opts.returning !== void 0 && typeof opts.returning !== "boolean") throw new Error("insertMany returning must be a boolean");
1843
+ if (opts.action !== void 0 && opts.action !== "ignore" && opts.action !== "update") throw new Error("insertMany action must be ignore or update");
1844
+ if (opts.onConflict !== void 0 && (!Array.isArray(opts.onConflict) || opts.onConflict.some((c) => typeof c !== "string" || c.length === 0))) {
1845
+ throw new Error("insertMany onConflict must be an array of column names");
1846
+ }
1847
+ if (opts.action !== void 0 && !opts.onConflict?.length) throw new Error("insertMany action requires nonempty onConflict columns");
1848
+ }
1849
+ __name(validateInsertManyOptions, "validateInsertManyOptions");
1850
+
1851
+ // src/db/query-contract.ts
1852
+ function queryColumnContract(column) {
1853
+ const def = column && typeof column === "object" && "_def" in column ? column._def : column;
1854
+ if (!def?.type) throw new Error("Query contract requires declared column types");
1855
+ 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");
1856
+ return {
1857
+ type: def.type,
1858
+ nullable: def.nullable === true,
1859
+ ...def.codec === void 0 ? {} : {
1860
+ codec: def.codec
1861
+ },
1862
+ ...def.enumValues === void 0 ? {} : {
1863
+ enumValues: [
1864
+ ...def.enumValues
1865
+ ]
1866
+ },
1867
+ ...def.dimensions === void 0 ? {} : {
1868
+ dimensions: def.dimensions
1869
+ }
1870
+ };
1871
+ }
1872
+ __name(queryColumnContract, "queryColumnContract");
1873
+ function stableContract(value) {
1874
+ if (Array.isArray(value)) return `[${value.map(stableContract).join(",")}]`;
1875
+ if (value && typeof value === "object") return `{${Object.keys(value).sort().map((key2) => `${JSON.stringify(key2)}:${stableContract(value[key2])}`).join(",")}}`;
1876
+ return JSON.stringify(value) ?? "null";
1877
+ }
1878
+ __name(stableContract, "stableContract");
1879
+ function assertReadContract(query, columns) {
1880
+ if (!query.options.select.length || new Set(query.options.select).size !== query.options.select.length) {
1881
+ throw new Error(`Query ${query.name}: select must contain distinct, explicit columns`);
1882
+ }
1883
+ for (const name of query.options.select) {
1884
+ if (!columns || !Object.hasOwn(columns, name) || !Object.hasOwn(query.columns, name) || stableContract(queryColumnContract(columns[name])) !== stableContract(query.columns[name])) {
1885
+ throw new Error(`Query ${query.name}: schema drift at ${query.table}.${name}; regenerate and review its response contract`);
1886
+ }
1887
+ }
1888
+ }
1889
+ __name(assertReadContract, "assertReadContract");
1890
+
1891
+ // src/db/unique.ts
1892
+ function uniqueKeysOf(table) {
1893
+ const columns = table.columns ?? {};
1894
+ const inlinePrimary = [], keys = [];
1895
+ for (const [name, value] of Object.entries(columns)) {
1896
+ const col = value && typeof value === "object" && "_def" in value ? value._def : value;
1897
+ if (col?.primaryKey) inlinePrimary.push(name);
1898
+ if (col?.unique) keys.push([
1899
+ name
1900
+ ]);
1901
+ }
1902
+ const primary = table.primaryKey ?? inlinePrimary;
1903
+ if (primary.length) keys.unshift([
1904
+ ...primary
1905
+ ]);
1906
+ for (const key2 of table.unique ?? []) keys.push([
1907
+ ...key2.columns
1908
+ ]);
1909
+ const seen = /* @__PURE__ */ new Set();
1910
+ return keys.filter((key2) => {
1911
+ if (!key2.length || new Set(key2).size !== key2.length || key2.some((col) => !Object.hasOwn(columns, col))) {
1912
+ throw new Error("Unique lookup requires a nonempty declared key of distinct, known columns");
1913
+ }
1914
+ const identity = JSON.stringify([
1915
+ ...key2
1916
+ ].sort());
1917
+ if (seen.has(identity)) return false;
1918
+ seen.add(identity);
1919
+ return true;
1920
+ });
1921
+ }
1922
+ __name(uniqueKeysOf, "uniqueKeysOf");
1923
+ function assertUniqueLookup(table, keys, where) {
1924
+ if (!where || typeof where !== "object" || Array.isArray(where)) throw new Error(`findUnique(${table}): where must contain one declared unique key`);
1925
+ const columns = Object.keys(where);
1926
+ if (!keys.some((key2) => key2.length === columns.length && key2.every((col) => Object.hasOwn(where, col)))) {
1927
+ throw new Error(`findUnique(${table}): supply exactly one declared unique key (${keys.map((key2) => key2.join(" + ")).join("; ") || "none declared"}); use findMany for other filters`);
1928
+ }
1929
+ for (const [name, value] of Object.entries(where)) {
1930
+ if (value === null || value === void 0 || typeof value === "object" && !(value instanceof Date) || typeof value === "function" || typeof value === "symbol") {
1931
+ throw new Error(`findUnique(${table}): ${name} requires a non-null scalar equality value`);
1932
+ }
1933
+ }
1934
+ }
1935
+ __name(assertUniqueLookup, "assertUniqueLookup");
1936
+
1937
+ // src/engine/commands.ts
1938
+ var installed = /* @__PURE__ */ new WeakSet();
1939
+ var hasCommandExecutor = /* @__PURE__ */ __name((sql) => installed.has(sql), "hasCommandExecutor");
1940
+ var COMMAND_EXECUTOR_DDL = `
1941
+ CREATE SCHEMA IF NOT EXISTS palbase_runtime;
1942
+ CREATE OR REPLACE FUNCTION palbase_runtime.command_v1(steps jsonb) RETURNS jsonb
1943
+ LANGUAGE plpgsql VOLATILE SECURITY INVOKER SET search_path = pg_catalog, public AS $pb_command$
1944
+ DECLARE
1945
+ results jsonb := '[]'::jsonb;
1946
+ step jsonb; chunk jsonb; binding jsonb; source jsonb; rows jsonb;
1947
+ statement text; rendered text; detail text; guard jsonb;
1948
+ index integer := 0; affected integer; passed boolean;
1949
+ BEGIN
1950
+ FOR step IN SELECT value FROM jsonb_array_elements(steps) LOOP
1951
+ IF step->>'empty' = 'true' THEN rows := '[]'::jsonb;
1952
+ ELSE
1953
+ statement := '';
1954
+ FOR chunk IN SELECT value FROM jsonb_array_elements(step->'chunks') LOOP
1955
+ IF jsonb_typeof(chunk) = 'string' THEN statement := statement || (chunk #>> '{}');
1956
+ ELSE
1957
+ binding := step->'bindings'->((chunk->>'bind')::integer);
1958
+ IF binding ? 'ref' THEN
1959
+ source := results->((binding->'ref'->>'op')::integer)->'rows'->0;
1960
+ IF source IS NULL OR NOT source ? (binding->'ref'->>'field') THEN
1961
+ RAISE EXCEPTION 'Command reference has no source row/column; use expectOne before reading it'
1962
+ USING ERRCODE = 'PBC02', DETAIL = jsonb_build_object('error_code','tx_ref_unresolved','op',index)::text;
1963
+ END IF;
1964
+ source := source->(binding->'ref'->>'field');
1965
+ ELSE source := binding->'value'; END IF;
1966
+ rendered := CASE WHEN source IS NULL OR source = 'null'::jsonb THEN NULL
1967
+ WHEN binding->>'json' = 'true' THEN source::text
1968
+ WHEN jsonb_typeof(source) = 'string' THEN source #>> '{}' ELSE source::text END;
1969
+ -- PostgreSQL quotes the value itself. Values reach this function as
1970
+ -- one bound JSON parameter, never as application-generated SQL text.
1971
+ statement := statement || quote_nullable(rendered);
1972
+ END IF;
1973
+ END LOOP;
1974
+ EXECUTE statement INTO rows;
1975
+ END IF;
1976
+ affected := jsonb_array_length(rows);
1977
+ guard := step->'guard';
1978
+ IF guard IS NOT NULL AND guard <> 'null'::jsonb THEN
1979
+ passed := CASE guard->>'kind'
1980
+ WHEN 'one' THEN affected = 1 WHEN 'none' THEN affected = 0
1981
+ WHEN 'atLeast' THEN affected >= (guard->>'n')::integer
1982
+ WHEN 'atMost' THEN affected <= (guard->>'n')::integer ELSE false END;
1983
+ IF NOT passed THEN
1984
+ RAISE EXCEPTION 'Command expectation failed'
1985
+ USING ERRCODE = 'PBC01', DETAIL = jsonb_build_object('error_code','tx_guard_failed','slot',guard->'slot','op',index)::text;
1986
+ END IF;
1987
+ END IF;
1988
+ results := results || jsonb_build_array(jsonb_build_object('rows',rows,'rows_affected',affected));
1989
+ index := index + 1;
1990
+ END LOOP;
1991
+ RETURN jsonb_build_object('results',results);
1992
+ EXCEPTION WHEN SQLSTATE 'PBC01' OR SQLSTATE 'PBC02' THEN
1993
+ GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL;
1994
+ RETURN jsonb_build_object('error',detail::jsonb);
1995
+ END
1996
+ $pb_command$;
1997
+ REVOKE ALL ON FUNCTION palbase_runtime.command_v1(jsonb) FROM PUBLIC;
1998
+ CREATE OR REPLACE FUNCTION palbase_runtime.command_rows_v1(rows jsonb, guard jsonb) RETURNS jsonb
1999
+ LANGUAGE plpgsql VOLATILE SECURITY INVOKER SET search_path = pg_catalog, public AS $pb_rows$
2000
+ DECLARE affected integer := jsonb_array_length(rows); passed boolean;
2001
+ BEGIN
2002
+ IF guard IS NOT NULL AND guard <> 'null'::jsonb THEN
2003
+ passed := CASE guard->>'kind'
2004
+ WHEN 'one' THEN affected = 1 WHEN 'none' THEN affected = 0
2005
+ WHEN 'atLeast' THEN affected >= (guard->>'n')::integer
2006
+ WHEN 'atMost' THEN affected <= (guard->>'n')::integer ELSE false END;
2007
+ IF NOT passed THEN
2008
+ RAISE EXCEPTION 'Command expectation failed'
2009
+ USING ERRCODE = 'PBC01', DETAIL = jsonb_build_object('error_code','tx_guard_failed','slot',guard->'slot')::text;
2010
+ END IF;
2011
+ END IF;
2012
+ RETURN rows;
2013
+ END
2014
+ $pb_rows$;
2015
+ REVOKE ALL ON FUNCTION palbase_runtime.command_rows_v1(jsonb,jsonb) FROM PUBLIC;
2016
+ `;
2017
+ async function installCommandExecutor(sql, roles) {
2018
+ const quote = /* @__PURE__ */ __name((s) => `"${s.replace(/"/g, '""')}"`, "quote");
2019
+ if (roles.length === 0) throw new Error("Command executor needs explicit runtime roles");
2020
+ await sql.begin(async (tx) => {
2021
+ await tx.unsafe("SELECT pg_advisory_xact_lock(196754721, 1)");
2022
+ await tx.unsafe(COMMAND_EXECUTOR_DDL);
2023
+ const list = [
2024
+ ...new Set(roles)
2025
+ ].map(quote).join(", ");
2026
+ await tx.unsafe(`GRANT USAGE ON SCHEMA palbase_runtime TO ${list}`);
2027
+ await tx.unsafe(`GRANT EXECUTE ON FUNCTION palbase_runtime.command_v1(jsonb) TO ${list}`);
2028
+ await tx.unsafe(`GRANT EXECUTE ON FUNCTION palbase_runtime.command_rows_v1(jsonb,jsonb) TO ${list}`);
2029
+ });
2030
+ installed.add(sql);
2031
+ }
2032
+ __name(installCommandExecutor, "installCommandExecutor");
2033
+ function parameterChunks(sql) {
2034
+ const chunks = [];
2035
+ let start = 0, quote = null;
2036
+ for (let i = 0; i < sql.length; i++) {
2037
+ const c = sql[i];
2038
+ if (quote) {
2039
+ if (c === quote) {
2040
+ if (sql[i + 1] === quote) i++;
2041
+ else quote = null;
2042
+ }
2043
+ continue;
2044
+ }
2045
+ if (c === "'" || c === '"') {
2046
+ quote = c;
2047
+ continue;
2048
+ }
2049
+ if (c !== "$") continue;
2050
+ const parameter = /^\$(\d+)\b/.exec(sql.slice(i));
2051
+ if (!parameter) continue;
2052
+ chunks.push(sql.slice(start, i), {
2053
+ bind: Number(parameter[1]) - 1
2054
+ });
2055
+ i += parameter[0].length - 1;
2056
+ start = i + 1;
2057
+ }
2058
+ chunks.push(sql.slice(start));
2059
+ return chunks;
2060
+ }
2061
+ __name(parameterChunks, "parameterChunks");
2062
+
2063
+ // src/engine/admission.ts
2064
+ var WorkloadAdmission = class {
2065
+ static {
2066
+ __name(this, "WorkloadAdmission");
2067
+ }
2068
+ definition;
2069
+ pending = [];
2070
+ active = 0;
2071
+ activeKeys = /* @__PURE__ */ new Set();
2072
+ tenants = /* @__PURE__ */ new Map();
2073
+ draining = false;
2074
+ constructor(definition) {
2075
+ this.definition = definition;
2076
+ validateWorkload(definition);
2077
+ }
2078
+ get snapshot() {
2079
+ return {
2080
+ active: this.active,
2081
+ queued: this.pending.length,
2082
+ tenants: this.tenants.size,
2083
+ keys: this.activeKeys.size
2084
+ };
2085
+ }
2086
+ acquire(tenant, keys, signal) {
2087
+ if (signal?.aborted) return Promise.reject(signal.reason ?? new Error("Database admission cancelled"));
2088
+ const normalized = [
2089
+ ...new Set(keys)
2090
+ ].sort();
2091
+ 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"));
2092
+ const scoped = normalized;
2093
+ const globalFull = this.pending.length >= this.definition.queueLimit;
2094
+ const tenantFull = this.pending.filter((w) => w.tenant === tenant).length >= this.definition.tenantQueueLimit;
2095
+ 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)));
2096
+ if (!canStart && (globalFull || tenantFull)) {
2097
+ return Promise.reject(new TooManyRequests({
2098
+ retryAfter: 1
2099
+ }, `Database workload ${this.definition.name}: ${tenantFull ? "tenant" : "global"} queue is full`));
2100
+ }
2101
+ return new Promise((grant, reject) => {
2102
+ const waiter = {
2103
+ tenant,
2104
+ keys: scoped,
2105
+ entered: performance.now(),
2106
+ grant,
2107
+ reject,
2108
+ cleanup: /* @__PURE__ */ __name(() => {
2109
+ }, "cleanup")
2110
+ };
2111
+ const remove = /* @__PURE__ */ __name((reason2) => {
2112
+ const index = this.pending.indexOf(waiter);
2113
+ if (index === -1) return;
2114
+ this.pending.splice(index, 1);
2115
+ waiter.cleanup();
2116
+ reject(reason2);
2117
+ this.drain();
2118
+ }, "remove");
2119
+ const abort = /* @__PURE__ */ __name(() => remove(signal?.reason ?? new Error("Database admission cancelled")), "abort");
2120
+ const timer = setTimeout(() => remove(new TooManyRequests({
2121
+ retryAfter: 1
2122
+ }, `Database workload ${this.definition.name}: queue deadline exceeded`)), this.definition.queueTimeoutMs);
2123
+ waiter.cleanup = () => {
2124
+ clearTimeout(timer);
2125
+ signal?.removeEventListener("abort", abort);
2126
+ };
2127
+ signal?.addEventListener("abort", abort, {
2128
+ once: true
2129
+ });
2130
+ this.pending.push(waiter);
2131
+ if (signal?.aborted) abort();
2132
+ else this.drain();
2133
+ });
2134
+ }
2135
+ drain() {
2136
+ if (this.draining) return;
2137
+ this.draining = true;
2138
+ try {
2139
+ const blockedKeys = /* @__PURE__ */ new Set();
2140
+ for (let index = 0; index < this.pending.length && this.active < this.definition.concurrency; ) {
2141
+ const waiter = this.pending[index];
2142
+ if ((this.tenants.get(waiter.tenant) ?? 0) >= this.definition.tenantConcurrency || waiter.keys.some((key2) => this.activeKeys.has(key2) || blockedKeys.has(key2))) {
2143
+ for (const key2 of waiter.keys) blockedKeys.add(key2);
2144
+ index++;
2145
+ continue;
1499
2146
  }
1500
- })();
1501
- inflight.set(key, fill);
1502
- return fill;
2147
+ this.pending.splice(index, 1);
2148
+ waiter.cleanup();
2149
+ this.active++;
2150
+ this.tenants.set(waiter.tenant, (this.tenants.get(waiter.tenant) ?? 0) + 1);
2151
+ for (const key2 of waiter.keys) this.activeKeys.add(key2);
2152
+ let released = false;
2153
+ waiter.grant({
2154
+ queueMs: performance.now() - waiter.entered,
2155
+ release: /* @__PURE__ */ __name(() => {
2156
+ if (released) return;
2157
+ released = true;
2158
+ this.active--;
2159
+ const left = this.tenants.get(waiter.tenant) - 1;
2160
+ if (left === 0) this.tenants.delete(waiter.tenant);
2161
+ else this.tenants.set(waiter.tenant, left);
2162
+ for (const key2 of waiter.keys) this.activeKeys.delete(key2);
2163
+ this.drain();
2164
+ }, "release")
2165
+ });
2166
+ }
2167
+ } finally {
2168
+ this.draining = false;
1503
2169
  }
1504
- };
2170
+ }
2171
+ };
2172
+ var registries = /* @__PURE__ */ new WeakMap();
2173
+ function workloadAdmission(owner, definition) {
2174
+ validateWorkload(definition);
2175
+ let registry2 = registries.get(owner);
2176
+ if (!registry2) {
2177
+ registry2 = /* @__PURE__ */ new Map();
2178
+ registries.set(owner, registry2);
2179
+ }
2180
+ const existing = registry2.get(definition.name);
2181
+ if (existing) {
2182
+ const d = existing.definition;
2183
+ if (Object.keys(d).some((k) => d[k] !== definition[k])) throw new Error(`Workload ${definition.name} was already registered with different limits`);
2184
+ return existing;
2185
+ }
2186
+ if (registry2.size >= 64) throw new Error("At most 64 named database workloads may be registered per engine; declare and reuse them at module scope");
2187
+ const admission = new WorkloadAdmission(Object.freeze({
2188
+ ...definition
2189
+ }));
2190
+ registry2.set(definition.name, admission);
2191
+ return admission;
1505
2192
  }
1506
- __name(makeMemoryCache, "makeMemoryCache");
2193
+ __name(workloadAdmission, "workloadAdmission");
2194
+ function workloadTenant(definition, claimsJson) {
2195
+ const claims = JSON.parse(claimsJson);
2196
+ const value = claims[definition.tenantClaim] ?? claims.sub;
2197
+ return typeof value === "string" && value.length > 0 ? value : "__anonymous__";
2198
+ }
2199
+ __name(workloadTenant, "workloadTenant");
2200
+
2201
+ // src/engine/transaction-control.ts
2202
+ var TransactionControl = class {
2203
+ static {
2204
+ __name(this, "TransactionControl");
2205
+ }
2206
+ controller = new AbortController();
2207
+ active = /* @__PURE__ */ new Set();
2208
+ cancelBackend;
2209
+ cancellation = Promise.resolve();
2210
+ timer;
2211
+ unlink;
2212
+ rejection;
2213
+ deadline;
2214
+ get signal() {
2215
+ return this.controller.signal;
2216
+ }
2217
+ constructor(timeoutMs, external) {
2218
+ this.deadline = timeoutMs === void 0 ? void 0 : performance.now() + timeoutMs;
2219
+ this.rejection = new Promise((_, reject) => {
2220
+ this.signal.addEventListener("abort", () => {
2221
+ reject(this.signal.reason);
2222
+ if (this.active.size && this.cancelBackend) {
2223
+ this.cancellation = this.cancelBackend().catch(() => void 0);
2224
+ } else {
2225
+ for (const query of this.active) {
2226
+ try {
2227
+ query.cancel?.();
2228
+ } catch {
2229
+ }
2230
+ }
2231
+ }
2232
+ }, {
2233
+ once: true
2234
+ });
2235
+ });
2236
+ void this.rejection.catch(() => void 0);
2237
+ const abort = /* @__PURE__ */ __name(() => this.controller.abort(external?.reason ?? new DOMException("Database transaction cancelled", "AbortError")), "abort");
2238
+ external?.addEventListener("abort", abort, {
2239
+ once: true
2240
+ });
2241
+ this.unlink = () => external?.removeEventListener("abort", abort);
2242
+ if (external?.aborted) abort();
2243
+ if (timeoutMs !== void 0) this.timer = setTimeout(() => this.controller.abort(new HttpError(503, "database_deadline_exceeded", "Database transaction exceeded its deadline")), timeoutMs);
2244
+ }
2245
+ check() {
2246
+ if (this.deadline !== void 0 && performance.now() >= this.deadline && !this.signal.aborted) {
2247
+ this.controller.abort(new HttpError(503, "database_deadline_exceeded", "Database transaction exceeded its deadline"));
2248
+ }
2249
+ this.signal.throwIfAborted();
2250
+ }
2251
+ remainingMs() {
2252
+ this.check();
2253
+ return this.deadline === void 0 ? void 0 : Math.max(1, Math.ceil(this.deadline - performance.now()));
2254
+ }
2255
+ race(work) {
2256
+ return Promise.race([
2257
+ work,
2258
+ this.rejection
2259
+ ]);
2260
+ }
2261
+ bindBackend(cancel) {
2262
+ this.cancelBackend = cancel;
2263
+ }
2264
+ /** Keep the application's connection parked until the cancellation request
2265
+ * and all statements settle; a late cancel must not hit its next borrower. */
2266
+ async drainCancellation() {
2267
+ await this.cancellation;
2268
+ await Promise.allSettled([
2269
+ ...this.active
2270
+ ]);
2271
+ }
2272
+ async watch(query) {
2273
+ this.active.add(query);
2274
+ try {
2275
+ if (this.signal.aborted) query.cancel?.();
2276
+ return await query;
2277
+ } finally {
2278
+ this.active.delete(query);
2279
+ }
2280
+ }
2281
+ dispose() {
2282
+ if (this.timer !== void 0) clearTimeout(this.timer);
2283
+ this.unlink();
2284
+ }
2285
+ };
2286
+
2287
+ // src/engine/diagnostics.ts
2288
+ var import_node_crypto2 = require("crypto");
2289
+ var DatabaseTrace = class {
2290
+ static {
2291
+ __name(this, "DatabaseTrace");
2292
+ }
2293
+ options;
2294
+ start = performance.now();
2295
+ ended;
2296
+ failure;
2297
+ state;
2298
+ constructor(options) {
2299
+ this.options = options;
2300
+ for (const key2 of [
2301
+ "maxQueries",
2302
+ "maxRows",
2303
+ "maxBytes"
2304
+ ]) {
2305
+ const value = options[key2];
2306
+ if (value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) throw new Error(`database ${key2} must be a nonnegative safe integer`);
2307
+ }
2308
+ this.state = {
2309
+ endpoint: options.endpoint,
2310
+ requestId: options.requestId,
2311
+ outcome: "active",
2312
+ elapsedMs: 0,
2313
+ queries: 0,
2314
+ rows: 0,
2315
+ bytes: 0,
2316
+ queueMs: 0,
2317
+ poolMs: 0,
2318
+ setupMs: 0,
2319
+ sqlMs: 0,
2320
+ commitMs: 0,
2321
+ rollbackMs: 0,
2322
+ commits: 0,
2323
+ commitFailures: 0,
2324
+ rollbackRequests: 0,
2325
+ retries: 0,
2326
+ statements: []
2327
+ };
2328
+ }
2329
+ check() {
2330
+ if (this.failure) throw this.failure;
2331
+ }
2332
+ time(key2, started) {
2333
+ this.state[key2] += performance.now() - started;
2334
+ }
2335
+ retry() {
2336
+ this.state.retries++;
2337
+ }
2338
+ committed() {
2339
+ this.state.commits++;
2340
+ }
2341
+ commitFailed() {
2342
+ this.state.commitFailures++;
2343
+ }
2344
+ failed() {
2345
+ this.state.outcome = "failed";
2346
+ }
2347
+ rolledBack() {
2348
+ this.state.rollbackRequests++;
2349
+ }
2350
+ refuse(kind) {
2351
+ this.failure ??= new HttpError(500, "database_budget_exceeded", `Database ${kind} budget exceeded${this.options.endpoint ? ` in ${this.options.endpoint}` : ""}`);
2352
+ throw this.failure;
2353
+ }
2354
+ emit(callback, event) {
2355
+ try {
2356
+ const result = callback?.(event);
2357
+ if (result) void Promise.resolve(result).catch(() => void 0);
2358
+ } catch {
2359
+ }
2360
+ }
2361
+ async query(sql, name, run) {
2362
+ this.check();
2363
+ if (this.options.maxQueries !== void 0 && this.state.queries >= this.options.maxQueries) this.refuse("query count");
2364
+ const ordinal = ++this.state.queries;
2365
+ const event = {
2366
+ ordinal,
2367
+ fingerprint: (0, import_node_crypto2.createHash)("sha256").update(sql).digest("hex").slice(0, 24),
2368
+ ...name === void 0 ? {} : {
2369
+ name: name.slice(0, 128)
2370
+ },
2371
+ ...this.options.includeSql ? {
2372
+ sql: sql.slice(0, 4096)
2373
+ } : {},
2374
+ durationMs: 0,
2375
+ rows: 0,
2376
+ bytes: 0
2377
+ };
2378
+ if (this.options.captureSource) event.source = new Error().stack?.split("\n").slice(1, 9).join("\n").slice(0, 2500);
2379
+ const started = performance.now();
2380
+ let measured = false;
2381
+ try {
2382
+ const result = await run();
2383
+ event.durationMs = performance.now() - started;
2384
+ measured = true;
2385
+ if (Array.isArray(result)) {
2386
+ event.rows = result.length;
2387
+ event.bytes = Buffer.byteLength(JSON.stringify(result, (_, value) => typeof value === "bigint" ? value.toString() : value));
2388
+ }
2389
+ this.state.rows += event.rows;
2390
+ this.state.bytes += event.bytes;
2391
+ if (this.options.maxRows !== void 0 && this.state.rows > this.options.maxRows) this.refuse("returned rows");
2392
+ if (this.options.maxBytes !== void 0 && this.state.bytes > this.options.maxBytes) this.refuse("result bytes");
2393
+ return result;
2394
+ } catch (error) {
2395
+ const e = error;
2396
+ event.errorCode = String(e?.sqlstate ?? e?.errno ?? e?.code ?? "query_failed").slice(0, 80);
2397
+ throw error;
2398
+ } finally {
2399
+ if (!measured) event.durationMs = performance.now() - started;
2400
+ this.state.sqlMs += event.durationMs;
2401
+ const frozen = Object.freeze(event);
2402
+ if (this.state.statements.length < 200) this.state.statements.push(frozen);
2403
+ this.emit(this.options.onQuery, frozen);
2404
+ }
2405
+ }
2406
+ get snapshot() {
2407
+ return Object.freeze({
2408
+ ...this.state,
2409
+ elapsedMs: (this.ended ?? performance.now()) - this.start,
2410
+ statements: Object.freeze([
2411
+ ...this.state.statements
2412
+ ].sort((a, b) => a.ordinal - b.ordinal))
2413
+ });
2414
+ }
2415
+ complete(outcome) {
2416
+ if (this.ended !== void 0) return;
2417
+ this.ended = performance.now();
2418
+ this.state.outcome = outcome;
2419
+ this.emit(this.options.onComplete, this.snapshot);
2420
+ }
2421
+ };
1507
2422
 
1508
2423
  // src/db/columns.ts
1509
2424
  var CODECS = {
@@ -1562,9 +2477,9 @@ function buildRelations(schemas) {
1562
2477
  const taken = /* @__PURE__ */ new Map();
1563
2478
  for (const schema of schemas) {
1564
2479
  for (const t of Object.values(schema.tables)) {
1565
- const key = qualifiedTableKey(schema.name, t.name);
1566
- out.set(key, []);
1567
- taken.set(key, /* @__PURE__ */ new Map());
2480
+ const key2 = qualifiedTableKey(schema.name, t.name);
2481
+ out.set(key2, []);
2482
+ taken.set(key2, /* @__PURE__ */ new Map());
1568
2483
  }
1569
2484
  }
1570
2485
  const claim = /* @__PURE__ */ __name((tableKey, name, origin, edge) => {
@@ -1628,10 +2543,10 @@ function quoteIdent(name) {
1628
2543
  return `"${name.replace(/"/g, '""')}"`;
1629
2544
  }
1630
2545
  __name(quoteIdent, "quoteIdent");
1631
- function quoteTable(key) {
1632
- const dot = key.indexOf(".");
1633
- if (dot === -1) return quoteIdent(key);
1634
- return `${quoteIdent(key.slice(0, dot))}.${quoteIdent(key.slice(dot + 1))}`;
2546
+ function quoteTable(key2) {
2547
+ const dot = key2.indexOf(".");
2548
+ if (dot === -1) return quoteIdent(key2);
2549
+ return `${quoteIdent(key2.slice(0, dot))}.${quoteIdent(key2.slice(dot + 1))}`;
1635
2550
  }
1636
2551
  __name(quoteTable, "quoteTable");
1637
2552
  function encodePgArray(value) {
@@ -1644,9 +2559,10 @@ function encodePgArray(value) {
1644
2559
  }
1645
2560
  __name(encodePgArray, "encodePgArray");
1646
2561
  var BIND_SQL = "select set_config('role',$1,true), set_config('search_path','public',true), set_config('request.jwt.claims',$2,true)";
2562
+ var atomicScope = new import_node_async_hooks2.AsyncLocalStorage();
1647
2563
  function createLazyTransaction(sql, role, claimsJson, options = {}) {
1648
2564
  const { lockTimeout } = options;
1649
- const bindSql = lockTimeout ? `${BIND_SQL}, set_config('lock_timeout',$3,true)` : BIND_SQL;
2565
+ let bindSql = lockTimeout ? `${BIND_SQL}, set_config('lock_timeout',$3,true)` : BIND_SQL;
1650
2566
  const bindParams = lockTimeout ? [
1651
2567
  role,
1652
2568
  claimsJson,
@@ -1655,25 +2571,118 @@ function createLazyTransaction(sql, role, claimsJson, options = {}) {
1655
2571
  role,
1656
2572
  claimsJson
1657
2573
  ];
2574
+ if (options.statementTimeout !== void 0) {
2575
+ bindParams.push(options.statementTimeout);
2576
+ bindSql += `, set_config('statement_timeout',$${bindParams.length},true)`;
2577
+ }
1658
2578
  let opening = null;
1659
2579
  let release = null;
1660
2580
  let fail = null;
1661
2581
  let settled = null;
1662
- const ensure = /* @__PURE__ */ __name(() => {
2582
+ let closed = false;
2583
+ const assertAccess = /* @__PURE__ */ __name(() => {
2584
+ if (closed) throw new Error("Database transaction has settled; its handle cannot be used again");
2585
+ options.control?.check();
2586
+ options.trace?.check();
2587
+ if (atomicScope.getStore() !== options.scope) {
2588
+ throw new Error("Database.$atomic cannot use a handle from another transaction or identity; use its tx argument");
2589
+ }
2590
+ }, "assertAccess");
2591
+ let savepointALS;
2592
+ let activeSavepoint;
2593
+ const pendingSavepoints = /* @__PURE__ */ new Set();
2594
+ const scoped = /* @__PURE__ */ __name((tx, checkParent = assertAccess, owner, children = /* @__PURE__ */ new Set()) => {
2595
+ const access = /* @__PURE__ */ __name(() => {
2596
+ checkParent();
2597
+ if (activeSavepoint !== owner || savepointALS?.getStore() !== owner) {
2598
+ throw new Error("Database connection belongs to another active savepoint; use its tx argument and await it before using the parent handle");
2599
+ }
2600
+ }, "access");
2601
+ return {
2602
+ async unsafe(text, params, metadata) {
2603
+ access();
2604
+ const run = /* @__PURE__ */ __name(() => {
2605
+ const query = tx.unsafe(text, params);
2606
+ return options.control ? options.control.watch(query) : query;
2607
+ }, "run");
2608
+ return options.trace ? options.trace.query(text, metadata?.name, run) : run();
2609
+ },
2610
+ async savepoint(fn) {
2611
+ access();
2612
+ const token = /* @__PURE__ */ Symbol("savepoint"), descendants = /* @__PURE__ */ new Set();
2613
+ savepointALS ??= new import_node_async_hooks2.AsyncLocalStorage();
2614
+ activeSavepoint = token;
2615
+ const work = (async () => {
2616
+ try {
2617
+ return await tx.savepoint(async (sp) => {
2618
+ let ended = false;
2619
+ const check = /* @__PURE__ */ __name(() => {
2620
+ checkParent();
2621
+ if (ended) throw new Error("Database savepoint has settled; its handle cannot be used again");
2622
+ }, "check");
2623
+ try {
2624
+ const callback = savepointALS.run(token, () => fn(scoped(sp, check, token, descendants)));
2625
+ return await (options.control ? options.control.race(callback) : callback);
2626
+ } finally {
2627
+ if (descendants.size) await Promise.allSettled([
2628
+ ...descendants
2629
+ ]);
2630
+ ended = true;
2631
+ }
2632
+ });
2633
+ } finally {
2634
+ activeSavepoint = owner;
2635
+ }
2636
+ })();
2637
+ pendingSavepoints.add(work);
2638
+ children.add(work);
2639
+ try {
2640
+ return await work;
2641
+ } finally {
2642
+ pendingSavepoints.delete(work);
2643
+ children.delete(work);
2644
+ }
2645
+ }
2646
+ };
2647
+ }, "scoped");
2648
+ const ensure = /* @__PURE__ */ __name(async () => {
2649
+ assertAccess();
1663
2650
  if (opening) return opening;
1664
2651
  opening = new Promise((resolveTx2, rejectTx) => {
1665
2652
  const parked = new Promise((res, rej) => {
1666
2653
  release = res;
1667
2654
  fail = rej;
1668
2655
  });
2656
+ void parked.catch(() => void 0);
2657
+ const openingAt = options.trace ? performance.now() : 0;
1669
2658
  settled = sql.begin(async (tx) => {
1670
- await tx.unsafe(bindSql, bindParams);
1671
- resolveTx2(tx);
2659
+ options.trace?.time("poolMs", openingAt);
2660
+ const setupAt = options.trace ? performance.now() : 0;
2661
+ options.control?.check();
2662
+ if (options.isolation !== void 0 || options.readOnly !== void 0) {
2663
+ const isolation = options.isolation ?? "read committed";
2664
+ validateAtomicOptions({
2665
+ isolation,
2666
+ readOnly: options.readOnly
2667
+ });
2668
+ await tx.unsafe(`SET TRANSACTION ISOLATION LEVEL ${isolation.toUpperCase()}${options.readOnly === void 0 ? "" : options.readOnly ? " READ ONLY" : " READ WRITE"}`);
2669
+ }
2670
+ if (options.control && sql.cancelBackend) {
2671
+ 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()");
2672
+ if (!Number.isInteger(ticket[0]?.pid) || typeof ticket[0]?.started !== "string") throw new Error("Database cancellation cannot identify its own backend");
2673
+ options.control.bindBackend(() => sql.cancelBackend(ticket[0].pid, ticket[0].started));
2674
+ options.control.check();
2675
+ }
2676
+ const binding = tx.unsafe(bindSql, bindParams);
2677
+ await (options.control ? options.control.watch(binding) : binding);
2678
+ options.trace?.time("setupMs", setupAt);
2679
+ resolveTx2(scoped(tx));
1672
2680
  await parked;
1673
2681
  }).catch((e) => {
1674
2682
  rejectTx(e);
1675
2683
  throw e;
1676
2684
  });
2685
+ void settled.catch(() => void 0);
1677
2686
  });
1678
2687
  return opening;
1679
2688
  }, "ensure");
@@ -1683,14 +2692,37 @@ function createLazyTransaction(sql, role, claimsJson, options = {}) {
1683
2692
  return opening !== null;
1684
2693
  },
1685
2694
  async commit() {
2695
+ if (pendingSavepoints.size) await Promise.allSettled([
2696
+ ...pendingSavepoints
2697
+ ]);
2698
+ options.control?.check();
2699
+ options.trace?.check();
2700
+ closed = true;
1686
2701
  if (!opening) return;
2702
+ const started = options.trace ? performance.now() : 0;
1687
2703
  release();
1688
- await settled;
2704
+ try {
2705
+ await settled;
2706
+ options.trace?.committed();
2707
+ } catch (error) {
2708
+ options.trace?.commitFailed();
2709
+ throw error;
2710
+ } finally {
2711
+ options.trace?.time("commitMs", started);
2712
+ }
1689
2713
  },
1690
2714
  async rollback(reason2) {
2715
+ closed = true;
1691
2716
  if (!opening) return;
2717
+ await options.control?.drainCancellation();
2718
+ if (pendingSavepoints.size) await Promise.allSettled([
2719
+ ...pendingSavepoints
2720
+ ]);
2721
+ const started = options.trace ? performance.now() : 0;
1692
2722
  fail(reason2);
1693
2723
  await settled?.catch(() => void 0);
2724
+ options.trace?.time("rollbackMs", started);
2725
+ options.trace?.rolledBack();
1694
2726
  }
1695
2727
  };
1696
2728
  }
@@ -1705,7 +2737,7 @@ __name(asWireValue, "asWireValue");
1705
2737
  function asWireRow(row) {
1706
2738
  if (row === null || typeof row !== "object") return row;
1707
2739
  const out = {};
1708
- for (const [key, value] of Object.entries(row)) out[key] = asWireValue(value);
2740
+ for (const [key2, value] of Object.entries(row)) out[key2] = asWireValue(value);
1709
2741
  return out;
1710
2742
  }
1711
2743
  __name(asWireRow, "asWireRow");
@@ -1717,7 +2749,7 @@ function toVectorLiteral(v) {
1717
2749
  return `[${v.join(",")}]`;
1718
2750
  }
1719
2751
  __name(toVectorLiteral, "toVectorLiteral");
1720
- function vectorColumnsOf(schema, table) {
2752
+ function computeVectorColumns(schema, table) {
1721
2753
  const out = /* @__PURE__ */ new Set();
1722
2754
  const def = tableDefOf(table, schema);
1723
2755
  if (def) {
@@ -1728,9 +2760,9 @@ function vectorColumnsOf(schema, table) {
1728
2760
  }
1729
2761
  return out;
1730
2762
  }
1731
- __name(vectorColumnsOf, "vectorColumnsOf");
1732
- function tableDefOf(key, schema = currentSchema) {
1733
- return schema.tables?.[key] ?? null;
2763
+ __name(computeVectorColumns, "computeVectorColumns");
2764
+ function tableDefOf(key2, schema = currentSchema) {
2765
+ return schema.tables?.[key2] ?? null;
1734
2766
  }
1735
2767
  __name(tableDefOf, "tableDefOf");
1736
2768
  function reviveVectors(row, vectorCols) {
@@ -1743,7 +2775,7 @@ function reviveVectors(row, vectorCols) {
1743
2775
  return row;
1744
2776
  }
1745
2777
  __name(reviveVectors, "reviveVectors");
1746
- function transformsOf(schema, table) {
2778
+ function computeTransforms(schema, table) {
1747
2779
  const out = /* @__PURE__ */ new Map();
1748
2780
  const def = tableDefOf(table, schema);
1749
2781
  if (def) {
@@ -1766,7 +2798,7 @@ function transformsOf(schema, table) {
1766
2798
  }
1767
2799
  return out;
1768
2800
  }
1769
- __name(transformsOf, "transformsOf");
2801
+ __name(computeTransforms, "computeTransforms");
1770
2802
  function applyFromDb(row, transforms) {
1771
2803
  if (transforms.size === 0) return row;
1772
2804
  for (const [col, t] of transforms) {
@@ -1783,10 +2815,23 @@ function asTableRow(table, row) {
1783
2815
  return applyFromDb(revived, transformsOf(currentSchema, table));
1784
2816
  }
1785
2817
  __name(asTableRow, "asTableRow");
1786
- function asTableRows(table, rows) {
2818
+ function asTableRows(table, rows, withSpec) {
1787
2819
  const vectorCols = vectorColumnsOf(currentSchema, table);
1788
2820
  const transforms = transformsOf(currentSchema, table);
1789
- return rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
2821
+ const result = rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
2822
+ if (withSpec) for (const rel of currentRelations.get(table) ?? []) {
2823
+ const spec = withSpec[rel.name];
2824
+ if (!spec) continue;
2825
+ const nested = typeof spec === "object" ? spec.with : void 0;
2826
+ for (const row of result) {
2827
+ const value = row[rel.name];
2828
+ if (Array.isArray(value)) row[rel.name] = asTableRows(rel.to, value, nested);
2829
+ else if (value && typeof value === "object") row[rel.name] = asTableRows(rel.to, [
2830
+ value
2831
+ ], nested)[0];
2832
+ }
2833
+ }
2834
+ return result;
1790
2835
  }
1791
2836
  __name(asTableRows, "asTableRows");
1792
2837
  function asBindParams(table, cols, data, caller) {
@@ -2029,7 +3074,7 @@ function excludeSql(pk, ids, add) {
2029
3074
  return ` AND t.${quoteIdent(pk)}::text <> ALL(ARRAY[${ids.map((x) => add(x)).join(", ")}])`;
2030
3075
  }
2031
3076
  __name(excludeSql, "excludeSql");
2032
- function primaryKeyOf(table) {
3077
+ function computePrimaryKey(table) {
2033
3078
  const t = tableDefOf(table);
2034
3079
  const columns = t?.columns ?? {};
2035
3080
  const cols = Object.keys(columns);
@@ -2049,7 +3094,7 @@ function primaryKeyOf(table) {
2049
3094
  const pks = cols.filter((c) => pkOf(columns[c]));
2050
3095
  return pks.length === 1 ? pks[0] : null;
2051
3096
  }
2052
- __name(primaryKeyOf, "primaryKeyOf");
3097
+ __name(computePrimaryKey, "computePrimaryKey");
2053
3098
  function isSinglePkEquality(where, pk) {
2054
3099
  if (pk === null) return false;
2055
3100
  const keys = Object.keys(where);
@@ -2064,12 +3109,43 @@ function assertNotAppendOnly(caller, table) {
2064
3109
  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).`);
2065
3110
  }
2066
3111
  __name(assertNotAppendOnly, "assertNotAppendOnly");
2067
- function schemaColumns(table) {
3112
+ function computeSchemaColumns(table) {
2068
3113
  const t = tableDefOf(table);
2069
3114
  const cols = t?.columns ? Object.keys(t.columns) : [];
2070
3115
  return cols.length > 0 ? new Set(cols) : null;
2071
3116
  }
2072
- __name(schemaColumns, "schemaColumns");
3117
+ __name(computeSchemaColumns, "computeSchemaColumns");
3118
+ function timestampResult(expr) {
3119
+ 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`;
3120
+ }
3121
+ __name(timestampResult, "timestampResult");
3122
+ function columnResult(table, column, alias) {
3123
+ const expr = `${alias ? `${quoteIdent(alias)}.` : ""}${quoteIdent(column)}`;
3124
+ return columnTypeOf(table, column) === "timestamp" ? timestampResult(expr) : expr;
3125
+ }
3126
+ __name(columnResult, "columnResult");
3127
+ function projectedColumn(table, column, alias) {
3128
+ const expr = columnResult(table, column, alias);
3129
+ return columnTypeOf(table, column) === "timestamp" ? `${expr} AS ${quoteIdent(column)}` : expr;
3130
+ }
3131
+ __name(projectedColumn, "projectedColumn");
3132
+ function rowProjection(table, alias, wildcard = alias ? `${alias}.*` : "*") {
3133
+ const cols = schemaColumns(table);
3134
+ if (!cols || !memoTable(timestampMemo, currentSchema, table, () => [
3135
+ ...cols
3136
+ ].some((c) => columnTypeOf(table, c) === "timestamp"))) return wildcard;
3137
+ const projections = memoTable(projectionMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
3138
+ const key2 = alias ?? "";
3139
+ let sql = projections.get(key2);
3140
+ if (sql === void 0) {
3141
+ sql = [
3142
+ ...cols
3143
+ ].map((c) => projectedColumn(table, c, alias)).join(", ");
3144
+ projections.set(key2, sql);
3145
+ }
3146
+ return sql;
3147
+ }
3148
+ __name(rowProjection, "rowProjection");
2073
3149
  function colRefSql(table, known, ref, caller, alias = "t") {
2074
3150
  const name = ref.$col;
2075
3151
  const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
@@ -2083,13 +3159,13 @@ function selectClause(table, known, select) {
2083
3159
  if (select !== void 0 && select.length === 0) {
2084
3160
  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.`);
2085
3161
  }
2086
- if (select === void 0) return "*";
3162
+ if (select === void 0) return rowProjection(table, "t", "*");
2087
3163
  return select.map((col) => {
2088
3164
  const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(col);
2089
3165
  if (known !== null ? !known.has(col) : !shapeOK) {
2090
3166
  throw new Error(`findMany(${table}): select kolonu "${col}" tabloda yok`);
2091
3167
  }
2092
- return `t.${quoteIdent(col)}`;
3168
+ return columnTypeOf(table, col) === "timestamp" ? projectedColumn(table, col, "t") : `t.${quoteIdent(col)}`;
2093
3169
  }).join(", ");
2094
3170
  }
2095
3171
  __name(selectClause, "selectClause");
@@ -2159,7 +3235,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
2159
3235
  const transforms = transformsOf(currentSchema, table);
2160
3236
  const bindFor = /* @__PURE__ */ __name((col) => {
2161
3237
  const t = transforms.get(col);
2162
- return t?.toDb === void 0 ? add : (v) => add(v === null || v === void 0 ? v : t.toDb(v));
3238
+ return (v) => add(t?.toDb === void 0 || v === null || v === void 0 ? v : t.toDb(v), col, table);
2163
3239
  }, "bindFor");
2164
3240
  assertUsableFilter(caller, table, where);
2165
3241
  if (isSqlFragment(where)) {
@@ -2190,8 +3266,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
2190
3266
  const known = rels.map((r) => r.name).sort();
2191
3267
  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.`));
2192
3268
  }
2193
- const ourCol = rel.kind === "many" ? primaryKeyOf(table) : rel.via;
2194
- const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
3269
+ const [ourCol, theirCol] = relationColumns(table, rel);
2195
3270
  if (ourCol === null || theirCol === null) {
2196
3271
  const missing = ourCol === null ? table : rel.to;
2197
3272
  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.`);
@@ -2329,8 +3404,8 @@ async function embedQuery(embed, text) {
2329
3404
  if (secretReader === null) {
2330
3405
  throw new Error(`query embed: secret reader ba\u011Flanmam\u0131\u015F \u2014 ${embed.apiKeyName} okunam\u0131yor`);
2331
3406
  }
2332
- const key = await secretReader(embed.apiKeyName);
2333
- if (key === null || key === "") {
3407
+ const key2 = await secretReader(embed.apiKeyName);
3408
+ if (key2 === null || key2 === "") {
2334
3409
  throw new Error(`query embed: vault'ta ${embed.apiKeyName} yok (FR-021/FR-025)`);
2335
3410
  }
2336
3411
  const url = (embed.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "") + "/embeddings";
@@ -2341,7 +3416,7 @@ async function embedQuery(embed, text) {
2341
3416
  method: "POST",
2342
3417
  headers: {
2343
3418
  "Content-Type": "application/json",
2344
- Authorization: `Bearer ${key}`
3419
+ Authorization: `Bearer ${key2}`
2345
3420
  },
2346
3421
  body: JSON.stringify({
2347
3422
  model: embed.model,
@@ -2399,14 +3474,14 @@ function buildValueTuple(table, cols, data, add, caller) {
2399
3474
  }).join(", ");
2400
3475
  }
2401
3476
  __name(buildValueTuple, "buildValueTuple");
2402
- function columnTypeOf(table, col) {
3477
+ function computeColumnType(table, col) {
2403
3478
  const def = tableDefOf(table);
2404
3479
  const c = def?.columns?.[col];
2405
3480
  if (c === void 0 || c === null) return null;
2406
3481
  const d = typeof c === "object" && "_def" in c ? c._def : c;
2407
3482
  return d !== null && typeof d === "object" && typeof d.type === "string" ? d.type : null;
2408
3483
  }
2409
- __name(columnTypeOf, "columnTypeOf");
3484
+ __name(computeColumnType, "computeColumnType");
2410
3485
  var NUMERIC_PG_TYPES = /* @__PURE__ */ new Set([
2411
3486
  "integer",
2412
3487
  "bigint",
@@ -2416,23 +3491,44 @@ function aggregateExpr(table, fn, col, alias) {
2416
3491
  const q = `t.${quoteIdent(col)}`;
2417
3492
  const t = columnTypeOf(table, col);
2418
3493
  if (fn === "sum" || fn === "avg") return `${fn}(${q})::text AS ${quoteIdent(alias)}`;
3494
+ if (t === "timestamp") return `${timestampResult(`${fn}(${q})`)} AS ${quoteIdent(alias)}`;
2419
3495
  return t !== null && NUMERIC_PG_TYPES.has(t) && t !== "integer" ? `${fn}(${q})::text AS ${quoteIdent(alias)}` : `${fn}(${q}) AS ${quoteIdent(alias)}`;
2420
3496
  }
2421
3497
  __name(aggregateExpr, "aggregateExpr");
2422
- function jsonObjectFor(table, cols, alias) {
3498
+ function jsonObjectFor(table, cols, alias, extra = []) {
2423
3499
  const pairs = cols.map((c) => {
2424
3500
  const t = columnTypeOf(table, c);
2425
- const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : `${alias}.${quoteIdent(c)}`;
3501
+ const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : t === "timestamp" ? timestampResult(`${alias}.${quoteIdent(c)}`) : `${alias}.${quoteIdent(c)}`;
2426
3502
  return `${quoteLiteral(c)}, ${expr}`;
2427
3503
  });
2428
- return `json_build_object(${pairs.join(", ")})`;
3504
+ return `json_build_object(${[
3505
+ ...pairs,
3506
+ ...extra
3507
+ ].join(", ")})`;
2429
3508
  }
2430
3509
  __name(jsonObjectFor, "jsonObjectFor");
2431
- function withClauses(table, spec, add, caller) {
3510
+ function relationColumns(table, relation) {
3511
+ const child = relation.kind === "many" ? relation.to : table;
3512
+ const value = tableDefOf(child)?.columns?.[relation.via];
3513
+ const column = value && typeof value === "object" && "_def" in value ? value._def : value;
3514
+ const target = column?.references?.column ?? primaryKeyOf(relation.kind === "many" ? table : relation.to);
3515
+ return relation.kind === "many" ? [
3516
+ target,
3517
+ relation.via
3518
+ ] : [
3519
+ relation.via,
3520
+ target
3521
+ ];
3522
+ }
3523
+ __name(relationColumns, "relationColumns");
3524
+ function withClauses(table, spec, add, caller, parentAlias = "t", depth = 0, counter = {
3525
+ value: 0
3526
+ }) {
3527
+ if (depth >= 3 && Object.values(spec).some(Boolean)) throw new Error(`${caller}(${table}): with supports at most three relation levels`);
2432
3528
  const rels = currentRelations.get(table) ?? [];
2433
3529
  const select = [];
2434
3530
  const joins = [];
2435
- let i = 0;
3531
+ const jsonPairs = [];
2436
3532
  for (const [name, opt] of Object.entries(spec)) {
2437
3533
  if (opt === false || opt === void 0) continue;
2438
3534
  const rel = rels.find((r) => r.name === name);
@@ -2440,13 +3536,13 @@ function withClauses(table, spec, add, caller) {
2440
3536
  const known = rels.map((r) => r.name).sort();
2441
3537
  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.`);
2442
3538
  }
2443
- const ourCol = rel.kind === "many" ? primaryKeyOf(table) : rel.via;
2444
- const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
3539
+ const [ourCol, theirCol] = relationColumns(table, rel);
2445
3540
  if (ourCol === null || theirCol === null) {
2446
3541
  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.`);
2447
3542
  }
2448
3543
  const o = typeof opt === "object" && opt !== null ? opt : {};
2449
- const childAlias = `w${++i}`;
3544
+ const i = ++counter.value;
3545
+ const childAlias = `w${i}`;
2450
3546
  const childKnown = schemaColumns(rel.to);
2451
3547
  const childCols = Array.isArray(o["select"]) && o["select"].length > 0 ? o["select"] : childKnown !== null ? [
2452
3548
  ...childKnown
@@ -2459,28 +3555,100 @@ function withClauses(table, spec, add, caller) {
2459
3555
  throw new Error(`${caller}(${table}): with.${name}.select kolonu "${c}" "${rel.to}" tablosunda yok`);
2460
3556
  }
2461
3557
  }
2462
- const obj = jsonObjectFor(rel.to, childCols, childAlias);
3558
+ const nestedSpec = o["with"];
3559
+ if (nestedSpec !== void 0 && (nestedSpec === null || typeof nestedSpec !== "object" || Array.isArray(nestedSpec))) {
3560
+ throw new Error(`${caller}(${table}): with.${name}.with must be a relation object`);
3561
+ }
3562
+ const nested = nestedSpec === void 0 ? {
3563
+ joins: [],
3564
+ jsonPairs: []
3565
+ } : withClauses(rel.to, nestedSpec, add, caller, childAlias, depth + 1, counter);
3566
+ const nestedJoins = nested.joins.length ? ` ${nested.joins.join(" ")}` : "";
3567
+ const obj = jsonObjectFor(rel.to, childCols, childAlias, nested.jsonPairs);
2463
3568
  const innerWhere = o["where"] !== void 0 && o["where"] !== null ? compileWhereBare(rel.to, childKnown, o["where"], add, caller, childAlias, i) : "";
2464
- const link = `${childAlias}.${quoteIdent(theirCol)} = t.${quoteIdent(ourCol)}`;
3569
+ const link = `${childAlias}.${quoteIdent(theirCol)} = ${parentAlias}.${quoteIdent(ourCol)}`;
2465
3570
  const cond = innerWhere === "" ? link : `${link} AND ${innerWhere}`;
2466
- const inner = orderClauseFor(rel.to, childKnown, o["orderBy"], childAlias) + limitClause(o["limit"]);
3571
+ const requestedLimit = o["limit"];
3572
+ limitClause(requestedLimit);
3573
+ const relationLimit = rel.kind === "many" ? requestedLimit : Math.min(1, requestedLimit ?? 1);
3574
+ const inner = orderClauseFor(rel.to, childKnown, o["orderBy"], childAlias) + limitClause(relationLimit);
2467
3575
  const outAlias = `wj${i}`;
2468
3576
  if (rel.kind === "many") {
2469
- 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`);
3577
+ 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`);
2470
3578
  } else {
2471
- joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias} WHERE ${cond}${inner} LIMIT 1) ${quoteIdent(outAlias)} ON true`);
3579
+ joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias}${nestedJoins} WHERE ${cond}${inner}) ${quoteIdent(outAlias)} ON true`);
2472
3580
  }
2473
3581
  select.push(`${quoteIdent(outAlias)}."d" AS ${quoteIdent(name)}`);
3582
+ jsonPairs.push(`${quoteLiteral(name)}, ${quoteIdent(outAlias)}."d"`);
2474
3583
  }
2475
3584
  return {
2476
3585
  select,
2477
- joins
3586
+ joins,
3587
+ jsonPairs
2478
3588
  };
2479
3589
  }
2480
3590
  __name(withClauses, "withClauses");
2481
- function createOps(tx) {
3591
+ function compileFindMany(table, query = {}, opts = {}) {
3592
+ const params = [];
3593
+ const add = /* @__PURE__ */ __name((v) => {
3594
+ params.push(v);
3595
+ return `$${params.length}`;
3596
+ }, "add");
3597
+ const known = schemaColumns(table);
3598
+ const whereSql = compileWhere(table, known, query, add, "findMany");
3599
+ const order = orderClause(table, known, opts.orderBy);
3600
+ const limit = limitClause(opts.limit);
3601
+ const offset = offsetClause(opts.offset, opts.limit);
3602
+ const cols = selectClause(table, known, opts.select);
3603
+ const w = opts.with !== void 0 && Object.keys(opts.with).length > 0 ? withClauses(table, opts.with, add, "findMany") : {
3604
+ select: [],
3605
+ joins: []
3606
+ };
3607
+ const selectList = w.select.length > 0 ? `${cols === "*" ? "t.*" : cols}, ${w.select.join(", ")}` : cols;
3608
+ const joinSql = w.joins.length > 0 ? ` ${w.joins.join(" ")}` : "";
3609
+ const sql = `SELECT ${selectList} FROM ${quoteTable(table)} t${joinSql} WHERE true${whereSql}${order}${limit}${offset}`;
3610
+ return {
3611
+ sql,
3612
+ params
3613
+ };
3614
+ }
3615
+ __name(compileFindMany, "compileFindMany");
3616
+ var uniqueKeysCache = /* @__PURE__ */ new WeakMap();
3617
+ var pageSchemaCache = /* @__PURE__ */ new WeakMap();
3618
+ function pageSchema(table) {
3619
+ return memoTable(pageSchemaCache, currentSchema, table, () => {
3620
+ const def = tableDefOf(table);
3621
+ if (!def?.columns) throw new Error(`page(${table}) requires an installed table declaration`);
3622
+ const columns = /* @__PURE__ */ Object.create(null);
3623
+ const inlineKeys = [];
3624
+ for (const [name, column] of Object.entries(def.columns)) {
3625
+ const c = column;
3626
+ const d = c._def ?? c;
3627
+ columns[name] = {
3628
+ type: d.type,
3629
+ nullable: d.nullable
3630
+ };
3631
+ if (d.primaryKey) inlineKeys.push(name);
3632
+ }
3633
+ const primaryKey = def.primaryKey ?? inlineKeys;
3634
+ for (const name of primaryKey) if (columns[name]) columns[name].nullable = false;
3635
+ return {
3636
+ columns,
3637
+ primaryKey,
3638
+ stamp: fingerprint({
3639
+ columns,
3640
+ primaryKey
3641
+ })
3642
+ };
3643
+ });
3644
+ }
3645
+ __name(pageSchema, "pageSchema");
3646
+ function createOps(tx, context = {}) {
2482
3647
  const at = /* @__PURE__ */ __name(() => resolveTx(tx), "at");
2483
3648
  const ops = {
3649
+ diagnostics() {
3650
+ return context.trace?.snapshot ?? null;
3651
+ },
2484
3652
  // Generic dönüş, çağıranın İDDİASIDIR (FR-021). Motor her zaman satır objeleri
2485
3653
  // üretir; `T` onlara bir ad vermekten ibarettir ve verilmezse `unknown` kalır —
2486
3654
  // yani "bilmiyorum", ki ham SQL için doğru cevap odur.
@@ -2494,7 +3662,7 @@ function createOps(tx) {
2494
3662
  const params = [];
2495
3663
  const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
2496
3664
  const tuple = buildValueTuple(table, cols, data, add, "insert");
2497
- const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING *`;
3665
+ const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`;
2498
3666
  const rows = await (await at()).unsafe(sql, params);
2499
3667
  const inserted = rows[0];
2500
3668
  if (!inserted) {
@@ -2516,8 +3684,9 @@ function createOps(tx) {
2516
3684
  * bir satırın fazla kolonu SESSİZCE YAZILMAZDI. Eksik kolon için `null`
2517
3685
  * yazın ya da varsayılanı istiyorsanız o satırları ayrı çağrıda ekleyin.
2518
3686
  */
2519
- async insertMany(table, rows, opts) {
2520
- if (rows.length === 0) return [];
3687
+ insertMany: /* @__PURE__ */ __name(async function(table, rows, opts) {
3688
+ validateInsertManyOptions(opts);
3689
+ if (rows.length === 0) return opts?.returning === false ? 0 : [];
2521
3690
  const first = rows[0];
2522
3691
  const cols = Object.keys(first);
2523
3692
  if (cols.length === 0) throw new Error(`insertMany into ${table}: no columns given`);
@@ -2532,11 +3701,15 @@ function createOps(tx) {
2532
3701
  const params = [];
2533
3702
  const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
2534
3703
  const tuples = rows.map((r) => `(${buildValueTuple(table, cols, r, add, "insertMany")})`);
2535
- const tail = opts !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
2536
- const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING *`;
3704
+ const tail = opts?.onConflict !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
3705
+ const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail}${opts?.returning === false ? "" : ` RETURNING ${rowProjection(table)}`}`;
2537
3706
  const out = await (await at()).unsafe(sql, params);
3707
+ if (opts?.returning === false) {
3708
+ if (!Number.isSafeInteger(out.count) || out.count < 0) throw new Error("insertMany returning:false requires a driver with an affected row count");
3709
+ return out.count;
3710
+ }
2538
3711
  return out.map((r) => asTableRow(table, r));
2539
- },
3712
+ }, "insertMany"),
2540
3713
  /**
2541
3714
  * Bir ADI kilitle — satırı değil (FR-054).
2542
3715
  *
@@ -2550,9 +3723,9 @@ function createOps(tx) {
2550
3723
  * Transaction kapsamlısı commit/rollback'te Postgres tarafından bırakılır,
2551
3724
  * yani unutulması imkânsız.
2552
3725
  */
2553
- async advisoryXactLock(key) {
3726
+ async advisoryXactLock(key2) {
2554
3727
  await (await at()).unsafe("select pg_advisory_xact_lock(hashtextextended($1, 0))", [
2555
- key
3728
+ key2
2556
3729
  ]);
2557
3730
  },
2558
3731
  /**
@@ -2626,7 +3799,7 @@ function createOps(tx) {
2626
3799
  const params = [];
2627
3800
  const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
2628
3801
  const tuple = buildValueTuple(table, cols, data, add, "claim");
2629
- const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING *`, params);
3802
+ const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
2630
3803
  if (!rows[0]) {
2631
3804
  throw new Error(`claim(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si).`);
2632
3805
  }
@@ -2643,7 +3816,7 @@ function createOps(tx) {
2643
3816
  return `$${params.length}`;
2644
3817
  }, "add");
2645
3818
  const where = keyCols.map((c) => `t.${quoteIdent(c)} = ${add(unique[c])}`).join(" AND ");
2646
- const found = await (await at()).unsafe(`SELECT * FROM ${quoteTable(table)} t WHERE ${where} LIMIT 1`, params);
3819
+ const found = await (await at()).unsafe(`SELECT ${rowProjection(table, "t", "*")} FROM ${quoteTable(table)} t WHERE ${where} LIMIT 1`, params);
2647
3820
  if (!found[0]) {
2648
3821
  const named = constraintOf(e);
2649
3822
  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.`);
@@ -2680,13 +3853,13 @@ function createOps(tx) {
2680
3853
  const bindData = [];
2681
3854
  const addPut = /* @__PURE__ */ __name((v) => `$${bindData.push(v)}`, "addPut");
2682
3855
  const ph = buildValueTuple(table, cols, data, addPut, "put");
2683
- 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 *`;
3856
+ 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)}`;
2684
3857
  const live = await at();
2685
3858
  const first = await live.unsafe(insertSql, bindData);
2686
3859
  if (first[0]) return asTableRow(table, first[0]);
2687
3860
  const condSql = conflict.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ");
2688
3861
  const condBind = conflict.map((c) => data[c]);
2689
- const cur = await live.unsafe(`SELECT * FROM ${quoteTable(table)} WHERE ${condSql} AND "valid_to" IS NULL`, condBind);
3862
+ const cur = await live.unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE ${condSql} AND "valid_to" IS NULL`, condBind);
2690
3863
  const old = cur[0];
2691
3864
  if (old === void 0) {
2692
3865
  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)`);
@@ -2712,7 +3885,7 @@ function createOps(tx) {
2712
3885
  const conflictSet = new Set(conflict);
2713
3886
  const assignments = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
2714
3887
  const action = assignments.length ? `DO UPDATE SET ${assignments.join(", ")}` : `DO UPDATE SET ${quoteIdent(conflict[0])} = EXCLUDED.${quoteIdent(conflict[0])}`;
2715
- const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING *`;
3888
+ const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING ${rowProjection(table)}`;
2716
3889
  const rows = await (await at()).unsafe(sql, params);
2717
3890
  const row = rows[0];
2718
3891
  if (!row) {
@@ -2730,7 +3903,7 @@ function createOps(tx) {
2730
3903
  return `$${params.length}`;
2731
3904
  }, "add");
2732
3905
  const assignments = buildSetAssignments(table, cols, data, add, "update");
2733
- const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING *`;
3906
+ const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING ${rowProjection(table)}`;
2734
3907
  const rows = await (await at()).unsafe(sql, params);
2735
3908
  return rows[0] ? asTableRow(table, rows[0]) : null;
2736
3909
  },
@@ -2782,9 +3955,9 @@ function createOps(tx) {
2782
3955
  assertHasPredicate(whereSql, "updateMany", table);
2783
3956
  const pk = primaryKeyOf(table);
2784
3957
  const wantRows = opts?.returning !== false;
2785
- const tail = wantRows ? "RETURNING *" : "RETURNING 1";
3958
+ const tail = wantRows ? `RETURNING ${rowProjection(table, "t", "*")}` : "RETURNING 1";
2786
3959
  const simple = isSinglePkEquality(where, pk) || pk === null;
2787
- 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");
3960
+ 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");
2788
3961
  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) `;
2789
3962
  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`;
2790
3963
  const out = await (await at()).unsafe(sql, params);
@@ -2869,7 +4042,7 @@ function createOps(tx) {
2869
4042
  }
2870
4043
  const groups = q.groupBy ?? [];
2871
4044
  for (const col of groups) check(col, "groupBy");
2872
- const groupSel = groups.map((c) => `t.${quoteIdent(c)} AS ${quoteIdent(`g__${c}`)}`);
4045
+ const groupSel = groups.map((c) => `${columnTypeOf(table, c) === "timestamp" ? columnResult(table, c, "t") : `t.${quoteIdent(c)}`} AS ${quoteIdent(`g__${c}`)}`);
2873
4046
  const params = [];
2874
4047
  const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
2875
4048
  const whereSql = compileWhere(table, known, q.where ?? {}, add, "aggregate");
@@ -2903,32 +4076,114 @@ function createOps(tx) {
2903
4076
  const rows = await (await at()).unsafe(sql, params);
2904
4077
  return Number(rows[0]?.n ?? 0);
2905
4078
  },
4079
+ async findUnique(table, where, opts = {}) {
4080
+ const keys = memoTable(uniqueKeysCache, currentSchema, table, () => uniqueKeysOf(tableDefOf(table) ?? {}));
4081
+ assertUniqueLookup(table, keys, where);
4082
+ const rows = await ops.findMany(table, where, {
4083
+ ...opts,
4084
+ select: opts.select === void 0 ? void 0 : [
4085
+ ...opts.select
4086
+ ],
4087
+ limit: 2
4088
+ });
4089
+ if (rows.length > 1) throw new Error(`findUnique(${table}): deployed constraints disagree with the schema; multiple rows matched`);
4090
+ return rows[0] ?? null;
4091
+ },
2906
4092
  async findById(table, id) {
2907
- const rows = await (await at()).unsafe(`SELECT * FROM ${quoteTable(table)} WHERE id = $1`, [
4093
+ const rows = await (await at()).unsafe(`SELECT ${rowProjection(table)} FROM ${quoteTable(table)} WHERE id = $1`, [
2908
4094
  id
2909
4095
  ]);
2910
4096
  return rows[0] ? asTableRow(table, rows[0]) : null;
2911
4097
  },
4098
+ compileQuery(query) {
4099
+ assertReadContract(query, tableDefOf(query.table)?.columns);
4100
+ return compileFindMany(query.table, query.where, query.options);
4101
+ },
4102
+ async readQuery(query) {
4103
+ const compiled = ops.compileQuery(query);
4104
+ return asTableRows(query.table, await (await at()).unsafe(compiled.sql, compiled.params, {
4105
+ name: query.name
4106
+ }));
4107
+ },
4108
+ async explainQuery(query) {
4109
+ const compiled = ops.compileQuery(query);
4110
+ return await (await at()).unsafe(`EXPLAIN (FORMAT JSON) ${compiled.sql}`, compiled.params, {
4111
+ name: `${query.name}.explain`
4112
+ });
4113
+ },
2912
4114
  async findMany(table, query = {}, opts = {}) {
4115
+ const compiled = compileFindMany(table, query, opts);
4116
+ return asTableRows(table, await (await at()).unsafe(compiled.sql, compiled.params), opts.with);
4117
+ },
4118
+ async page(table, query = {}, opts = {}) {
4119
+ const secret = cursorKey();
4120
+ const schema = pageSchema(table);
4121
+ const { order, size, backward, token } = pageOrder(schema.columns, schema.primaryKey, opts);
2913
4122
  const params = [];
2914
- const add = /* @__PURE__ */ __name((v) => {
2915
- params.push(v);
2916
- return `$${params.length}`;
2917
- }, "add");
4123
+ const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
2918
4124
  const known = schemaColumns(table);
2919
- const whereSql = compileWhere(table, known, query, add, "findMany");
2920
- const order = orderClause(table, known, opts.orderBy);
2921
- const limit = limitClause(opts.limit);
2922
- const offset = offsetClause(opts.offset, opts.limit);
2923
- const cols = selectClause(table, known, opts.select);
2924
- const w = opts.with !== void 0 && Object.keys(opts.with).length > 0 ? withClauses(table, opts.with, add, "findMany") : {
4125
+ const where = compileWhere(table, known, query, add, "page");
4126
+ const cols = selectClause(table, known, opts.select === void 0 ? void 0 : [
4127
+ ...opts.select
4128
+ ]);
4129
+ const scope = fingerprint({
4130
+ table,
4131
+ schema: schema.stamp,
4132
+ order,
4133
+ where,
4134
+ params,
4135
+ select: opts.select,
4136
+ with: opts.with,
4137
+ identity: context.cursorScope ?? "direct"
4138
+ });
4139
+ const values = token === void 0 ? void 0 : decodeCursor(secret, scope, token, order.length);
4140
+ const column = /* @__PURE__ */ __name((name) => `t.${quoteIdent(name)}`, "column");
4141
+ const boundary = values ? ` AND ${pageBoundary(order, values, backward, add, column)}` : "";
4142
+ const opposite = values ? `EXISTS (SELECT 1 FROM ${quoteTable(table)} t WHERE true${where} AND ${pageBoundary(order, values, !backward, add, column, true)})` : "false";
4143
+ let prefix = "__pb_page_";
4144
+ const names = [
4145
+ ...Object.keys(schema.columns),
4146
+ ...Object.keys(opts.with ?? {})
4147
+ ];
4148
+ while (names.some((n) => n.startsWith(prefix))) prefix += "_";
4149
+ const ordinal = `${prefix}ordinal`, other = `${prefix}opposite`;
4150
+ const hidden = order.map((o, i) => `${prefix}${i}`);
4151
+ const cursorCols = order.map((o, i) => `${columnTypeOf(table, o.column) === "timestamp" ? timestampResult(column(o.column)) : `${column(o.column)}::text`} AS ${quoteIdent(hidden[i])}`);
4152
+ const w = opts.with && Object.keys(opts.with).length ? withClauses(table, opts.with, add, "page") : {
2925
4153
  select: [],
2926
4154
  joins: []
2927
4155
  };
2928
- const selectList = w.select.length > 0 ? `${cols === "*" ? "t.*" : cols}, ${w.select.join(", ")}` : cols;
2929
- const joinSql = w.joins.length > 0 ? ` ${w.joins.join(" ")}` : "";
2930
- const sql = `SELECT ${selectList} FROM ${quoteTable(table)} t${joinSql} WHERE true${whereSql}${order}${limit}${offset}`;
2931
- return asTableRows(table, await (await at()).unsafe(sql, params));
4156
+ const nativeOrder = order.map((o) => `${column(o.column)} ${o.direction === "asc" !== backward ? "ASC" : "DESC"} NULLS ${o.nulls === "first" !== backward ? "FIRST" : "LAST"}`).join(", ");
4157
+ const visible = cols === "*" ? "t.*" : cols;
4158
+ const inner = `SELECT ${[
4159
+ visible,
4160
+ ...w.select,
4161
+ ...cursorCols,
4162
+ `row_number() OVER (ORDER BY ${nativeOrder}) AS ${quoteIdent(ordinal)}`
4163
+ ].join(", ")} FROM ${quoteTable(table)} t ${w.joins.join(" ")} WHERE true${where}${boundary} ORDER BY ${nativeOrder} LIMIT ${size + 1}`;
4164
+ 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)}`;
4165
+ const result = await (await at()).unsafe(sql, params);
4166
+ const hasOpposite = result[0]?.[other] === true;
4167
+ let rows = result.filter((row) => row[ordinal] !== null && row[ordinal] !== void 0);
4168
+ const hasMore = rows.length > size;
4169
+ rows = rows.slice(0, size);
4170
+ if (backward) rows.reverse();
4171
+ const cursor = /* @__PURE__ */ __name((row) => row ? encodeCursor(secret, scope, hidden.map((name) => row[name] === null ? null : String(row[name]))) : null, "cursor");
4172
+ const pageInfo = {
4173
+ startCursor: cursor(rows[0]),
4174
+ endCursor: cursor(rows.at(-1)),
4175
+ hasNextPage: backward ? hasOpposite : hasMore,
4176
+ hasPreviousPage: backward ? hasMore : hasOpposite
4177
+ };
4178
+ for (const row of rows) for (const name of [
4179
+ ...hidden,
4180
+ ordinal,
4181
+ other
4182
+ ]) delete row[name];
4183
+ return {
4184
+ rows: asTableRows(table, rows, opts.with),
4185
+ pageInfo
4186
+ };
2932
4187
  },
2933
4188
  /**
2934
4189
  * Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
@@ -3033,7 +4288,7 @@ function createOps(tx) {
3033
4288
  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}`;
3034
4289
  }
3035
4290
  const msC = params.minScore === void 0 ? "" : addC(params.minScore);
3036
- const colListC = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
4291
+ const colListC = cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`).join(", ");
3037
4292
  const scoreC = scoreMul === "" ? "g._score" : `(g._score)${scoreMul}`;
3038
4293
  const assembleC = /* @__PURE__ */ __name((kwIn, withKwn) => {
3039
4294
  const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
@@ -3115,7 +4370,7 @@ function createOps(tx) {
3115
4370
  qpRef = qp;
3116
4371
  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}`;
3117
4372
  }
3118
- const colList = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
4373
+ const colList = cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`).join(", ");
3119
4374
  const msP = params.minScore === void 0 ? "" : add(params.minScore);
3120
4375
  const hlCol = params.highlight === true && wantText ? `, ts_headline('simple', ${ftsExprOf(cfg.ftsCols)}, websearch_to_tsquery('simple', ${qpRef})) AS _highlight` : "";
3121
4376
  const assemble = /* @__PURE__ */ __name((kwIn, withKwn) => {
@@ -3323,7 +4578,7 @@ function createOps(tx) {
3323
4578
  const params = [];
3324
4579
  const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
3325
4580
  const tuple = buildValueTuple(table, cols, data, add, "supersede");
3326
- const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING *`, params);
4581
+ const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING ${rowProjection(table)}`, params);
3327
4582
  if (!rows[0]) {
3328
4583
  throw new Error(`supersede(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si)`);
3329
4584
  }
@@ -3333,7 +4588,7 @@ function createOps(tx) {
3333
4588
  /** A real SAVEPOINT inside the request's transaction. */
3334
4589
  async transaction(cb) {
3335
4590
  const live = await at();
3336
- return live.savepoint(async (sp) => cb(withTables(createOps(sp), currentSchema)));
4591
+ return live.savepoint(async (sp) => cb(withTables(createOps(sp, context), currentSchema)));
3337
4592
  },
3338
4593
  /**
3339
4594
  * Run `fn` against a handle bound to a SAVEPOINT, so a failure inside it
@@ -3354,7 +4609,7 @@ function createOps(tx) {
3354
4609
  */
3355
4610
  async attempt(fn) {
3356
4611
  const live = await at();
3357
- return live.savepoint(async (sp) => fn(withTables(createOps(sp), currentSchema)));
4612
+ return live.savepoint(async (sp) => fn(withTables(createOps(sp, context), currentSchema)));
3358
4613
  },
3359
4614
  /**
3360
4615
  * Execute a whole transaction plan — what `Database.$transaction(fn)` builds.
@@ -3374,6 +4629,62 @@ function createOps(tx) {
3374
4629
  * "transport.txPlan is not a function" while every test that covered
3375
4630
  * transactions passed against a mock that did implement it.
3376
4631
  */
4632
+ async command(plan, options = {}) {
4633
+ if (options.mode !== void 0 && ![
4634
+ "auto",
4635
+ "compiled",
4636
+ "sequential"
4637
+ ].includes(options.mode)) throw new Error("command mode must be auto, compiled or sequential");
4638
+ if (options.mode === "sequential") return ops.txPlan(plan);
4639
+ const compiled = context.commands ? compileCommand(plan) : {
4640
+ reason: "the PostgreSQL command executor is not installed"
4641
+ };
4642
+ if ("reason" in compiled) {
4643
+ if (options.mode === "compiled") throw new Error(`Command cannot compile: ${compiled.reason}. Use auto/sequential mode or installCommandExecutor at boot.`);
4644
+ return ops.txPlan(plan);
4645
+ }
4646
+ if (compiled.steps.length === 0) return {
4647
+ results: []
4648
+ };
4649
+ if (compiled.pipeline) {
4650
+ return (await at()).savepoint(async (sp) => {
4651
+ const settled = await Promise.allSettled(compiled.pipeline.map((statement) => sp.unsafe(statement.sql, statement.params)));
4652
+ const failed = settled.find((result) => result.status === "rejected");
4653
+ if (failed?.status === "rejected") {
4654
+ const error = failed.reason;
4655
+ if (sqlstateOf(error) === SQLSTATE.commandGuardFailed && typeof error.detail === "string") {
4656
+ const detail = JSON.parse(error.detail);
4657
+ if (detail.error_code === "tx_guard_failed" && Number.isInteger(detail.slot)) {
4658
+ throw Object.assign(new Error("Command expectation failed"), detail);
4659
+ }
4660
+ }
4661
+ throw error;
4662
+ }
4663
+ return {
4664
+ results: settled.map((result, i) => {
4665
+ const rows2 = result.value[0].rows;
4666
+ return {
4667
+ rows: asTableRows(plan.ops[i].table, rows2),
4668
+ rows_affected: rows2.length
4669
+ };
4670
+ })
4671
+ };
4672
+ });
4673
+ }
4674
+ const rows = await (await at()).savepoint((sp) => sp.unsafe("SELECT palbase_runtime.command_v1($1::jsonb -> 'steps') AS result", [
4675
+ {
4676
+ steps: compiled.steps
4677
+ }
4678
+ ]));
4679
+ const response = rows[0].result;
4680
+ 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);
4681
+ return {
4682
+ results: response.results.map((result, i) => ({
4683
+ ...result,
4684
+ rows: asTableRows(plan.ops[i].table, result.rows)
4685
+ }))
4686
+ };
4687
+ },
3377
4688
  async txPlan(plan) {
3378
4689
  const live = await at();
3379
4690
  return live.savepoint(async (sp) => {
@@ -3397,27 +4708,55 @@ function createOps(tx) {
3397
4708
  }
3398
4709
  __name(createOps, "createOps");
3399
4710
  var currentSchema = {};
4711
+ var columnsMemo = /* @__PURE__ */ new WeakMap();
4712
+ var primaryMemo = /* @__PURE__ */ new WeakMap();
4713
+ var vectorMemo = /* @__PURE__ */ new WeakMap();
4714
+ var transformMemo = /* @__PURE__ */ new WeakMap();
4715
+ var typeMemo = /* @__PURE__ */ new WeakMap();
4716
+ var timestampMemo = /* @__PURE__ */ new WeakMap();
4717
+ var projectionMemo = /* @__PURE__ */ new WeakMap();
4718
+ function schemaColumns(table) {
4719
+ return memoTable(columnsMemo, currentSchema, table, () => computeSchemaColumns(table));
4720
+ }
4721
+ __name(schemaColumns, "schemaColumns");
4722
+ function primaryKeyOf(table) {
4723
+ return memoTable(primaryMemo, currentSchema, table, () => computePrimaryKey(table));
4724
+ }
4725
+ __name(primaryKeyOf, "primaryKeyOf");
4726
+ function vectorColumnsOf(schema, table) {
4727
+ return memoTable(vectorMemo, schema, table, () => computeVectorColumns(schema, table));
4728
+ }
4729
+ __name(vectorColumnsOf, "vectorColumnsOf");
4730
+ function transformsOf(schema, table) {
4731
+ return memoTable(transformMemo, schema, table, () => computeTransforms(schema, table));
4732
+ }
4733
+ __name(transformsOf, "transformsOf");
4734
+ function columnTypeOf(table, col) {
4735
+ const types = memoTable(typeMemo, currentSchema, table, () => new Map([
4736
+ ...schemaColumns(table) ?? []
4737
+ ].map((c) => [
4738
+ c,
4739
+ computeColumnType(table, c)
4740
+ ])));
4741
+ return types.get(col) ?? null;
4742
+ }
4743
+ __name(columnTypeOf, "columnTypeOf");
3400
4744
  var currentRelations = /* @__PURE__ */ new Map();
3401
4745
  function setSchema(schemas) {
3402
- cachedVectorSchema = null;
3403
- cachedTrgmSchema = null;
3404
- const tables = {};
4746
+ const tables = /* @__PURE__ */ Object.create(null);
3405
4747
  for (const entry of schemas) {
3406
4748
  const mod = entry;
3407
4749
  const def = (mod && "default" in mod ? mod.default : mod) ?? {};
3408
4750
  const schemaName = def.name ?? "public";
3409
- for (const [key, table] of Object.entries(def.tables ?? {})) {
3410
- tables[qualifiedTableKey(schemaName, table.name ?? key)] = table;
4751
+ for (const [key2, table] of Object.entries(def.tables ?? {})) {
4752
+ tables[qualifiedTableKey(schemaName, table.name ?? key2)] = table;
3411
4753
  }
3412
4754
  }
3413
- currentSchema = {
3414
- tables
3415
- };
3416
- currentRelations = buildRelations(schemas.map((entry) => {
4755
+ const relations = buildRelations(schemas.map((entry) => {
3417
4756
  const mod = entry;
3418
4757
  const def = (mod && "default" in mod ? mod.default : mod) ?? {};
3419
- const tables2 = {};
3420
- for (const [key, t] of Object.entries(def.tables ?? {})) {
4758
+ const tables2 = /* @__PURE__ */ Object.create(null);
4759
+ for (const [key2, t] of Object.entries(def.tables ?? {})) {
3421
4760
  const table = t ?? {};
3422
4761
  const columns = {};
3423
4762
  for (const [cname, c] of Object.entries(table.columns ?? {})) {
@@ -3427,9 +4766,9 @@ function setSchema(schemas) {
3427
4766
  _def: cd
3428
4767
  };
3429
4768
  }
3430
- tables2[key] = {
4769
+ tables2[key2] = {
3431
4770
  ...table,
3432
- name: table.name ?? key,
4771
+ name: table.name ?? key2,
3433
4772
  columns
3434
4773
  };
3435
4774
  }
@@ -3438,40 +4777,86 @@ function setSchema(schemas) {
3438
4777
  tables: tables2
3439
4778
  };
3440
4779
  }));
4780
+ for (const key2 of Object.keys(tables)) tables[key2] = snapshotDeclaration(tables[key2]);
4781
+ currentSchema = Object.freeze({
4782
+ tables: Object.freeze(tables)
4783
+ });
4784
+ currentRelations = relations;
4785
+ cachedVectorSchema = null;
4786
+ cachedTrgmSchema = null;
3441
4787
  }
3442
4788
  __name(setSchema, "setSchema");
4789
+ var tableNamespaces = /* @__PURE__ */ new WeakMap();
3443
4790
  function withTables(ops, schema = currentSchema) {
3444
- const bind = /* @__PURE__ */ __name((key) => ({
3445
- insert: /* @__PURE__ */ __name((data) => ops.insert(key, data), "insert"),
3446
- update: /* @__PURE__ */ __name((id, data) => ops.update(key, id, data), "update"),
3447
- delete: /* @__PURE__ */ __name((id) => ops.delete(key, id), "delete"),
3448
- findById: /* @__PURE__ */ __name((id) => ops.findById(key, id), "findById"),
3449
- findMany: /* @__PURE__ */ __name((query, opts) => ops.findMany(key, query ?? {}, opts), "findMany"),
3450
- put: /* @__PURE__ */ __name((q) => ops.put(key, q.data, {
4791
+ let namespaces = tableNamespaces.get(schema);
4792
+ if (!namespaces) {
4793
+ namespaces = /* @__PURE__ */ new Map();
4794
+ for (const key2 of Object.keys(schema.tables ?? {})) {
4795
+ const dot = key2.indexOf(".");
4796
+ const name = dot === -1 ? "public" : key2.slice(0, dot);
4797
+ const short = dot === -1 ? key2 : key2.slice(dot + 1);
4798
+ if (!namespaces.has(name)) namespaces.set(name, /* @__PURE__ */ new Map());
4799
+ namespaces.get(name).set(short, key2);
4800
+ }
4801
+ if (Object.isFrozen(schema) && Object.isFrozen(schema.tables)) tableNamespaces.set(schema, namespaces);
4802
+ }
4803
+ const bind = /* @__PURE__ */ __name((key2) => ({
4804
+ insert: /* @__PURE__ */ __name((data) => ops.insert(key2, data), "insert"),
4805
+ update: /* @__PURE__ */ __name((id, data) => ops.update(key2, id, data), "update"),
4806
+ delete: /* @__PURE__ */ __name((id) => ops.delete(key2, id), "delete"),
4807
+ findById: /* @__PURE__ */ __name((id) => ops.findById(key2, id), "findById"),
4808
+ findUnique: /* @__PURE__ */ __name((where, opts) => ops.findUnique(key2, where, opts), "findUnique"),
4809
+ findMany: /* @__PURE__ */ __name((query, opts) => ops.findMany(key2, query ?? {}, opts), "findMany"),
4810
+ page: /* @__PURE__ */ __name((query, opts) => ops.page(key2, query ?? {}, opts), "page"),
4811
+ put: /* @__PURE__ */ __name((q) => ops.put(key2, q.data, {
3451
4812
  onConflict: q.onConflict
3452
4813
  }), "put")
3453
4814
  }), "bind");
3454
- const tables = {};
3455
- for (const key of Object.keys(schema.tables ?? {})) {
3456
- if (!key.includes(".")) tables[key] = bind(key);
3457
- }
4815
+ const surfaces = /* @__PURE__ */ new Map();
3458
4816
  const schemaOf = /* @__PURE__ */ __name((name) => {
3459
- const out = {};
3460
- const prefix = `${name}.`;
3461
- for (const key of Object.keys(schema.tables ?? {})) {
3462
- if (name === "public") {
3463
- if (!key.includes(".")) out[key] = bind(key);
3464
- } else if (key.startsWith(prefix)) {
3465
- out[key.slice(prefix.length)] = bind(key);
4817
+ const cached = surfaces.get(name);
4818
+ if (cached) return cached;
4819
+ const names = namespaces.get(name) ?? /* @__PURE__ */ new Map();
4820
+ const target = /* @__PURE__ */ Object.create(null);
4821
+ const get = /* @__PURE__ */ __name((key2) => {
4822
+ if (!Object.hasOwn(target, key2) && names.has(key2)) {
4823
+ Object.defineProperty(target, key2, {
4824
+ value: bind(names.get(key2)),
4825
+ configurable: true,
4826
+ enumerable: true,
4827
+ writable: true
4828
+ });
3466
4829
  }
3467
- }
3468
- return {
3469
- tables: out
4830
+ return Reflect.get(target, key2);
4831
+ }, "get");
4832
+ const tables = new Proxy(target, {
4833
+ get: /* @__PURE__ */ __name((t, key2, receiver) => typeof key2 === "string" ? get(key2) : Reflect.get(t, key2, receiver), "get"),
4834
+ has: /* @__PURE__ */ __name((t, key2) => typeof key2 === "string" && names.has(key2) || Reflect.has(t, key2), "has"),
4835
+ ownKeys: /* @__PURE__ */ __name((t) => [
4836
+ .../* @__PURE__ */ new Set([
4837
+ ...names.keys(),
4838
+ ...Reflect.ownKeys(t)
4839
+ ])
4840
+ ], "ownKeys"),
4841
+ getOwnPropertyDescriptor: /* @__PURE__ */ __name((t, key2) => Reflect.getOwnPropertyDescriptor(t, key2) ?? (typeof key2 === "string" && names.has(key2) ? {
4842
+ configurable: true,
4843
+ enumerable: true,
4844
+ get: /* @__PURE__ */ __name(() => get(key2), "get")
4845
+ } : void 0), "getOwnPropertyDescriptor"),
4846
+ preventExtensions: /* @__PURE__ */ __name((t) => {
4847
+ for (const key2 of names.keys()) get(key2);
4848
+ return Reflect.preventExtensions(t);
4849
+ }, "preventExtensions")
4850
+ });
4851
+ const surface = {
4852
+ tables
3470
4853
  };
4854
+ if (namespaces.has(name) || name === "public") surfaces.set(name, surface);
4855
+ return surface;
3471
4856
  }, "schemaOf");
3472
4857
  const base = /* @__PURE__ */ Object.create(null);
3473
4858
  return Object.assign(base, ops, {
3474
- tables,
4859
+ tables: schemaOf("public").tables,
3475
4860
  schema: schemaOf
3476
4861
  });
3477
4862
  }
@@ -3529,8 +4914,8 @@ function diagnosingDriver(sql, surface = "service") {
3529
4914
  return markEngineRaised(new UniqueViolation(constraintOf(e)));
3530
4915
  }
3531
4916
  const state = sqlstateOf(e);
3532
- if (state === "40001") return markEngineRaised(new SerializationFailure(state));
3533
- if (state === "40P01") return markEngineRaised(new DeadlockDetected(state));
4917
+ if (state === SQLSTATE.serializationFailure) return markEngineRaised(new SerializationFailure(state));
4918
+ if (state === SQLSTATE.deadlockDetected) return markEngineRaised(new DeadlockDetected(state));
3534
4919
  if (surface === "service" && isForeignKeyViolation(e)) {
3535
4920
  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)})`);
3536
4921
  }
@@ -3540,11 +4925,16 @@ function diagnosingDriver(sql, surface = "service") {
3540
4925
  return e;
3541
4926
  }, "explain");
3542
4927
  const wrapTx = /* @__PURE__ */ __name((tx) => ({
3543
- async unsafe(text, params) {
4928
+ unsafe(text, params) {
3544
4929
  try {
3545
- return await tx.unsafe(text, params);
4930
+ const query = tx.unsafe(text, params);
4931
+ const mapped = query.catch((e) => {
4932
+ throw explain(e);
4933
+ });
4934
+ if (query.cancel) mapped.cancel = () => query.cancel();
4935
+ return mapped;
3546
4936
  } catch (e) {
3547
- throw explain(e);
4937
+ return Promise.reject(explain(e));
3548
4938
  }
3549
4939
  },
3550
4940
  savepoint(cb) {
@@ -3553,34 +4943,120 @@ function diagnosingDriver(sql, surface = "service") {
3553
4943
  }), "wrapTx");
3554
4944
  return {
3555
4945
  unsafe: /* @__PURE__ */ __name((text, params) => sql.unsafe(text, params), "unsafe"),
3556
- begin: /* @__PURE__ */ __name((cb) => sql.begin((tx) => cb(wrapTx(tx))), "begin")
4946
+ cancelBackend: sql.cancelBackend?.bind(sql),
4947
+ begin: /* @__PURE__ */ __name((cb) => sql.begin((tx) => cb(wrapTx(tx))).catch((e) => {
4948
+ throw explain(e);
4949
+ }), "begin")
3557
4950
  };
3558
4951
  }
3559
4952
  __name(diagnosingDriver, "diagnosingDriver");
3560
- function createRequestDatabase(sql, identity) {
3561
- const tx = createLazyTransaction(diagnosingDriver(sql, "user"), identity.role, identity.claimsJson);
4953
+ function createRequestDatabase(sql, identity, diagnostics) {
4954
+ const trace = diagnostics === void 0 ? void 0 : new DatabaseTrace(diagnostics);
4955
+ let requestClosed = false;
4956
+ const contextFor = /* @__PURE__ */ __name((role) => {
4957
+ let scope;
4958
+ return {
4959
+ trace,
4960
+ get cursorScope() {
4961
+ return scope ??= cursorIdentity(role, identity.claimsJson);
4962
+ },
4963
+ get commands() {
4964
+ return hasCommandExecutor(sql);
4965
+ }
4966
+ };
4967
+ }, "contextFor");
4968
+ const userContext = contextFor(identity.role);
4969
+ const serviceContext = contextFor(identity.serviceRole);
4970
+ const atomic = /* @__PURE__ */ __name((role, surface) => async (fn, options = {}) => {
4971
+ validateAtomicOptions(options);
4972
+ if (requestClosed) throw new Error("Database request has settled; it cannot open another transaction");
4973
+ if (atomicScope.getStore() !== void 0) throw new Error("Nested Database.$atomic is not supported; use tx.$attempt for a savepoint");
4974
+ const retries = retryBudget(options.retry);
4975
+ const control = options.timeoutMs !== void 0 || options.signal !== void 0 ? new TransactionControl(options.timeoutMs, options.signal) : void 0;
4976
+ let lease;
4977
+ try {
4978
+ control?.check();
4979
+ if (options.workload) {
4980
+ const queuedAt = trace ? performance.now() : 0;
4981
+ try {
4982
+ lease = await workloadAdmission(sql, options.workload).acquire(workloadTenant(options.workload, identity.claimsJson), options.conflictKeys ?? [], control?.signal);
4983
+ } finally {
4984
+ trace?.time("queueMs", queuedAt);
4985
+ }
4986
+ }
4987
+ for (let attempt = 0; ; attempt++) {
4988
+ control?.check();
4989
+ const timeouts = [
4990
+ options.statementTimeoutMs,
4991
+ control?.remainingMs()
4992
+ ].filter((n) => n !== void 0);
4993
+ const scope = /* @__PURE__ */ Symbol("atomic transaction");
4994
+ const root = createLazyTransaction(diagnosingDriver(sql, surface), role, identity.claimsJson, {
4995
+ scope,
4996
+ isolation: options.isolation,
4997
+ readOnly: options.readOnly,
4998
+ control,
4999
+ trace,
5000
+ lockTimeout: `${options.lockTimeoutMs ?? 5e3}ms`,
5001
+ statementTimeout: timeouts.length === 0 ? void 0 : `${Math.min(...timeouts)}ms`
5002
+ });
5003
+ try {
5004
+ const callback = atomicScope.run(scope, () => fn(withTables(createOps(root, surface === "user" ? userContext : serviceContext))));
5005
+ const value = await (control ? control.race(callback) : callback);
5006
+ control?.check();
5007
+ await root.commit();
5008
+ return value;
5009
+ } catch (error) {
5010
+ await root.rollback(error);
5011
+ if (attempt >= retries || !isRetryable(error)) throw error;
5012
+ trace?.retry();
5013
+ const delay = new Promise((resolve2) => setTimeout(resolve2, Math.min(100, 2 ** attempt)));
5014
+ await (control ? control.race(delay) : delay);
5015
+ }
5016
+ }
5017
+ } finally {
5018
+ control?.dispose();
5019
+ lease?.release();
5020
+ }
5021
+ }, "atomic");
5022
+ const tx = createLazyTransaction(diagnosingDriver(sql, "user"), identity.role, identity.claimsJson, {
5023
+ trace
5024
+ });
3562
5025
  let serviceTx = null;
3563
5026
  let serviceClient = null;
3564
5027
  const asService = /* @__PURE__ */ __name(() => {
3565
5028
  if (serviceClient === null) {
3566
5029
  serviceTx = createLazyTransaction(diagnosingDriver(sql, "service"), identity.serviceRole, identity.claimsJson, {
3567
- lockTimeout: SERVICE_LOCK_TIMEOUT
5030
+ lockTimeout: SERVICE_LOCK_TIMEOUT,
5031
+ trace
5032
+ });
5033
+ serviceClient = Object.assign(withTables(createOps(serviceTx, serviceContext)), {
5034
+ atomic: atomic(identity.serviceRole, "service")
3568
5035
  });
3569
- serviceClient = withTables(createOps(serviceTx));
3570
5036
  }
3571
5037
  return serviceClient;
3572
5038
  }, "asService");
3573
5039
  return {
3574
- client: Object.assign(withTables(createOps(tx)), {
3575
- asService
5040
+ client: Object.assign(withTables(createOps(tx, userContext)), {
5041
+ asService,
5042
+ atomic: atomic(identity.role, "user")
3576
5043
  }),
3577
5044
  async commit() {
3578
- await tx.commit();
3579
- await serviceTx?.commit();
5045
+ requestClosed = true;
5046
+ try {
5047
+ await tx.commit();
5048
+ await serviceTx?.commit();
5049
+ trace?.complete("committed");
5050
+ } catch (error) {
5051
+ trace?.failed();
5052
+ throw error;
5053
+ }
3580
5054
  },
3581
5055
  async rollback(reason2) {
5056
+ requestClosed = true;
3582
5057
  await tx.rollback(reason2);
3583
5058
  await serviceTx?.rollback(reason2);
5059
+ trace?.complete("failed");
3584
5060
  }
3585
5061
  };
3586
5062
  }
@@ -3590,8 +5066,13 @@ var Args = class Args2 {
3590
5066
  __name(this, "Args");
3591
5067
  }
3592
5068
  values = [];
3593
- bind(value) {
5069
+ columns = [];
5070
+ bind(value, column, table) {
3594
5071
  this.values.push(value);
5072
+ this.columns.push({
5073
+ column,
5074
+ table
5075
+ });
3595
5076
  return `$${this.values.length}`;
3596
5077
  }
3597
5078
  };
@@ -3677,7 +5158,7 @@ function planWhere(table, where, args, results) {
3677
5158
  }
3678
5159
  const resolved = resolveRefsDeep(where, results);
3679
5160
  const known = schemaColumns(table);
3680
- const bare = compileWhereBare(table, known, resolved, (v) => args.bind(v), "transaction plan");
5161
+ const bare = compileWhereBare(table, known, resolved, (v, column, from) => args.bind(v, column, from), "transaction plan");
3681
5162
  return {
3682
5163
  sql: bare === "" ? "" : ` AND ${bare}`,
3683
5164
  resolved
@@ -3686,16 +5167,16 @@ function planWhere(table, where, args, results) {
3686
5167
  __name(planWhere, "planWhere");
3687
5168
  function bindMaybeVector(args, column, vectorCols, value, transforms) {
3688
5169
  if (column !== void 0 && vectorCols?.has(column) && Array.isArray(value)) {
3689
- return args.bind(toVectorLiteral(value));
5170
+ return args.bind(toVectorLiteral(value), column);
3690
5171
  }
3691
5172
  const t = column !== void 0 ? transforms?.get(column) : void 0;
3692
5173
  if (t?.toDb !== void 0 && value !== null && value !== void 0) {
3693
- return args.bind(t.toDb(value));
5174
+ return args.bind(t.toDb(value), column);
3694
5175
  }
3695
- return args.bind(value);
5176
+ return args.bind(value, column);
3696
5177
  }
3697
5178
  __name(bindMaybeVector, "bindMaybeVector");
3698
- async function runPlanOp(sp, op, results) {
5179
+ function compilePlanOp(op, results) {
3699
5180
  const opVectorCols = vectorColumnsOf(currentSchema, op.table);
3700
5181
  const opTransforms = transformsOf(currentSchema, op.table);
3701
5182
  const args = new Args();
@@ -3705,7 +5186,7 @@ async function runPlanOp(sp, op, results) {
3705
5186
  case "insert": {
3706
5187
  const cols = Object.keys(op.values ?? {});
3707
5188
  const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols, opTransforms));
3708
- sql = cols.length ? `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) RETURNING *` : `INSERT INTO ${table} DEFAULT VALUES RETURNING *`;
5189
+ 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)}`;
3709
5190
  break;
3710
5191
  }
3711
5192
  case "upsert": {
@@ -3718,17 +5199,17 @@ async function runPlanOp(sp, op, results) {
3718
5199
  });
3719
5200
  }
3720
5201
  const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols, opTransforms));
3721
- sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING *`;
5202
+ sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING ${rowProjection(op.table)}`;
3722
5203
  break;
3723
5204
  }
3724
5205
  case "insertMany": {
3725
5206
  const rows = op.rows ?? [];
3726
- if (rows.length === 0 || !rows[0]) return [];
5207
+ if (rows.length === 0 || !rows[0]) return null;
3727
5208
  const cols = Object.keys(rows[0]);
3728
5209
  const tuples = rows.map((r) => `(${cols.map((c) => renderValue(r[c], c, args, results, opVectorCols, opTransforms)).join(", ")})`);
3729
5210
  const conflictCols = op.onConflict ?? [];
3730
5211
  const tail = op.action !== void 0 && conflictCols.length > 0 ? ` ${onConflictTail(cols, conflictCols, op.action)}` : "";
3731
- sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING *`;
5212
+ sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING ${rowProjection(op.table)}`;
3732
5213
  break;
3733
5214
  }
3734
5215
  case "update": {
@@ -3744,12 +5225,12 @@ async function runPlanOp(sp, op, results) {
3744
5225
  const assignments = cols.map((c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols, opTransforms)}`);
3745
5226
  const planned = planWhere(op.table, op.where, args, results);
3746
5227
  const planPk = primaryKeyOf(op.table);
3747
- 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.*`;
5228
+ 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")}`;
3748
5229
  break;
3749
5230
  }
3750
5231
  case "delete": {
3751
5232
  assertNotAppendOnly("deleteWhere", op.table);
3752
- sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING t.*`;
5233
+ sql = `DELETE FROM ${table} AS t WHERE true${renderWhere(op.table, op.where, args, results)} RETURNING ${rowProjection(op.table, "t")}`;
3753
5234
  break;
3754
5235
  }
3755
5236
  case "select": {
@@ -3762,15 +5243,157 @@ async function runPlanOp(sp, op, results) {
3762
5243
  limit = ` LIMIT ${n}`;
3763
5244
  }
3764
5245
  const lock = op.lock === "update" ? " FOR UPDATE" : "";
3765
- sql = `SELECT t.* FROM ${table} t WHERE true${renderWhere(op.table, op.where, args, results)}${limit}${lock}`;
5246
+ sql = `SELECT ${rowProjection(op.table, "t")} FROM ${table} t WHERE true${renderWhere(op.table, op.where, args, results)}${limit}${lock}`;
3766
5247
  break;
3767
5248
  }
3768
5249
  default:
3769
5250
  throw new Error(`unknown operation "${String(op.op)}" in a transaction plan`);
3770
5251
  }
3771
- return await sp.unsafe(sql, args.values);
5252
+ return {
5253
+ sql,
5254
+ params: args.values,
5255
+ columns: args.columns
5256
+ };
5257
+ }
5258
+ __name(compilePlanOp, "compilePlanOp");
5259
+ async function runPlanOp(sp, op, results) {
5260
+ const statement = compilePlanOp(op, results);
5261
+ return statement ? await sp.unsafe(statement.sql, statement.params) : [];
3772
5262
  }
3773
5263
  __name(runPlanOp, "runPlanOp");
5264
+ function compileCommand(plan) {
5265
+ const symbols = /* @__PURE__ */ new Map();
5266
+ const results = [];
5267
+ const steps = [];
5268
+ const pipeline = [];
5269
+ let hasRefs = false;
5270
+ const prefix = `command:${crypto.randomUUID()}:`;
5271
+ const containsFragment = /* @__PURE__ */ __name((v) => isSqlFragment(v) || v !== null && typeof v === "object" && Object.values(v).some(containsFragment), "containsFragment");
5272
+ const hasNullableFilterRef = /* @__PURE__ */ __name((v) => {
5273
+ if (isRef(v)) {
5274
+ const source = plan.ops[v.$ref.op];
5275
+ return source !== void 0 && pageSchema(source.table).columns[v.$ref.field]?.nullable !== false;
5276
+ }
5277
+ return v !== null && typeof v === "object" && Object.values(v).some(hasNullableFilterRef);
5278
+ }, "hasNullableFilterRef");
5279
+ for (const op of plan.ops) {
5280
+ if (!schemaColumns(op.table)?.size) return {
5281
+ reason: `table ${op.table} has no installed declaration`
5282
+ };
5283
+ if (transformsOf(currentSchema, op.table).size || vectorColumnsOf(currentSchema, op.table).size) return {
5284
+ reason: `${op.table} uses JavaScript codecs/transforms or vector values`
5285
+ };
5286
+ if (containsFragment(op)) return {
5287
+ reason: "SQL fragments require the sequential path"
5288
+ };
5289
+ if (hasNullableFilterRef(op.where)) return {
5290
+ reason: "nullable references in filters require the sequential path"
5291
+ };
5292
+ }
5293
+ for (const [index, op] of plan.ops.entries()) {
5294
+ const statement = compilePlanOp(op, results);
5295
+ if (statement === null) {
5296
+ steps.push({
5297
+ empty: true,
5298
+ guard: op.guard
5299
+ });
5300
+ pipeline.push({
5301
+ sql: "SELECT palbase_runtime.command_rows_v1('[]'::jsonb,$1::jsonb) AS rows",
5302
+ params: [
5303
+ op.guard ?? null
5304
+ ]
5305
+ });
5306
+ } else {
5307
+ if (statement.params.some((v) => v === void 0 || typeof v === "symbol" || typeof v === "function" || typeof v === "number" && (!Number.isInteger(v) || v < -2147483648 || v > 2147483647))) {
5308
+ return {
5309
+ reason: "non-integer/wide numeric or non-JSON parameters require the sequential driver path; use exact decimal strings"
5310
+ };
5311
+ }
5312
+ for (const [i, value] of statement.params.entries()) {
5313
+ const target = statement.columns[i];
5314
+ if (target.column === void 0 || columnTypeOf(target.table ?? op.table, target.column) !== "jsonb") continue;
5315
+ const source = typeof value === "string" ? symbols.get(value) : void 0;
5316
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || source && "ref" in source && ![
5317
+ "text",
5318
+ "uuid",
5319
+ "timestamp",
5320
+ "enum"
5321
+ ].includes(columnTypeOf(plan.ops[source.ref.op].table, source.ref.field) ?? "")) {
5322
+ return {
5323
+ reason: "JSONB scalar/ref binding requires the sequential driver path"
5324
+ };
5325
+ }
5326
+ }
5327
+ const wide = [
5328
+ ...schemaColumns(op.table)
5329
+ ].filter((c) => [
5330
+ "bigint",
5331
+ "numeric"
5332
+ ].includes(columnTypeOf(op.table, c) ?? ""));
5333
+ const exact = wide.length ? ` || jsonb_build_object(${wide.map((c) => `${quoteLiteral(c)}, r.${quoteIdent(c)}::text`).join(", ")})` : "";
5334
+ const sql = `WITH command_rows AS (${statement.sql}) SELECT coalesce(jsonb_agg(to_jsonb(r)${exact}), '[]'::jsonb) FROM command_rows r`;
5335
+ pipeline.push({
5336
+ 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`,
5337
+ params: [
5338
+ ...statement.params,
5339
+ op.guard ?? null
5340
+ ]
5341
+ });
5342
+ const bindings = statement.params.map((value, i) => {
5343
+ const target = statement.columns[i];
5344
+ const json = target.column !== void 0 && columnTypeOf(target.table ?? op.table, target.column) === "jsonb";
5345
+ if (typeof value === "string" && symbols.has(value)) {
5346
+ hasRefs = true;
5347
+ return {
5348
+ ...symbols.get(value),
5349
+ json
5350
+ };
5351
+ }
5352
+ if (typeof value === "bigint") return {
5353
+ value: value.toString(),
5354
+ json
5355
+ };
5356
+ if (value instanceof Date) return {
5357
+ value: value.toISOString(),
5358
+ json
5359
+ };
5360
+ return {
5361
+ value,
5362
+ json
5363
+ };
5364
+ });
5365
+ steps.push({
5366
+ chunks: parameterChunks(sql),
5367
+ bindings,
5368
+ guard: op.guard
5369
+ });
5370
+ }
5371
+ const row = {};
5372
+ for (const field of schemaColumns(op.table)) {
5373
+ const token = `${prefix}${index}:${field}`;
5374
+ symbols.set(token, {
5375
+ ref: {
5376
+ op: index,
5377
+ field
5378
+ }
5379
+ });
5380
+ row[field] = token;
5381
+ }
5382
+ results.push({
5383
+ rows: [
5384
+ row
5385
+ ],
5386
+ rows_affected: 1
5387
+ });
5388
+ }
5389
+ return {
5390
+ steps,
5391
+ ...hasRefs ? {} : {
5392
+ pipeline
5393
+ }
5394
+ };
5395
+ }
5396
+ __name(compileCommand, "compileCommand");
3774
5397
  function assertGuard(op, result) {
3775
5398
  const guard = op.guard;
3776
5399
  if (!guard) return;
@@ -3784,6 +5407,33 @@ function assertGuard(op, result) {
3784
5407
  }
3785
5408
  __name(assertGuard, "assertGuard");
3786
5409
 
5410
+ // src/engine/cancellation.ts
5411
+ function withQueryCancellation(application, control) {
5412
+ if (application === control) throw new Error("Query cancellation requires a separate control pool");
5413
+ const close = /* @__PURE__ */ __name(async (driver) => {
5414
+ const d = driver;
5415
+ if (d.close) await d.close();
5416
+ else if (d.end) await d.end();
5417
+ }, "close");
5418
+ return {
5419
+ unsafe: /* @__PURE__ */ __name((sql, params) => application.unsafe(sql, params), "unsafe"),
5420
+ begin: /* @__PURE__ */ __name((fn) => application.begin(fn), "begin"),
5421
+ async cancelBackend(pid, startedAt) {
5422
+ await control.unsafe(`SELECT pg_cancel_backend(pid) FROM pg_stat_activity
5423
+ WHERE pid=$1::integer AND backend_start=$2::timestamptz
5424
+ AND usename=session_user AND datname=current_database() AND backend_type='client backend'`, [
5425
+ pid,
5426
+ startedAt
5427
+ ]);
5428
+ },
5429
+ async close() {
5430
+ await close(application);
5431
+ await close(control);
5432
+ }
5433
+ };
5434
+ }
5435
+ __name(withQueryCancellation, "withQueryCancellation");
5436
+
3787
5437
  // src/decorators/registry.ts
3788
5438
  var ROUTES = /* @__PURE__ */ Symbol.for("palbase.backend.routes");
3789
5439
  var RETURN_BUFFER = /* @__PURE__ */ Symbol.for("palbase.backend.returnBuffer");
@@ -4671,10 +6321,13 @@ async function defaultSqlDriver(config) {
4671
6321
  if (!g.Bun?.SQL) {
4672
6322
  throw new BootRefused([], "boot refused: no SQL driver. Running outside Bun means the driver must be supplied \u2014 pass `sql` to createApp().");
4673
6323
  }
4674
- return new g.Bun.SQL({
6324
+ return withQueryCancellation(new g.Bun.SQL({
4675
6325
  url: config.databaseUrl,
4676
6326
  max: config.poolMax
4677
- });
6327
+ }), new g.Bun.SQL({
6328
+ url: config.databaseUrl,
6329
+ max: 2
6330
+ }));
4678
6331
  }
4679
6332
  __name(defaultSqlDriver, "defaultSqlDriver");
4680
6333
  var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([
@@ -4733,6 +6386,11 @@ async function createApp(opts) {
4733
6386
  const sql = opts.sql ?? await defaultSqlDriver(config);
4734
6387
  await sql.unsafe("select 1");
4735
6388
  setSchema(opts.schemas ?? []);
6389
+ setCursorKey(config.cursorSecret || config.serviceRoleKey || null);
6390
+ if (config.enableCommands) await installCommandExecutor(sql, [
6391
+ config.dbRole,
6392
+ config.dbServiceRole
6393
+ ]);
4736
6394
  setSecretReader(opts.secretReader ?? null);
4737
6395
  const auth = new AuthVerifier({
4738
6396
  jwksUrl: config.authJwksUrl,
@@ -4778,6 +6436,9 @@ async function createApp(opts) {
4778
6436
  role: config.dbRole,
4779
6437
  serviceRole: config.dbServiceRole,
4780
6438
  claimsJson: "{}"
6439
+ }, config.databaseDiagnostics === void 0 ? void 0 : {
6440
+ ...config.databaseDiagnostics,
6441
+ endpoint: "scheduled job"
4781
6442
  });
4782
6443
  try {
4783
6444
  const out = await runWithRuntime(buildServices(db), fn);
@@ -4807,6 +6468,10 @@ async function createApp(opts) {
4807
6468
  role: config.dbRole,
4808
6469
  serviceRole: config.dbServiceRole,
4809
6470
  claimsJson: JSON.stringify(callerClaims ?? {})
6471
+ }, config.databaseDiagnostics === void 0 ? void 0 : {
6472
+ ...config.databaseDiagnostics,
6473
+ endpoint: "upload authorization",
6474
+ requestId
4810
6475
  });
4811
6476
  try {
4812
6477
  const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId, makeRoleResolver(uploadDb.client));
@@ -4854,6 +6519,11 @@ async function createApp(opts) {
4854
6519
  role: config.dbRole,
4855
6520
  serviceRole: config.dbServiceRole,
4856
6521
  claimsJson: JSON.stringify(claims ?? {})
6522
+ }, config.databaseDiagnostics === void 0 && meta.options.databaseBudget === void 0 ? void 0 : {
6523
+ ...config.databaseDiagnostics,
6524
+ ...meta.options.databaseBudget,
6525
+ endpoint: hit.entry.id,
6526
+ requestId
4857
6527
  });
4858
6528
  const rolesOfCaller = makeRoleResolver(db.client);
4859
6529
  let dispatched = false;
@@ -5306,13 +6976,13 @@ async function loadDotEnv(root) {
5306
6976
  if (!line || line.startsWith("#")) continue;
5307
6977
  const eq = line.indexOf("=");
5308
6978
  if (eq < 1) continue;
5309
- const key = line.slice(0, eq).trim();
5310
- if (process.env[key] !== void 0) continue;
6979
+ const key2 = line.slice(0, eq).trim();
6980
+ if (process.env[key2] !== void 0) continue;
5311
6981
  let value = line.slice(eq + 1).trim();
5312
6982
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
5313
6983
  value = value.slice(1, -1);
5314
6984
  }
5315
- process.env[key] = value;
6985
+ process.env[key2] = value;
5316
6986
  }
5317
6987
  }
5318
6988
  __name(loadDotEnv, "loadDotEnv");