@palbase/backend 27.1.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.
- package/dist/bin/palbase-backend.cjs +284 -50
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +4 -4
- package/dist/{chunk-JVZQCC77.js → chunk-3TUJWHC2.js} +8 -3
- package/dist/chunk-3TUJWHC2.js.map +1 -0
- package/dist/{chunk-EB3TUX5J.js → chunk-DHJ3SYAE.js} +251 -50
- package/dist/chunk-DHJ3SYAE.js.map +1 -0
- package/dist/{chunk-XABHGMUT.js → chunk-ENZ2RFFJ.js} +2 -2
- package/dist/{chunk-BQN723PL.js → chunk-ESSQ3YML.js} +73 -7
- package/dist/chunk-ESSQ3YML.js.map +1 -0
- package/dist/{chunk-OZKSM3JW.js → chunk-SG4UTNOP.js} +7 -3
- package/dist/chunk-SG4UTNOP.js.map +1 -0
- package/dist/db/index.cjs +8 -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 +3 -3
- package/dist/engine/index.cjs +294 -50
- 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 +11 -4
- package/dist/{index-V7QRh1wg.d.ts → index-BOe82fsK.d.ts} +256 -19
- package/dist/{index-CAKOgAlP.d.ts → index-BYDUKexK.d.ts} +139 -10
- package/dist/{index-H-0qv5d4.d.cts → index-BZfmdRmh.d.cts} +139 -10
- package/dist/{index-CgE4sVhg.d.cts → index-ChvoVczS.d.cts} +256 -19
- package/dist/index.cjs +81 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -22
- package/dist/index.d.ts +6 -22
- package/dist/index.js +10 -4
- 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/{registry-DHsPDY0_.d.cts → registry-B2KfKWv1.d.cts} +1 -1
- package/dist/{registry-4EI8aaFs.d.ts → registry-DaqHsOq8.d.ts} +1 -1
- package/dist/test/index.cjs +129 -6
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +126 -6
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/database.md +118 -1
- package/docs/llms-full.txt +119 -2
- package/package.json +1 -1
- package/template/package.json +1 -1
- package/dist/chunk-BQN723PL.js.map +0 -1
- package/dist/chunk-EB3TUX5J.js.map +0 -1
- package/dist/chunk-JVZQCC77.js.map +0 -1
- package/dist/chunk-OZKSM3JW.js.map +0 -1
- /package/dist/{chunk-XABHGMUT.js.map → chunk-ENZ2RFFJ.js.map} +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DeclarationRefused,
|
|
2
3
|
__requestALS,
|
|
3
4
|
__runStartHooks,
|
|
4
5
|
__runWithRuntime,
|
|
5
6
|
assertNoOrphanEntryPoints,
|
|
6
7
|
buildContainer,
|
|
7
8
|
buildRelations
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ESSQ3YML.js";
|
|
9
10
|
import {
|
|
10
11
|
qualifiedTableKey
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-SG4UTNOP.js";
|
|
12
13
|
import {
|
|
13
14
|
assertNoExpressionHandles,
|
|
14
15
|
assertUsableFilter,
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
isSqlFragment,
|
|
22
23
|
looksLikeUnbrandedPlanRef,
|
|
23
24
|
looksLikeUnbrandedRef
|
|
24
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-3TUJWHC2.js";
|
|
25
26
|
import {
|
|
26
27
|
getRoutes,
|
|
27
28
|
resolveEffectiveAuth
|
|
@@ -227,9 +228,11 @@ function effectiveAuth(routeAuth, controllerAuth) {
|
|
|
227
228
|
};
|
|
228
229
|
const o = spec;
|
|
229
230
|
const role = typeof o.role === "string" && o.role.trim() !== "" ? o.role.trim() : void 0;
|
|
231
|
+
const permission = typeof o.permission === "string" && o.permission.trim() !== "" ? o.permission.trim() : void 0;
|
|
230
232
|
return {
|
|
231
233
|
required: o.required !== false,
|
|
232
234
|
role,
|
|
235
|
+
permission,
|
|
233
236
|
verifiedEmail: o.verifiedEmail === true
|
|
234
237
|
};
|
|
235
238
|
}
|
|
@@ -919,7 +922,7 @@ function compileWhereBare(table, colSet, where, add, caller = "search", alias =
|
|
|
919
922
|
const rel = rels.find((r) => r.name === relName);
|
|
920
923
|
if (rel === void 0) {
|
|
921
924
|
const known = rels.map((r) => r.name).sort();
|
|
922
|
-
throw new Error(`${caller}(${table}): has.${relName} diye bir ili\u015Fki yok
|
|
925
|
+
throw new Error(`${caller}(${table}): has.${relName} diye bir ili\u015Fki yok` + (known.length > 0 ? ` (bilinenler: ${known.join(", ")}). \u0130li\u015Fkiler yabanc\u0131 anahtarlardan T\xDCRET\u0130L\u0130R; ad\u0131n\u0131 de\u011Fi\u015Ftirmek i\xE7in references(..., { as, reverseAs }).` : `. Bu tablo H\u0130\xC7 yabanc\u0131 anahtar bildirmiyor, ve ili\u015Fkiler onlardan T\xDCRET\u0130L\u0130R. Var olan bir veritaban\u0131ndan introspect edilmi\u015F \u015Femalarda bu s\u0131k olur: kolon \`text()\`/\`uuid()\` olarak durur, \`references(() => <tablo>.<kolon>)\` yoktur. Bildirdi\u011Finiz anda ili\u015Fki iki tarafta da adland\u0131r\u0131l\u0131r \u2014 ama bu CANLI veritaban\u0131na giden bir k\u0131s\u0131tt\u0131r ve mevcut yetim sat\u0131rlar onu d\xFC\u015F\xFCr\xFCr; \xF6nce onlar\u0131 say\u0131n.`));
|
|
923
926
|
}
|
|
924
927
|
const ourCol = rel.kind === "many" ? primaryKeyOf(table) : rel.via;
|
|
925
928
|
const theirCol = rel.kind === "many" ? rel.via : primaryKeyOf(rel.to);
|
|
@@ -1099,6 +1102,57 @@ async function embedQuery(embed, text) {
|
|
|
1099
1102
|
}
|
|
1100
1103
|
}
|
|
1101
1104
|
__name(embedQuery, "embedQuery");
|
|
1105
|
+
function buildSetAssignments(table, cols, set, add, caller) {
|
|
1106
|
+
return cols.map((c) => {
|
|
1107
|
+
const expr = columnExprOf(set[c]);
|
|
1108
|
+
if (expr !== null && expr.fn === "now") {
|
|
1109
|
+
return `${quoteIdent(c)} = now()`;
|
|
1110
|
+
}
|
|
1111
|
+
if (expr !== null) {
|
|
1112
|
+
const operator = expr.fn === "inc" ? "+" : "-";
|
|
1113
|
+
const t = transformsOf(currentSchema, table).get(c);
|
|
1114
|
+
const amount = t?.toDb === void 0 || expr.by === null || expr.by === void 0 ? expr.by : t.toDb(expr.by);
|
|
1115
|
+
return `${quoteIdent(c)} = ${quoteIdent(c)} ${operator} ${add(amount)}`;
|
|
1116
|
+
}
|
|
1117
|
+
return `${quoteIdent(c)} = ${add(asBindParams(table, [
|
|
1118
|
+
c
|
|
1119
|
+
], set, caller)[0])}`;
|
|
1120
|
+
}).join(", ");
|
|
1121
|
+
}
|
|
1122
|
+
__name(buildSetAssignments, "buildSetAssignments");
|
|
1123
|
+
function buildValueTuple(table, cols, data, add, caller) {
|
|
1124
|
+
return cols.map((c) => {
|
|
1125
|
+
const expr = columnExprOf(data[c]);
|
|
1126
|
+
if (expr !== null && expr.fn === "now") return "now()";
|
|
1127
|
+
if (expr !== null) {
|
|
1128
|
+
throw new Error(`${caller}(${table}): "${c}" ${expr.fn === "inc" ? "increment()" : "decrement()"} ald\u0131 \u2014 sat\u0131r HEN\xDCZ YOK, yani "kolonun \u015Fu anki de\u011Feri" diye bir \u015Fey yok. Ba\u015Flang\u0131\xE7 de\u011Ferini yaz\u0131n; art\u0131rmak i\xE7in sat\u0131r olu\u015Ftuktan sonra updateMany({ where, set: { ${c}: ${expr.fn === "inc" ? "increment" : "decrement"}(n) } }).`);
|
|
1129
|
+
}
|
|
1130
|
+
return add(asBindParams(table, [
|
|
1131
|
+
c
|
|
1132
|
+
], data, caller)[0]);
|
|
1133
|
+
}).join(", ");
|
|
1134
|
+
}
|
|
1135
|
+
__name(buildValueTuple, "buildValueTuple");
|
|
1136
|
+
function columnTypeOf(table, col) {
|
|
1137
|
+
const def = tableDefOf(table);
|
|
1138
|
+
const c = def?.columns?.[col];
|
|
1139
|
+
if (c === void 0 || c === null) return null;
|
|
1140
|
+
const d = typeof c === "object" && "_def" in c ? c._def : c;
|
|
1141
|
+
return d !== null && typeof d === "object" && typeof d.type === "string" ? d.type : null;
|
|
1142
|
+
}
|
|
1143
|
+
__name(columnTypeOf, "columnTypeOf");
|
|
1144
|
+
var NUMERIC_PG_TYPES = /* @__PURE__ */ new Set([
|
|
1145
|
+
"integer",
|
|
1146
|
+
"bigint",
|
|
1147
|
+
"numeric"
|
|
1148
|
+
]);
|
|
1149
|
+
function aggregateExpr(table, fn, col, alias) {
|
|
1150
|
+
const q = `t.${quoteIdent(col)}`;
|
|
1151
|
+
const t = columnTypeOf(table, col);
|
|
1152
|
+
if (fn === "sum" || fn === "avg") return `${fn}(${q})::text AS ${quoteIdent(alias)}`;
|
|
1153
|
+
return t !== null && NUMERIC_PG_TYPES.has(t) && t !== "integer" ? `${fn}(${q})::text AS ${quoteIdent(alias)}` : `${fn}(${q}) AS ${quoteIdent(alias)}`;
|
|
1154
|
+
}
|
|
1155
|
+
__name(aggregateExpr, "aggregateExpr");
|
|
1102
1156
|
function createOps(tx) {
|
|
1103
1157
|
const at = /* @__PURE__ */ __name(() => resolveTx(tx), "at");
|
|
1104
1158
|
const ops = {
|
|
@@ -1112,9 +1166,11 @@ function createOps(tx) {
|
|
|
1112
1166
|
async insert(table, data) {
|
|
1113
1167
|
const cols = Object.keys(data);
|
|
1114
1168
|
if (cols.length === 0) throw new Error(`insert into ${table}: no columns given`);
|
|
1115
|
-
const
|
|
1116
|
-
const
|
|
1117
|
-
const
|
|
1169
|
+
const params = [];
|
|
1170
|
+
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1171
|
+
const tuple = buildValueTuple(table, cols, data, add, "insert");
|
|
1172
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING *`;
|
|
1173
|
+
const rows = await (await at()).unsafe(sql, params);
|
|
1118
1174
|
const inserted = rows[0];
|
|
1119
1175
|
if (!inserted) {
|
|
1120
1176
|
throw new Error(`insert into ${table} returned no row \u2014 the write was rejected (an RLS policy, most likely).`);
|
|
@@ -1122,6 +1178,41 @@ function createOps(tx) {
|
|
|
1122
1178
|
return asTableRow(table, inserted);
|
|
1123
1179
|
},
|
|
1124
1180
|
/**
|
|
1181
|
+
* Çok satırı TEK statement'la ekle.
|
|
1182
|
+
*
|
|
1183
|
+
* Motor bunu zaten yapıyordu ve yalnız PLAN yolundan erişilebiliyordu
|
|
1184
|
+
* (`tx.public.x.insertMany`). Dışarıda tek kapı `insert(row)` olduğu için
|
|
1185
|
+
* "şu 200 satırı yaz" ya 200 gidiş-dönüş ya da ham SQL oluyordu — ölçüldü:
|
|
1186
|
+
* bir müşteri projesinde bir seed rutini satırları tek tek siliyor
|
|
1187
|
+
* (`deleteMany` VARKEN) ve tek tek ekliyor.
|
|
1188
|
+
*
|
|
1189
|
+
* SATIRLARIN ANAHTAR KÜMESİ AYNI OLMAK ZORUNDA. Kolon listesi tek bir
|
|
1190
|
+
* statement'ta paylaşıldığı için ilk satırdan türer; farklı anahtar taşıyan
|
|
1191
|
+
* bir satırın fazla kolonu SESSİZCE YAZILMAZDI. Eksik kolon için `null`
|
|
1192
|
+
* yazın ya da varsayılanı istiyorsanız o satırları ayrı çağrıda ekleyin.
|
|
1193
|
+
*/
|
|
1194
|
+
async insertMany(table, rows, opts) {
|
|
1195
|
+
if (rows.length === 0) return [];
|
|
1196
|
+
const first = rows[0];
|
|
1197
|
+
const cols = Object.keys(first);
|
|
1198
|
+
if (cols.length === 0) throw new Error(`insertMany into ${table}: no columns given`);
|
|
1199
|
+
for (let i = 1; i < rows.length; i++) {
|
|
1200
|
+
const keys = Object.keys(rows[i]);
|
|
1201
|
+
const missing = cols.filter((c) => !(c in rows[i]));
|
|
1202
|
+
const extra = keys.filter((k) => !cols.includes(k));
|
|
1203
|
+
if (missing.length > 0 || extra.length > 0) {
|
|
1204
|
+
throw new Error(`insertMany(${table}): ${i}. sat\u0131r\u0131n kolonlar\u0131 ilk sat\u0131rla ayn\u0131 de\u011Fil` + (missing.length > 0 ? ` (eksik: ${missing.join(", ")})` : "") + (extra.length > 0 ? ` (fazla: ${extra.join(", ")})` : "") + `. Tek statement kolon listesini PAYLA\u015EIR, yani fazla kolon sessizce yaz\u0131lmaz ve eksik kolon yanl\u0131\u015F s\xFCtuna kayar. Eksikler i\xE7in null yaz\u0131n, ya da farkl\u0131 \u015Fekilli sat\u0131rlar\u0131 ayr\u0131 \xE7a\u011Fr\u0131larda ekleyin.`);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
const params = [];
|
|
1208
|
+
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1209
|
+
const tuples = rows.map((r) => `(${buildValueTuple(table, cols, r, add, "insertMany")})`);
|
|
1210
|
+
const tail = opts !== void 0 && opts.onConflict.length > 0 ? ` ${onConflictTail(cols, opts.onConflict, opts.action ?? "ignore")}` : "";
|
|
1211
|
+
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING *`;
|
|
1212
|
+
const out = await (await at()).unsafe(sql, params);
|
|
1213
|
+
return out.map((r) => asTableRow(table, r));
|
|
1214
|
+
},
|
|
1215
|
+
/**
|
|
1125
1216
|
* Bir ADI kilitle — satırı değil (FR-054).
|
|
1126
1217
|
*
|
|
1127
1218
|
* "Aynı anda tek bir fatura kapanışı koşsun" gibi, kilitlenecek satırı
|
|
@@ -1207,8 +1298,10 @@ function createOps(tx) {
|
|
|
1207
1298
|
const live = await at();
|
|
1208
1299
|
try {
|
|
1209
1300
|
return await live.savepoint(async (sp) => {
|
|
1210
|
-
const
|
|
1211
|
-
const
|
|
1301
|
+
const params = [];
|
|
1302
|
+
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1303
|
+
const tuple = buildValueTuple(table, cols, data, add, "claim");
|
|
1304
|
+
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING *`, params);
|
|
1212
1305
|
if (!rows[0]) {
|
|
1213
1306
|
throw new Error(`claim(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si).`);
|
|
1214
1307
|
}
|
|
@@ -1259,10 +1352,11 @@ function createOps(tx) {
|
|
|
1259
1352
|
const cols = Object.keys(data);
|
|
1260
1353
|
if (cols.length === 0) throw new Error(`upsert into ${table}: no columns given`);
|
|
1261
1354
|
if (searchConfigFor(table)?.validity) {
|
|
1262
|
-
const
|
|
1355
|
+
const bindData = [];
|
|
1356
|
+
const addPut = /* @__PURE__ */ __name((v) => `$${bindData.push(v)}`, "addPut");
|
|
1357
|
+
const ph = buildValueTuple(table, cols, data, addPut, "put");
|
|
1263
1358
|
const insertSql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${ph}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) WHERE "valid_to" IS NULL DO NOTHING RETURNING *`;
|
|
1264
1359
|
const live = await at();
|
|
1265
|
-
const bindData = asBindParams(table, cols, data, "put");
|
|
1266
1360
|
const first = await live.unsafe(insertSql, bindData);
|
|
1267
1361
|
if (first[0]) return asTableRow(table, first[0]);
|
|
1268
1362
|
const condSql = conflict.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ");
|
|
@@ -1287,12 +1381,14 @@ function createOps(tx) {
|
|
|
1287
1381
|
]);
|
|
1288
1382
|
return asTableRow(table, second[0]);
|
|
1289
1383
|
}
|
|
1290
|
-
const
|
|
1384
|
+
const params = [];
|
|
1385
|
+
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1386
|
+
const placeholders = buildValueTuple(table, cols, data, add, "put");
|
|
1291
1387
|
const conflictSet = new Set(conflict);
|
|
1292
1388
|
const assignments = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
|
|
1293
1389
|
const action = assignments.length ? `DO UPDATE SET ${assignments.join(", ")}` : `DO UPDATE SET ${quoteIdent(conflict[0])} = EXCLUDED.${quoteIdent(conflict[0])}`;
|
|
1294
1390
|
const sql = `INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING *`;
|
|
1295
|
-
const rows = await (await at()).unsafe(sql,
|
|
1391
|
+
const rows = await (await at()).unsafe(sql, params);
|
|
1296
1392
|
const row = rows[0];
|
|
1297
1393
|
if (!row) {
|
|
1298
1394
|
throw new Error(`upsert into ${table} returned no row \u2014 the write was rejected (an RLS policy, most likely).`);
|
|
@@ -1303,12 +1399,14 @@ function createOps(tx) {
|
|
|
1303
1399
|
assertNotAppendOnly("update", table);
|
|
1304
1400
|
const cols = Object.keys(data);
|
|
1305
1401
|
if (cols.length === 0) return ops.findById(table, id);
|
|
1306
|
-
const
|
|
1307
|
-
const
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1402
|
+
const params = [];
|
|
1403
|
+
const add = /* @__PURE__ */ __name((v) => {
|
|
1404
|
+
params.push(v);
|
|
1405
|
+
return `$${params.length}`;
|
|
1406
|
+
}, "add");
|
|
1407
|
+
const assignments = buildSetAssignments(table, cols, data, add, "update");
|
|
1408
|
+
const sql = `UPDATE ${quoteTable(table)} SET ${assignments} WHERE id = ${add(id)} RETURNING *`;
|
|
1409
|
+
const rows = await (await at()).unsafe(sql, params);
|
|
1312
1410
|
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
1313
1411
|
},
|
|
1314
1412
|
async delete(table, id) {
|
|
@@ -1335,7 +1433,7 @@ function createOps(tx) {
|
|
|
1335
1433
|
* should not silently succeed. Callers who mean every row say so with a
|
|
1336
1434
|
* predicate that is true for every row.
|
|
1337
1435
|
*/
|
|
1338
|
-
async updateMany(table, where, set) {
|
|
1436
|
+
async updateMany(table, where, set, opts) {
|
|
1339
1437
|
assertNotAppendOnly("updateMany", table);
|
|
1340
1438
|
const cols = Object.keys(set);
|
|
1341
1439
|
if (cols.length === 0) {
|
|
@@ -1354,26 +1452,19 @@ function createOps(tx) {
|
|
|
1354
1452
|
params.push(v);
|
|
1355
1453
|
return `$${params.length}`;
|
|
1356
1454
|
}, "add");
|
|
1357
|
-
const assignments =
|
|
1358
|
-
const expr = columnExprOf(set[c]);
|
|
1359
|
-
if (expr !== null && expr.fn === "now") {
|
|
1360
|
-
return `${quoteIdent(c)} = now()`;
|
|
1361
|
-
}
|
|
1362
|
-
if (expr !== null) {
|
|
1363
|
-
const operator = expr.fn === "inc" ? "+" : "-";
|
|
1364
|
-
const t = transformsOf(currentSchema, table).get(c);
|
|
1365
|
-
const amount = t?.toDb === void 0 || expr.by === null || expr.by === void 0 ? expr.by : t.toDb(expr.by);
|
|
1366
|
-
return `${quoteIdent(c)} = ${quoteIdent(c)} ${operator} ${add(amount)}`;
|
|
1367
|
-
}
|
|
1368
|
-
return `${quoteIdent(c)} = ${add(asBindParams(table, [
|
|
1369
|
-
c
|
|
1370
|
-
], set, "updateMany")[0])}`;
|
|
1371
|
-
}).join(", ");
|
|
1455
|
+
const assignments = buildSetAssignments(table, cols, set, add, "updateMany");
|
|
1372
1456
|
const whereSql = compileWhere(table, known, where, add, "updateMany");
|
|
1373
1457
|
assertHasPredicate(whereSql, "updateMany", table);
|
|
1374
1458
|
const pk = primaryKeyOf(table);
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1459
|
+
const wantRows = opts?.returning !== false;
|
|
1460
|
+
const tail = wantRows ? "RETURNING *" : "RETURNING 1";
|
|
1461
|
+
const simple = isSinglePkEquality(where, pk) || pk === null;
|
|
1462
|
+
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");
|
|
1463
|
+
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) `;
|
|
1464
|
+
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`;
|
|
1465
|
+
const out = await (await at()).unsafe(sql, params);
|
|
1466
|
+
if (!wantRows) return Number(out[0]?.["n"] ?? 0);
|
|
1467
|
+
return asTableRows(table, out);
|
|
1377
1468
|
},
|
|
1378
1469
|
/**
|
|
1379
1470
|
* Delete every row the filter matches, in ONE statement; resolves to how
|
|
@@ -1403,6 +1494,78 @@ function createOps(tx) {
|
|
|
1403
1494
|
* An empty filter is legitimate HERE: counting a whole table is a read, and
|
|
1404
1495
|
* reads do not destroy anything.
|
|
1405
1496
|
*/
|
|
1497
|
+
/**
|
|
1498
|
+
* Toplama — `count`'un yanına `sum`/`avg`/`min`/`max` ve `groupBy`.
|
|
1499
|
+
*
|
|
1500
|
+
* Var olma nedeni: bunlar olmadan "bu hesabın toplamı" ya `$query`'ye ya da
|
|
1501
|
+
* TÜM satırları çekip JS'te toplamaya düşüyor. İkincisi hem N+1 hem de para
|
|
1502
|
+
* için YANLIŞ — `numeric` JS `number`'ına uğradığı anda kesinliğini
|
|
1503
|
+
* kaybeder.
|
|
1504
|
+
*
|
|
1505
|
+
* `groupBy` verilirse SATIRLAR döner, verilmezse TEK sonuç. Boş kümede
|
|
1506
|
+
* Postgres NULL döndürür, 0 değil — ve bu SDK onu `null` olarak geçiriyor:
|
|
1507
|
+
* "hiç satır yoktu" ile "toplam sıfırdı" farklı şeyler.
|
|
1508
|
+
*/
|
|
1509
|
+
async aggregate(table, q = {}) {
|
|
1510
|
+
const known = schemaColumns(table);
|
|
1511
|
+
const check = /* @__PURE__ */ __name((col, where) => {
|
|
1512
|
+
if (known !== null && !known.has(col)) {
|
|
1513
|
+
throw new Error(`aggregate(${table}): ${where} kolonu "${col}" tabloda yok (FR-016)`);
|
|
1514
|
+
}
|
|
1515
|
+
}, "check");
|
|
1516
|
+
const parts = [];
|
|
1517
|
+
const wants = [];
|
|
1518
|
+
for (const fn of [
|
|
1519
|
+
"sum",
|
|
1520
|
+
"avg",
|
|
1521
|
+
"min",
|
|
1522
|
+
"max"
|
|
1523
|
+
]) {
|
|
1524
|
+
for (const col of q[fn] ?? []) {
|
|
1525
|
+
check(col, fn);
|
|
1526
|
+
if (fn === "sum" || fn === "avg") {
|
|
1527
|
+
const t = columnTypeOf(table, col);
|
|
1528
|
+
if (t !== null && !NUMERIC_PG_TYPES.has(t)) {
|
|
1529
|
+
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.`);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
const alias = `${fn}__${col}`;
|
|
1533
|
+
wants.push({
|
|
1534
|
+
fn,
|
|
1535
|
+
col,
|
|
1536
|
+
alias
|
|
1537
|
+
});
|
|
1538
|
+
parts.push(aggregateExpr(table, fn, col, alias));
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
if (q.count === true) parts.push('count(*) AS "_count"');
|
|
1542
|
+
if (parts.length === 0) {
|
|
1543
|
+
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.`);
|
|
1544
|
+
}
|
|
1545
|
+
const groups = q.groupBy ?? [];
|
|
1546
|
+
for (const col of groups) check(col, "groupBy");
|
|
1547
|
+
const groupSel = groups.map((c) => `t.${quoteIdent(c)} AS ${quoteIdent(`g__${c}`)}`);
|
|
1548
|
+
const params = [];
|
|
1549
|
+
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1550
|
+
const whereSql = compileWhere(table, known, q.where ?? {}, add, "aggregate");
|
|
1551
|
+
const groupSql = groups.length > 0 ? ` GROUP BY ${groups.map((c) => `t.${quoteIdent(c)}`).join(", ")}` : "";
|
|
1552
|
+
const sql = `SELECT ${[
|
|
1553
|
+
...groupSel,
|
|
1554
|
+
...parts
|
|
1555
|
+
].join(", ")} FROM ${quoteTable(table)} t WHERE true${whereSql}${groupSql}`;
|
|
1556
|
+
const rows = await (await at()).unsafe(sql, params);
|
|
1557
|
+
const shape = /* @__PURE__ */ __name((r) => {
|
|
1558
|
+
const out = {};
|
|
1559
|
+
for (const c of groups) out[c] = r[`g__${c}`];
|
|
1560
|
+
for (const w of wants) {
|
|
1561
|
+
const bucket = out[w.fn] ??= {};
|
|
1562
|
+
bucket[w.col] = r[w.alias] ?? null;
|
|
1563
|
+
}
|
|
1564
|
+
if (q.count === true) out["count"] = Number(r["_count"] ?? 0);
|
|
1565
|
+
return out;
|
|
1566
|
+
}, "shape");
|
|
1567
|
+
return groups.length > 0 ? rows.map(shape) : shape(rows[0] ?? {});
|
|
1568
|
+
},
|
|
1406
1569
|
async count(table, where = {}) {
|
|
1407
1570
|
const params = [];
|
|
1408
1571
|
const add = /* @__PURE__ */ __name((v) => {
|
|
@@ -1826,8 +1989,10 @@ function createOps(tx) {
|
|
|
1826
1989
|
[cfg.pk]: newId
|
|
1827
1990
|
};
|
|
1828
1991
|
const cols = Object.keys(data);
|
|
1829
|
-
const
|
|
1830
|
-
const
|
|
1992
|
+
const params = [];
|
|
1993
|
+
const add = /* @__PURE__ */ __name((v) => `$${params.push(v)}`, "add");
|
|
1994
|
+
const tuple = buildValueTuple(table, cols, data, add, "supersede");
|
|
1995
|
+
const rows = await sp.unsafe(`INSERT INTO ${quoteTable(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${tuple}) RETURNING *`, params);
|
|
1831
1996
|
if (!rows[0]) {
|
|
1832
1997
|
throw new Error(`supersede(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si)`);
|
|
1833
1998
|
}
|
|
@@ -2806,6 +2971,26 @@ var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([
|
|
|
2806
2971
|
409,
|
|
2807
2972
|
429
|
|
2808
2973
|
]);
|
|
2974
|
+
async function refuseUnlessAuthorized(client, spec, requestId) {
|
|
2975
|
+
if (spec.permission !== void 0) {
|
|
2976
|
+
const [row] = await client.query("select auth.has_permission($1) as ok", [
|
|
2977
|
+
spec.permission
|
|
2978
|
+
]);
|
|
2979
|
+
if (row?.ok !== true) {
|
|
2980
|
+
return envelope("forbidden", `This endpoint requires the "${spec.permission}" permission`, 403, requestId);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
if (spec.role !== void 0) {
|
|
2984
|
+
const [row] = await client.query("select $1 = any(auth.app_roles()) as ok", [
|
|
2985
|
+
spec.role
|
|
2986
|
+
]);
|
|
2987
|
+
if (row?.ok !== true) {
|
|
2988
|
+
return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId);
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
return null;
|
|
2992
|
+
}
|
|
2993
|
+
__name(refuseUnlessAuthorized, "refuseUnlessAuthorized");
|
|
2809
2994
|
async function createApp(opts) {
|
|
2810
2995
|
const { config, controllers } = opts;
|
|
2811
2996
|
const container = buildContainer();
|
|
@@ -2823,7 +3008,7 @@ async function createApp(opts) {
|
|
|
2823
3008
|
assertNoOrphanEntryPoints(controllers, container.owned);
|
|
2824
3009
|
const routes = buildRouteTable(controllers, liveContainer);
|
|
2825
3010
|
if (routes.length === 0) {
|
|
2826
|
-
throw new
|
|
3011
|
+
throw new DeclarationRefused("boot refused: zero endpoints collected \u2014 nothing would answer.");
|
|
2827
3012
|
}
|
|
2828
3013
|
const sql = opts.sql ?? await defaultSqlDriver(config);
|
|
2829
3014
|
await sql.unsafe("select 1");
|
|
@@ -2897,8 +3082,23 @@ async function createApp(opts) {
|
|
|
2897
3082
|
if (spec.required && !callerClaims) {
|
|
2898
3083
|
return envelope("unauthorized", "A valid access token is required", 401, requestId);
|
|
2899
3084
|
}
|
|
2900
|
-
if (callerClaims
|
|
2901
|
-
|
|
3085
|
+
if (callerClaims) {
|
|
3086
|
+
const uploadDb = createRequestDatabase(sql, {
|
|
3087
|
+
role: config.dbRole,
|
|
3088
|
+
serviceRole: config.dbServiceRole,
|
|
3089
|
+
claimsJson: JSON.stringify(callerClaims)
|
|
3090
|
+
});
|
|
3091
|
+
try {
|
|
3092
|
+
const refusal = await refuseUnlessAuthorized(uploadDb.client, spec, requestId);
|
|
3093
|
+
if (refusal) {
|
|
3094
|
+
await uploadDb.commit();
|
|
3095
|
+
return refusal;
|
|
3096
|
+
}
|
|
3097
|
+
await uploadDb.commit();
|
|
3098
|
+
} catch (err) {
|
|
3099
|
+
await uploadDb.rollback(err);
|
|
3100
|
+
throw err;
|
|
3101
|
+
}
|
|
2902
3102
|
}
|
|
2903
3103
|
const grant = grantFor(target?.entry, {
|
|
2904
3104
|
userId: typeof callerClaims?.sub === "string" ? callerClaims.sub : null,
|
|
@@ -2931,8 +3131,14 @@ async function createApp(opts) {
|
|
|
2931
3131
|
return envelope("unauthorized", "A valid access token is required", 401, requestId);
|
|
2932
3132
|
}
|
|
2933
3133
|
const userId = typeof claims?.sub === "string" ? claims.sub : void 0;
|
|
2934
|
-
|
|
2935
|
-
|
|
3134
|
+
const db = createRequestDatabase(sql, {
|
|
3135
|
+
role: config.dbRole,
|
|
3136
|
+
serviceRole: config.dbServiceRole,
|
|
3137
|
+
claimsJson: JSON.stringify(claims ?? {})
|
|
3138
|
+
});
|
|
3139
|
+
if (claims) {
|
|
3140
|
+
const refusal = await refuseUnlessAuthorized(db.client, spec, requestId);
|
|
3141
|
+
if (refusal) return refusal;
|
|
2936
3142
|
}
|
|
2937
3143
|
if (claims && spec.verifiedEmail && claims.email_verified !== true) {
|
|
2938
3144
|
return envelope("email_not_verified", "A verified email address is required", 403, requestId);
|
|
@@ -3153,11 +3359,6 @@ async function createApp(opts) {
|
|
|
3153
3359
|
args[p.index] = void 0;
|
|
3154
3360
|
}
|
|
3155
3361
|
}
|
|
3156
|
-
const db = createRequestDatabase(sql, {
|
|
3157
|
-
role: config.dbRole,
|
|
3158
|
-
serviceRole: config.dbServiceRole,
|
|
3159
|
-
claimsJson: JSON.stringify(claims ?? {})
|
|
3160
|
-
});
|
|
3161
3362
|
try {
|
|
3162
3363
|
const services = buildServices(db);
|
|
3163
3364
|
if (meta.options?.sseConfig !== void 0) {
|
|
@@ -3350,4 +3551,4 @@ export {
|
|
|
3350
3551
|
installEgressFence,
|
|
3351
3552
|
createApp
|
|
3352
3553
|
};
|
|
3353
|
-
//# sourceMappingURL=chunk-
|
|
3554
|
+
//# sourceMappingURL=chunk-DHJ3SYAE.js.map
|