@palbase/backend 28.0.0 → 29.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 (48) hide show
  1. package/dist/bin/palbase-backend.cjs +151 -12
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +3 -3
  4. package/dist/{chunk-75YROPRZ.js → chunk-DHJ3SYAE.js} +152 -15
  5. package/dist/chunk-DHJ3SYAE.js.map +1 -0
  6. package/dist/{chunk-IVZERLTM.js → chunk-ENZ2RFFJ.js} +2 -2
  7. package/dist/{chunk-RVP6BTEZ.js → chunk-ESSQ3YML.js} +13 -4
  8. package/dist/chunk-ESSQ3YML.js.map +1 -0
  9. package/dist/{chunk-SNDXY565.js → chunk-SG4UTNOP.js} +5 -2
  10. package/dist/chunk-SG4UTNOP.js.map +1 -0
  11. package/dist/db/index.cjs +4 -1
  12. package/dist/db/index.cjs.map +1 -1
  13. package/dist/db/index.d.cts +1 -1
  14. package/dist/db/index.d.ts +1 -1
  15. package/dist/db/index.js +2 -2
  16. package/dist/engine/index.cjs +151 -12
  17. package/dist/engine/index.cjs.map +1 -1
  18. package/dist/engine/index.d.cts +3 -3
  19. package/dist/engine/index.d.ts +3 -3
  20. package/dist/engine/index.js +3 -3
  21. package/dist/{index-dTTLlHIn.d.ts → index-BOe82fsK.d.ts} +162 -8
  22. package/dist/{index-BbvOoZFr.d.ts → index-BYDUKexK.d.ts} +29 -7
  23. package/dist/{index-9C3JHxg-.d.cts → index-BZfmdRmh.d.cts} +29 -7
  24. package/dist/{index-DtISj9QX.d.cts → index-ChvoVczS.d.cts} +162 -8
  25. package/dist/index.cjs +15 -3
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +6 -6
  28. package/dist/index.d.ts +6 -6
  29. package/dist/index.js +3 -3
  30. package/dist/openapi/index.d.cts +2 -2
  31. package/dist/openapi/index.d.ts +2 -2
  32. package/dist/{registry-qIPM5BQe.d.cts → registry-B2KfKWv1.d.cts} +1 -1
  33. package/dist/{registry-JQNIX-eA.d.ts → registry-DaqHsOq8.d.ts} +1 -1
  34. package/dist/test/index.cjs +56 -0
  35. package/dist/test/index.cjs.map +1 -1
  36. package/dist/test/index.d.cts +1 -1
  37. package/dist/test/index.d.ts +1 -1
  38. package/dist/test/index.js +56 -0
  39. package/dist/test/index.js.map +1 -1
  40. package/docs/README.md +1 -1
  41. package/docs/database.md +49 -0
  42. package/docs/llms-full.txt +50 -1
  43. package/package.json +1 -1
  44. package/template/package.json +1 -1
  45. package/dist/chunk-75YROPRZ.js.map +0 -1
  46. package/dist/chunk-RVP6BTEZ.js.map +0 -1
  47. package/dist/chunk-SNDXY565.js.map +0 -1
  48. /package/dist/{chunk-IVZERLTM.js.map → chunk-ENZ2RFFJ.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- export { f as AnyColumn, k as ColumnBuilder, l as ColumnDef, m as ColumnMap, n as ColumnType, E as EXTENSION_DEPENDENCIES, s as EmbeddingModelRef, u as EnvServiceDatabase, w as EnvTypedDatabase, N as InsertShape, V as Materialized, a0 as OnDeleteAction, a1 as PALBASE_EXTENSIONS, aA as PalbaseExtension, bx as PolicyBuilder, by as PolicyCommand, bz as PolicyDef, bC as PolicyMode, bE as RawConstraintDef, bG as Ref, bI as RowShape, S as SchemaDef, bR as TABLE_META, bS as TableDef, bT as TableHandle, bU as TableInput, bW as TxColumnExpr, bX as TxInsertShape, bZ as TxNow, b_ as TxPlan, T as TxPlanBody, b$ as TxPlanError, c0 as TxPlanHandle, c1 as TxPlanOpResult, c2 as TxPlanRejection, b as TxPlanResponse, c3 as TxRefError, c4 as TxRow, c5 as TxRows, c6 as TxSelectOptions, c7 as TxSetShape, c9 as TxTable, ca as TxTables, cb as TxWhere, ch as TypedDB, ci as TypedTable, cj as TypedTx, cs as bigint, ct as boolean, cv as dec, cy as defineSchema, cz as defineTable, cA as enumType, cC as inc, cF as installationRef, cG as integer, cH as isPalbaseExtension, cJ as jsonb, cK as makeTypedDB, cL as now, cP as openai, cQ as ownedByUser, cR as policy, cS as raw, cU as text, cV as timestamp, cW as userRef, cX as uuid, cY as vector } from '../index-DtISj9QX.cjs';
1
+ export { f as AnyColumn, k as ColumnBuilder, l as ColumnDef, m as ColumnMap, n as ColumnType, E as EXTENSION_DEPENDENCIES, s as EmbeddingModelRef, u as EnvServiceDatabase, w as EnvTypedDatabase, N as InsertShape, V as Materialized, a0 as OnDeleteAction, a1 as PALBASE_EXTENSIONS, aA as PalbaseExtension, bx as PolicyBuilder, by as PolicyCommand, bz as PolicyDef, bC as PolicyMode, bE as RawConstraintDef, bG as Ref, bI as RowShape, S as SchemaDef, bR as TABLE_META, bS as TableDef, bT as TableHandle, bU as TableInput, bW as TxColumnExpr, bX as TxInsertShape, bZ as TxNow, b_ as TxPlan, T as TxPlanBody, b$ as TxPlanError, c0 as TxPlanHandle, c1 as TxPlanOpResult, c2 as TxPlanRejection, b as TxPlanResponse, c3 as TxRefError, c4 as TxRow, c5 as TxRows, c6 as TxSelectOptions, c7 as TxSetShape, c9 as TxTable, ca as TxTables, cb as TxWhere, ch as TypedDB, ci as TypedTable, cj as TypedTx, cs as bigint, ct as boolean, cv as dec, cy as defineSchema, cz as defineTable, cA as enumType, cC as inc, cF as installationRef, cG as integer, cH as isPalbaseExtension, cJ as jsonb, cK as makeTypedDB, cL as now, cP as openai, cQ as ownedByUser, cR as policy, cS as raw, cU as text, cV as timestamp, cW as userRef, cX as uuid, cY as vector } from '../index-ChvoVczS.cjs';
2
2
  import './env.cjs';
3
3
  import '../stack.cjs';
4
4
  import 'zod';
@@ -1,4 +1,4 @@
1
- export { f as AnyColumn, k as ColumnBuilder, l as ColumnDef, m as ColumnMap, n as ColumnType, E as EXTENSION_DEPENDENCIES, s as EmbeddingModelRef, u as EnvServiceDatabase, w as EnvTypedDatabase, N as InsertShape, V as Materialized, a0 as OnDeleteAction, a1 as PALBASE_EXTENSIONS, aA as PalbaseExtension, bx as PolicyBuilder, by as PolicyCommand, bz as PolicyDef, bC as PolicyMode, bE as RawConstraintDef, bG as Ref, bI as RowShape, S as SchemaDef, bR as TABLE_META, bS as TableDef, bT as TableHandle, bU as TableInput, bW as TxColumnExpr, bX as TxInsertShape, bZ as TxNow, b_ as TxPlan, T as TxPlanBody, b$ as TxPlanError, c0 as TxPlanHandle, c1 as TxPlanOpResult, c2 as TxPlanRejection, b as TxPlanResponse, c3 as TxRefError, c4 as TxRow, c5 as TxRows, c6 as TxSelectOptions, c7 as TxSetShape, c9 as TxTable, ca as TxTables, cb as TxWhere, ch as TypedDB, ci as TypedTable, cj as TypedTx, cs as bigint, ct as boolean, cv as dec, cy as defineSchema, cz as defineTable, cA as enumType, cC as inc, cF as installationRef, cG as integer, cH as isPalbaseExtension, cJ as jsonb, cK as makeTypedDB, cL as now, cP as openai, cQ as ownedByUser, cR as policy, cS as raw, cU as text, cV as timestamp, cW as userRef, cX as uuid, cY as vector } from '../index-dTTLlHIn.js';
1
+ export { f as AnyColumn, k as ColumnBuilder, l as ColumnDef, m as ColumnMap, n as ColumnType, E as EXTENSION_DEPENDENCIES, s as EmbeddingModelRef, u as EnvServiceDatabase, w as EnvTypedDatabase, N as InsertShape, V as Materialized, a0 as OnDeleteAction, a1 as PALBASE_EXTENSIONS, aA as PalbaseExtension, bx as PolicyBuilder, by as PolicyCommand, bz as PolicyDef, bC as PolicyMode, bE as RawConstraintDef, bG as Ref, bI as RowShape, S as SchemaDef, bR as TABLE_META, bS as TableDef, bT as TableHandle, bU as TableInput, bW as TxColumnExpr, bX as TxInsertShape, bZ as TxNow, b_ as TxPlan, T as TxPlanBody, b$ as TxPlanError, c0 as TxPlanHandle, c1 as TxPlanOpResult, c2 as TxPlanRejection, b as TxPlanResponse, c3 as TxRefError, c4 as TxRow, c5 as TxRows, c6 as TxSelectOptions, c7 as TxSetShape, c9 as TxTable, ca as TxTables, cb as TxWhere, ch as TypedDB, ci as TypedTable, cj as TypedTx, cs as bigint, ct as boolean, cv as dec, cy as defineSchema, cz as defineTable, cA as enumType, cC as inc, cF as installationRef, cG as integer, cH as isPalbaseExtension, cJ as jsonb, cK as makeTypedDB, cL as now, cP as openai, cQ as ownedByUser, cR as policy, cS as raw, cU as text, cV as timestamp, cW as userRef, cX as uuid, cY as vector } from '../index-BOe82fsK.js';
2
2
  import './env.js';
3
3
  import '../stack.js';
4
4
  import 'zod';
package/dist/db/index.js CHANGED
@@ -21,10 +21,10 @@ import {
21
21
  userRef,
22
22
  uuid,
23
23
  vector
24
- } from "../chunk-IVZERLTM.js";
24
+ } from "../chunk-ENZ2RFFJ.js";
25
25
  import {
26
26
  makeTypedDB
27
- } from "../chunk-SNDXY565.js";
27
+ } from "../chunk-SG4UTNOP.js";
28
28
  import {
29
29
  TxPlanError,
30
30
  TxRefError,
@@ -948,6 +948,7 @@ function makeTableProxy(ops, prefix) {
948
948
  // DÖRDÜNCÜ FİİL, aynı gerekçeyle: tip söz veriyor, ops katmanı
949
949
  // uyguluyor, ve bir handler'ın gerçekten dokunduğu yer BURASI.
950
950
  // `runtime-table-verbs` kapısı bunu adıyla saydı.
951
+ aggregate: /* @__PURE__ */ __name((q) => ops().aggregate(name, q), "aggregate"),
951
952
  insertMany: /* @__PURE__ */ __name((rows, opts) => ops().insertMany(name, rows, opts), "insertMany"),
952
953
  update: /* @__PURE__ */ __name((q) => ops().update(name, q.where.id, q.set), "update"),
953
954
  delete: /* @__PURE__ */ __name((id) => ops().delete(name, id), "delete"),
@@ -1005,6 +1006,7 @@ function makeTypedSurface(raw) {
1005
1006
  $claim: /* @__PURE__ */ __name((table, unique, extra) => reco.claim(table, unique, extra), "$claim"),
1006
1007
  $lockRows: /* @__PURE__ */ __name((table, ids) => reco.lockRows(table, ids), "$lockRows"),
1007
1008
  $advisoryXactLock: /* @__PURE__ */ __name((key) => reco.advisoryXactLock(key), "$advisoryXactLock"),
1009
+ $aggregate: /* @__PURE__ */ __name((table, q) => raw.aggregate(table, q), "$aggregate"),
1008
1010
  $insertMany: /* @__PURE__ */ __name((table, rows, opts) => raw.insertMany(table, rows, opts), "$insertMany"),
1009
1011
  $supersede: /* @__PURE__ */ __name((table, id, row) => raw.supersede(table, id, row), "$supersede")
1010
1012
  };
@@ -1339,9 +1341,11 @@ function effectiveAuth(routeAuth, controllerAuth) {
1339
1341
  };
1340
1342
  const o = spec;
1341
1343
  const role = typeof o.role === "string" && o.role.trim() !== "" ? o.role.trim() : void 0;
1344
+ const permission = typeof o.permission === "string" && o.permission.trim() !== "" ? o.permission.trim() : void 0;
1342
1345
  return {
1343
1346
  required: o.required !== false,
1344
1347
  role,
1348
+ permission,
1345
1349
  verifiedEmail: o.verifiedEmail === true
1346
1350
  };
1347
1351
  }
@@ -2329,6 +2333,26 @@ function buildValueTuple(table, cols, data, add, caller) {
2329
2333
  }).join(", ");
