@palbase/backend 30.0.1 → 33.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 (69) hide show
  1. package/dist/bin/palbase-backend.cjs +210 -24
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +7 -7
  4. package/dist/{chunk-TWX6JTGJ.js → chunk-26GH54D2.js} +3 -5
  5. package/dist/{chunk-TWX6JTGJ.js.map → chunk-26GH54D2.js.map} +1 -1
  6. package/dist/{chunk-VVMJEVQP.js → chunk-BRLJOXWS.js} +2 -2
  7. package/dist/{chunk-3TUJWHC2.js → chunk-C4ZA5AT2.js} +378 -19
  8. package/dist/chunk-C4ZA5AT2.js.map +1 -0
  9. package/dist/{chunk-RADVOY7Y.js → chunk-DCDHAKF3.js} +147 -33
  10. package/dist/chunk-DCDHAKF3.js.map +1 -0
  11. package/dist/{chunk-ENZ2RFFJ.js → chunk-GOPZPM2A.js} +3 -310
  12. package/dist/chunk-GOPZPM2A.js.map +1 -0
  13. package/dist/{chunk-CGNN2PUH.js → chunk-KATPXCJ5.js} +37 -4
  14. package/dist/{chunk-CGNN2PUH.js.map → chunk-KATPXCJ5.js.map} +1 -1
  15. package/dist/{chunk-YRVIWHJC.js → chunk-N54QZER3.js} +23 -323
  16. package/dist/chunk-N54QZER3.js.map +1 -0
  17. package/dist/chunk-QMFOL2K6.js +371 -0
  18. package/dist/chunk-QMFOL2K6.js.map +1 -0
  19. package/dist/chunk-XOX6RFPZ.js +383 -0
  20. package/dist/chunk-XOX6RFPZ.js.map +1 -0
  21. package/dist/db/index.cjs +41 -2
  22. package/dist/db/index.cjs.map +1 -1
  23. package/dist/db/index.d.cts +1 -1
  24. package/dist/db/index.d.ts +1 -1
  25. package/dist/db/index.js +12 -13
  26. package/dist/engine/index.cjs +210 -24
  27. package/dist/engine/index.cjs.map +1 -1
  28. package/dist/engine/index.d.cts +3 -3
  29. package/dist/engine/index.d.ts +3 -3
  30. package/dist/engine/index.js +7 -7
  31. package/dist/{index-DtCgaZAg.d.cts → index-BWgnGj68.d.cts} +185 -35
  32. package/dist/{index-b-Q3l7W5.d.ts → index-Bi74dcOu.d.ts} +9 -2
  33. package/dist/{index-8qy3kIuA.d.ts → index-Dg10RnZO.d.ts} +185 -35
  34. package/dist/{index-Zi7MptvP.d.cts → index-g6iQyYci.d.cts} +9 -2
  35. package/dist/index.cjs +135 -8
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +6 -23
  38. package/dist/index.d.ts +6 -23
  39. package/dist/index.js +48 -48
  40. package/dist/index.js.map +1 -1
  41. package/dist/openapi/index.d.cts +2 -2
  42. package/dist/openapi/index.d.ts +2 -2
  43. package/dist/openapi/index.js +3 -4
  44. package/dist/openapi/index.js.map +1 -1
  45. package/dist/{registry-CEYLH-Iz.d.cts → registry-6VT5RPeO.d.cts} +1 -1
  46. package/dist/{registry-BGJ-Al6F.d.ts → registry-B-sxJJN0.d.ts} +1 -1
  47. package/dist/test/index.cjs +733 -17
  48. package/dist/test/index.cjs.map +1 -1
  49. package/dist/test/index.d.cts +59 -4
  50. package/dist/test/index.d.ts +59 -4
  51. package/dist/test/index.js +56 -16
  52. package/dist/test/index.js.map +1 -1
  53. package/docs/README.md +1 -1
  54. package/docs/auth.md +102 -20
  55. package/docs/database.md +54 -0
  56. package/docs/llms-full.txt +215 -21
  57. package/docs/schema.md +58 -0
  58. package/package.json +1 -1
  59. package/template/db/public.ts +23 -0
  60. package/template/package.json +1 -1
  61. package/dist/chunk-3TUJWHC2.js.map +0 -1
  62. package/dist/chunk-ENZ2RFFJ.js.map +0 -1
  63. package/dist/chunk-RADVOY7Y.js.map +0 -1
  64. package/dist/chunk-SG4UTNOP.js +0 -374
  65. package/dist/chunk-SG4UTNOP.js.map +0 -1
  66. package/dist/chunk-VXPNPVAG.js +0 -40
  67. package/dist/chunk-VXPNPVAG.js.map +0 -1
  68. package/dist/chunk-YRVIWHJC.js.map +0 -1
  69. /package/dist/{chunk-VVMJEVQP.js.map → chunk-BRLJOXWS.js.map} +0 -0
@@ -25,7 +25,8 @@ __export(test_exports, {
25
25
  api: () => api,
26
26
  createTestApi: () => createTestApi,
27
27
  fakeDatabase: () => fakeDatabase,
28
- isolated: () => isolated
28
+ isolated: () => isolated,
29
+ withServices: () => withServices
29
30
  });
30
31
  module.exports = __toCommonJS(test_exports);
31
32
 
@@ -53,8 +54,8 @@ var encoder = new TextEncoder();
53
54
  var hasher = null;
