@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.
- package/dist/bin/palbase-backend.cjs +210 -24
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +7 -7
- package/dist/{chunk-TWX6JTGJ.js → chunk-26GH54D2.js} +3 -5
- package/dist/{chunk-TWX6JTGJ.js.map → chunk-26GH54D2.js.map} +1 -1
- package/dist/{chunk-VVMJEVQP.js → chunk-BRLJOXWS.js} +2 -2
- package/dist/{chunk-3TUJWHC2.js → chunk-C4ZA5AT2.js} +378 -19
- package/dist/chunk-C4ZA5AT2.js.map +1 -0
- package/dist/{chunk-RADVOY7Y.js → chunk-DCDHAKF3.js} +147 -33
- package/dist/chunk-DCDHAKF3.js.map +1 -0
- package/dist/{chunk-ENZ2RFFJ.js → chunk-GOPZPM2A.js} +3 -310
- package/dist/chunk-GOPZPM2A.js.map +1 -0
- package/dist/{chunk-CGNN2PUH.js → chunk-KATPXCJ5.js} +37 -4
- package/dist/{chunk-CGNN2PUH.js.map → chunk-KATPXCJ5.js.map} +1 -1
- package/dist/{chunk-YRVIWHJC.js → chunk-N54QZER3.js} +23 -323
- package/dist/chunk-N54QZER3.js.map +1 -0
- package/dist/chunk-QMFOL2K6.js +371 -0
- package/dist/chunk-QMFOL2K6.js.map +1 -0
- package/dist/chunk-XOX6RFPZ.js +383 -0
- package/dist/chunk-XOX6RFPZ.js.map +1 -0
- package/dist/db/index.cjs +41 -2
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +12 -13
- package/dist/engine/index.cjs +210 -24
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +3 -3
- package/dist/engine/index.d.ts +3 -3
- package/dist/engine/index.js +7 -7
- package/dist/{index-DtCgaZAg.d.cts → index-BWgnGj68.d.cts} +185 -35
- package/dist/{index-b-Q3l7W5.d.ts → index-Bi74dcOu.d.ts} +9 -2
- package/dist/{index-8qy3kIuA.d.ts → index-Dg10RnZO.d.ts} +185 -35
- package/dist/{index-Zi7MptvP.d.cts → index-g6iQyYci.d.cts} +9 -2
- package/dist/index.cjs +135 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -23
- package/dist/index.d.ts +6 -23
- package/dist/index.js +48 -48
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.d.cts +2 -2
- package/dist/openapi/index.d.ts +2 -2
- package/dist/openapi/index.js +3 -4
- package/dist/openapi/index.js.map +1 -1
- package/dist/{registry-CEYLH-Iz.d.cts → registry-6VT5RPeO.d.cts} +1 -1
- package/dist/{registry-BGJ-Al6F.d.ts → registry-B-sxJJN0.d.ts} +1 -1
- package/dist/test/index.cjs +733 -17
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +59 -4
- package/dist/test/index.d.ts +59 -4
- package/dist/test/index.js +56 -16
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/auth.md +102 -20
- package/docs/database.md +54 -0
- package/docs/llms-full.txt +215 -21
- package/docs/schema.md +58 -0
- package/package.json +1 -1
- package/template/db/public.ts +23 -0
- package/template/package.json +1 -1
- package/dist/chunk-3TUJWHC2.js.map +0 -1
- package/dist/chunk-ENZ2RFFJ.js.map +0 -1
- package/dist/chunk-RADVOY7Y.js.map +0 -1
- package/dist/chunk-SG4UTNOP.js +0 -374
- package/dist/chunk-SG4UTNOP.js.map +0 -1
- package/dist/chunk-VXPNPVAG.js +0 -40
- package/dist/chunk-VXPNPVAG.js.map +0 -1
- package/dist/chunk-YRVIWHJC.js.map +0 -1
- /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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4692
|
-
|
|
4693
|
-
if (
|
|
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) {
|