@palbase/backend 34.0.0 → 34.1.1
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/LICENSE +21 -0
- package/dist/bin/palbase-backend.cjs +275 -30
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +4 -4
- package/dist/{chunk-SEM36BWY.js → chunk-5UGPXKMJ.js} +60 -12
- package/dist/chunk-5UGPXKMJ.js.map +1 -0
- package/dist/{chunk-FOWA7PF4.js → chunk-P7MGSAFT.js} +6 -1
- package/dist/{chunk-FOWA7PF4.js.map → chunk-P7MGSAFT.js.map} +1 -1
- package/dist/{chunk-YX7UA7VC.js → chunk-X2UYIXDS.js} +8 -2
- package/dist/chunk-X2UYIXDS.js.map +1 -0
- package/dist/{chunk-MNGZE2MM.js → chunk-XZPDPYVY.js} +279 -34
- package/dist/chunk-XZPDPYVY.js.map +1 -0
- package/dist/{chunk-LOGL2ZHD.js → chunk-YO5GYM73.js} +2 -2
- package/dist/db/index.cjs +34 -10
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +2 -2
- package/dist/{endpoint-DpNUx-ON.d.ts → endpoint-BXPdsTA5.d.ts} +56 -2
- package/dist/{endpoint-kYgEHrrV.d.cts → endpoint-DqhUP7RT.d.cts} +56 -2
- package/dist/engine/index.cjs +275 -30
- 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 +4 -4
- package/dist/{index-pnDsEfxJ.d.cts → index-Co0Hj_aO.d.cts} +2 -2
- package/dist/{index-JrHqNsjQ.d.ts → index-MhCL7ll4.d.ts} +2 -2
- package/dist/index.cjs +79 -21
- 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 +6 -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-BcOd8Zhf.d.cts → registry-BkIVHPzB.d.cts} +1 -1
- package/dist/{registry-BoX5iNyY.d.ts → registry-CvGLR5j1.d.ts} +1 -1
- 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 +2 -2
- package/dist/test/index.js.map +1 -1
- package/docs/auth.md +58 -0
- package/docs/llms-full.txt +73 -0
- package/docs/schema.md +15 -0
- package/package.json +15 -15
- package/template/db/public.ts +12 -0
- package/template/scripts/test.sh +0 -0
- package/dist/chunk-MNGZE2MM.js.map +0 -1
- package/dist/chunk-SEM36BWY.js.map +0 -1
- package/dist/chunk-YX7UA7VC.js.map +0 -1
- /package/dist/{chunk-LOGL2ZHD.js.map → chunk-YO5GYM73.js.map} +0 -0
package/dist/engine/index.cjs
CHANGED
|
@@ -2856,10 +2856,10 @@ function applyFromDb(row, transforms) {
|
|
|
2856
2856
|
__name(applyFromDb, "applyFromDb");
|
|
2857
2857
|
function asTableRow(table, row) {
|
|
2858
2858
|
const revived = reviveVectors(asWireRow(row), vectorColumnsOf(currentSchema, table));
|
|
2859
|
-
return applyFromDb(revived, transformsOf(currentSchema, table));
|
|
2859
|
+
return foldCan(table, applyFromDb(revived, transformsOf(currentSchema, table)), false);
|
|
2860
2860
|
}
|
|
2861
2861
|
__name(asTableRow, "asTableRow");
|
|
2862
|
-
function asTableRows(table, rows, withSpec) {
|
|
2862
|
+
function asTableRows(table, rows, withSpec, partial = false) {
|
|
2863
2863
|
const vectorCols = vectorColumnsOf(currentSchema, table);
|
|
2864
2864
|
const transforms = transformsOf(currentSchema, table);
|
|
2865
2865
|
const result = rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
|
|
@@ -2875,6 +2875,7 @@ function asTableRows(table, rows, withSpec) {
|
|
|
2875
2875
|
], nested)[0];
|
|
2876
2876
|
}
|
|
2877
2877
|
}
|
|
2878
|
+
for (const row of result) foldCan(table, row, partial);
|
|
2878
2879
|
return result;
|
|
2879
2880
|
}
|
|
2880
2881
|
__name(asTableRows, "asTableRows");
|
|
@@ -3173,21 +3174,248 @@ function projectedColumn(table, column, alias) {
|
|
|
3173
3174
|
return columnTypeOf(table, column) === "timestamp" ? `${expr} AS ${quoteIdent(column)}` : expr;
|
|
3174
3175
|
}
|
|
3175
3176
|
__name(projectedColumn, "projectedColumn");
|
|
3177
|
+
var CAN_PREFIX = "can:";
|
|
3178
|
+
var canMemo = /* @__PURE__ */ new WeakMap();
|
|
3179
|
+
function canRoleGate(roles) {
|
|
3180
|
+
if (roles.length === 0) return null;
|
|
3181
|
+
const names = roles.flatMap((r) => [
|
|
3182
|
+
r,
|
|
3183
|
+
`backend_${r}`
|
|
3184
|
+
]).map((r) => `'${r.replace(/'/g, "''")}'`);
|
|
3185
|
+
return `(current_user = ANY (ARRAY[${names.join(", ")}]))`;
|
|
3186
|
+
}
|
|
3187
|
+
__name(canRoleGate, "canRoleGate");
|
|
3188
|
+
function canEntries(def, alias) {
|
|
3189
|
+
const policies = def.policies ?? [];
|
|
3190
|
+
const dropped = [];
|
|
3191
|
+
const restrictive = [];
|
|
3192
|
+
for (const p of policies) {
|
|
3193
|
+
if (p.permissive !== false || p.command !== "update" && p.command !== "all") continue;
|
|
3194
|
+
const r = renderCanUsing(p, alias, def.name);
|
|
3195
|
+
if (r === null) {
|
|
3196
|
+
return {
|
|
3197
|
+
entries: [],
|
|
3198
|
+
dropped: policies.filter((q) => q.command === "update" && q.permissive !== false).map((q) => ({
|
|
3199
|
+
name: q.name,
|
|
3200
|
+
reason: `restrictive policy "${p.name}" cannot be evaluated as a column`
|
|
3201
|
+
}))
|
|
3202
|
+
};
|
|
3203
|
+
}
|
|
3204
|
+
const rGate = canRoleGate(p.roles ?? []);
|
|
3205
|
+
restrictive.push(rGate === null ? r : `((NOT ${rGate}) OR ${r})`);
|
|
3206
|
+
}
|
|
3207
|
+
const entries = [];
|
|
3208
|
+
for (const p of policies) {
|
|
3209
|
+
if (p.command !== "update" || p.permissive === false) continue;
|
|
3210
|
+
if (Buffer.byteLength(CAN_PREFIX + p.name, "utf8") > 63) {
|
|
3211
|
+
dropped.push({
|
|
3212
|
+
name: p.name,
|
|
3213
|
+
reason: "its name is longer than 59 bytes and Postgres would truncate the column alias"
|
|
3214
|
+
});
|
|
3215
|
+
continue;
|
|
3216
|
+
}
|
|
3217
|
+
const using = renderCanUsing(p, alias, def.name);
|
|
3218
|
+
if (using === null) {
|
|
3219
|
+
dropped.push({
|
|
3220
|
+
name: p.name,
|
|
3221
|
+
reason: "its using expression cannot be selected as a column (null; a raw string that is table-qualified, carries a comment or unbalanced parentheses, or calls auth.has_permission outside a (select \u2026) wrapper; or a shape this engine does not render)"
|
|
3222
|
+
});
|
|
3223
|
+
continue;
|
|
3224
|
+
}
|
|
3225
|
+
const gate = canRoleGate(p.roles ?? []);
|
|
3226
|
+
const parts = [
|
|
3227
|
+
...gate === null ? [] : [
|
|
3228
|
+
gate
|
|
3229
|
+
],
|
|
3230
|
+
using,
|
|
3231
|
+
...restrictive
|
|
3232
|
+
];
|
|
3233
|
+
entries.push({
|
|
3234
|
+
name: p.name,
|
|
3235
|
+
using: parts.length === 1 ? using : `(${parts.join(" AND ")})`
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
return {
|
|
3239
|
+
entries,
|
|
3240
|
+
dropped
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
__name(canEntries, "canEntries");
|
|
3244
|
+
var CanRenderError = class CanRenderError2 extends Error {
|
|
3245
|
+
static {
|
|
3246
|
+
__name(this, "CanRenderError");
|
|
3247
|
+
}
|
|
3248
|
+
};
|
|
3249
|
+
var ENGINE_ALIASES = /^(t|locked|w\d+|wj\d+|x|q|g|p|upd)$/;
|
|
3250
|
+
function renderCanUsing(p, qualifier, tableName) {
|
|
3251
|
+
if (p.using === null || p.using === void 0) return null;
|
|
3252
|
+
if (typeof p.using === "string") {
|
|
3253
|
+
const withoutLiterals = p.using.replace(/'(?:[^']|'')*'/g, "''");
|
|
3254
|
+
if (/--|\/\*/.test(withoutLiterals)) return null;
|
|
3255
|
+
if (withoutLiterals.replace(/''/g, "").includes("'")) return null;
|
|
3256
|
+
let depth = 0;
|
|
3257
|
+
for (const ch of withoutLiterals) {
|
|
3258
|
+
if (ch === "(") depth++;
|
|
3259
|
+
else if (ch === ")" && --depth < 0) return null;
|
|
3260
|
+
}
|
|
3261
|
+
if (depth !== 0) return null;
|
|
3262
|
+
const tbl = tableName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3263
|
+
const qualified = new RegExp(`(^|[^A-Za-z0-9_])(?:"?[A-Za-z_][A-Za-z0-9_]*"?\\s*\\.\\s*)?"?${tbl}"?\\s*\\.`);
|
|
3264
|
+
if (qualified.test(withoutLiterals)) return null;
|
|
3265
|
+
const calls = withoutLiterals.match(/auth\s*\.\s*"?has_permission"?\s*\(/gi) ?? [];
|
|
3266
|
+
const wrapped = withoutLiterals.match(/\(\s*select\s*[(\s]*auth\s*\.\s*"?has_permission"?\s*\(/gi) ?? [];
|
|
3267
|
+
if (calls.length !== wrapped.length) return null;
|
|
3268
|
+
return `(${p.using})`;
|
|
3269
|
+
}
|
|
3270
|
+
try {
|
|
3271
|
+
const rendered = canExprSql(p.using, qualifier);
|
|
3272
|
+
const selfParenthesized = p.using.kind === "binop" || p.using.kind === "and" || p.using.kind === "or" || p.using.kind === "not" || p.using.kind === "isNull" || p.using.kind === "exists";
|
|
3273
|
+
return selfParenthesized ? rendered : `(${rendered})`;
|
|
3274
|
+
} catch (err) {
|
|
3275
|
+
if (err instanceof CanRenderError) return null;
|
|
3276
|
+
throw err;
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
__name(renderCanUsing, "renderCanUsing");
|
|
3280
|
+
var CAN_BINOPS = {
|
|
3281
|
+
eq: "=",
|
|
3282
|
+
neq: "<>",
|
|
3283
|
+
gt: ">",
|
|
3284
|
+
gte: ">=",
|
|
3285
|
+
lt: "<",
|
|
3286
|
+
lte: "<="
|
|
3287
|
+
};
|
|
3288
|
+
function canLiteral(v) {
|
|
3289
|
+
if (v === null) return "NULL";
|
|
3290
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
3291
|
+
if (typeof v === "number") {
|
|
3292
|
+
if (!Number.isFinite(v)) throw new CanRenderError("can: non-finite literal");
|
|
3293
|
+
return String(v);
|
|
3294
|
+
}
|
|
3295
|
+
return `'${v.replace(/'/g, "''")}'`;
|
|
3296
|
+
}
|
|
3297
|
+
__name(canLiteral, "canLiteral");
|
|
3298
|
+
function canExprSql(e, qualifier) {
|
|
3299
|
+
switch (e.kind) {
|
|
3300
|
+
case "col":
|
|
3301
|
+
return e.alias !== void 0 && e.alias !== "" ? `${quoteIdent(e.alias)}.${quoteIdent(e.name)}` : `${qualifier}.${quoteIdent(e.name)}`;
|
|
3302
|
+
case "authUid":
|
|
3303
|
+
return "(SELECT auth.uid())";
|
|
3304
|
+
case "lit":
|
|
3305
|
+
return canLiteral(e.value);
|
|
3306
|
+
case "binop": {
|
|
3307
|
+
const op = CAN_BINOPS[e.op];
|
|
3308
|
+
if (op === void 0) throw new CanRenderError(`can: unknown operator ${String(e.op)}`);
|
|
3309
|
+
return `(${canExprSql(e.left, qualifier)} ${op} ${canExprSql(e.right, qualifier)})`;
|
|
3310
|
+
}
|
|
3311
|
+
case "and":
|
|
3312
|
+
return e.parts.length === 0 ? "(true)" : `(${e.parts.map((p) => canExprSql(p, qualifier)).join(" AND ")})`;
|
|
3313
|
+
case "or":
|
|
3314
|
+
return e.parts.length === 0 ? "(false)" : `(${e.parts.map((p) => canExprSql(p, qualifier)).join(" OR ")})`;
|
|
3315
|
+
case "not":
|
|
3316
|
+
return `(NOT ${canExprSql(e.expr, qualifier)})`;
|
|
3317
|
+
case "isNull":
|
|
3318
|
+
return `(${canExprSql(e.expr, qualifier)} IS ${e.negated ? "NOT " : ""}NULL)`;
|
|
3319
|
+
case "exists":
|
|
3320
|
+
if (e.alias === "" || ENGINE_ALIASES.test(e.alias)) {
|
|
3321
|
+
throw new CanRenderError(`can: exists alias "${e.alias}" collides with the engine's own aliases \u2014 the correlation would silently collapse`);
|
|
3322
|
+
}
|
|
3323
|
+
return `EXISTS (SELECT 1 FROM ${quoteTable(e.table)} ${quoteIdent(e.alias)} WHERE ${canExprSql(e.predicate, qualifier)})`;
|
|
3324
|
+
default:
|
|
3325
|
+
throw new CanRenderError(`can: unknown policy expression kind ${String(e.kind)}`);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
__name(canExprSql, "canExprSql");
|
|
3329
|
+
function canEntriesOf(table, alias) {
|
|
3330
|
+
const byAlias = memoTable(canMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3331
|
+
const hit = byAlias.get(alias);
|
|
3332
|
+
if (hit !== void 0) return hit;
|
|
3333
|
+
const def = tableDefOf(table);
|
|
3334
|
+
const warnOnce = /* @__PURE__ */ __name((msg) => {
|
|
3335
|
+
if (byAlias.has("__warned")) return;
|
|
3336
|
+
byAlias.set("__warned", []);
|
|
3337
|
+
console.warn(msg);
|
|
3338
|
+
}, "warnOnce");
|
|
3339
|
+
if (def === null || def.policies === void 0) {
|
|
3340
|
+
byAlias.set(alias, []);
|
|
3341
|
+
return [];
|
|
3342
|
+
}
|
|
3343
|
+
if (hasCanColumn(table)) {
|
|
3344
|
+
warnOnce(`can(${table}): the table declares a column or relation named "can", so the policy echo is disabled for it \u2014 rename it to get can`);
|
|
3345
|
+
byAlias.set(alias, []);
|
|
3346
|
+
return [];
|
|
3347
|
+
}
|
|
3348
|
+
const { entries, dropped } = canEntries(def, alias === "" ? quoteTable(table) : alias);
|
|
3349
|
+
if (dropped.length > 0) {
|
|
3350
|
+
warnOnce(dropped.map((d) => `can(${table}): policy "${d.name}" is not exposed in can \u2014 ${d.reason}; the database still enforces it`).join("\n"));
|
|
3351
|
+
}
|
|
3352
|
+
byAlias.set(alias, entries);
|
|
3353
|
+
return entries;
|
|
3354
|
+
}
|
|
3355
|
+
__name(canEntriesOf, "canEntriesOf");
|
|
3356
|
+
function canOuter(table) {
|
|
3357
|
+
const cols = canProjection(table, "q");
|
|
3358
|
+
return cols.length === 0 ? "" : `, ${cols.join(", ")}`;
|
|
3359
|
+
}
|
|
3360
|
+
__name(canOuter, "canOuter");
|
|
3361
|
+
function canProjection(table, alias) {
|
|
3362
|
+
return canEntriesOf(table, alias).map((e) => `${e.using} AS ${quoteIdent(CAN_PREFIX + e.name)}`);
|
|
3363
|
+
}
|
|
3364
|
+
__name(canProjection, "canProjection");
|
|
3365
|
+
function canJsonPairs(table, alias) {
|
|
3366
|
+
return canEntriesOf(table, alias).map((e) => `${quoteLiteral(CAN_PREFIX + e.name)}, ${e.using}`);
|
|
3367
|
+
}
|
|
3368
|
+
__name(canJsonPairs, "canJsonPairs");
|
|
3369
|
+
function foldCan(table, row, partial) {
|
|
3370
|
+
let can = null;
|
|
3371
|
+
for (const key2 of Object.keys(row)) {
|
|
3372
|
+
if (!key2.startsWith(CAN_PREFIX)) continue;
|
|
3373
|
+
can ??= [];
|
|
3374
|
+
if (row[key2] === true) can.push(key2.slice(CAN_PREFIX.length));
|
|
3375
|
+
delete row[key2];
|
|
3376
|
+
}
|
|
3377
|
+
if (hasCanColumn(table)) return row;
|
|
3378
|
+
if (can !== null) row.can = can;
|
|
3379
|
+
else if (!partial && tableDefOf(table) !== null) {
|
|
3380
|
+
if (canEntriesOf(table, "").length > 0) {
|
|
3381
|
+
const flags = memoTable(canMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3382
|
+
if (!flags.has("__missing_warned")) {
|
|
3383
|
+
flags.set("__missing_warned", []);
|
|
3384
|
+
console.warn(`can(${table}): a full-row read returned no "can:*" columns although the table declares update policies \u2014 this statement shape does not carry can; the row gets can: []`);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
row.can = [];
|
|
3388
|
+
}
|
|
3389
|
+
return row;
|
|
3390
|
+
}
|
|
3391
|
+
__name(foldCan, "foldCan");
|
|
3392
|
+
function hasCanColumn(table) {
|
|
3393
|
+
const cols = tableDefOf(table)?.columns;
|
|
3394
|
+
if (cols !== void 0 && Object.prototype.hasOwnProperty.call(cols, "can")) return true;
|
|
3395
|
+
return (currentRelations.get(table) ?? []).some((rel) => rel.name === "can");
|
|
3396
|
+
}
|
|
3397
|
+
__name(hasCanColumn, "hasCanColumn");
|
|
3176
3398
|
function rowProjection(table, alias, wildcard = alias ? `${alias}.*` : "*") {
|
|
3177
3399
|
const cols = schemaColumns(table);
|
|
3178
|
-
|
|
3400
|
+
let base = wildcard;
|
|
3401
|
+
if (cols && memoTable(timestampMemo, currentSchema, table, () => [
|
|
3179
3402
|
...cols
|
|
3180
|
-
].some((c) => columnTypeOf(table, c) === "timestamp")))
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3403
|
+
].some((c) => columnTypeOf(table, c) === "timestamp"))) {
|
|
3404
|
+
const projections = memoTable(projectionMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3405
|
+
const key2 = alias ?? "";
|
|
3406
|
+
let sql = projections.get(key2);
|
|
3407
|
+
if (sql === void 0) {
|
|
3408
|
+
sql = [
|
|
3409
|
+
...cols
|
|
3410
|
+
].map((c) => projectedColumn(table, c, alias)).join(", ");
|
|
3411
|
+
projections.set(key2, sql);
|
|
3412
|
+
}
|
|
3413
|
+
base = sql;
|
|
3414
|
+
}
|
|
3415
|
+
const can = canProjection(table, alias ?? "");
|
|
3416
|
+
if (can.length === 0) return base;
|
|
3417
|
+
const rows = base === "*" && alias !== void 0 ? `${alias}.*` : base;
|
|
3418
|
+
return `${rows}, ${can.join(", ")}`;
|
|
3191
3419
|
}
|
|
3192
3420
|
__name(rowProjection, "rowProjection");
|
|
3193
3421
|
function colRefSql(table, known, ref, caller, alias = "t") {
|
|
@@ -3608,7 +3836,10 @@ function withClauses(table, spec, add, caller, parentAlias = "t", depth = 0, cou
|
|
|
3608
3836
|
jsonPairs: []
|
|
3609
3837
|
} : withClauses(rel.to, nestedSpec, add, caller, childAlias, depth + 1, counter);
|
|
3610
3838
|
const nestedJoins = nested.joins.length ? ` ${nested.joins.join(" ")}` : "";
|
|
3611
|
-
const obj = jsonObjectFor(rel.to, childCols, childAlias,
|
|
3839
|
+
const obj = jsonObjectFor(rel.to, childCols, childAlias, [
|
|
3840
|
+
...nested.jsonPairs,
|
|
3841
|
+
...canJsonPairs(rel.to, childAlias)
|
|
3842
|
+
]);
|
|
3612
3843
|
const innerWhere = o["where"] !== void 0 && o["where"] !== null ? compileWhereBare(rel.to, childKnown, o["where"], add, caller, childAlias, i) : "";
|
|
3613
3844
|
const link = `${childAlias}.${quoteIdent(theirCol)} = ${parentAlias}.${quoteIdent(ourCol)}`;
|
|
3614
3845
|
const cond = innerWhere === "" ? link : `${link} AND ${innerWhere}`;
|
|
@@ -4001,8 +4232,8 @@ function createOps(tx, context = {}) {
|
|
|
4001
4232
|
const wantRows = opts?.returning !== false;
|
|
4002
4233
|
const tail = wantRows ? `RETURNING ${rowProjection(table, "t", "*")}` : "RETURNING 1";
|
|
4003
4234
|
const simple = isSinglePkEquality(where, pk) || pk === null;
|
|
4004
|
-
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
|
|
4005
|
-
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) `;
|
|
4235
|
+
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."__pb_lock" ` + (wantRows ? `RETURNING ${rowProjection(table, "t")}` : "RETURNING 1");
|
|
4236
|
+
const lockedCte = simple ? "" : `WITH locked AS (SELECT t.${quoteIdent(pk)} AS "__pb_lock" FROM ${quoteTable(table)} t WHERE true${whereSql} ORDER BY t.${quoteIdent(pk)} FOR NO KEY UPDATE) `;
|
|
4006
4237
|
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`;
|
|
4007
4238
|
const out = await (await at()).unsafe(sql, params);
|
|
4008
4239
|
if (!wantRows) return Number(out[0]?.["n"] ?? 0);
|
|
@@ -4147,7 +4378,7 @@ function createOps(tx, context = {}) {
|
|
|
4147
4378
|
const compiled = ops.compileQuery(query);
|
|
4148
4379
|
return asTableRows(query.table, await (await at()).unsafe(compiled.sql, compiled.params, {
|
|
4149
4380
|
name: query.name
|
|
4150
|
-
}));
|
|
4381
|
+
}), void 0, true);
|
|
4151
4382
|
},
|
|
4152
4383
|
async explainQuery(query) {
|
|
4153
4384
|
const compiled = ops.compileQuery(query);
|
|
@@ -4157,7 +4388,7 @@ function createOps(tx, context = {}) {
|
|
|
4157
4388
|
},
|
|
4158
4389
|
async findMany(table, query = {}, opts = {}) {
|
|
4159
4390
|
const compiled = compileFindMany(table, query, opts);
|
|
4160
|
-
return asTableRows(table, await (await at()).unsafe(compiled.sql, compiled.params), opts.with);
|
|
4391
|
+
return asTableRows(table, await (await at()).unsafe(compiled.sql, compiled.params), opts.with, opts.select !== void 0);
|
|
4161
4392
|
},
|
|
4162
4393
|
async page(table, query = {}, opts = {}) {
|
|
4163
4394
|
const secret = cursorKey();
|
|
@@ -4225,7 +4456,7 @@ function createOps(tx, context = {}) {
|
|
|
4225
4456
|
other
|
|
4226
4457
|
]) delete row[name];
|
|
4227
4458
|
return {
|
|
4228
|
-
rows: asTableRows(table, rows, opts.with),
|
|
4459
|
+
rows: asTableRows(table, rows, opts.with, opts.select !== void 0),
|
|
4229
4460
|
pageInfo
|
|
4230
4461
|
};
|
|
4231
4462
|
},
|
|
@@ -4332,7 +4563,9 @@ function createOps(tx, context = {}) {
|
|
|
4332
4563
|
kwC = `SELECT c.${pidQ} AS pid, c.chunk_seq, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(c.palbase_fts, websearch_to_tsquery('simple', ${qpC})) DESC) AS r FROM ${quoteTable(ck.table)} c${parentJoinC}c.palbase_fts @@ websearch_to_tsquery('simple', ${qpC}) ORDER BY r LIMIT ${cpool}`;
|
|
4333
4564
|
}
|
|
4334
4565
|
const msC = params.minScore === void 0 ? "" : addC(params.minScore);
|
|
4335
|
-
const colListC =
|
|
4566
|
+
const colListC = [
|
|
4567
|
+
...cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`)
|
|
4568
|
+
].join(", ");
|
|
4336
4569
|
const scoreC = scoreMul === "" ? "g._score" : `(g._score)${scoreMul}`;
|
|
4337
4570
|
const assembleC = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
4338
4571
|
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
@@ -4345,7 +4578,7 @@ function createOps(tx, context = {}) {
|
|
|
4345
4578
|
fusedSrc = `WITH kw AS (${kwIn}), fused AS (SELECT kw.pid, kw.chunk_seq, (1.0/(${K2} + kw.r))::float8 AS cscore FROM kw)`;
|
|
4346
4579
|
}
|
|
4347
4580
|
const inner = `${fusedSrc}, ranked AS (SELECT f.pid, f.chunk_seq, f.cscore, ROW_NUMBER() OVER (PARTITION BY f.pid ORDER BY f.cscore DESC, f.chunk_seq) AS rn FROM fused f), grouped AS (SELECT r.pid, MAX(r.cscore) AS _score, json_agg(json_build_object('content', c.content, 'score', r.cscore, 'chunkSeq', r.chunk_seq, 'charStart', c.char_start) ORDER BY r.cscore DESC, r.chunk_seq) FILTER (WHERE r.rn <= ${bpr}) AS blocks FROM ranked r JOIN ${quoteTable(ck.table)} c ON c.${pidQ} = r.pid AND c.chunk_seq = r.chunk_seq GROUP BY r.pid) SELECT ${colListC}, ${scoreC}::float8 AS _score, g.blocks AS blocks${kwnCol} FROM grouped g JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = g.pid`;
|
|
4348
|
-
return msC === "" ? `SELECT
|
|
4581
|
+
return msC === "" ? `SELECT q.*${canOuter(table)} FROM (${inner}) q ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}` : `SELECT q.*${canOuter(table)} FROM (${inner}) q WHERE q._score >= ${msC} ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
|
|
4349
4582
|
}, "assembleC");
|
|
4350
4583
|
let rowsC = await liveC.unsafe(assembleC(kwC, kwC !== ""), bindC);
|
|
4351
4584
|
if (wantTextC && kwC !== "" && (rowsC.length === 0 || rowsC[0]._kwn === 0)) {
|
|
@@ -4414,26 +4647,26 @@ function createOps(tx, context = {}) {
|
|
|
4414
4647
|
qpRef = qp;
|
|
4415
4648
|
kwSql = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(t.palbase_fts, websearch_to_tsquery('simple', ${qp})) DESC) AS r FROM ${quoteTable(table)} t WHERE t.palbase_fts @@ websearch_to_tsquery('simple', ${qp})${whereSql} ORDER BY r LIMIT ${pool}`;
|
|
4416
4649
|
}
|
|
4417
|
-
const colList =
|
|
4650
|
+
const colList = [
|
|
4651
|
+
...cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`)
|
|
4652
|
+
].join(", ");
|
|
4418
4653
|
const msP = params.minScore === void 0 ? "" : add(params.minScore);
|
|
4419
4654
|
const hlCol = params.highlight === true && wantText ? `, ts_headline('simple', ${ftsExprOf(cfg.ftsCols)}, websearch_to_tsquery('simple', ${qpRef})) AS _highlight` : "";
|
|
4420
4655
|
const assemble = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
4421
4656
|
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
4422
4657
|
let inner;
|
|
4423
|
-
let orderScore;
|
|
4424
4658
|
if (semSql !== "" && kwIn !== "") {
|
|
4425
4659
|
const score = scoreMul === "" ? "fused._score" : `(fused._score)${scoreMul}`;
|
|
4426
4660
|
inner = `WITH sem AS (${semSql}), kw AS (${kwIn}), fused AS (SELECT COALESCE(sem.id, kw.id) AS id, (COALESCE(1.0/(${K} + sem.r), 0) + COALESCE(1.0/(${K} + kw.r), 0))::float8 AS _score FROM sem FULL OUTER JOIN kw ON sem.id = kw.id) SELECT ${colList}, ${score} AS _score${hlCol}${kwnCol} FROM fused JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = fused.id`;
|
|
4427
|
-
orderScore = scoreMul === "" ? "fused._score" : "_score";
|
|
4428
4661
|
} else {
|
|
4429
4662
|
const single = semSql !== "" ? `sem AS (${semSql})` : `kw AS (${kwIn})`;
|
|
4430
4663
|
const alias = semSql !== "" ? "sem" : "kw";
|
|
4431
4664
|
const base = `(1.0/(${K} + ${alias}.r))::float8`;
|
|
4432
4665
|
const score = scoreMul === "" ? base : `(${base})${scoreMul}`;
|
|
4433
4666
|
inner = `WITH ${single} SELECT ${colList}, ${score} AS _score${hlCol}${semSql !== "" ? "" : kwnCol} FROM ${alias} JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = ${alias}.id`;
|
|
4434
|
-
orderScore = "_score";
|
|
4435
4667
|
}
|
|
4436
|
-
|
|
4668
|
+
const ms = msP === "" ? "" : ` WHERE q._score >= ${msP}`;
|
|
4669
|
+
return `SELECT q.*${canOuter(table)} FROM (${inner}) q${ms} ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
|
|
4437
4670
|
}, "assemble");
|
|
4438
4671
|
let rows = await live.unsafe(assemble(kwSql, kwSql !== ""), bind);
|
|
4439
4672
|
if (wantText && kwSql !== "" && (rows.length === 0 || rows[0]._kwn === 0)) {
|
|
@@ -5269,7 +5502,7 @@ function compilePlanOp(op, results) {
|
|
|
5269
5502
|
const assignments = cols.map((c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols, opTransforms)}`);
|
|
5270
5503
|
const planned = planWhere(op.table, op.where, args, results);
|
|
5271
5504
|
const planPk = primaryKeyOf(op.table);
|
|
5272
|
-
sql = isSinglePkEquality(planned.resolved, planPk) || planPk === null ? `UPDATE ${table} AS t SET ${assignments.join(", ")} WHERE true${planned.sql} RETURNING ${rowProjection(op.table, "t")}` : `WITH locked AS (SELECT t.${quoteIdent(planPk)} FROM ${table} t WHERE true${planned.sql} ORDER BY t.${quoteIdent(planPk)} FOR NO KEY UPDATE) UPDATE ${table} AS t SET ${assignments.join(", ")} FROM locked WHERE t.${quoteIdent(planPk)} = locked
|
|
5505
|
+
sql = isSinglePkEquality(planned.resolved, planPk) || planPk === null ? `UPDATE ${table} AS t SET ${assignments.join(", ")} WHERE true${planned.sql} RETURNING ${rowProjection(op.table, "t")}` : `WITH locked AS (SELECT t.${quoteIdent(planPk)} AS "__pb_lock" FROM ${table} t WHERE true${planned.sql} ORDER BY t.${quoteIdent(planPk)} FOR NO KEY UPDATE) UPDATE ${table} AS t SET ${assignments.join(", ")} FROM locked WHERE t.${quoteIdent(planPk)} = locked."__pb_lock" RETURNING ${rowProjection(op.table, "t")}`;
|
|
5273
5506
|
break;
|
|
5274
5507
|
}
|
|
5275
5508
|
case "delete": {
|
|
@@ -6442,13 +6675,25 @@ async function refuseUnlessAuthorized(client, spec, requestId, rolesOfCaller) {
|
|
|
6442
6675
|
spec.permission
|
|
6443
6676
|
]);
|
|
6444
6677
|
if (row?.ok !== true) {
|
|
6445
|
-
return envelope(
|
|
6678
|
+
return envelope(
|
|
6679
|
+
"forbidden",
|
|
6680
|
+
`This endpoint requires the "${spec.permission}" permission`,
|
|
6681
|
+
403,
|
|
6682
|
+
requestId,
|
|
6683
|
+
// FR-008 / D-7: reddin ÇARESİ gövdede — istemci `required`'ı görünce ilgili
|
|
6684
|
+
// nesneyi yeniden çeker (Entra CAE deseni); cümle değişmez.
|
|
6685
|
+
{
|
|
6686
|
+
required: spec.permission
|
|
6687
|
+
}
|
|
6688
|
+
);
|
|
6446
6689
|
}
|
|
6447
6690
|
}
|
|
6448
6691
|
if (spec.role !== void 0) {
|
|
6449
6692
|
const held = await rolesOfCaller();
|
|
6450
6693
|
if (!held.includes(spec.role)) {
|
|
6451
|
-
return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId
|
|
6694
|
+
return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId, {
|
|
6695
|
+
required_role: spec.role
|
|
6696
|
+
});
|
|
6452
6697
|
}
|
|
6453
6698
|
}
|
|
6454
6699
|
return null;
|