@palbase/backend 32.0.0 → 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 (53) hide show
  1. package/dist/bin/palbase-backend.cjs +79 -26
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +5 -5
  4. package/dist/{chunk-IXAX5CON.js → chunk-C4ZA5AT2.js} +1 -1
  5. package/dist/{chunk-IXAX5CON.js.map → chunk-C4ZA5AT2.js.map} +1 -1
  6. package/dist/{chunk-AZJIKCOR.js → chunk-DCDHAKF3.js} +39 -23
  7. package/dist/{chunk-AZJIKCOR.js.map → chunk-DCDHAKF3.js.map} +1 -1
  8. package/dist/{chunk-Z4CZRMNF.js → chunk-GOPZPM2A.js} +2 -2
  9. package/dist/{chunk-5C5UCILO.js → chunk-N54QZER3.js} +3 -3
  10. package/dist/{chunk-XEGZ3S2Q.js → chunk-QMFOL2K6.js} +32 -7
  11. package/dist/chunk-QMFOL2K6.js.map +1 -0
  12. package/dist/{chunk-KGP6ALIU.js → chunk-XOX6RFPZ.js} +30 -8
  13. package/dist/chunk-XOX6RFPZ.js.map +1 -0
  14. package/dist/db/index.cjs +14 -4
  15. package/dist/db/index.cjs.map +1 -1
  16. package/dist/db/index.d.cts +1 -1
  17. package/dist/db/index.d.ts +1 -1
  18. package/dist/db/index.js +3 -3
  19. package/dist/engine/index.cjs +79 -26
  20. package/dist/engine/index.cjs.map +1 -1
  21. package/dist/engine/index.d.cts +3 -3
  22. package/dist/engine/index.d.ts +3 -3
  23. package/dist/engine/index.js +5 -5
  24. package/dist/{index-DgYkdNT9.d.cts → index-BWgnGj68.d.cts} +68 -20
  25. package/dist/{index-BILC9WwS.d.ts → index-Bi74dcOu.d.ts} +2 -2
  26. package/dist/{index-BgWnP07a.d.ts → index-Dg10RnZO.d.ts} +68 -20
  27. package/dist/{index-CWGiJ2Up.d.cts → index-g6iQyYci.d.cts} +2 -2
  28. package/dist/index.cjs +59 -12
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +6 -6
  31. package/dist/index.d.ts +6 -6
  32. package/dist/index.js +5 -5
  33. package/dist/openapi/index.d.cts +2 -2
  34. package/dist/openapi/index.d.ts +2 -2
  35. package/dist/{registry-BcRYIQ-R.d.cts → registry-6VT5RPeO.d.cts} +1 -1
  36. package/dist/{registry-ClzjxIWy.d.ts → registry-B-sxJJN0.d.ts} +1 -1
  37. package/dist/test/index.cjs +35 -8
  38. package/dist/test/index.cjs.map +1 -1
  39. package/dist/test/index.d.cts +1 -1
  40. package/dist/test/index.d.ts +1 -1
  41. package/dist/test/index.js +7 -5
  42. package/dist/test/index.js.map +1 -1
  43. package/docs/README.md +1 -1
  44. package/docs/auth.md +102 -20
  45. package/docs/llms-full.txt +129 -21
  46. package/docs/schema.md +26 -0
  47. package/package.json +1 -1
  48. package/template/db/public.ts +23 -0
  49. package/template/package.json +1 -1
  50. package/dist/chunk-KGP6ALIU.js.map +0 -1
  51. package/dist/chunk-XEGZ3S2Q.js.map +0 -1
  52. /package/dist/{chunk-Z4CZRMNF.js.map → chunk-GOPZPM2A.js.map} +0 -0
  53. /package/dist/{chunk-5C5UCILO.js.map → chunk-N54QZER3.js.map} +0 -0
@@ -839,9 +839,29 @@ function __runWithRuntime(services, fn) {
839
839
  }, fn);
840
840
  }
841
841
  __name(__runWithRuntime, "__runWithRuntime");
