@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
@@ -827,7 +827,11 @@ function qualifiedTableKey(schemaName, tableName) {
827
827
  __name(qualifiedTableKey, "qualifiedTableKey");
828
828
 
829
829
  // src/runtime.ts
830
- var __requestALS = new import_node_async_hooks.AsyncLocalStorage();
830
+ var REQUEST_ALS = /* @__PURE__ */ Symbol.for("palbase.backend.requestALS");
831
+ var __requestALS = (() => {
832
+ const g = globalThis;
833
+ return g[REQUEST_ALS] ??= new import_node_async_hooks.AsyncLocalStorage();
834
+ })();
831
835
  var runtime = null;
832
836
  function __runWithRuntime(services, fn) {
833
837
  return __requestALS.run({
@@ -835,9 +839,29 @@ function __runWithRuntime(services, fn) {
835
839
  }, fn);
836
840
  }
837
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");
838
862
  function __getRuntime() {
839
863
  const scoped = __requestALS.getStore();
840
- if (scoped) return scoped.runtime;
864
+ if (scoped) return refuseIncompleteBox(scoped.runtime);
841
865
  if (runtime === null) {
842
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).");
843
867
  }
@@ -960,7 +984,12 @@ function makeTypedSurface(raw) {
960
984
  $findById: /* @__PURE__ */ __name((table, id) => raw.findById(table, id), "$findById"),
961
985
  $findMany: /* @__PURE__ */ __name((table, query, opts) => raw.findMany(table, query, opts), "$findMany"),
962
986
  $put: /* @__PURE__ */ __name((table, data, opts) => raw.put(table, data, opts), "$put"),
963
- $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"),
964
993
  $deleteMany: /* @__PURE__ */ __name((table, where) => raw.deleteMany(table, where), "$deleteMany"),
965
994
  $count: /* @__PURE__ */ __name((table, where) => raw.count(table, where), "$count"),
966
995
  $search: /* @__PURE__ */ __name((table, params) => raw.search(table, params), "$search"),
@@ -1091,10 +1120,23 @@ var Flags = Object.assign({
1091
1120
  * Lazily resolve the runtime's cross-user sibling on each call. We do NOT
1092
1121
  * cache it: `rawFlags.asService()` reads the CURRENT request scope through
1093
1122
  * the runtime proxy, so caching would leak one request's sibling into
1094
- * another concurrent request. Mirrors `Database.asService()`.
1123
+ * another concurrent request. Mirrors `Database.$asService()`.
1095
1124
  */
1096
- asService() {
1125
+ $asService() {
1097
1126
  return rawFlags.asService();
1127
+ },
1128
+ /**
1129
+ * The name this member carried before 32.0.0.
1130
+ *
1131
+ * It does NOT work silently. A retired member that quietly keeps returning
1132
+ * is how a rename becomes a mystery: the old call site goes on compiling,
1133
+ * the new name never spreads, and the two live side by side until somebody
1134
+ * greps for one and misses half the codebase. `Database` made this split
1135
+ * first (`$asService`), and this JSDoc promised "exactly like
1136
+ * Database.$asService()" while the promise went unkept.
1137
+ */
1138
+ 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().");
1098
1140
  }
1099
1141
  });
1100
1142
  var Realtime = makeServiceProxy("Realtime");
@@ -1463,6 +1505,37 @@ function makeMemoryCache(opts = {}) {
1463
1505
  }
1464
1506
  __name(makeMemoryCache, "makeMemoryCache");
1465
1507
 
1508
+ // src/db/columns.ts
1509
+ var CODECS = {
1510
+ // Exact-precision column → JS number. Safe below 2^53; a value above it is
1511
+ // not representable and the author wants `asDecimal()` instead.
1512
+ number: {
1513
+ fromDb: /* @__PURE__ */ __name((v) => Number(v), "fromDb"),
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"),
1524
+ tsType: "number"
1525
+ },
1526
+ // Exact-precision column → the string Postgres sent. It exists so the
1527
+ // DECLARATION is explicit: "this column is a decimal I handle as text",
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.
1532
+ decimal: {
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"),
1535
+ tsType: "string"
1536
+ }
1537
+ };
1538
+
1466
1539
  // src/db/env-gen.ts
1467
1540
  function forwardName(column) {
1468
1541
  return column.endsWith("_id") ? column.slice(0, -3) : column;
@@ -1676,6 +1749,15 @@ function transformsOf(schema, table) {
1676
1749
  if (def) {
1677
1750
  for (const [col, c] of Object.entries(def.columns ?? {})) {
1678
1751
  const d = c !== null && typeof c === "object" && "_def" in c ? c._def : c;
1752
+ const codec = d?.codec;
1753
+ if (typeof codec === "string" && codec in CODECS) {
1754
+ const c2 = CODECS[codec];
1755
+ out.set(col, {
1756
+ fromDb: c2.fromDb,
1757
+ toDb: c2.toDb
1758
+ });
1759
+ continue;
1760
+ }
1679
1761
  const t = d?.transform;
1680
1762
  if (t !== null && typeof t === "object") {
1681
1763
  out.set(col, t);
@@ -2011,6 +2093,29 @@ function selectClause(table, known, select) {
2011
2093
  }).join(", ");
2012
2094
  }
2013
2095
  __name(selectClause, "selectClause");
2096
+ function quoteLiteral(v) {
2097
+ return `'${v.replace(/'/g, "''")}'`;
2098
+ }
2099
+ __name(quoteLiteral, "quoteLiteral");
2100
+ function orderClauseFor(table, known, orderBy, alias) {
2101
+ if (orderBy === void 0 || orderBy === null) return "";
2102
+ const items = Array.isArray(orderBy) ? orderBy : [
2103
+ orderBy
2104
+ ];
2105
+ if (items.length === 0) return "";
2106
+ const rendered = items.map((o) => {
2107
+ const col = o.column;
2108
+ const shapeOK = /^[A-Za-z_][A-Za-z0-9_]*$/.test(col);
2109
+ if (known !== null ? !known.has(col) : !shapeOK) {
2110
+ throw new Error(`with(${table}): orderBy kolonu "${col}" tabloda yok`);
2111
+ }
2112
+ const dir = o.direction === "desc" ? "DESC" : "ASC";
2113
+ const nulls = o.nulls === "first" ? " NULLS FIRST" : o.nulls === "last" ? " NULLS LAST" : "";
2114
+ return `${alias}.${quoteIdent(col)} ${dir}${nulls}`;
2115
+ });
2116
+ return ` ORDER BY ${rendered.join(", ")}`;
2117
+ }
2118
+ __name(orderClauseFor, "orderClauseFor");
2014
2119
  function orderClause(table, known, orderBy) {
2015
2120
  if (!orderBy) return "";
2016
2121
  const items = Array.isArray(orderBy) ? orderBy : [
@@ -2314,6 +2419,65 @@ function aggregateExpr(table, fn, col, alias) {
2314
2419
  return t !== null && NUMERIC_PG_TYPES.has(t) && t !== "integer" ? `${fn}(${q})::text AS ${quoteIdent(alias)}` : `${fn}(${q}) AS ${quoteIdent(alias)}`;
2315
2420
  }
2316
2421
  __name(aggregateExpr, "aggregateExpr");
2422
+ function jsonObjectFor(table, cols, alias) {
2423
+ const pairs = cols.map((c) => {
2424
+ const t = columnTypeOf(table, c);
2425
+ const expr = t !== null && (t === "numeric" || t === "bigint") ? `${alias}.${quoteIdent(c)}::text` : `${alias}.${quoteIdent(c)}`;
2426
+ return `${quoteLiteral(c)}, ${expr}`;
2427
+ });
2428
+ return `json_build_object(${pairs.join(", ")})`;
2429
+ }
2430
+ __name(jsonObjectFor, "jsonObjectFor");
2431
+ function withClauses(table, spec, add, caller) {
2432
+ const rels = currentRelations.get(table) ?? [];
2433
+ const select = [];
2434
+ const joins = [];
2435
+ let i = 0;
2436
+ for (const [name, opt] of Object.entries(spec)) {
2437
+ if (opt === false || opt === void 0) continue;
2438
+ const rel = rels.find((r) => r.name === name);
2439
+ if (rel === void 0) {
2440
+ const known = rels.map((r) => r.name).sort();
2441
+ throw new Error(`${caller}(${table}): with.${name} diye bir ili\u015Fki yok` + (known.length > 0 ? ` (bilinenler: ${known.join(", ")})` : `. Bu tablo hi\xE7 yabanc\u0131 anahtar bildirmiyor ve ili\u015Fkiler onlardan T\xDCRET\u0130L\u0130R`) + `. \u0130li\u015Fkiler references(() => <tablo>.<kolon>) ile bildirilir.`);
2442
+ }
2443
+ const ourCol = rel.kind === "many" ? primaryKeyOf(table) : rel.via;
2444
+ const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
2445
+ if (ourCol === null || theirCol === null) {
2446
+ throw new Error(`${caller}(${table}): with.${name} birincil anahtar bilmeden kurulamaz \u2014 ili\u015Fkinin bir ucu tablonun PK's\u0131 ve bu tablo tek kolonlu bir PK bildirmiyor.`);
2447
+ }
2448
+ const o = typeof opt === "object" && opt !== null ? opt : {};
2449
+ const childAlias = `w${++i}`;
2450
+ const childKnown = schemaColumns(rel.to);
2451
+ const childCols = Array.isArray(o["select"]) && o["select"].length > 0 ? o["select"] : childKnown !== null ? [
2452
+ ...childKnown
2453
+ ] : [];
2454
+ if (childCols.length === 0) {
2455
+ throw new Error(`${caller}(${table}): with.${name} i\xE7in "${rel.to}" tablosunun kolonlar\u0131 bilinmiyor \u2014 select ile hangi kolonlar\u0131 istedi\u011Finizi yaz\u0131n.`);
2456
+ }
2457
+ for (const c of childCols) {
2458
+ if (childKnown !== null && !childKnown.has(c)) {
2459
+ throw new Error(`${caller}(${table}): with.${name}.select kolonu "${c}" "${rel.to}" tablosunda yok`);
2460
+ }
2461
+ }
2462
+ const obj = jsonObjectFor(rel.to, childCols, childAlias);
2463
+ const innerWhere = o["where"] !== void 0 && o["where"] !== null ? compileWhereBare(rel.to, childKnown, o["where"], add, caller, childAlias, i) : "";
2464
+ const link = `${childAlias}.${quoteIdent(theirCol)} = t.${quoteIdent(ourCol)}`;
2465
+ const cond = innerWhere === "" ? link : `${link} AND ${innerWhere}`;
2466
+ const inner = orderClauseFor(rel.to, childKnown, o["orderBy"], childAlias) + limitClause(o["limit"]);
2467
+ const outAlias = `wj${i}`;
2468
+ if (rel.kind === "many") {
2469
+ joins.push(`LEFT JOIN LATERAL (SELECT coalesce(json_agg(x.o), '[]'::json) AS d FROM (SELECT ${obj} AS o FROM ${quoteTable(rel.to)} ${childAlias} WHERE ${cond}${inner}) x) ${quoteIdent(outAlias)} ON true`);
2470
+ } else {
2471
+ joins.push(`LEFT JOIN LATERAL (SELECT ${obj} AS d FROM ${quoteTable(rel.to)} ${childAlias} WHERE ${cond}${inner} LIMIT 1) ${quoteIdent(outAlias)} ON true`);
2472
+ }
2473
+ select.push(`${quoteIdent(outAlias)}."d" AS ${quoteIdent(name)}`);
2474
+ }
2475
+ return {
2476
+ select,
2477
+ joins
2478
+ };
2479
+ }
2480
+ __name(withClauses, "withClauses");
2317
2481
  function createOps(tx) {
2318
2482
  const at = /* @__PURE__ */ __name(() => resolveTx(tx), "at");
2319
2483
  const ops = {
@@ -2757,7 +2921,13 @@ function createOps(tx) {
2757
2921
  const limit = limitClause(opts.limit);
2758
2922
  const offset = offsetClause(opts.offset, opts.limit);
2759
2923
  const cols = selectClause(table, known, opts.select);
2760
- const sql = `SELECT ${cols} FROM ${quoteTable(table)} t WHERE true${whereSql}${order}${limit}${offset}`;
2924
+ const w = opts.with !== void 0 && Object.keys(opts.with).length > 0 ? withClauses(table, opts.with, add, "findMany") : {
2925
+ select: [],
2926
+ joins: []
2927
+ };
2928
+ const selectList = w.select.length > 0 ? `${cols === "*" ? "t.*" : cols}, ${w.select.join(", ")}` : cols;
2929
+ const joinSql = w.joins.length > 0 ? ` ${w.joins.join(" ")}` : "";
2930
+ const sql = `SELECT ${selectList} FROM ${quoteTable(table)} t${joinSql} WHERE true${whereSql}${order}${limit}${offset}`;
2761
2931
  return asTableRows(table, await (await at()).unsafe(sql, params));
2762
2932
  },
2763
2933
  /**
@@ -4633,23 +4803,21 @@ async function createApp(opts) {
4633
4803
  if (spec.required && !callerClaims) {
4634
4804
  return envelope("unauthorized", "A valid access token is required", 401, requestId);
4635
4805
  }
4636
- if (callerClaims) {
4637
- const uploadDb = createRequestDatabase(sql, {
4638
- role: config.dbRole,
4639
- serviceRole: config.dbServiceRole,
4640
- claimsJson: JSON.stringify(callerClaims)
4641
- });
4642
- try {
4643
- const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId, makeRoleResolver(uploadDb.client));
4644
- if (refusal) {
4645
- await uploadDb.commit();
4646
- return refusal;
4647
- }
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) {
4648
4814
  await uploadDb.commit();
4649
- } catch (err) {
4650
- await uploadDb.rollback(err);
4651
- throw err;
4815
+ return refusal;
4652
4816
  }
4817
+ await uploadDb.commit();
4818
+ } catch (err) {
4819
+ await uploadDb.rollback(err);
4820
+ throw err;
4653
4821
  }
4654
4822
  const grant = grantFor(target?.entry, {
4655
4823
  userId: typeof callerClaims?.sub === "string" ? callerClaims.sub : null,
@@ -4688,11 +4856,18 @@ async function createApp(opts) {
4688
4856
  claimsJson: JSON.stringify(claims ?? {})
4689
4857
  });
4690
4858
  const rolesOfCaller = makeRoleResolver(db.client);
4691
- if (claims) {
4692
- const refusal = await refuseUnlessAuthorized(db.client, spec, requestId, rolesOfCaller);
4693
- 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;
4694
4868
  }
4695
4869
  if (claims && spec.verifiedEmail && claims.email_verified !== true) {
4870
+ await settleBeforeRefusal();
4696
4871
  return envelope("email_not_verified", "A verified email address is required", 403, requestId);
4697
4872
  }
4698
4873
  let parsedBody;
@@ -4701,6 +4876,7 @@ async function createApp(opts) {
4701
4876
  if (req.method !== "GET" && req.method !== "HEAD") {
4702
4877
  const declared = Number(req.headers.get("content-length") ?? "");
4703
4878
  if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
4879
+ await settleBeforeRefusal();
4704
4880
  return new Response(JSON.stringify({
4705
4881
  error: "payload_too_large",
4706
4882
  error_description: `Request body exceeds ${MAX_BODY_BYTES} bytes`,
@@ -4713,6 +4889,7 @@ async function createApp(opts) {
4713
4889
  }
4714
4890
  const buf = await req.arrayBuffer().catch(() => null);
4715
4891
  if (buf && buf.byteLength > MAX_BODY_BYTES) {
4892
+ await settleBeforeRefusal();
4716
4893
  return new Response(JSON.stringify({
4717
4894
  error: "payload_too_large",
4718
4895
  error_description: `Request body exceeds ${MAX_BODY_BYTES} bytes`,
@@ -4748,6 +4925,7 @@ async function createApp(opts) {
4748
4925
  ...JSON_HEADERS
4749
4926
  };
4750
4927
  if (attest.nextChallenge) headers["x-palbase-attest-challenge"] = attest.nextChallenge;
4928
+ await settleBeforeRefusal();
4751
4929
  return new Response(JSON.stringify({
4752
4930
  error: attest.reason,
4753
4931
  error_description: "This endpoint requires a verified app instance",
@@ -4762,6 +4940,7 @@ async function createApp(opts) {
4762
4940
  attestChallengeOut = attest.nextChallenge;
4763
4941
  const retryAfter = limiter.check(meta.options?.rateLimit, RateLimiter.key(hit.entry.id, userId, req.headers), Date.now());
4764
4942
  if (retryAfter !== null) {
4943
+ await settleBeforeRefusal();
4765
4944
  return new Response(JSON.stringify({
4766
4945
  error: "too_many_requests",
4767
4946
  error_description: "Rate limit exceeded for this endpoint",
@@ -4793,6 +4972,7 @@ async function createApp(opts) {
4793
4972
  readParsedBody();
4794
4973
  const r = p.schema.safeParse(parsedBody);
4795
4974
  if (!r.success) {
4975
+ await settleBeforeRefusal();
4796
4976
  return envelope("bad_request", "Request body failed validation", 400, requestId, {
4797
4977
  data: {
4798
4978
  fields: fieldErrors(r.error)
@@ -4805,6 +4985,7 @@ async function createApp(opts) {
4805
4985
  case "query": {
4806
4986
  const r = p.schema.safeParse(Object.fromEntries(url.searchParams));
4807
4987
  if (!r.success) {
4988
+ await settleBeforeRefusal();
4808
4989
  return envelope("bad_request", "Query parameters failed validation", 400, requestId, {
4809
4990
  data: {
4810
4991
  fields: fieldErrors(r.error)
@@ -4825,6 +5006,7 @@ async function createApp(opts) {
4825
5006
  }
4826
5007
  const r = p.schema.safeParse(headersFor(raw, p.schema));
4827
5008
  if (!r.success) {
5009
+ await settleBeforeRefusal();
4828
5010
  return envelope("bad_request", "Request headers failed validation", 400, requestId, {
4829
5011
  data: {
4830
5012
  fields: fieldErrors(r.error)
@@ -4857,17 +5039,20 @@ async function createApp(opts) {
4857
5039
  break;
4858
5040
  case "uploadedObject": {
4859
5041
  if (uploadSecret === "" || !verifySignature(req.headers.get(SIGNATURE_HEADER) ?? "", uploadSecret)) {
5042
+ await settleBeforeRefusal();
4860
5043
  return envelope("unauthorized", "This endpoint accepts uploads through storage, not directly", 401, requestId);
4861
5044
  }
4862
5045
  readParsedBody();
4863
5046
  const envelopeIn = parsedBody;
4864
5047
  if (!envelopeIn?.uploadedObject) {
5048
+ await settleBeforeRefusal();
4865
5049
  return envelope("bad_request", "the completion call carried no uploaded object", 400, requestId);
4866
5050
  }
4867
5051
  const uploadId = envelopeIn.uploadedObject.uploadId;
4868
5052
  if (typeof uploadId === "string" && uploadId !== "") {
4869
5053
  const already = completions.recall(uploadId);
4870
5054
  if (already) {
5055
+ await settleBeforeRefusal();
4871
5056
  return new Response(already.body, {
4872
5057
  status: already.status,
4873
5058
  headers: already.contentType ? {
@@ -4917,6 +5102,7 @@ async function createApp(opts) {
4917
5102
  args[p.index] = void 0;
4918
5103
  }
4919
5104
  }
5105
+ dispatched = true;
4920
5106
  try {
4921
5107
  const services = buildServices(db);
4922
5108
  if (meta.options?.sseConfig !== void 0) {