@palbase/backend 33.0.2 → 34.1.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 (84) hide show
  1. package/dist/bin/palbase-backend.cjs +2273 -358
  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-YIQ4RS4F.js → chunk-5UGPXKMJ.js} +331 -5
  6. package/dist/chunk-5UGPXKMJ.js.map +1 -0
  7. package/dist/chunk-GHNC2UHS.js +20 -0
  8. package/dist/chunk-GHNC2UHS.js.map +1 -0
  9. package/dist/{chunk-AAT5G7KY.js → chunk-JJW3L62T.js} +2 -2
  10. package/dist/chunk-JJW3L62T.js.map +1 -0
  11. package/dist/{chunk-SI4KGEM3.js → chunk-P7MGSAFT.js} +102 -96
  12. package/dist/chunk-P7MGSAFT.js.map +1 -0
  13. package/dist/{chunk-XOX6RFPZ.js → chunk-PRH45NZU.js} +46 -3
  14. package/dist/chunk-PRH45NZU.js.map +1 -0
  15. package/dist/{chunk-26GH54D2.js → chunk-RZLY4BOY.js} +5 -3
  16. package/dist/{chunk-26GH54D2.js.map → chunk-RZLY4BOY.js.map} +1 -1
  17. package/dist/{chunk-KATPXCJ5.js → chunk-UMESJJRS.js} +19 -40
  18. package/dist/chunk-UMESJJRS.js.map +1 -0
  19. package/dist/chunk-VXPNPVAG.js +40 -0
  20. package/dist/chunk-VXPNPVAG.js.map +1 -0
  21. package/dist/{chunk-WWUG2QXF.js → chunk-X2UYIXDS.js} +59 -4
  22. package/dist/chunk-X2UYIXDS.js.map +1 -0
  23. package/dist/{chunk-C6COAB3E.js → chunk-XZPDPYVY.js} +1905 -195
  24. package/dist/chunk-XZPDPYVY.js.map +1 -0
  25. package/dist/{chunk-TVCCR6SO.js → chunk-YO5GYM73.js} +58 -9
  26. package/dist/chunk-YO5GYM73.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 +285 -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-BXPdsTA5.d.ts} +370 -58
  36. package/dist/{index-DB_nW-AV.d.cts → endpoint-DqhUP7RT.d.cts} +370 -58
  37. package/dist/engine/index.cjs +2279 -356
  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-DLveQoOf.d.cts → index-Co0Hj_aO.d.cts} +108 -50
  43. package/dist/{index-D-3duy8Y.d.ts → index-MhCL7ll4.d.ts} +108 -50
  44. package/dist/index.cjs +806 -386
  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 +27 -15
  49. package/dist/index.js.map +1 -1
  50. package/dist/openapi/index.cjs +7 -0
  51. package/dist/openapi/index.cjs.map +1 -1
  52. package/dist/openapi/index.d.cts +3 -3
  53. package/dist/openapi/index.d.ts +3 -3
  54. package/dist/openapi/index.js +4 -3
  55. package/dist/openapi/index.js.map +1 -1
  56. package/dist/{registry-DSTThhKf.d.cts → registry-BkIVHPzB.d.cts} +3 -1
  57. package/dist/{registry-JjF5lcj4.d.ts → registry-CvGLR5j1.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/auth.md +58 -0
  66. package/docs/database.md +308 -0
  67. package/docs/examples/check-query-contracts.ts +5 -0
  68. package/docs/examples/database-dx.contract.json +65 -0
  69. package/docs/examples/database-dx.ts +63 -0
  70. package/docs/examples/money-transfer.ts +73 -0
  71. package/docs/llms-full.txt +382 -1
  72. package/docs/schema.md +15 -0
  73. package/package.json +1 -2
  74. package/template/db/public.ts +12 -0
  75. package/template/package.json +1 -1
  76. package/dist/chunk-AAT5G7KY.js.map +0 -1
  77. package/dist/chunk-C6COAB3E.js.map +0 -1
  78. package/dist/chunk-KATPXCJ5.js.map +0 -1
  79. package/dist/chunk-SI4KGEM3.js.map +0 -1
  80. package/dist/chunk-TVCCR6SO.js.map +0 -1
  81. package/dist/chunk-WWUG2QXF.js.map +0 -1
  82. package/dist/chunk-XOX6RFPZ.js.map +0 -1
  83. package/dist/chunk-YIQ4RS4F.js.map +0 -1
  84. package/stack-images.json +0 -24
@@ -1,4 +1,4 @@
1
- import { A as AuthSpec, R as RateLimitConfig } from './index-DB_nW-AV.cjs';
1
+ import { D as DatabaseBudget, A as AuthSpec, R as RateLimitConfig } from './endpoint-DqhUP7RT.cjs';
2
2
  import { ZodTypeAny } from 'zod';
3
3
  import { PalbaseBucketName } from './stack.cjs';
4
4
 
@@ -200,6 +200,8 @@ declare function Signal(): ParameterDecorator;
200
200
  type HttpMethodUpper = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "QUERY";
201
201
  /** Route-level options accepted by the method decorators (`@Get`/`@Post`/…). */
202
202
  interface RouteOptions {
203
+ /** Enforced before statements and COMMIT; include retries and service queries. */
204
+ databaseBudget?: DatabaseBudget;
203
205
  /** OVERRIDES the controller-level default auth for this one route. */
204
206
  auth?: AuthSpec;
205
207
  /** Per-route rate limit. */
@@ -1,4 +1,4 @@
1
- import { A as AuthSpec, R as RateLimitConfig } from './index-DMZG3kpo.js';
1
+ import { D as DatabaseBudget, A as AuthSpec, R as RateLimitConfig } from './endpoint-BXPdsTA5.js';
2
2
  import { ZodTypeAny } from 'zod';
3
3
  import { PalbaseBucketName } from './stack.js';
4
4
 
@@ -200,6 +200,8 @@ declare function Signal(): ParameterDecorator;
200
200
  type HttpMethodUpper = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "QUERY";
201
201
  /** Route-level options accepted by the method decorators (`@Get`/`@Post`/…). */
202
202
  interface RouteOptions {
203
+ /** Enforced before statements and COMMIT; include retries and service queries. */
204
+ databaseBudget?: DatabaseBudget;
203
205
  /** OVERRIDES the controller-level default auth for this one route. */
204
206
  auth?: AuthSpec;
205
207
  /** Per-route rate limit. */
@@ -1458,6 +1458,18 @@ function isolated() {
1458
1458
  }
1459
1459
  __name(isolated, "isolated");
1460
1460
 
1461
+ // src/db/bulk.ts
1462
+ function validateInsertManyOptions(opts) {
1463
+ if (!opts) return;
1464
+ if (opts.returning !== void 0 && typeof opts.returning !== "boolean") throw new Error("insertMany returning must be a boolean");
1465
+ if (opts.action !== void 0 && opts.action !== "ignore" && opts.action !== "update") throw new Error("insertMany action must be ignore or update");
1466
+ if (opts.onConflict !== void 0 && (!Array.isArray(opts.onConflict) || opts.onConflict.some((c) => typeof c !== "string" || c.length === 0))) {
1467
+ throw new Error("insertMany onConflict must be an array of column names");
1468
+ }
1469
+ if (opts.action !== void 0 && !opts.onConflict?.length) throw new Error("insertMany action requires nonempty onConflict columns");
1470
+ }
1471
+ __name(validateInsertManyOptions, "validateInsertManyOptions");
1472
+
1461
1473
  // src/db/input-guards.ts
1462
1474
  var REF_BRAND = /* @__PURE__ */ Symbol.for("palbase.db.ref");
1463
1475
  function brandRef(v, kind) {
@@ -2279,6 +2291,14 @@ function createMockDB() {
2279
2291
  }
2280
2292
  __name(resolveInsertValues, "resolveInsertValues");
2281
2293
  const ops = {
2294
+ diagnostics: /* @__PURE__ */ __name(() => null, "diagnostics"),
2295
+ readQuery: /* @__PURE__ */ __name((query) => ops.findMany(query.table, query.where, query.options), "readQuery"),
2296
+ compileQuery() {
2297
+ throw new Error("MockDB cannot compile PostgreSQL query plans; use the real engine to inspect SQL");
2298
+ },
2299
+ async explainQuery() {
2300
+ throw new Error("MockDB cannot EXPLAIN PostgreSQL plans; use a PostgreSQL integration test");
2301
+ },
2282
2302
  // The bulk ops and count run against the SAME in-memory store the direct
2283
2303
  // ops write to, so a test that writes three rows and counts them gets 3 —
2284
2304
  // a mock that answered 0 would make the surface look broken in exactly the
@@ -2416,8 +2436,10 @@ function createMockDB() {
2416
2436
  return out;
2417
2437
  });
2418
2438
  },
2419
- async insertMany(table, rows) {
2420
- if (rows.length === 0) return [];
2439
+ insertMany: /* @__PURE__ */ __name(async function(table, rows, opts) {
2440
+ validateInsertManyOptions(opts);
2441
+ if (opts?.onConflict?.length) throw new Error("fakeDatabase insertMany cannot enforce PostgreSQL unique constraints; use an integration test for onConflict");
2442
+ if (rows.length === 0) return opts?.returning === false ? 0 : [];
2421
2443
  const cols = Object.keys(rows[0]);
2422
2444
  for (let i = 1; i < rows.length; i++) {
2423
2445
  const missing = cols.filter((c) => !(c in rows[i]));
@@ -2439,8 +2461,8 @@ function createMockDB() {
2439
2461
  track(tracked.inserted, table, record);
2440
2462
  out.push(record);
2441
2463
  }
2442
- return out;
2443
- },
2464
+ return opts?.returning === false ? out.length : out;
2465
+ }, "insertMany"),
2444
2466
  /**
2445
2467
  * `claim` — motorla PARİTE (FR-033).
2446
2468
  *
@@ -2562,6 +2584,9 @@ function createMockDB() {
2562
2584
  id
2563
2585
  ]);
2564
2586
  },
2587
+ async findUnique() {
2588
+ throw new Error("MockDB.findUnique cannot verify declared PostgreSQL unique constraints; use a PostgreSQL integration test");
2589
+ },
2565
2590
  async findById(table, id) {
2566
2591
  const rows = store.get(table) ?? [];
2567
2592
  return rows.find((r) => r["id"] === id) ?? null;
@@ -2570,6 +2595,9 @@ function createMockDB() {
2570
2595
  // equality, an object is an operator set. A fake that understood less would
2571
2596
  // pass a service test that the live database then fails — which is the one
2572
2597
  // thing a stand-in must never do.
2598
+ async page() {
2599
+ throw new Error("fakeDatabase cannot reproduce PostgreSQL cursor ordering, collations or RLS; use a PostgreSQL integration test or inject an explicit page response");
2600
+ },
2573
2601
  async findMany(table, query, opts) {
2574
2602
  assertUsableFilter("findMany", table, query);
2575
2603
  refuseFragment("findMany", table, query);
@@ -2764,6 +2792,13 @@ function createMockDB() {
2764
2792
  __name(applyOp, "applyOp");
2765
2793
  const client = {
2766
2794
  ...ops,
2795
+ async command(plan, options) {
2796
+ if (options?.mode === "compiled") throw new Error("fakeDatabase does not run the PostgreSQL command executor; verify compiled mode against PostgreSQL");
2797
+ return client.txPlan(plan);
2798
+ },
2799
+ async atomic() {
2800
+ throw new Error("fakeDatabase cannot verify physical transaction isolation, COMMIT or retry; test $atomic against PostgreSQL or inject a transaction test double explicitly");
2801
+ },
2767
2802
  // No real savepoint in memory: the fake runs the callback against the SAME
2768
2803
  // store. An assertion about rollback here would be asserting the fake.
2769
2804
  attempt: /* @__PURE__ */ __name((fn) => fn(ops), "attempt"),
@@ -2860,68 +2895,21 @@ __name(txRejection, "txRejection");
2860
2895
  // src/runtime.ts
2861
2896
  var import_node_async_hooks = require("async_hooks");
2862
2897
 
2863
- // src/errors.ts
2864
- var HTTP_ERROR_BRAND = /* @__PURE__ */ Symbol.for("palbase.backend.httpError");
2865
- var HttpError = class extends Error {
2866
- static {
2867
- __name(this, "HttpError");
2898
+ // src/db/transaction-options.ts
2899
+ function retryBudget(value) {
2900
+ const n = value ?? 0;
2901
+ if (!Number.isInteger(n) || n < 0 || n > 10) {
2902
+ throw new Error("transaction retry must be an integer between 0 and 10");
2868
2903
  }
2869
- status;
2870
- error;
2871
- errorDescription;
2872
- data;
2873
- /** See {@link HTTP_ERROR_BRAND} — how the engine recognises this across SDK copies. */
2874
- [HTTP_ERROR_BRAND] = true;
2875
- constructor(status, error, errorDescription, data) {
2876
- super(errorDescription);
2877
- this.name = "HttpError";
2878
- this.status = status;
2879
- this.error = error;
2880
- this.errorDescription = errorDescription;
2881
- if (data !== void 0) {
2882
- this.data = data;
2883
- }
2884
- }
2885
- /**
2886
- * Serialize to the standard Palbase error response format.
2887
- * The `requestId` is injected by the runtime layer from the request context.
2888
- * When called without arguments (e.g. JSON.stringify), request_id is omitted.
2889
- * When `data` is set, it is appended as a strict-superset field.
2890
- */
2891
- toJSON(requestId) {
2892
- const result = {
2893
- error: this.error,
2894
- error_description: this.errorDescription,
2895
- status: this.status
2896
- };
2897
- if (requestId) {
2898
- result.request_id = requestId;
2899
- }
2900
- if (this.data !== void 0) {
2901
- result.data = this.data;
2902
- }
2903
- return result;
2904
- }
2905
- };
2906
- function isRetryable(e) {
2907
- return typeof e === "object" && e !== null && e.retryable === true && typeof e.sqlstate === "string";
2904
+ return n;
2908
2905
  }
2909
- __name(isRetryable, "isRetryable");
2910
-
2911
- // src/db/typed-db.ts
2912
- async function withRetry(fn, opts = {}) {
2913
- const budget = opts.retry ?? 0;
2914
- let attempt = 0;
2915
- for (; ; ) {
2916
- try {
2917
- return await fn();
2918
- } catch (e) {
2919
- if (attempt >= budget || !isRetryable(e)) throw e;
2920
- attempt += 1;
2921
- }
2906
+ __name(retryBudget, "retryBudget");
2907
+ function assertPlanRetry(options) {
2908
+ if (retryBudget(options?.retry) !== 0) {
2909
+ 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.");
2922
2910
  }
2923
2911
  }
2924
- __name(withRetry, "withRetry");
2912
+ __name(assertPlanRetry, "assertPlanRetry");
2925
2913
 
2926
2914
  // src/db/schema-json.ts
2927
2915
  function qualifiedTableKey(schemaName, tableName) {
@@ -2991,6 +2979,14 @@ function makeTableProxy(ops, prefix) {
2991
2979
  update: /* @__PURE__ */ __name((q) => ops().update(name, q.where.id, q.set), "update"),
2992
2980
  delete: /* @__PURE__ */ __name((id) => ops().delete(name, id), "delete"),
2993
2981
  findById: /* @__PURE__ */ __name((id) => ops().findById(name, id), "findById"),
2982
+ findUnique: /* @__PURE__ */ __name((q) => ops().findUnique(name, q.where, {
2983
+ select: q.select,
2984
+ with: q.with
2985
+ }), "findUnique"),
2986
+ page: /* @__PURE__ */ __name((q) => {
2987
+ const { where, ...opts } = q ?? {};
2988
+ return ops().page(name, where, opts);
2989
+ }, "page"),
2994
2990
  findMany: /* @__PURE__ */ __name((q) => {
2995
2991
  const { where, ...opts } = q ?? {};
2996
2992
  return ops().findMany(name, where, opts);
@@ -3028,10 +3024,16 @@ function makeTypedSurface(raw) {
3028
3024
  const reco = raw;
3029
3025
  const ops = {
3030
3026
  $query: /* @__PURE__ */ __name((sql, params) => raw.query(sql, params), "$query"),
3027
+ $diagnostics: /* @__PURE__ */ __name(() => raw.diagnostics(), "$diagnostics"),
3028
+ $readQuery: /* @__PURE__ */ __name((query) => raw.readQuery(query), "$readQuery"),
3029
+ $compileQuery: /* @__PURE__ */ __name((query) => raw.compileQuery(query), "$compileQuery"),
3030
+ $explainQuery: /* @__PURE__ */ __name((query) => raw.explainQuery(query), "$explainQuery"),
3031
3031
  $insert: /* @__PURE__ */ __name((table, data) => raw.insert(table, data), "$insert"),
3032
3032
  $update: /* @__PURE__ */ __name((table, id, data) => raw.update(table, id, data), "$update"),
3033
3033
  $delete: /* @__PURE__ */ __name((table, id) => raw.delete(table, id), "$delete"),
3034
3034
  $findById: /* @__PURE__ */ __name((table, id) => raw.findById(table, id), "$findById"),
3035
+ $findUnique: /* @__PURE__ */ __name((table, where, opts) => raw.findUnique(table, where, opts), "$findUnique"),
3036
+ $page: /* @__PURE__ */ __name((table, query, opts) => raw.page(table, query, opts), "$page"),
3035
3037
  $findMany: /* @__PURE__ */ __name((table, query, opts) => raw.findMany(table, query, opts), "$findMany"),
3036
3038
  $put: /* @__PURE__ */ __name((table, data, opts) => raw.put(table, data, opts), "$put"),
3037
3039
  // `opts` İLETİLİR. Düşürüldüğü sürece `Database.$updateMany(t, w, s,
@@ -3054,14 +3056,49 @@ function makeTypedSurface(raw) {
3054
3056
  $supersede: /* @__PURE__ */ __name((table, id, row) => raw.supersede(table, id, row), "$supersede")
3055
3057
  };
3056
3058
  const base = Object.assign(ops, {
3059
+ $atomic(fn, options) {
3060
+ if (typeof raw.atomic !== "function") {
3061
+ throw new Error("This Database handle cannot open a root transaction; nested $atomic is not supported");
3062
+ }
3063
+ const parent = __requestALS.getStore();
3064
+ let runtime2;
3065
+ try {
3066
+ runtime2 = __getRuntime();
3067
+ } catch {
3068
+ }
3069
+ return raw.atomic(async (tx) => {
3070
+ const typed = makeTypedSurface(tx);
3071
+ const invoke = /* @__PURE__ */ __name(() => fn(typed), "invoke");
3072
+ if (!runtime2) return invoke();
3073
+ const refuse = /* @__PURE__ */ __name(() => {
3074
+ throw new Error("Database.$atomic owns one transaction and identity; nested roots and $asService are not allowed");
3075
+ }, "refuse");
3076
+ const ambient = Object.assign({}, tx, {
3077
+ atomic: refuse,
3078
+ asService: refuse
3079
+ });
3080
+ return __requestALS.run({
3081
+ ...parent,
3082
+ runtime: {
3083
+ ...runtime2,
3084
+ Database: ambient
3085
+ }
3086
+ }, invoke);
3087
+ }, options);
3088
+ },
3057
3089
  // Both surfaces get it: a savepoint on the service transaction is as useful
3058
3090
  // as one on the request's, and each is bound to its own connection.
3059
3091
  $attempt: /* @__PURE__ */ __name((fn) => raw.attempt(fn), "$attempt"),
3060
3092
  $transaction(fn, opts) {
3061
- return withRetry(() => {
3062
- const builder = new TxPlanBuilder();
3063
- return runTxPlan(raw, makeTxPlanHandle(builder), builder, fn);
3064
- }, opts ?? {});
3093
+ assertPlanRetry(opts);
3094
+ const builder = new TxPlanBuilder();
3095
+ return runTxPlan(raw, makeTxPlanHandle(builder), builder, fn);
3096
+ },
3097
+ $command(fn, options) {
3098
+ const builder = new TxPlanBuilder();
3099
+ return runTxPlan({
3100
+ txPlan: /* @__PURE__ */ __name((plan) => raw.command(plan, options), "txPlan")
3101
+ }, makeTxPlanHandle(builder), builder, fn);
3065
3102
  }
3066
3103
  });
3067
3104
  return new Proxy(base, {