842
+ var REQUIRED_SERVICES = [
843
+ "Database",
844
+ "Auth",
845
+ "Secrets",
846
+ "Documents",
847
+ "Storage",
848
+ "Cache",
849
+ "Log",
850
+ "Notifications",
851
+ "Flags",
852
+ "Realtime"
853
+ ];
854
+ function refuseIncompleteBox(services) {
855
+ const missing = REQUIRED_SERVICES.filter((k) => services[k] === void 0);
856
+ if (missing.length > 0) {
857
+ 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.`);
858
+ }
859
+ return services;
860
+ }
861
+ __name(refuseIncompleteBox, "refuseIncompleteBox");
842
862
  function __getRuntime() {
843
863
  const scoped = __requestALS.getStore();
844
- if (scoped) return scoped.runtime;
864
+ if (scoped) return refuseIncompleteBox(scoped.runtime);
845
865
  if (runtime === null) {
846
866
  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).");
847
867
  }
@@ -964,7 +984,12 @@ function makeTypedSurface(raw) {
964
984
  $findById: /* @__PURE__ */ __name((table, id) => raw.findById(table, id), "$findById"),
965
985
  $findMany: /* @__PURE__ */ __name((table, query, opts) => raw.findMany(table, query, opts), "$findMany"),
966
986
  $put: /* @__PURE__ */ __name((table, data, opts) => raw.put(table, data, opts), "$put"),
967
- $updateMany: /* @__PURE__ */ __name((table, where, set) => raw.updateMany(table, where, set), "$updateMany"),
987
+ // `opts` İLETİLİR. Düşürüldüğü sürece `Database.$updateMany(t, w, s,
988
+ // { returning: false })` sessizce SATIRLARI döndürüyordu, sayıyı değil —
989
+ // ve `if (n === 0) throw new Conflict(...)` guard'ı HİÇ çalışmıyordu,
990
+ // çünkü `[] === 0` yanlıştır. Komşu forwarder'lar (`$findMany`, `$put`)
991
+ // kendi opsiyonlarını zaten iletiyordu; bu biri unutulmuştu.
992
+ $updateMany: /* @__PURE__ */ __name((table, where, set, opts) => raw.updateMany(table, where, set, opts), "$updateMany"),
968
993
  $deleteMany: /* @__PURE__ */ __name((table, where) => raw.deleteMany(table, where), "$deleteMany"),
969
994
  $count: /* @__PURE__ */ __name((table, where) => raw.count(table, where), "$count"),
970
995
  $search: /* @__PURE__ */ __name((table, params) => raw.search(table, params), "$search"),
@@ -1095,13 +1120,13 @@ var Flags = Object.assign({
1095
1120
  * Lazily resolve the runtime's cross-user sibling on each call. We do NOT
1096
1121
  * cache it: `rawFlags.asService()` reads the CURRENT request scope through
1097
1122
  * the runtime proxy, so caching would leak one request's sibling into
1098
- * another concurrent request. Mirrors `Database.asService()`.
1123
+ * another concurrent request. Mirrors `Database.$asService()`.
1099
1124
  */
1100
1125
  $asService() {
1101
1126
  return rawFlags.asService();
1102
1127
  },
1103
1128
  /**
1104
- * The name this member carried before 31.0.0.
1129
+ * The name this member carried before 32.0.0.
1105
1130
  *
1106
1131
  * It does NOT work silently. A retired member that quietly keeps returning
1107
1132
  * is how a rename becomes a mystery: the old call site goes on compiling,
@@ -1111,7 +1136,7 @@ var Flags = Object.assign({
1111
1136
  * Database.$asService()" while the promise went unkept.
1112
1137
  */
1113
1138
  asService() {
1114
- throw new Error("Flags.asService() was renamed to Flags.$asService() in 31.0.0 \u2014 system members carry the `$` prefix, like Database.$asService().");
1139
+ throw new Error("Flags.asService() was renamed to Flags.$asService() in 32.0.0 \u2014 system members carry the `$` prefix, like Database.$asService().");
1115
1140
  }
1116
1141
  });
1117
1142
  var Realtime = makeServiceProxy("Realtime");
@@ -1486,15 +1511,27 @@ var CODECS = {
1486
1511
  // not representable and the author wants `asDecimal()` instead.
1487
1512
  number: {
1488
1513
  fromDb: /* @__PURE__ */ __name((v) => Number(v), "fromDb"),
1489
- toDb: /* @__PURE__ */ __name((v) => String(v), "toDb"),
1514
+ // YAZMA YOLUNDA SESSİZ BOZULMA YOK. `toDb(9007199254740993)` bir zamanlar
1515
+ // "9007199254740992" yazıyordu: uygulamanın tuttuğundan BAŞKA bir sayı
1516
+ // kolona gidiyordu ve hiçbir şey söylemiyordu. `asNumber()`'ın JSDoc'u
1517
+ // "2^53'ün altında güvenli" diyordu ama bunu uygulayan bir şey yoktu.
1518
+ toDb: /* @__PURE__ */ __name((v) => {
1519
+ if (typeof v === "number" && !Number.isSafeInteger(v)) {
1520
+ throw new Error(`asNumber() cannot write ${v}: it is outside the safe integer range and would be stored as a different number. Declare .asDecimal() and hand this column a string.`);
1521
+ }
1522
+ return String(v);
1523
+ }, "toDb"),
1490
1524
  tsType: "number"
1491
1525
  },
1492
1526
  // Exact-precision column → the string Postgres sent. It exists so the
1493
1527
  // DECLARATION is explicit: "this column is a decimal I handle as text",
1494
1528
  // rather than the ABSENCE of a declaration meaning the same thing by default.
1529
+ // NULL güvencesi ÇİFTİN KENDİSİNDE. Motor NULL'ı zaten atlıyor (üç ayrı
1530
+ // yerde), ama bir değişmez, onu koruyan üç `if`'in DIŞINDA yazılmalı: biri
1531
+ // düşerse `String(null)` kolonu "null" dizesine çevirirdi.
1495
1532
  decimal: {
1496
- fromDb: /* @__PURE__ */ __name((v) => String(v), "fromDb"),
1497
- toDb: /* @__PURE__ */ __name((v) => String(v), "toDb"),
1533
+ fromDb: /* @__PURE__ */ __name((v) => v === null || v === void 0 ? v : String(v), "fromDb"),
1534
+ toDb: /* @__PURE__ */ __name((v) => v === null || v === void 0 ? v : String(v), "toDb"),
1498
1535
  tsType: "string"
1499
1536
  }
1500
1537
  };
@@ -4766,23 +4803,21 @@ async function createApp(opts) {
4766
4803
  if (spec.required && !callerClaims) {
4767
4804
  return envelope("unauthorized", "A valid access token is required", 401, requestId);
4768
4805
  }
4769
- if (callerClaims) {
4770
- const uploadDb = createRequestDatabase(sql, {
4771
- role: config.dbRole,
4772
- serviceRole: config.dbServiceRole,
4773
- claimsJson: JSON.stringify(callerClaims)
4774
- });
4775
- try {
4776
- const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId, makeRoleResolver(uploadDb.client));
4777
- if (refusal) {
4778
- await uploadDb.commit();
4779
- return refusal;
4780
- }
4806
+ const uploadDb = createRequestDatabase(sql, {
4807
+ role: config.dbRole,
4808
+ serviceRole: config.dbServiceRole,
4809
+ claimsJson: JSON.stringify(callerClaims ?? {})
4810
+ });
4811
+ try {
4812
+ const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId, makeRoleResolver(uploadDb.client));
4813
+ if (refusal) {
4781
4814
  await uploadDb.commit();
4782
- } catch (err) {
4783
- await uploadDb.rollback(err);
4784
- throw err;
4815
+ return refusal;
4785
4816
  }
4817
+ await uploadDb.commit();
4818
+ } catch (err) {
4819
+ await uploadDb.rollback(err);
4820
+ throw err;
4786
4821
  }
4787
4822
  const grant = grantFor(target?.entry, {
4788
4823
  userId: typeof callerClaims?.sub === "string" ? callerClaims.sub : null,
@@ -4821,11 +4856,18 @@ async function createApp(opts) {
4821
4856
  claimsJson: JSON.stringify(claims ?? {})
4822
4857
  });
4823
4858
  const rolesOfCaller = makeRoleResolver(db.client);
4824
- if (claims) {
4825
- const refusal = await refuseUnlessAuthorized(db.client, spec, requestId, rolesOfCaller);
4826
- if (refusal) return refusal;
4859
+ let dispatched = false;
4860
+ const settleBeforeRefusal = /* @__PURE__ */ __name(async () => {
4861
+ if (dispatched) return;
4862
+ await db.rollback(new Error("request refused before dispatch"));
4863
+ }, "settleBeforeRefusal");
4864
+ const refusal = await refuseUnlessAuthorized(db.client, spec, requestId, rolesOfCaller);
4865
+ if (refusal) {
4866
+ await settleBeforeRefusal();
4867
+ return refusal;
4827
4868
  }
4828
4869
  if (claims && spec.verifiedEmail && claims.email_verified !== true) {
4870
+ await settleBeforeRefusal();
4829
4871
  return envelope("email_not_verified", "A verified email address is required", 403, requestId);
4830
4872
  }
4831
4873
  let parsedBody;
@@ -4834,6 +4876,7 @@ async function createApp(opts) {
4834
4876
  if (req.method !== "GET" && req.method !== "HEAD") {
4835
4877
  const declared = Number(req.headers.get("content-length") ?? "");
4836
4878
  if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
4879
+ await settleBeforeRefusal();
4837
4880
  return new Response(JSON.stringify({
4838
4881
  error: "payload_too_large",
4839
4882
  error_description: `Request body exceeds ${MAX_BODY_BYTES} bytes`,
@@ -4846,6 +4889,7 @@ async function createApp(opts) {
4846
4889
  }
4847
4890
  const buf = await req.arrayBuffer().catch(() => null);
4848
4891
  if (buf && buf.byteLength > MAX_BODY_BYTES) {
4892
+ await settleBeforeRefusal();
4849
4893
  return new Response(JSON.stringify({
4850
4894
  error: "payload_too_large",
4851
4895
  error_description: `Request body exceeds ${MAX_BODY_BYTES} bytes`,
@@ -4881,6 +4925,7 @@ async function createApp(opts) {
4881
4925
  ...JSON_HEADERS
4882
4926
  };
4883
4927
  if (attest.nextChallenge) headers["x-palbase-attest-challenge"] = attest.nextChallenge;
4928
+ await settleBeforeRefusal();
4884
4929
  return new Response(JSON.stringify({
4885
4930
  error: attest.reason,
4886
4931
  error_description: "This endpoint requires a verified app instance",
@@ -4895,6 +4940,7 @@ async function createApp(opts) {
4895
4940
  attestChallengeOut = attest.nextChallenge;
4896
4941
  const retryAfter = limiter.check(meta.options?.rateLimit, RateLimiter.key(hit.entry.id, userId, req.headers), Date.now());
4897
4942
  if (retryAfter !== null) {
4943
+ await settleBeforeRefusal();
4898
4944
  return new Response(JSON.stringify({
4899
4945
  error: "too_many_requests",
4900
4946
  error_description: "Rate limit exceeded for this endpoint",
@@ -4926,6 +4972,7 @@ async function createApp(opts) {
4926
4972
  readParsedBody();
4927
4973
  const r = p.schema.safeParse(parsedBody);
4928
4974
  if (!r.success) {
4975
+ await settleBeforeRefusal();
4929
4976
  return envelope("bad_request", "Request body failed validation", 400, requestId, {
4930
4977
  data: {
4931
4978
  fields: fieldErrors(r.error)
@@ -4938,6 +4985,7 @@ async function createApp(opts) {
4938
4985
  case "query": {
4939
4986
  const r = p.schema.safeParse(Object.fromEntries(url.searchParams));
4940
4987
  if (!r.success) {
4988
+ await settleBeforeRefusal();
4941
4989
  return envelope("bad_request", "Query parameters failed validation", 400, requestId, {
4942
4990
  data: {
4943
4991
  fields: fieldErrors(r.error)
@@ -4958,6 +5006,7 @@ async function createApp(opts) {
4958
5006
  }
4959
5007
  const r = p.schema.safeParse(headersFor(raw, p.schema));
4960
5008
  if (!r.success) {
5009
+ await settleBeforeRefusal();
4961
5010
  return envelope("bad_request", "Request headers failed validation", 400, requestId, {
4962
5011
  data: {
4963
5012
  fields: fieldErrors(r.error)
@@ -4990,17 +5039,20 @@ async function createApp(opts) {
4990
5039
  break;
4991
5040
  case "uploadedObject": {
4992
5041
  if (uploadSecret === "" || !verifySignature(req.headers.get(SIGNATURE_HEADER) ?? "", uploadSecret)) {
5042
+ await settleBeforeRefusal();
4993
5043
  return envelope("unauthorized", "This endpoint accepts uploads through storage, not directly", 401, requestId);
4994
5044
  }
4995
5045
  readParsedBody();
4996
5046
  const envelopeIn = parsedBody;
4997
5047
  if (!envelopeIn?.uploadedObject) {
5048
+ await settleBeforeRefusal();
4998
5049
  return envelope("bad_request", "the completion call carried no uploaded object", 400, requestId);
4999
5050
  }
5000
5051
  const uploadId = envelopeIn.uploadedObject.uploadId;
5001
5052
  if (typeof uploadId === "string" && uploadId !== "") {
5002
5053
  const already = completions.recall(uploadId);
5003
5054
  if (already) {
5055
+ await settleBeforeRefusal();
5004
5056
  return new Response(already.body, {
5005
5057
  status: already.status,
5006
5058
  headers: already.contentType ? {
@@ -5050,6 +5102,7 @@ async function createApp(opts) {
5050
5102
  args[p.index] = void 0;
5051
5103
  }
5052
5104
  }
5105
+ dispatched = true;
5053
5106
  try {
5054
5107
  const services = buildServices(db);
5055
5108
  if (meta.options?.sseConfig !== void 0) {