@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.
- package/dist/bin/palbase-backend.cjs +151 -12
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +3 -3
- package/dist/{chunk-75YROPRZ.js → chunk-DHJ3SYAE.js} +152 -15
- package/dist/chunk-DHJ3SYAE.js.map +1 -0
- package/dist/{chunk-IVZERLTM.js → chunk-ENZ2RFFJ.js} +2 -2
- package/dist/{chunk-RVP6BTEZ.js → chunk-ESSQ3YML.js} +13 -4
- package/dist/chunk-ESSQ3YML.js.map +1 -0
- package/dist/{chunk-SNDXY565.js → chunk-SG4UTNOP.js} +5 -2
- package/dist/chunk-SG4UTNOP.js.map +1 -0
- package/dist/db/index.cjs +4 -1
- 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 +2 -2
- package/dist/engine/index.cjs +151 -12
- 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 +3 -3
- package/dist/{index-dTTLlHIn.d.ts → index-BOe82fsK.d.ts} +162 -8
- package/dist/{index-BbvOoZFr.d.ts → index-BYDUKexK.d.ts} +29 -7
- package/dist/{index-9C3JHxg-.d.cts → index-BZfmdRmh.d.cts} +29 -7
- package/dist/{index-DtISj9QX.d.cts → index-ChvoVczS.d.cts} +162 -8
- package/dist/index.cjs +15 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +3 -3
- package/dist/openapi/index.d.cts +2 -2
- package/dist/openapi/index.d.ts +2 -2
- package/dist/{registry-qIPM5BQe.d.cts → registry-B2KfKWv1.d.cts} +1 -1
- package/dist/{registry-JQNIX-eA.d.ts → registry-DaqHsOq8.d.ts} +1 -1
- package/dist/test/index.cjs +56 -0
- 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 +56 -0
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/database.md +49 -0
- package/docs/llms-full.txt +50 -1
- package/package.json +1 -1
- package/template/package.json +1 -1
- package/dist/chunk-75YROPRZ.js.map +0 -1
- package/dist/chunk-RVP6BTEZ.js.map +0 -1
- package/dist/chunk-SNDXY565.js.map +0 -1
- /package/dist/{chunk-IVZERLTM.js.map → chunk-ENZ2RFFJ.js.map} +0 -0
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
BootRefused,
|
|
4
4
|
createApp,
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import "../chunk-
|
|
8
|
-
import "../chunk-
|
|
6
|
+
} from "../chunk-DHJ3SYAE.js";
|
|
7
|
+
import "../chunk-ESSQ3YML.js";
|
|
8
|
+
import "../chunk-SG4UTNOP.js";
|
|
9
9
|
import "../chunk-3TUJWHC2.js";
|
|
10
10
|
import {
|
|
11
11
|
getRegisteredControllers
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
assertNoOrphanEntryPoints,
|
|
7
7
|
buildContainer,
|
|
8
8
|
buildRelations
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ESSQ3YML.js";
|
|
10
10
|
import {
|
|
11
11
|
qualifiedTableKey
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-SG4UTNOP.js";
|
|
13
13
|
import {
|
|
14
14
|
assertNoExpressionHandles,
|
|
15
15
|
assertUsableFilter,
|
|
@@ -228,9 +228,11 @@ function effectiveAuth(routeAuth, controllerAuth) {
|
|
|
228
228
|
};
|
|
229
229
|
const o = spec;
|
|
230
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;
|
|
231
232
|
return {
|
|
232
233
|
required: o.required !== false,
|
|
233
234
|
role,
|
|
235
|
+
permission,
|
|
234
236
|
verifiedEmail: o.verifiedEmail === true
|
|
235
237
|
};
|
|
236
238
|
}
|
|
@@ -1131,6 +1133,26 @@ function buildValueTuple(table, cols, data, add, caller) {
|
|
|
1131
1133
|
}).join(", ");
|
|
1132
1134
|
}
|
|
1133
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");
|
|
1134
1156
|
function createOps(tx) {
|
|
1135
1157
|
const at = /* @__PURE__ */ __name(() => resolveTx(tx), "at");
|
|
1136
1158
|
const ops = {
|
|
@@ -1411,7 +1433,7 @@ function createOps(tx) {
|
|
|
1411
1433
|
* should not silently succeed. Callers who mean every row say so with a
|
|
1412
1434
|
* predicate that is true for every row.
|
|
1413
1435
|
*/
|
|
1414
|
-
async updateMany(table, where, set) {
|
|
1436
|
+
async updateMany(table, where, set, opts) {
|
|
1415
1437
|
assertNotAppendOnly("updateMany", table);
|
|
1416
1438
|
const cols = Object.keys(set);
|
|
1417
1439
|
if (cols.length === 0) {
|
|
@@ -1434,8 +1456,15 @@ function createOps(tx) {
|
|
|
1434
1456
|
const whereSql = compileWhere(table, known, where, add, "updateMany");
|
|
1435
1457
|
assertHasPredicate(whereSql, "updateMany", table);
|
|
1436
1458
|
const pk = primaryKeyOf(table);
|
|
1437
|
-
const
|
|
1438
|
-
|
|
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);
|
|
1439
1468
|
},
|
|
1440
1469
|
/**
|
|
1441
1470
|
* Delete every row the filter matches, in ONE statement; resolves to how
|
|
@@ -1465,6 +1494,78 @@ function createOps(tx) {
|
|
|
1465
1494
|
* An empty filter is legitimate HERE: counting a whole table is a read, and
|
|
1466
1495
|
* reads do not destroy anything.
|
|
1467
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
|
+
},
|
|
1468
1569
|
async count(table, where = {}) {
|
|
1469
1570
|
const params = [];
|
|
1470
1571
|
const add = /* @__PURE__ */ __name((v) => {
|
|
@@ -2870,6 +2971,26 @@ var AUTHOR_ANSWERED = /* @__PURE__ */ new Set([
|
|
|
2870
2971
|
409,
|
|
2871
2972
|
429
|
|
2872
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");
|
|
2873
2994
|
async function createApp(opts) {
|
|
2874
2995
|
const { config, controllers } = opts;
|
|
2875
2996
|
const container = buildContainer();
|
|
@@ -2961,8 +3082,23 @@ async function createApp(opts) {
|
|
|
2961
3082
|
if (spec.required && !callerClaims) {
|
|
2962
3083
|
return envelope("unauthorized", "A valid access token is required", 401, requestId);
|
|
2963
3084
|
}
|
|
2964
|
-
if (callerClaims
|
|
2965
|
-
|
|
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
|
+
}
|
|
2966
3102
|
}
|
|
2967
3103
|
const grant = grantFor(target?.entry, {
|
|
2968
3104
|
userId: typeof callerClaims?.sub === "string" ? callerClaims.sub : null,
|
|
@@ -2995,8 +3131,14 @@ async function createApp(opts) {
|
|
|
2995
3131
|
return envelope("unauthorized", "A valid access token is required", 401, requestId);
|
|
2996
3132
|
}
|
|
2997
3133
|
const userId = typeof claims?.sub === "string" ? claims.sub : void 0;
|
|
2998
|
-
|
|
2999
|
-
|
|
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;
|
|
3000
3142
|
}
|
|
3001
3143
|
if (claims && spec.verifiedEmail && claims.email_verified !== true) {
|
|
3002
3144
|
return envelope("email_not_verified", "A verified email address is required", 403, requestId);
|
|
@@ -3217,11 +3359,6 @@ async function createApp(opts) {
|
|
|
3217
3359
|
args[p.index] = void 0;
|
|
3218
3360
|
}
|
|
3219
3361
|
}
|
|
3220
|
-
const db = createRequestDatabase(sql, {
|
|
3221
|
-
role: config.dbRole,
|
|
3222
|
-
serviceRole: config.dbServiceRole,
|
|
3223
|
-
claimsJson: JSON.stringify(claims ?? {})
|
|
3224
|
-
});
|
|
3225
3362
|
try {
|
|
3226
3363
|
const services = buildServices(db);
|
|
3227
3364
|
if (meta.options?.sseConfig !== void 0) {
|
|
@@ -3414,4 +3551,4 @@ export {
|
|
|
3414
3551
|
installEgressFence,
|
|
3415
3552
|
createApp
|
|
3416
3553
|
};
|
|
3417
|
-
//# sourceMappingURL=chunk-
|
|
3554
|
+
//# sourceMappingURL=chunk-DHJ3SYAE.js.map
|