54
55
  async function digester() {
55
56
  if (hasher) return hasher;
56
- const runtime = globalThis;
57
- const nodeish = runtime.process?.versions?.node !== void 0 || runtime.process?.versions?.bun !== void 0;
57
+ const runtime2 = globalThis;
58
+ const nodeish = runtime2.process?.versions?.node !== void 0 || runtime2.process?.versions?.bun !== void 0;
58
59
  if (nodeish) {
59
60
  try {
60
61
  const mod = await import(
@@ -350,6 +351,14 @@ __name(isolated, "isolated");
350
351
 
351
352
  // src/db/input-guards.ts
352
353
  var REF_BRAND = /* @__PURE__ */ Symbol.for("palbase.db.ref");
354
+ function brandRef(v, kind) {
355
+ Object.defineProperty(v, REF_BRAND, {
356
+ value: kind,
357
+ enumerable: false
358
+ });
359
+ return v;
360
+ }
361
+ __name(brandRef, "brandRef");
353
362
  function brandOf(v) {
354
363
  if (typeof v !== "object" || v === null) return void 0;
355
364
  return Object.hasOwn(v, REF_BRAND) ? v[REF_BRAND] : void 0;
@@ -569,12 +578,130 @@ function makeRowHandle(op) {
569
578
  });
570
579
  }
571
580
  __name(makeRowHandle, "makeRowHandle");
581
+ function refDescriptor(v) {
582
+ if (typeof v !== "object" || v === null) return null;
583
+ const d = v[REF];
584
+ return isRefDescriptor(d) ? d : null;
585
+ }
586
+ __name(refDescriptor, "refDescriptor");
587
+ function isRefDescriptor(d) {
588
+ return typeof d === "object" && d !== null && typeof d.op === "number" && typeof d.field === "string";
589
+ }
590
+ __name(isRefDescriptor, "isRefDescriptor");
591
+ function rowOpIndex(v) {
592
+ if (typeof v !== "object" || v === null) return null;
593
+ const op = v[ROW];
594
+ return typeof op === "number" ? op : null;
595
+ }
596
+ __name(rowOpIndex, "rowOpIndex");
572
597
  function exprOf(v) {
573
598
  if (typeof v !== "object" || v === null) return null;
574
599
  const e = v[EXPR];
575
600
  return typeof e === "object" && e !== null ? e : null;
576
601
  }
577
602
  __name(exprOf, "exprOf");
603
+ function isRowsHandle(v) {
604
+ return typeof v === "object" && v !== null && v[ROWS] !== void 0;
605
+ }
606
+ __name(isRowsHandle, "isRowsHandle");
607
+ function encodeValue(value, column, allowColumnExpr) {
608
+ const ref = refDescriptor(value);
609
+ if (ref) return brandRef({
610
+ $ref: {
611
+ op: ref.op,
612
+ field: ref.field
613
+ }
614
+ }, "ref");
615
+ if (isNowExpr(value)) return {
616
+ $expr: {
617
+ fn: "now"
618
+ }
619
+ };
620
+ const expr = exprOf(value);
621
+ if (expr) {
622
+ if (expr.fn !== "now" && !allowColumnExpr) {
623
+ throw new TxPlanError(`\`${column}\`: ${expr.fn}() reads the column's current value, so it is only valid in updateWhere(where, set).`);
624
+ }
625
+ return {
626
+ $expr: expr
627
+ };
628
+ }
629
+ if (rowOpIndex(value) !== null) {
630
+ throw new TxPlanError(`\`${column}\`: a row handle is not a value. Read the column you meant (e.g. \`row.id\`).`);
631
+ }
632
+ if (isRowsHandle(value)) {
633
+ throw new TxPlanError(`\`${column}\`: an operation result is not a value. Declare an expectation first (\`.expectOne(err)\`) and read a column from the row.`);
634
+ }
635
+ assertNoNestedHandles(value, column);
636
+ return value;
637
+ }
638
+ __name(encodeValue, "encodeValue");
639
+ function encodeFilterValue(value, column) {
640
+ const ref = refDescriptor(value);
641
+ if (ref) return brandRef({
642
+ $ref: {
643
+ op: ref.op,
644
+ field: ref.field
645
+ }
646
+ }, "ref");
647
+ const expr = exprOf(value);
648
+ if (expr) {
649
+ throw new TxPlanError(`\`${column}\`: ${expr.fn}() bir YAZMA ifadesi, kar\u015F\u0131la\u015Ft\u0131rma de\u011Fil \u2014 filtrede kullan\u0131lamaz. Kolonu bir de\u011Ferle ya da col() ile kar\u015F\u0131la\u015Ft\u0131r\u0131n.`);
650
+ }
651
+ if (rowOpIndex(value) !== null) {
652
+ throw new TxPlanError(`\`${column}\`: a row handle is not a value. Read the column you meant (e.g. \`row.id\`).`);
653
+ }
654
+ if (isRowsHandle(value)) {
655
+ throw new TxPlanError(`\`${column}\`: an operation result is not a value. Declare an expectation first (\`.expectOne(err)\`) and read a column from the row.`);
656
+ }
657
+ if (Array.isArray(value)) return value.map((v) => encodeFilterValue(v, column));
658
+ if (value !== null && typeof value === "object" && !(value instanceof Date) && !isColRef(value) && !isSqlFragment(value)) {
659
+ const out = {};
660
+ for (const [k, v] of Object.entries(value)) {
661
+ out[k] = encodeFilterValue(v, column);
662
+ }
663
+ return out;
664
+ }
665
+ return value;
666
+ }
667
+ __name(encodeFilterValue, "encodeFilterValue");
668
+ function encodeFilterMap(map) {
669
+ const out = {};
670
+ for (const key of Object.keys(map).sort()) {
671
+ const value = map[key];
672
+ if (value === void 0) continue;
673
+ out[key] = encodeFilterValue(value, key);
674
+ }
675
+ return out;
676
+ }
677
+ __name(encodeFilterMap, "encodeFilterMap");
678
+ function assertNoNestedHandles(value, column) {
679
+ if (typeof value !== "object" || value === null) return;
680
+ if (value instanceof Date) return;
681
+ if (refDescriptor(value) || exprOf(value) || rowOpIndex(value) !== null || isRowsHandle(value)) {
682
+ throw new TxPlanError(`\`${column}\`: a plan handle is nested inside a value. The server would store it as literal JSON, not resolve it. Put the reference directly in the column.`);
683
+ }
684
+ if (Array.isArray(value)) {
685
+ for (const item of value) assertNoNestedHandles(item, column);
686
+ return;
687
+ }
688
+ for (const item of Object.values(value)) {
689
+ assertNoNestedHandles(item, column);
690
+ }
691
+ }
692
+ __name(assertNoNestedHandles, "assertNoNestedHandles");
693
+ function encodeMap(map, allowColumnExpr) {
694
+ const out = {};
695
+ for (const key of Object.keys(map).sort()) {
696
+ const value = map[key];
697
+ if (value === void 0) {
698
+ 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.`);
699
+ }
700
+ out[key] = encodeValue(value, key, allowColumnExpr);
701
+ }
702
+ return out;
703
+ }
704
+ __name(encodeMap, "encodeMap");
578
705
  var SKIPPED_OP = -1;
579
706
  var TxRowsImpl = class TxRowsImpl2 {
580
707
  static {
@@ -631,6 +758,225 @@ function assertGuardCount(n, fn) {
631
758
  }
632
759
  }
633
760
  __name(assertGuardCount, "assertGuardCount");
761
+ var MAX_OPS = 1e3;
762
+ var MAX_ROWS = 5e3;
763
+ var TxPlanBuilder = class {
764
+ static {
765
+ __name(this, "TxPlanBuilder");
766
+ }
767
+ ops = [];
768
+ /** Errors handed to expectations, indexed by the `slot` the server echoes. */
769
+ slots = [];
770
+ /** The table surface handed to the callback. Untyped here; the public
771
+ * `transaction()` signatures put the schema types on top. */
772
+ table(name) {
773
+ return {
774
+ insert: /* @__PURE__ */ __name((values) => {
775
+ const encoded = encodeMap(values, false);
776
+ if (Object.keys(encoded).length === 0) {
777
+ throw new TxPlanError(`${name}.insert() needs at least one column`);
778
+ }
779
+ return this.push({
780
+ op: "insert",
781
+ table: name,
782
+ values: encoded
783
+ }, `${name}.insert()`);
784
+ }, "insert"),
785
+ put: /* @__PURE__ */ __name((values, options) => {
786
+ const encoded = encodeMap(values, false);
787
+ if (Object.keys(encoded).length === 0) {
788
+ throw new TxPlanError(`${name}.put() needs at least one column`);
789
+ }
790
+ if (options.onConflict.length === 0) {
791
+ throw new TxPlanError(`${name}.put() needs at least one onConflict column`);
792
+ }
793
+ return this.push({
794
+ op: "upsert",
795
+ table: name,
796
+ values: encoded,
797
+ onConflict: options.onConflict
798
+ }, `${name}.upsert()`);
799
+ }, "put"),
800
+ insertMany: /* @__PURE__ */ __name((rows, opts) => {
801
+ if (rows.length === 0) {
802
+ return new TxRowsImpl(this, SKIPPED_OP, `${name}.insertMany()`);
803
+ }
804
+ if (rows.length > MAX_ROWS) {
805
+ throw new TxPlanError(`${name}.insertMany() has ${rows.length} rows; the limit is ${MAX_ROWS}. Split the write across requests.`);
806
+ }
807
+ const encoded = rows.map((row) => encodeMap(row, false));
808
+ assertUniformRows(encoded, name);
809
+ if (opts !== void 0 && opts.onConflict.length === 0) {
810
+ throw new TxPlanError(`${name}.insertMany() was given a conflict action with no onConflict columns. Postgres matches a collision on columns, so name them.`);
811
+ }
812
+ return this.push({
813
+ op: "insertMany",
814
+ table: name,
815
+ rows: encoded,
816
+ // Omitted entirely when no options were given, so the op a plain
817
+ // insertMany produces is byte-identical to the one it produced
818
+ // before this option existed.
819
+ ...opts !== void 0 ? {
820
+ onConflict: opts.onConflict,
821
+ action: opts.action ?? "ignore"
822
+ } : {}
823
+ }, `${name}.insertMany()`);
824
+ }, "insertMany"),
825
+ updateWhere: /* @__PURE__ */ __name((where, set) => {
826
+ const encodedWhere = encodeFilterMap(where);
827
+ const encodedSet = encodeMap(set, true);
828
+ if (Object.keys(encodedWhere).length === 0) {
829
+ throw new TxPlanError(`${name}.updateWhere() needs a filter. An update with no filter rewrites the whole table.`);
830
+ }
831
+ if (Object.keys(encodedSet).length === 0) {
832
+ throw new TxPlanError(`${name}.updateWhere() needs at least one column to set`);
833
+ }
834
+ return this.push({
835
+ op: "update",
836
+ table: name,
837
+ set: encodedSet,
838
+ where: encodedWhere
839
+ }, `${name}.updateWhere()`);
840
+ }, "updateWhere"),
841
+ deleteWhere: /* @__PURE__ */ __name((where) => {
842
+ const encodedWhere = encodeFilterMap(where);
843
+ if (Object.keys(encodedWhere).length === 0) {
844
+ throw new TxPlanError(`${name}.deleteWhere() needs a filter. A delete with no filter empties the table.`);
845
+ }
846
+ return this.push({
847
+ op: "delete",
848
+ table: name,
849
+ where: encodedWhere
850
+ }, `${name}.deleteWhere()`);
851
+ }, "deleteWhere"),
852
+ select: /* @__PURE__ */ __name((where, options) => {
853
+ const op = {
854
+ op: "select",
855
+ table: name
856
+ };
857
+ const encodedWhere = encodeFilterMap(where ?? {});
858
+ if (Object.keys(encodedWhere).length > 0) op.where = encodedWhere;
859
+ if (options?.limit !== void 0) {
860
+ if (!Number.isInteger(options.limit) || options.limit < 0) {
861
+ throw new TxPlanError(`${name}.select(): limit needs a non-negative integer, got ${String(options.limit)}`);
862
+ }
863
+ op.limit = options.limit;
864
+ }
865
+ if (options?.lock !== void 0) op.lock = options.lock;
866
+ return this.push(op, `${name}.select()`);
867
+ }, "select")
868
+ };
869
+ }
870
+ push(op, what) {
871
+ if (this.ops.length >= MAX_OPS) {
872
+ throw new TxPlanError(`this transaction has ${MAX_OPS} operations, which is the limit. Use insertMany() for bulk writes, or split the work across requests.`);
873
+ }
874
+ const index = this.ops.length;
875
+ this.ops.push(op);
876
+ return new TxRowsImpl(this, index, what);
877
+ }
878
+ /** Attach an expectation to an op and record its error in the slot table. */
879
+ attachGuard(opIndex, kind, n, error) {
880
+ const op = this.ops[opIndex];
881
+ if (!op) throw new TxPlanError(`internal: expectation on unknown operation ${opIndex}`);
882
+ const slot = this.slots.length;
883
+ this.slots.push(error);
884
+ op.guard = {
885
+ kind,
886
+ n,
887
+ slot
888
+ };
889
+ }
890
+ /** The serialisable plan. Empty when the callback described no writes. */
891
+ body() {
892
+ return {
893
+ ops: this.ops
894
+ };
895
+ }
896
+ /** The error the server's `slot` selects, or `null` when it names one this
897
+ * plan never declared (a server/client disagreement, not a tenant error). */
898
+ errorForSlot(slot) {
899
+ return this.slots[slot] ?? null;
900
+ }
901
+ };
902
+ function assertUniformRows(rows, table) {
903
+ const first = rows[0];
904
+ if (!first) return;
905
+ const want = Object.keys(first);
906
+ const wantKey = want.join(",");
907
+ for (let i = 1; i < rows.length; i++) {
908
+ const got = Object.keys(rows[i]);
909
+ if (got.join(",") !== wantKey) {
910
+ throw new TxPlanError(`${table}.insertMany(): every row must set the same columns. Row 0 sets [${want.join(", ")}] but row ${i} sets [${got.join(", ")}]. (A property set to \`undefined\` counts as absent \u2014 use \`null\`.)`);
911
+ }
912
+ }
913
+ }
914
+ __name(assertUniformRows, "assertUniformRows");
915
+ function materializeResult(value, results) {
916
+ const ref = refDescriptor(value);
917
+ if (ref) {
918
+ const row = rowOf(results, ref.op, `\`${ref.field}\``);
919
+ if (!(ref.field in row)) {
920
+ throw new TxPlanError(`the transaction's operation ${ref.op} returned no column \`${ref.field}\`.`);
921
+ }
922
+ return row[ref.field];
923
+ }
924
+ const rowOp = rowOpIndex(value);
925
+ if (rowOp !== null) return rowOf(results, rowOp, "a row");
926
+ if (isRowsHandle(value)) {
927
+ throw new TxPlanError("an operation result cannot be returned from a transaction callback: its row count is not known until the plan runs. Declare an expectation (`.expectOne(err)`) and return the row, or a column of it.");
928
+ }
929
+ if (Array.isArray(value)) return value.map((item) => materializeResult(item, results));
930
+ if (isPlainObject(value)) {
931
+ const out = {};
932
+ for (const [key, item] of Object.entries(value)) out[key] = materializeResult(item, results);
933
+ return out;
934
+ }
935
+ return value;
936
+ }
937
+ __name(materializeResult, "materializeResult");
938
+ function rowOf(results, opIndex, what) {
939
+ const result = results[opIndex];
940
+ if (!result) {
941
+ throw new TxPlanError(`the transaction returned no result for operation ${opIndex}, so ${what} cannot be read.`);
942
+ }
943
+ const row = result.rows[0];
944
+ if (!row) {
945
+ throw new TxPlanError(`the transaction's operation ${opIndex} returned no row, so ${what} cannot be read.`);
946
+ }
947
+ return row;
948
+ }
949
+ __name(rowOf, "rowOf");
950
+ function isPlainObject(value) {
951
+ if (typeof value !== "object" || value === null) return false;
952
+ const proto = Object.getPrototypeOf(value);
953
+ return proto === Object.prototype || proto === null;
954
+ }
955
+ __name(isPlainObject, "isPlainObject");
956
+ async function runTxPlan(transport, handle, builder, fn) {
957
+ const returned = fn(handle);
958
+ const body = builder.body();
959
+ if (body.ops.length === 0) {
960
+ return materializeResult(returned, []);
961
+ }
962
+ let response;
963
+ try {
964
+ response = await transport.txPlan(body);
965
+ } catch (err) {
966
+ throw translateRejection(err, builder);
967
+ }
968
+ return materializeResult(returned, response.results);
969
+ }
970
+ __name(runTxPlan, "runTxPlan");
971
+ function translateRejection(err, builder) {
972
+ if (typeof err !== "object" || err === null) return err;
973
+ const rejection = err;
974
+ if (rejection.error_code !== "tx_guard_failed" || typeof rejection.slot !== "number") {
975
+ return err;
976
+ }
977
+ return builder.errorForSlot(rejection.slot) ?? err;
978
+ }
979
+ __name(translateRejection, "translateRejection");
634
980
 
635
981
  // src/__tests__/helpers/mock-db.ts
636
982
  function refuseFragment(caller, table, where) {
@@ -828,7 +1174,7 @@ function createMockDB() {
828
1174
  // ops write to, so a test that writes three rows and counts them gets 3 —
829
1175
  // a mock that answered 0 would make the surface look broken in exactly the
830
1176
  // tests meant to prove it works.
831
- async updateMany(table, where, set) {
1177
+ async updateMany(table, where, set, opts) {
832
1178
  if (Object.keys(where).length === 0) throw new Error(`updateMany(${table}): bo\u015F filtre`);
833
1179
  assertUsableFilter("updateMany", table, where);
834
1180
  refuseFragment("updateMany", table, where);
@@ -848,7 +1194,8 @@ function createMockDB() {
848
1194
  row[k] = v;
849
1195
  }
850
1196
  }
851
- return hit;
1197
+ for (const row of hit) track(tracked.updated, table, row);
1198
+ return opts?.returning === false ? hit.length : hit;
852
1199
  },
853
1200
  async deleteMany(table, where) {
854
1201
  if (Object.keys(where).length === 0) throw new Error(`deleteMany(${table}): bo\u015F filtre`);
@@ -925,7 +1272,7 @@ function createMockDB() {
925
1272
  const vals = bucket.map((r) => r[c]).filter((v) => v !== null && v !== void 0);
926
1273
  (out["avg"] ??= {})[c] = vals.length === 0 ? null : String(Number(vals.reduce((a, v) => addDecimal(a, v, 1), "0")) / vals.length);
927
1274
  }
928
- for (const [fn, pick] of [
1275
+ for (const [fn, pick2] of [
929
1276
  [
930
1277
  "min",
931
1278
  -1
@@ -937,7 +1284,7 @@ function createMockDB() {
937
1284
  ]) {
938
1285
  for (const c of q[fn] ?? []) {
939
1286
  const vals = bucket.map((r) => r[c]).filter((v) => v !== null && v !== void 0);
940
- (out[fn] ??= {})[c] = vals.length === 0 ? null : vals.reduce((a, v) => cmp(v, a, pick === 1 ? "gt" : "lt") ? v : a);
1287
+ (out[fn] ??= {})[c] = vals.length === 0 ? null : vals.reduce((a, v) => cmp(v, a, pick2 === 1 ? "gt" : "lt") ? v : a);
941
1288
  }
942
1289
  }
943
1290
  if (q.count === true) out["count"] = bucket.length;
@@ -1086,23 +1433,20 @@ function createMockDB() {
1086
1433
  applied[k] = v;
1087
1434
  }
1088
1435
  assertNoExpressionHandles("update", table, Object.keys(applied), applied);
1089
- const updated = idx >= 0 ? {
1436
+ if (idx < 0) return null;
1437
+ const updated = {
1090
1438
  ...rows[idx],
1091
1439
  ...applied
1092
- } : {
1093
- id,
1094
- ...applied
1095
1440
  };
1096
- if (idx >= 0) {
1097
- rows[idx] = updated;
1098
- }
1441
+ rows[idx] = updated;
1099
1442
  track(tracked.updated, table, updated);
1100
1443
  return updated;
1101
1444
  },
1102
1445
  async delete(table, id) {
1103
1446
  const rows = store.get(table) ?? [];
1104
1447
  const idx = rows.findIndex((r) => r["id"] === id);
1105
- if (idx >= 0) rows.splice(idx, 1);
1448
+ if (idx < 0) return;
1449
+ rows.splice(idx, 1);
1106
1450
  const list = tracked.deleted.get(table);
1107
1451
  if (list) list.push(id);
1108
1452
  else tracked.deleted.set(table, [
@@ -1120,6 +1464,9 @@ function createMockDB() {
1120
1464
  async findMany(table, query, opts) {
1121
1465
  assertUsableFilter("findMany", table, query);
1122
1466
  refuseFragment("findMany", table, query);
1467
+ if (opts?.with !== void 0 && Object.keys(opts.with).length > 0) {
1468
+ throw new Error(`findMany(${table}): fakeDatabase \`with\` desteklemiyor \u2014 ili\u015Fki grafi\u011Fi yabanc\u0131 anahtarlardan T\xDCRET\u0130L\u0130YOR ve fake \u015Femay\u0131 okumuyor. \u0130li\u015Fkili sat\u0131rlar\u0131 \xF6l\xE7en bir test ger\xE7ek motoru kullanmal\u0131; fake ile \xF6l\xE7mek istiyorsan\u0131z ilgili sat\u0131rlar\u0131 \`seed\` ile ayr\u0131 koyup ayr\u0131 sorgulay\u0131n.`);
1469
+ }
1123
1470
  const rows = store.get(table) ?? [];
1124
1471
  let out = query ? rows.filter((row) => rowMatchesFilter("findMany", table, row, query)) : [
1125
1472
  ...rows
@@ -1401,6 +1748,340 @@ function txRejection(status, code, extra) {
1401
1748
  }
1402
1749
  __name(txRejection, "txRejection");
1403
1750
 
1751
+ // src/runtime.ts
1752
+ var import_node_async_hooks = require("async_hooks");
1753
+
1754
+ // src/errors.ts
1755
+ var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
1756
+ var HttpError = class extends Error {
1757
+ static {
1758
+ __name(this, "HttpError");
1759
+ }
1760
+ status;
1761
+ error;
1762
+ errorDescription;
1763
+ data;
1764
+ /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
1765
+ [HTTP_ERROR_BRAND] = true;
1766
+ constructor(status, error, errorDescription, data) {
1767
+ super(errorDescription);
1768
+ this.name = "HttpError";
1769
+ this.status = status;
1770
+ this.error = error;
1771
+ this.errorDescription = errorDescription;
1772
+ if (data !== void 0) {
1773
+ this.data = data;
1774
+ }
1775
+ }
1776
+ /**
1777
+ * Serialize to the standard Palbase error response format.
1778
+ * The `requestId` is injected by the runtime layer from the request context.
1779
+ * When called without arguments (e.g. JSON.stringify), request_id is omitted.
1780
+ * When `data` is set, it is appended as a strict-superset field.
1781
+ */
1782
+ toJSON(requestId) {
1783
+ const result = {
1784
+ error: this.error,
1785
+ error_description: this.errorDescription,
1786
+ status: this.status
1787
+ };
1788
+ if (requestId) {
1789
+ result.request_id = requestId;
1790
+ }
1791
+ if (this.data !== void 0) {
1792
+ result.data = this.data;
1793
+ }
1794
+ return result;
1795
+ }
1796
+ };
1797
+ function isRetryable(e) {
1798
+ return typeof e === "object" && e !== null && e.retryable === true && typeof e.sqlstate === "string";
1799
+ }
1800
+ __name(isRetryable, "isRetryable");
1801
+
1802
+ // src/db/typed-db.ts
1803
+ async function withRetry(fn, opts = {}) {
1804
+ const budget = opts.retry ?? 0;
1805
+ let attempt = 0;
1806
+ for (; ; ) {
1807
+ try {
1808
+ return await fn();
1809
+ } catch (e) {
1810
+ if (attempt >= budget || !isRetryable(e)) throw e;
1811
+ attempt += 1;
1812
+ }
1813
+ }
1814
+ }
1815
+ __name(withRetry, "withRetry");
1816
+
1817
+ // src/db/schema-json.ts
1818
+ function qualifiedTableKey(schemaName, tableName) {
1819
+ return schemaName === "" || schemaName === "public" ? tableName : `${schemaName}.${tableName}`;
1820
+ }
1821
+ __name(qualifiedTableKey, "qualifiedTableKey");
1822
+
1823
+ // src/runtime.ts
1824
+ var REQUEST_ALS = /* @__PURE__ */ Symbol.for("palbase.backend.requestALS");
1825
+ var __requestALS = (() => {
1826
+ const g = globalThis;
1827
+ return g[REQUEST_ALS] ??= new import_node_async_hooks.AsyncLocalStorage();
1828
+ })();
1829
+ var runtime = null;
1830
+ var REQUIRED_SERVICES = [
1831
+ "Database",
1832
+ "Auth",
1833
+ "Secrets",
1834
+ "Documents",
1835
+ "Storage",
1836
+ "Cache",
1837
+ "Log",
1838
+ "Notifications",
1839
+ "Flags",
1840
+ "Realtime"
1841
+ ];
1842
+ function refuseIncompleteBox(services) {
1843
+ const missing = REQUIRED_SERVICES.filter((k) => services[k] === void 0);
1844
+ if (missing.length > 0) {
1845
+ throw new Error(`the request scope is missing ${missing.join(", ")} \u2014 this box was most likely written by a DIFFERENT @palbase/backend major sharing the same process (the request store is a process-wide well-known Symbol, deliberately, so two bundles of ONE version can share it). Align the versions, or pass every service when building the scope yourself.`);
1846
+ }
1847
+ return services;
1848
+ }
1849
+ __name(refuseIncompleteBox, "refuseIncompleteBox");
1850
+ function __getRuntime() {
1851
+ const scoped = __requestALS.getStore();
1852
+ if (scoped) return refuseIncompleteBox(scoped.runtime);
1853
+ if (runtime === null) {
1854
+ throw new Error("Palbase services accessed outside a request scope. The Database/Documents/\u2026 singletons are only available inside an endpoint handler (or after the runtime has called __runWithRuntime / __setRuntime).");
1855
+ }
1856
+ return runtime;
1857
+ }
1858
+ __name(__getRuntime, "__getRuntime");
1859
+ function makeServiceProxy(key) {
1860
+ const handler = {
1861
+ get(_target, prop, receiver) {
1862
+ const client = __getRuntime()[key];
1863
+ const value = Reflect.get(client, prop, receiver);
1864
+ return typeof value === "function" ? value.bind(client) : value;
1865
+ }
1866
+ };
1867
+ return new Proxy({}, handler);
1868
+ }
1869
+ __name(makeServiceProxy, "makeServiceProxy");
1870
+ function makeTableProxy(ops, prefix) {
1871
+ return new Proxy({}, {
1872
+ get(_t, prop) {
1873
+ if (typeof prop !== "string") return void 0;
1874
+ const name = `${prefix}${prop}`;
1875
+ return {
1876
+ insert: /* @__PURE__ */ __name((data) => ops().insert(name, data), "insert"),
1877
+ // DÖRDÜNCÜ FİİL, aynı gerekçeyle: tip söz veriyor, ops katmanı
1878
+ // uyguluyor, ve bir handler'ın gerçekten dokunduğu yer BURASI.
1879
+ // `runtime-table-verbs` kapısı bunu adıyla saydı.
1880
+ aggregate: /* @__PURE__ */ __name((q) => ops().aggregate(name, q), "aggregate"),
1881
+ insertMany: /* @__PURE__ */ __name((rows, opts) => ops().insertMany(name, rows, opts), "insertMany"),
1882
+ update: /* @__PURE__ */ __name((q) => ops().update(name, q.where.id, q.set), "update"),
1883
+ delete: /* @__PURE__ */ __name((id) => ops().delete(name, id), "delete"),
1884
+ findById: /* @__PURE__ */ __name((id) => ops().findById(name, id), "findById"),
1885
+ findMany: /* @__PURE__ */ __name((q) => {
1886
+ const { where, ...opts } = q ?? {};
1887
+ return ops().findMany(name, where, opts);
1888
+ }, "findMany"),
1889
+ put: /* @__PURE__ */ __name((q) => ops().put(name, q.data, {
1890
+ onConflict: q.onConflict
1891
+ }), "put"),
1892
+ // THREE VERBS THE TYPE PROMISED AND THIS PROXY DID NOT EMIT.
1893
+ //
1894
+ // `EnvTypedTableBase` declares `updateMany`, `deleteMany` and `count`
1895
+ // (typed-db.ts) and the ops layer implements all three — only this
1896
+ // proxy, which is what a handler actually touches, left them out. So
1897
+ // the type said the verb exists, autocomplete offered it, and the call
1898
+ // answered `undefined is not a function`.
1899
+ //
1900
+ // Older than this run, but the run rewrote this proxy for
1901
+ // `Database.schema(name).tables.*` and would have carried the gap onto
1902
+ // the new surface too.
1903
+ updateMany: /* @__PURE__ */ __name((q) => ops().updateMany(name, q.where, q.set), "updateMany"),
1904
+ deleteMany: /* @__PURE__ */ __name((q) => ops().deleteMany(name, q.where), "deleteMany"),
1905
+ count: /* @__PURE__ */ __name((q) => ops().count(name, q?.where), "count"),
1906
+ search: /* @__PURE__ */ __name((params) => ops().search(name, params), "search"),
1907
+ similar: /* @__PURE__ */ __name((id, params) => ops().similar(name, id, params), "similar"),
1908
+ recommend: /* @__PURE__ */ __name((params) => ops().recommend(name, params), "recommend"),
1909
+ facets: /* @__PURE__ */ __name((params) => ops().facets(name, params), "facets"),
1910
+ supersede: /* @__PURE__ */ __name((id, row) => ops().supersede(name, id, row), "supersede"),
1911
+ claim: /* @__PURE__ */ __name((unique, extra) => ops().claim(name, unique, extra), "claim")
1912
+ };
1913
+ }
1914
+ });
1915
+ }
1916
+ __name(makeTableProxy, "makeTableProxy");
1917
+ var rawDatabase = makeServiceProxy("Database");
1918
+ function makeTypedSurface(raw) {
1919
+ const reco = raw;
1920
+ const ops = {
1921
+ $query: /* @__PURE__ */ __name((sql, params) => raw.query(sql, params), "$query"),
1922
+ $insert: /* @__PURE__ */ __name((table, data) => raw.insert(table, data), "$insert"),
1923
+ $update: /* @__PURE__ */ __name((table, id, data) => raw.update(table, id, data), "$update"),
1924
+ $delete: /* @__PURE__ */ __name((table, id) => raw.delete(table, id), "$delete"),
1925
+ $findById: /* @__PURE__ */ __name((table, id) => raw.findById(table, id), "$findById"),
1926
+ $findMany: /* @__PURE__ */ __name((table, query, opts) => raw.findMany(table, query, opts), "$findMany"),
1927
+ $put: /* @__PURE__ */ __name((table, data, opts) => raw.put(table, data, opts), "$put"),
1928
+ // `opts` İLETİLİR. Düşürüldüğü sürece `Database.$updateMany(t, w, s,
1929
+ // { returning: false })` sessizce SATIRLARI döndürüyordu, sayıyı değil —
1930
+ // ve `if (n === 0) throw new Conflict(...)` guard'ı HİÇ çalışmıyordu,
1931
+ // çünkü `[] === 0` yanlıştır. Komşu forwarder'lar (`$findMany`, `$put`)
1932
+ // kendi opsiyonlarını zaten iletiyordu; bu biri unutulmuştu.
1933
+ $updateMany: /* @__PURE__ */ __name((table, where, set, opts) => raw.updateMany(table, where, set, opts), "$updateMany"),
1934
+ $deleteMany: /* @__PURE__ */ __name((table, where) => raw.deleteMany(table, where), "$deleteMany"),
1935
+ $count: /* @__PURE__ */ __name((table, where) => raw.count(table, where), "$count"),
1936
+ $search: /* @__PURE__ */ __name((table, params) => raw.search(table, params), "$search"),
1937
+ $similar: /* @__PURE__ */ __name((table, id, params) => reco.similar(table, id, params), "$similar"),
1938
+ $recommend: /* @__PURE__ */ __name((table, params) => reco.recommend(table, params), "$recommend"),
1939
+ $facets: /* @__PURE__ */ __name((table, params) => reco.facets(table, params), "$facets"),
1940
+ $claim: /* @__PURE__ */ __name((table, unique, extra) => reco.claim(table, unique, extra), "$claim"),
1941
+ $lockRows: /* @__PURE__ */ __name((table, ids) => reco.lockRows(table, ids), "$lockRows"),
1942
+ $advisoryXactLock: /* @__PURE__ */ __name((key) => reco.advisoryXactLock(key), "$advisoryXactLock"),
1943
+ $aggregate: /* @__PURE__ */ __name((table, q) => raw.aggregate(table, q), "$aggregate"),
1944
+ $insertMany: /* @__PURE__ */ __name((table, rows, opts) => raw.insertMany(table, rows, opts), "$insertMany"),
1945
+ $supersede: /* @__PURE__ */ __name((table, id, row) => raw.supersede(table, id, row), "$supersede")
1946
+ };
1947
+ const base = Object.assign(ops, {
1948
+ // Both surfaces get it: a savepoint on the service transaction is as useful
1949
+ // as one on the request's, and each is bound to its own connection.
1950
+ $attempt: /* @__PURE__ */ __name((fn) => raw.attempt(fn), "$attempt"),
1951
+ $transaction(fn, opts) {
1952
+ return withRetry(() => {
1953
+ const builder = new TxPlanBuilder();
1954
+ return runTxPlan(raw, makeTxPlanHandle(builder), builder, fn);
1955
+ }, opts ?? {});
1956
+ }
1957
+ });
1958
+ return new Proxy(base, {
1959
+ get(target, prop, receiver) {
1960
+ if (prop === "tables") return makeTableProxy(() => reco, "");
1961
+ if (typeof prop === "string" && !prop.startsWith("$") && !(prop in target)) {
1962
+ return makeTableProxy(() => reco, qualifiedTableKey(prop, ""));
1963
+ }
1964
+ return Reflect.get(target, prop, receiver);
1965
+ }
1966
+ });
1967
+ }
1968
+ __name(makeTypedSurface, "makeTypedSurface");
1969
+ function makeTxTablesAccessor(builder, prefix = "") {
1970
+ const tablesProxy = new Proxy({}, {
1971
+ get(_t, prop) {
1972
+ if (typeof prop !== "string") return void 0;
1973
+ return builder.table(prefix + prop);
1974
+ }
1975
+ });
1976
+ return tablesProxy;
1977
+ }
1978
+ __name(makeTxTablesAccessor, "makeTxTablesAccessor");
1979
+ function makeTxPlanHandle(builder) {
1980
+ const publicTables = makeTxTablesAccessor(builder);
1981
+ return new Proxy({}, {
1982
+ get(_t, prop) {
1983
+ if (typeof prop !== "string") return void 0;
1984
+ if (prop === "tables") return publicTables;
1985
+ return makeTxTablesAccessor(builder, qualifiedTableKey(prop, ""));
1986
+ }
1987
+ });
1988
+ }
1989
+ __name(makeTxPlanHandle, "makeTxPlanHandle");
1990
+ var Database = Object.assign(makeTypedSurface(rawDatabase), {
1991
+ /**
1992
+ * Lazily resolve the runtime's service-role sibling on each call. We do NOT
1993
+ * cache it: `rawDatabase.asService()` reads the CURRENT request scope through
1994
+ * the runtime proxy, and the per-request runtime injects a service client
1995
+ * bound to that request's identity headers — caching would leak one request's
1996
+ * sibling into another concurrent request.
1997
+ */
1998
+ $asService() {
1999
+ return makeTypedSurface(rawDatabase.asService());
2000
+ }
2001
+ });
2002
+ var Documents = makeServiceProxy("Documents");
2003
+ function makeBucketsAccessor(storage) {
2004
+ return new Proxy({}, {
2005
+ get(_t, prop) {
2006
+ if (typeof prop !== "string") return void 0;
2007
+ return storage().bucket(prop);
2008
+ }
2009
+ });
2010
+ }
2011
+ __name(makeBucketsAccessor, "makeBucketsAccessor");
2012
+ var rawStorage = makeServiceProxy("Storage");
2013
+ var Storage = Object.assign({
2014
+ // FORWARDED explicitly, not assigned onto the service proxy.
2015
+ //
2016
+ // `Object.assign(rawStorage, {buckets})` writes onto the proxy's TARGET, and
2017
+ // the proxy's only trap is `get`, which forwards every read to the module
2018
+ // client — so the property landed somewhere nothing reads and
2019
+ // `Storage.buckets.docs` was `undefined` in a deployed handler. It
2020
+ // typechecked, every test passed, and the live call answered
2021
+ // "TypeError: undefined is not an object". `Database` never had the bug
2022
+ // because it builds a plain surface the same way this now does.
2023
+ bucket: /* @__PURE__ */ __name((name) => rawStorage.bucket(name), "bucket")
2024
+ }, {
2025
+ buckets: makeBucketsAccessor(() => rawStorage)
2026
+ });
2027
+ var Cache = makeServiceProxy("Cache");
2028
+ var Secrets = makeServiceProxy("Secrets");
2029
+ var Auth = makeServiceProxy("Auth");
2030
+ var Log = makeServiceProxy("Log");
2031
+ var Notifications = makeServiceProxy("Notifications");
2032
+ var rawFlags = makeServiceProxy("Flags");
2033
+ var Flags = Object.assign({
2034
+ isEnabled(flagName, context) {
2035
+ return rawFlags.isEnabled(flagName, context);
2036
+ },
2037
+ getVariant(flagName, context) {
2038
+ return rawFlags.getVariant(flagName, context);
2039
+ },
2040
+ getAll(context) {
2041
+ return rawFlags.getAll(context);
2042
+ },
2043
+ /**
2044
+ * Resolve a flag's value, with an optional fallback.
2045
+ *
2046
+ * FORWARDED as of 2026-08-15. This surface is written out by hand, method
2047
+ * by method, and `get` was missing from it — so the client implemented it,
2048
+ * thirty assertions covered it, and `Flags.get("x")` was `undefined` in a
2049
+ * deployed handler. Exactly the shape of the `Storage.buckets` defect found
2050
+ * the same day: a hand-maintained forwarding list is a list somebody has to
2051
+ * remember to update.
2052
+ */
2053
+ get(flagName, defaultOrContext, maybeContext) {
2054
+ return rawFlags.get(flagName, defaultOrContext, maybeContext);
2055
+ },
2056
+ setOverride(key, value) {
2057
+ return rawFlags.setOverride(key, value);
2058
+ }
2059
+ }, {
2060
+ /**
2061
+ * Lazily resolve the runtime's cross-user sibling on each call. We do NOT
2062
+ * cache it: `rawFlags.asService()` reads the CURRENT request scope through
2063
+ * the runtime proxy, so caching would leak one request's sibling into
2064
+ * another concurrent request. Mirrors `Database.$asService()`.
2065
+ */
2066
+ $asService() {
2067
+ return rawFlags.asService();
2068
+ },
2069
+ /**
2070
+ * The name this member carried before 32.0.0.
2071
+ *
2072
+ * It does NOT work silently. A retired member that quietly keeps returning
2073
+ * is how a rename becomes a mystery: the old call site goes on compiling,
2074
+ * the new name never spreads, and the two live side by side until somebody
2075
+ * greps for one and misses half the codebase. `Database` made this split
2076
+ * first (`$asService`), and this JSDoc promised "exactly like
2077
+ * Database.$asService()" while the promise went unkept.
2078
+ */
2079
+ asService() {
2080
+ throw new Error("Flags.asService() was renamed to Flags.$asService() in 32.0.0 \u2014 system members carry the `$` prefix, like Database.$asService().");
2081
+ }
2082
+ });
2083
+ var Realtime = makeServiceProxy("Realtime");
2084
+
1404
2085
  // src/test/fake-db.ts
1405
2086
  function fakeDatabase() {
1406
2087
  const mock = createMockDB();
@@ -1415,7 +2096,8 @@ function fakeDatabase() {
1415
2096
  }, "query")
1416
2097
  });
1417
2098
  return {
1418
- db,
2099
+ db: makeTypedSurface(db),
2100
+ raw: db,
1419
2101
  queries,
1420
2102
  seed: /* @__PURE__ */ __name((table, rows) => mock.seed(table, rows), "seed"),
1421
2103
  inserted: /* @__PURE__ */ __name((table) => mock.inserted(table), "inserted"),
@@ -1424,12 +2106,46 @@ function fakeDatabase() {
1424
2106
  };
1425
2107
  }
1426
2108
  __name(fakeDatabase, "fakeDatabase");
2109
+
2110
+ // src/test/with-services.ts
2111
+ function absent(name) {
2112
+ return new Proxy({}, {
2113
+ get(_target, prop) {
2114
+ throw new Error(`${String(name)}.${String(prop)} was read, but this test did not provide ${String(name)}. Pass it: withServices({ ${String(name)}: \u2026 }, () => \u2026)`);
2115
+ }
2116
+ });
2117
+ }
2118
+ __name(absent, "absent");
2119
+ function pick(services, key) {
2120
+ return services[key] ?? absent(key);
2121
+ }
2122
+ __name(pick, "pick");
2123
+ function withServices(services, fn) {
2124
+ const filled = {
2125
+ Database: pick(services, "Database"),
2126
+ Auth: pick(services, "Auth"),
2127
+ Secrets: pick(services, "Secrets"),
2128
+ Documents: pick(services, "Documents"),
2129
+ Storage: pick(services, "Storage"),
2130
+ Cache: pick(services, "Cache"),
2131
+ Log: pick(services, "Log"),
2132
+ Notifications: pick(services, "Notifications"),
2133
+ Flags: pick(services, "Flags"),
2134
+ Realtime: pick(services, "Realtime")
2135
+ };
2136
+ return __requestALS.run({
2137
+ runtime: filled,
2138
+ userId: null
2139
+ }, fn);
2140
+ }
2141
+ __name(withServices, "withServices");
1427
2142
  // Annotate the CommonJS export names for ESM import in node:
1428
2143
  0 && (module.exports = {
1429
2144
  TestApiError,
1430
2145
  api,
1431
2146
  createTestApi,
1432
2147
  fakeDatabase,
1433
- isolated
2148
+ isolated,
2149
+ withServices
1434
2150
  });
1435
2151
  //# sourceMappingURL=index.cjs.map