2330
2334
  }
2331
2335
  __name(buildValueTuple, "buildValueTuple");
2336
+ function columnTypeOf(table, col) {
2337
+ const def = tableDefOf(table);
2338
+ const c = def?.columns?.[col];
2339
+ if (c === void 0 || c === null) return null;
2340
+ const d = typeof c === "object" && "_def" in c ? c._def : c;
2341
+ return d !== null && typeof d === "object" && typeof d.type === "string" ? d.type : null;
2342
+ }
2343
+ __name(columnTypeOf, "columnTypeOf");
2344
+ var NUMERIC_PG_TYPES = /* @__PURE__ */ new Set([
2345
+ "integer",
2346
+ "bigint",
2347
+ "numeric"
2348
+ ]);
2349
+ function aggregateExpr(table, fn, col, alias) {
2350
+ const q = `t.${quoteIdent(col)}`;
2351
+ const t = columnTypeOf(table, col);
2352
+ if (fn === "sum" || fn === "avg") return `${fn}(${q})::text AS ${quoteIdent(alias)}`;
2353
+ return t !== null && NUMERIC_PG_TYPES.has(t) && t !== "integer" ? `${fn}(${q})::text AS ${quoteIdent(alias)}` : `${fn}(${q}) AS ${quoteIdent(alias)}`;
2354
+ }
2355
+ __name(aggregateExpr, "aggregateExpr");
2332
2356
  function createOps(tx) {
2333
2357
  const at = /* @__PURE__ */ __name(() => resolveTx(tx), "at");
2334
2358
  const ops = {
@@ -2609,7 +2633,7 @@ function createOps(tx) {
2609
2633
  * should not silently succeed. Callers who mean every row say so with a
2610
2634
  * predicate that is true for every row.
2611
2635
  */
2612
- async updateMany(table, where, set) {
2636
+ async updateMany(table, where, set, opts) {
2613
2637
  assertNotAppendOnly("updateMany", table);
2614
2638
  const cols = Object.keys(set);
2615
2639
  if (cols.length === 0) {
@@ -2632,8 +2656,15 @@ function createOps(tx) {
2632
2656
  const whereSql = compileWhere(table, known, where, add, "updateMany");
2633
2657
  assertHasPredicate(whereSql, "updateMany", table);
2634
2658
  const pk = primaryKeyOf(table);
2635
- const sql = isSinglePkEquality(where, pk) || pk === null ? `UPDATE ${quoteTable(table)} AS t SET ${assignments} WHERE true${whereSql} RETURNING *` : `WITH locked AS (SELECT t.${quoteIdent(pk)} FROM ${quoteTable(table)} t WHERE true${whereSql} ORDER BY t.${quoteIdent(pk)} FOR NO KEY UPDATE) UPDATE ${quoteTable(table)} AS t SET ${assignments} FROM locked WHERE t.${quoteIdent(pk)} = locked.${quoteIdent(pk)} RETURNING t.*`;
2636
- return asTableRows(table, await (await at()).unsafe(sql, params));
2659
+ const wantRows = opts?.returning !== false;
2660
+ const tail = wantRows ? "RETURNING *" : "RETURNING 1";
2661
+ const simple = isSinglePkEquality(where, pk) || pk === null;
2662
+ const body = simple ? `UPDATE ${quoteTable(table)} AS t SET ${assignments} WHERE true${whereSql} ${tail}` : `UPDATE ${quoteTable(table)} AS t SET ${assignments} FROM locked WHERE t.${quoteIdent(pk)} = locked.${quoteIdent(pk)} ` + (wantRows ? "RETURNING t.*" : "RETURNING 1");
2663
+ const lockedCte = simple ? "" : `WITH locked AS (SELECT t.${quoteIdent(pk)} FROM ${quoteTable(table)} t WHERE true${whereSql} ORDER BY t.${quoteIdent(pk)} FOR NO KEY UPDATE) `;
2664
+ const sql = wantRows ? `${lockedCte}${body}` : simple ? `WITH upd AS (${body}) SELECT count(*)::int AS n FROM upd` : `${lockedCte.replace(/ $/, "")}, upd AS (${body}) SELECT count(*)::int AS n FROM upd`;
2665
+ const out = await (await at()).unsafe(sql, params);
2666
+ if (!wantRows) return Number(out[0]?.["n"] ?? 0);
2667
+ return asTableRows(table, out);
2637
2668
  },
2638
2669
  /**
2639
2670
  * Delete every row the filter matches, in ONE statement; resolves to how
@@ -2663,6 +2694,78 @@ function createOps(tx) {
2663
2694
  * An empty filter is legitimate HERE: counting a whole table is a read, and
2664
2695
  * reads do not destroy anything.
2665
2696
  */
2697
+ /**
2698
+ * Toplama — `count`'un yanına `sum`/`avg`/`min`/`max` ve `groupBy`.
2699
+ *
2700
+ * Var olma nedeni: bunlar olmadan "bu hesabın toplamı" ya `$query`'ye ya da
2701
+ * TÜM satırları çekip JS'te toplamaya düşüyor. İkincisi hem N+1 hem de para
2702
+ * için YANLIŞ — `numeric` JS `number`'ına uğradığı anda kesinliğini
2703
+ * kaybeder.
2704
+ *
2705
+ * `groupBy` verilirse SATIRLAR döner, verilmezse TEK sonuç. Boş kümede
2706
+ * Postgres NULL döndürür, 0 değil — ve bu SDK onu `null` olarak geçiriyor:
2707
+ * "hiç satır yoktu" ile "toplam sıfırdı" farklı şeyler.
2708
+ */
2709
+ async aggregate(table, q = {}) {
2710
+ const known = schemaColumns(table);
2711
+ const check = /* @__PURE__ */ __name((col, where) => {
2712
+ if (known !== null && !known.has(col)) {
2713
+ throw new Error(`aggregate(${table}): ${where} kolonu "${col}" tabloda yok (FR-016)`);
2714
+ }
2715
+ }, "check");
2716
+ const parts = [];
2717
+ const wants = [];
2718
+ for (const fn of [
2719
+ "sum",
2720
+ "avg",
2721
+ "min",
2722
+ "max"
2723
+ ]) {
2724
+ for (const col of q[fn] ?? []) {
2725
+ check(col, fn);
2726
+ if (fn === "sum" || fn === "avg") {
2727
+ const t = columnTypeOf(table, col);
2728
+ if (t !== null && !NUMERIC_PG_TYPES.has(t)) {
2729
+ throw new Error(`aggregate(${table}): ${fn}("${col}") say\u0131sal olmayan bir kolonda \u2014 "${col}" bir ${t} kolonu. Toplama yaln\u0131z integer/bigint/numeric kolonlarda anlaml\u0131; ka\xE7 sat\u0131r oldu\u011Funu saymak i\xE7in count: true kullan\u0131n.`);
2730
+ }
2731
+ }
2732
+ const alias = `${fn}__${col}`;
2733
+ wants.push({
2734
+ fn,
2735
+ col,
2736
+ alias
2737
+ });
2738
+ parts.push(aggregateExpr(table, fn, col, alias));
2739
+ }
2740
+ }
2741
+ if (q.count === true) parts.push('count(*) AS "_count"');
2742
+ if (parts.length === 0) {
2743
+ throw new Error(`aggregate(${table}): hi\xE7bir toplama istenmedi \u2014 sonu\xE7 bo\u015F bir nesne olurdu. sum/avg/min/max kolonlar\u0131n\u0131 ya da count: true'yu verin.`);
2744
+ }
2745
+ const groups = q.groupBy ?? [];
2746
+ for (const col of groups) check(col, "groupBy");
2747
+ const groupSel = groups.map((c) => `t.${quoteIdent(c)} AS ${quoteIdent(`g__${c}`)}`);
2748
+ const params = [];
2749
+ const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
2750
+ const whereSql = compileWhere(table, known, q.where ?? {}, add, "aggregate");
2751
+ const groupSql = groups.length > 0 ? ` GROUP BY ${groups.map((c) => `t.${quoteIdent(c)}`).join(", ")}` : "";
2752
+ const sql = `SELECT ${[
2753
+ ...groupSel,
2754
+ ...parts
2755
+ ].join(", ")} FROM ${quoteTable(table)} t WHERE true${whereSql}${groupSql}`;
2756
+ const rows = await (await at()).unsafe(sql, params);
2757
+ const shape = /* @__PURE__ */ __name((r) => {
2758
+ const out = {};
2759
+ for (const c of groups) out[c] = r[`g__${c}`];
2760
+ for (const w of wants) {
2761
+ const bucket = out[w.fn] ??= {};
2762
+ bucket[w.col] = r[w.alias] ?? null;
2763
+ }
2764
+ if (q.count === true) out["count"] = Number(r["_count"] ?? 0);
2765
+ return out;
2766
+ }, "shape");
2767
+ return groups.length > 0 ? rows.map(shape) : shape(rows[0] ?? {});
2768
+ },
2666
2769
  async count(table, where = {}) {
2667
2770
  const params = [];
2668
2771
  const add = /* @__PURE__ */ __name((v) => {
@@ -4505,6 +4608,26 @@ var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([
4505
4608
  409,
4506
4609
  429
4507
4610
  ]);
4611
+ async function refuseUnlessAuthorized(client, spec, requestId) {
4612
+ if (spec.permission !== void 0) {
4613
+ const [row] = await client.query("select auth.has_permission($1) as ok", [
4614
+ spec.permission
4615
+ ]);
4616
+ if (row?.ok !== true) {
4617
+ return envelope("forbidden", `This endpoint requires the "${spec.permission}" permission`, 403, requestId);
4618
+ }
4619
+ }
4620
+ if (spec.role !== void 0) {
4621
+ const [row] = await client.query("select $1 = any(auth.app_roles()) as ok", [
4622
+ spec.role
4623
+ ]);
4624
+ if (row?.ok !== true) {
4625
+ return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId);
4626
+ }
4627
+ }
4628
+ return null;
4629
+ }
4630
+ __name(refuseUnlessAuthorized, "refuseUnlessAuthorized");
4508
4631
  async function createApp(opts) {
4509
4632
  const { config, controllers } = opts;
4510
4633
  const container = buildContainer();
@@ -4596,8 +4719,23 @@ async function createApp(opts) {
4596
4719
  if (spec.required && !callerClaims) {
4597
4720
  return envelope("unauthorized", "A valid access token is required", 401, requestId);
4598
4721
  }
4599
- if (callerClaims && spec.role && callerClaims.role !== spec.role) {
4600
- return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId);
4722
+ if (callerClaims) {
4723
+ const uploadDb = createRequestDatabase(sql, {
4724
+ role: config.dbRole,
4725
+ serviceRole: config.dbServiceRole,
4726
+ claimsJson: JSON.stringify(callerClaims)
4727
+ });
4728
+ try {
4729
+ const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId);
4730
+ if (refusal) {
4731
+ await uploadDb.commit();
4732
+ return refusal;
4733
+ }
4734
+ await uploadDb.commit();
4735
+ } catch (err) {
4736
+ await uploadDb.rollback(err);
4737
+ throw err;
4738
+ }
4601
4739
  }
4602
4740
  const grant = grantFor(target?.entry, {
4603
4741
  userId: typeof callerClaims?.sub === "string" ? callerClaims.sub : null,
@@ -4630,8 +4768,14 @@ async function createApp(opts) {
4630
4768
  return envelope("unauthorized", "A valid access token is required", 401, requestId);
4631
4769
  }
4632
4770
  const userId = typeof claims?.sub === "string" ? claims.sub : void 0;
4633
- if (claims && spec.role && claims.role !== spec.role) {
4634
- return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId);
4771
+ const db = createRequestDatabase(sql, {
4772
+ role: config.dbRole,
4773
+ serviceRole: config.dbServiceRole,
4774
+ claimsJson: JSON.stringify(claims ?? {})
4775
+ });
4776
+ if (claims) {
4777
+ const refusal = await refuseUnlessAuthorized(db.client, spec, requestId);
4778
+ if (refusal) return refusal;
4635
4779
  }
4636
4780
  if (claims && spec.verifiedEmail && claims.email_verified !== true) {
4637
4781
  return envelope("email_not_verified", "A verified email address is required", 403, requestId);
@@ -4852,11 +4996,6 @@ async function createApp(opts) {
4852
4996
  args[p.index] = void 0;
4853
4997
  }
4854
4998
  }
4855
- const db = createRequestDatabase(sql, {
4856
- role: config.dbRole,
4857
- serviceRole: config.dbServiceRole,
4858
- claimsJson: JSON.stringify(claims ?? {})
4859
- });
4860
4999
  try {
4861
5000
  const services = buildServices(db);
4862
5001
  if (meta.options?.sseConfig !== void 0) {