@palbase/backend 30.0.0 → 32.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 (67) hide show
  1. package/dist/bin/palbase-backend.cjs +136 -3
  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-5J5W75A6.js → chunk-5C5UCILO.js} +27 -325
  7. package/dist/chunk-5C5UCILO.js.map +1 -0
  8. package/dist/{chunk-VN7NMDUH.js → chunk-AZJIKCOR.js} +113 -15
  9. package/dist/chunk-AZJIKCOR.js.map +1 -0
  10. package/dist/{chunk-VVMJEVQP.js → chunk-BRLJOXWS.js} +2 -2
  11. package/dist/{chunk-3TUJWHC2.js → chunk-IXAX5CON.js} +378 -19
  12. package/dist/chunk-IXAX5CON.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-KGP6ALIU.js +361 -0
  16. package/dist/chunk-KGP6ALIU.js.map +1 -0
  17. package/dist/chunk-XEGZ3S2Q.js +346 -0
  18. package/dist/chunk-XEGZ3S2Q.js.map +1 -0
  19. package/dist/{chunk-ENZ2RFFJ.js → chunk-Z4CZRMNF.js} +3 -310
  20. package/dist/chunk-Z4CZRMNF.js.map +1 -0
  21. package/dist/db/index.cjs +29 -0
  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 +136 -3
  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-b-Q3l7W5.d.ts → index-BILC9WwS.d.ts} +9 -2
  32. package/dist/{index-8qy3kIuA.d.ts → index-BgWnP07a.d.ts} +119 -17
  33. package/dist/{index-Zi7MptvP.d.cts → index-CWGiJ2Up.d.cts} +9 -2
  34. package/dist/{index-DtCgaZAg.d.cts → index-DgYkdNT9.d.cts} +119 -17
  35. package/dist/index.cjs +87 -5
  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-BcRYIQ-R.d.cts} +1 -1
  46. package/dist/{registry-BGJ-Al6F.d.ts → registry-ClzjxIWy.d.ts} +1 -1
  47. package/dist/test/index.cjs +703 -14
  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 +51 -13
  52. package/dist/test/index.js.map +1 -1
  53. package/docs/README.md +1 -1
  54. package/docs/database.md +54 -0
  55. package/docs/llms-full.txt +87 -1
  56. package/docs/schema.md +32 -0
  57. package/package.json +1 -1
  58. package/template/package.json +1 -1
  59. package/dist/chunk-3TUJWHC2.js.map +0 -1
  60. package/dist/chunk-5J5W75A6.js.map +0 -1
  61. package/dist/chunk-ENZ2RFFJ.js.map +0 -1
  62. package/dist/chunk-SG4UTNOP.js +0 -374
  63. package/dist/chunk-SG4UTNOP.js.map +0 -1
  64. package/dist/chunk-VN7NMDUH.js.map +0 -1
  65. package/dist/chunk-VXPNPVAG.js +0 -40
  66. package/dist/chunk-VXPNPVAG.js.map +0 -1
  67. /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) {
@@ -925,7 +1271,7 @@ function createMockDB() {
925
1271
  const vals = bucket.map((r) => r[c]).filter((v) => v !== null && v !== void 0);
926
1272
  (out["avg"] ??= {})[c] = vals.length === 0 ? null : String(Number(vals.reduce((a, v) => addDecimal(a, v, 1), "0")) / vals.length);
927
1273
  }
928
- for (const [fn, pick] of [
1274
+ for (const [fn, pick2] of [
929
1275
  [
930
1276
  "min",
931
1277
  -1
@@ -937,7 +1283,7 @@ function createMockDB() {
937
1283
  ]) {
938
1284
  for (const c of q[fn] ?? []) {
939
1285
  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);
1286
+ (out[fn] ??= {})[c] = vals.length === 0 ? null : vals.reduce((a, v) => cmp(v, a, pick2 === 1 ? "gt" : "lt") ? v : a);
941
1287
  }
942
1288
  }
943
1289
  if (q.count === true) out["count"] = bucket.length;
@@ -1086,16 +1432,12 @@ function createMockDB() {
1086
1432
  applied[k] = v;
1087
1433
  }
1088
1434
  assertNoExpressionHandles("update", table, Object.keys(applied), applied);
1089
- const updated = idx >= 0 ? {
1435
+ if (idx < 0) return null;
1436
+ const updated = {
1090
1437
  ...rows[idx],
1091
1438
  ...applied
1092
- } : {
1093
- id,
1094
- ...applied
1095
1439
  };
1096
- if (idx >= 0) {
1097
- rows[idx] = updated;
1098
- }
1440
+ rows[idx] = updated;
1099
1441
  track(tracked.updated, table, updated);
1100
1442
  return updated;
1101
1443
  },
@@ -1120,6 +1462,9 @@ function createMockDB() {
1120
1462
  async findMany(table, query, opts) {
1121
1463
  assertUsableFilter("findMany", table, query);
1122
1464
  refuseFragment("findMany", table, query);
1465
+ if (opts?.with !== void 0 && Object.keys(opts.with).length > 0) {
1466
+ 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.`);
1467
+ }
1123
1468
  const rows = store.get(table) ?? [];
1124
1469
  let out = query ? rows.filter((row) => rowMatchesFilter("findMany", table, row, query)) : [
1125
1470
  ...rows
@@ -1401,6 +1746,315 @@ function txRejection(status, code, extra) {
1401
1746
  }
1402
1747
  __name(txRejection, "txRejection");
1403
1748
 
1749
+ // src/runtime.ts
1750
+ var import_node_async_hooks = require("async_hooks");
1751
+
1752
+ // src/errors.ts
1753
+ var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
1754
+ var HttpError = class extends Error {
1755
+ static {
1756
+ __name(this, "HttpError");
1757
+ }
1758
+ status;
1759
+ error;
1760
+ errorDescription;
1761
+ data;
1762
+ /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
1763
+ [HTTP_ERROR_BRAND] = true;
1764
+ constructor(status, error, errorDescription, data) {
1765
+ super(errorDescription);
1766
+ this.name = "HttpError";
1767
+ this.status = status;
1768
+ this.error = error;
1769
+ this.errorDescription = errorDescription;
1770
+ if (data !== void 0) {
1771
+ this.data = data;
1772
+ }
1773
+ }
1774
+ /**
1775
+ * Serialize to the standard Palbase error response format.
1776
+ * The `requestId` is injected by the runtime layer from the request context.
1777
+ * When called without arguments (e.g. JSON.stringify), request_id is omitted.
1778
+ * When `data` is set, it is appended as a strict-superset field.
1779
+ */
1780
+ toJSON(requestId) {
1781
+ const result = {
1782
+ error: this.error,
1783
+ error_description: this.errorDescription,
1784
+ status: this.status
1785
+ };
1786
+ if (requestId) {
1787
+ result.request_id = requestId;
1788
+ }
1789
+ if (this.data !== void 0) {
1790
+ result.data = this.data;
1791
+ }
1792
+ return result;
1793
+ }
1794
+ };
1795
+ function isRetryable(e) {
1796
+ return typeof e === "object" && e !== null && e.retryable === true && typeof e.sqlstate === "string";
1797
+ }
1798
+ __name(isRetryable, "isRetryable");
1799
+
1800
+ // src/db/typed-db.ts
1801
+ async function withRetry(fn, opts = {}) {
1802
+ const budget = opts.retry ?? 0;
1803
+ let attempt = 0;
1804
+ for (; ; ) {
1805
+ try {
1806
+ return await fn();
1807
+ } catch (e) {
1808
+ if (attempt >= budget || !isRetryable(e)) throw e;
1809
+ attempt += 1;
1810
+ }
1811
+ }
1812
+ }
1813
+ __name(withRetry, "withRetry");
1814
+
1815
+ // src/db/schema-json.ts
1816
+ function qualifiedTableKey(schemaName, tableName) {
1817
+ return schemaName === "" || schemaName === "public" ? tableName : `${schemaName}.${tableName}`;
1818
+ }
1819
+ __name(qualifiedTableKey, "qualifiedTableKey");
1820
+
1821
+ // src/runtime.ts
1822
+ var REQUEST_ALS = /* @__PURE__ */ Symbol.for("palbase.backend.requestALS");
1823
+ var __requestALS = (() => {
1824
+ const g = globalThis;
1825
+ return g[REQUEST_ALS] ??= new import_node_async_hooks.AsyncLocalStorage();
1826
+ })();
1827
+ var runtime = null;
1828
+ function __getRuntime() {
1829
+ const scoped = __requestALS.getStore();
1830
+ if (scoped) return scoped.runtime;
1831
+ if (runtime === null) {
1832
+ 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).");
1833
+ }
1834
+ return runtime;
1835
+ }
1836
+ __name(__getRuntime, "__getRuntime");
1837
+ function makeServiceProxy(key) {
1838
+ const handler = {
1839
+ get(_target, prop, receiver) {
1840
+ const client = __getRuntime()[key];
1841
+ const value = Reflect.get(client, prop, receiver);
1842
+ return typeof value === "function" ? value.bind(client) : value;
1843
+ }
1844
+ };
1845
+ return new Proxy({}, handler);
1846
+ }
1847
+ __name(makeServiceProxy, "makeServiceProxy");
1848
+ function makeTableProxy(ops, prefix) {
1849
+ return new Proxy({}, {
1850
+ get(_t, prop) {
1851
+ if (typeof prop !== "string") return void 0;
1852
+ const name = `${prefix}${prop}`;
1853
+ return {
1854
+ insert: /* @__PURE__ */ __name((data) => ops().insert(name, data), "insert"),
1855
+ // DÖRDÜNCÜ FİİL, aynı gerekçeyle: tip söz veriyor, ops katmanı
1856
+ // uyguluyor, ve bir handler'ın gerçekten dokunduğu yer BURASI.
1857
+ // `runtime-table-verbs` kapısı bunu adıyla saydı.
1858
+ aggregate: /* @__PURE__ */ __name((q) => ops().aggregate(name, q), "aggregate"),
1859
+ insertMany: /* @__PURE__ */ __name((rows, opts) => ops().insertMany(name, rows, opts), "insertMany"),
1860
+ update: /* @__PURE__ */ __name((q) => ops().update(name, q.where.id, q.set), "update"),
1861
+ delete: /* @__PURE__ */ __name((id) => ops().delete(name, id), "delete"),
1862
+ findById: /* @__PURE__ */ __name((id) => ops().findById(name, id), "findById"),
1863
+ findMany: /* @__PURE__ */ __name((q) => {
1864
+ const { where, ...opts } = q ?? {};
1865
+ return ops().findMany(name, where, opts);
1866
+ }, "findMany"),
1867
+ put: /* @__PURE__ */ __name((q) => ops().put(name, q.data, {
1868
+ onConflict: q.onConflict
1869
+ }), "put"),
1870
+ // THREE VERBS THE TYPE PROMISED AND THIS PROXY DID NOT EMIT.
1871
+ //
1872
+ // `EnvTypedTableBase` declares `updateMany`, `deleteMany` and `count`
1873
+ // (typed-db.ts) and the ops layer implements all three — only this
1874
+ // proxy, which is what a handler actually touches, left them out. So
1875
+ // the type said the verb exists, autocomplete offered it, and the call
1876
+ // answered `undefined is not a function`.
1877
+ //
1878
+ // Older than this run, but the run rewrote this proxy for
1879
+ // `Database.schema(name).tables.*` and would have carried the gap onto
1880
+ // the new surface too.
1881
+ updateMany: /* @__PURE__ */ __name((q) => ops().updateMany(name, q.where, q.set), "updateMany"),
1882
+ deleteMany: /* @__PURE__ */ __name((q) => ops().deleteMany(name, q.where), "deleteMany"),
1883
+ count: /* @__PURE__ */ __name((q) => ops().count(name, q?.where), "count"),
1884
+ search: /* @__PURE__ */ __name((params) => ops().search(name, params), "search"),
1885
+ similar: /* @__PURE__ */ __name((id, params) => ops().similar(name, id, params), "similar"),
1886
+ recommend: /* @__PURE__ */ __name((params) => ops().recommend(name, params), "recommend"),
1887
+ facets: /* @__PURE__ */ __name((params) => ops().facets(name, params), "facets"),
1888
+ supersede: /* @__PURE__ */ __name((id, row) => ops().supersede(name, id, row), "supersede"),
1889
+ claim: /* @__PURE__ */ __name((unique, extra) => ops().claim(name, unique, extra), "claim")
1890
+ };
1891
+ }
1892
+ });
1893
+ }
1894
+ __name(makeTableProxy, "makeTableProxy");
1895
+ var rawDatabase = makeServiceProxy("Database");
1896
+ function makeTypedSurface(raw) {
1897
+ const reco = raw;
1898
+ const ops = {
1899
+ $query: /* @__PURE__ */ __name((sql, params) => raw.query(sql, params), "$query"),
1900
+ $insert: /* @__PURE__ */ __name((table, data) => raw.insert(table, data), "$insert"),
1901
+ $update: /* @__PURE__ */ __name((table, id, data) => raw.update(table, id, data), "$update"),
1902
+ $delete: /* @__PURE__ */ __name((table, id) => raw.delete(table, id), "$delete"),
1903
+ $findById: /* @__PURE__ */ __name((table, id) => raw.findById(table, id), "$findById"),
1904
+ $findMany: /* @__PURE__ */ __name((table, query, opts) => raw.findMany(table, query, opts), "$findMany"),
1905
+ $put: /* @__PURE__ */ __name((table, data, opts) => raw.put(table, data, opts), "$put"),
1906
+ $updateMany: /* @__PURE__ */ __name((table, where, set) => raw.updateMany(table, where, set), "$updateMany"),
1907
+ $deleteMany: /* @__PURE__ */ __name((table, where) => raw.deleteMany(table, where), "$deleteMany"),
1908
+ $count: /* @__PURE__ */ __name((table, where) => raw.count(table, where), "$count"),
1909
+ $search: /* @__PURE__ */ __name((table, params) => raw.search(table, params), "$search"),
1910
+ $similar: /* @__PURE__ */ __name((table, id, params) => reco.similar(table, id, params), "$similar"),
1911
+ $recommend: /* @__PURE__ */ __name((table, params) => reco.recommend(table, params), "$recommend"),
1912
+ $facets: /* @__PURE__ */ __name((table, params) => reco.facets(table, params), "$facets"),
1913
+ $claim: /* @__PURE__ */ __name((table, unique, extra) => reco.claim(table, unique, extra), "$claim"),
1914
+ $lockRows: /* @__PURE__ */ __name((table, ids) => reco.lockRows(table, ids), "$lockRows"),
1915
+ $advisoryXactLock: /* @__PURE__ */ __name((key) => reco.advisoryXactLock(key), "$advisoryXactLock"),
1916
+ $aggregate: /* @__PURE__ */ __name((table, q) => raw.aggregate(table, q), "$aggregate"),
1917
+ $insertMany: /* @__PURE__ */ __name((table, rows, opts) => raw.insertMany(table, rows, opts), "$insertMany"),
1918
+ $supersede: /* @__PURE__ */ __name((table, id, row) => raw.supersede(table, id, row), "$supersede")
1919
+ };
1920
+ const base = Object.assign(ops, {
1921
+ // Both surfaces get it: a savepoint on the service transaction is as useful
1922
+ // as one on the request's, and each is bound to its own connection.
1923
+ $attempt: /* @__PURE__ */ __name((fn) => raw.attempt(fn), "$attempt"),
1924
+ $transaction(fn, opts) {
1925
+ return withRetry(() => {
1926
+ const builder = new TxPlanBuilder();
1927
+ return runTxPlan(raw, makeTxPlanHandle(builder), builder, fn);
1928
+ }, opts ?? {});
1929
+ }
1930
+ });
1931
+ return new Proxy(base, {
1932
+ get(target, prop, receiver) {
1933
+ if (prop === "tables") return makeTableProxy(() => reco, "");
1934
+ if (typeof prop === "string" && !prop.startsWith("$") && !(prop in target)) {
1935
+ return makeTableProxy(() => reco, qualifiedTableKey(prop, ""));
1936
+ }
1937
+ return Reflect.get(target, prop, receiver);
1938
+ }
1939
+ });
1940
+ }
1941
+ __name(makeTypedSurface, "makeTypedSurface");
1942
+ function makeTxTablesAccessor(builder, prefix = "") {
1943
+ const tablesProxy = new Proxy({}, {
1944
+ get(_t, prop) {
1945
+ if (typeof prop !== "string") return void 0;
1946
+ return builder.table(prefix + prop);
1947
+ }
1948
+ });
1949
+ return tablesProxy;
1950
+ }
1951
+ __name(makeTxTablesAccessor, "makeTxTablesAccessor");
1952
+ function makeTxPlanHandle(builder) {
1953
+ const publicTables = makeTxTablesAccessor(builder);
1954
+ return new Proxy({}, {
1955
+ get(_t, prop) {
1956
+ if (typeof prop !== "string") return void 0;
1957
+ if (prop === "tables") return publicTables;
1958
+ return makeTxTablesAccessor(builder, qualifiedTableKey(prop, ""));
1959
+ }
1960
+ });
1961
+ }
1962
+ __name(makeTxPlanHandle, "makeTxPlanHandle");
1963
+ var Database = Object.assign(makeTypedSurface(rawDatabase), {
1964
+ /**
1965
+ * Lazily resolve the runtime's service-role sibling on each call. We do NOT
1966
+ * cache it: `rawDatabase.asService()` reads the CURRENT request scope through
1967
+ * the runtime proxy, and the per-request runtime injects a service client
1968
+ * bound to that request's identity headers — caching would leak one request's
1969
+ * sibling into another concurrent request.
1970
+ */
1971
+ $asService() {
1972
+ return makeTypedSurface(rawDatabase.asService());
1973
+ }
1974
+ });
1975
+ var Documents = makeServiceProxy("Documents");
1976
+ function makeBucketsAccessor(storage) {
1977
+ return new Proxy({}, {
1978
+ get(_t, prop) {
1979
+ if (typeof prop !== "string") return void 0;
1980
+ return storage().bucket(prop);
1981
+ }
1982
+ });
1983
+ }
1984
+ __name(makeBucketsAccessor, "makeBucketsAccessor");
1985
+ var rawStorage = makeServiceProxy("Storage");
1986
+ var Storage = Object.assign({
1987
+ // FORWARDED explicitly, not assigned onto the service proxy.
1988
+ //
1989
+ // `Object.assign(rawStorage, {buckets})` writes onto the proxy's TARGET, and
1990
+ // the proxy's only trap is `get`, which forwards every read to the module
1991
+ // client — so the property landed somewhere nothing reads and
1992
+ // `Storage.buckets.docs` was `undefined` in a deployed handler. It
1993
+ // typechecked, every test passed, and the live call answered
1994
+ // "TypeError: undefined is not an object". `Database` never had the bug
1995
+ // because it builds a plain surface the same way this now does.
1996
+ bucket: /* @__PURE__ */ __name((name) => rawStorage.bucket(name), "bucket")
1997
+ }, {
1998
+ buckets: makeBucketsAccessor(() => rawStorage)
1999
+ });
2000
+ var Cache = makeServiceProxy("Cache");
2001
+ var Secrets = makeServiceProxy("Secrets");
2002
+ var Auth = makeServiceProxy("Auth");
2003
+ var Log = makeServiceProxy("Log");
2004
+ var Notifications = makeServiceProxy("Notifications");
2005
+ var rawFlags = makeServiceProxy("Flags");
2006
+ var Flags = Object.assign({
2007
+ isEnabled(flagName, context) {
2008
+ return rawFlags.isEnabled(flagName, context);
2009
+ },
2010
+ getVariant(flagName, context) {
2011
+ return rawFlags.getVariant(flagName, context);
2012
+ },
2013
+ getAll(context) {
2014
+ return rawFlags.getAll(context);
2015
+ },
2016
+ /**
2017
+ * Resolve a flag's value, with an optional fallback.
2018
+ *
2019
+ * FORWARDED as of 2026-08-15. This surface is written out by hand, method
2020
+ * by method, and `get` was missing from it — so the client implemented it,
2021
+ * thirty assertions covered it, and `Flags.get("x")` was `undefined` in a
2022
+ * deployed handler. Exactly the shape of the `Storage.buckets` defect found
2023
+ * the same day: a hand-maintained forwarding list is a list somebody has to
2024
+ * remember to update.
2025
+ */
2026
+ get(flagName, defaultOrContext, maybeContext) {
2027
+ return rawFlags.get(flagName, defaultOrContext, maybeContext);
2028
+ },
2029
+ setOverride(key, value) {
2030
+ return rawFlags.setOverride(key, value);
2031
+ }
2032
+ }, {
2033
+ /**
2034
+ * Lazily resolve the runtime's cross-user sibling on each call. We do NOT
2035
+ * cache it: `rawFlags.asService()` reads the CURRENT request scope through
2036
+ * the runtime proxy, so caching would leak one request's sibling into
2037
+ * another concurrent request. Mirrors `Database.asService()`.
2038
+ */
2039
+ $asService() {
2040
+ return rawFlags.asService();
2041
+ },
2042
+ /**
2043
+ * The name this member carried before 31.0.0.
2044
+ *
2045
+ * It does NOT work silently. A retired member that quietly keeps returning
2046
+ * is how a rename becomes a mystery: the old call site goes on compiling,
2047
+ * the new name never spreads, and the two live side by side until somebody
2048
+ * greps for one and misses half the codebase. `Database` made this split
2049
+ * first (`$asService`), and this JSDoc promised "exactly like
2050
+ * Database.$asService()" while the promise went unkept.
2051
+ */
2052
+ asService() {
2053
+ throw new Error("Flags.asService() was renamed to Flags.$asService() in 31.0.0 \u2014 system members carry the `$` prefix, like Database.$asService().");
2054
+ }
2055
+ });
2056
+ var Realtime = makeServiceProxy("Realtime");
2057
+
1404
2058
  // src/test/fake-db.ts
1405
2059
  function fakeDatabase() {
1406
2060
  const mock = createMockDB();
@@ -1415,7 +2069,8 @@ function fakeDatabase() {
1415
2069
  }, "query")
1416
2070
  });
1417
2071
  return {
1418
- db,
2072
+ db: makeTypedSurface(db),
2073
+ raw: db,
1419
2074
  queries,
1420
2075
  seed: /* @__PURE__ */ __name((table, rows) => mock.seed(table, rows), "seed"),
1421
2076
  inserted: /* @__PURE__ */ __name((table) => mock.inserted(table), "inserted"),
@@ -1424,12 +2079,46 @@ function fakeDatabase() {
1424
2079
  };
1425
2080
  }
1426
2081
  __name(fakeDatabase, "fakeDatabase");
2082
+
2083
+ // src/test/with-services.ts
2084
+ function absent(name) {
2085
+ return new Proxy({}, {
2086
+ get(_target, prop) {
2087
+ throw new Error(`${String(name)}.${String(prop)} was read, but this test did not provide ${String(name)}. Pass it: withServices({ ${String(name)}: \u2026 }, () => \u2026)`);
2088
+ }
2089
+ });
2090
+ }
2091
+ __name(absent, "absent");
2092
+ function pick(services, key) {
2093
+ return services[key] ?? absent(key);
2094
+ }
2095
+ __name(pick, "pick");
2096
+ function withServices(services, fn) {
2097
+ const filled = {
2098
+ Database: pick(services, "Database"),
2099
+ Auth: pick(services, "Auth"),
2100
+ Secrets: pick(services, "Secrets"),
2101
+ Documents: pick(services, "Documents"),
2102
+ Storage: pick(services, "Storage"),
2103
+ Cache: pick(services, "Cache"),
2104
+ Log: pick(services, "Log"),
2105
+ Notifications: pick(services, "Notifications"),
2106
+ Flags: pick(services, "Flags"),
2107
+ Realtime: pick(services, "Realtime")
2108
+ };
2109
+ return __requestALS.run({
2110
+ runtime: filled,
2111
+ userId: null
2112
+ }, fn);
2113
+ }
2114
+ __name(withServices, "withServices");
1427
2115
  // Annotate the CommonJS export names for ESM import in node:
1428
2116
  0 && (module.exports = {
1429
2117
  TestApiError,
1430
2118
  api,
1431
2119
  createTestApi,
1432
2120
  fakeDatabase,
1433
- isolated
2121
+ isolated,
2122
+ withServices
1434
2123
  });
1435
2124
  //# sourceMappingURL=index.cjs.map