@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present Palbase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -2812,10 +2812,10 @@ function applyFromDb(row, transforms) {
|
|
|
2812
2812
|
__name(applyFromDb, "applyFromDb");
|
|
2813
2813
|
function asTableRow(table, row) {
|
|
2814
2814
|
const revived = reviveVectors(asWireRow(row), vectorColumnsOf(currentSchema, table));
|
|
2815
|
-
return applyFromDb(revived, transformsOf(currentSchema, table));
|
|
2815
|
+
return foldCan(table, applyFromDb(revived, transformsOf(currentSchema, table)), false);
|
|
2816
2816
|
}
|
|
2817
2817
|
__name(asTableRow, "asTableRow");
|
|
2818
|
-
function asTableRows(table, rows, withSpec) {
|
|
2818
|
+
function asTableRows(table, rows, withSpec, partial = false) {
|
|
2819
2819
|
const vectorCols = vectorColumnsOf(currentSchema, table);
|
|
2820
2820
|
const transforms = transformsOf(currentSchema, table);
|
|
2821
2821
|
const result = rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
|
|
@@ -2831,6 +2831,7 @@ function asTableRows(table, rows, withSpec) {
|
|
|
2831
2831
|
], nested)[0];
|
|
2832
2832
|
}
|
|
2833
2833
|
}
|
|
2834
|
+
for (const row of result) foldCan(table, row, partial);
|
|
2834
2835
|
return result;
|
|
2835
2836
|
}
|
|
2836
2837
|
__name(asTableRows, "asTableRows");
|
|
@@ -3129,21 +3130,248 @@ function projectedColumn(table, column, alias) {
|
|
|
3129
3130
|
return columnTypeOf(table, column) === "timestamp" ? `${expr} AS ${quoteIdent(column)}` : expr;
|
|
3130
3131
|
}
|
|
3131
3132
|
__name(projectedColumn, "projectedColumn");
|
|
3133
|
+
var CAN_PREFIX = "can:";
|
|
3134
|
+
var canMemo = /* @__PURE__ */ new WeakMap();
|
|
3135
|
+
function canRoleGate(roles) {
|
|
3136
|
+
if (roles.length === 0) return null;
|
|
3137
|
+
const names = roles.flatMap((r) => [
|
|
3138
|
+
r,
|
|
3139
|
+
`backend_${r}`
|
|
3140
|
+
]).map((r) => `'${r.replace(/'/g, "''")}'`);
|
|
3141
|
+
return `(current_user = ANY (ARRAY[${names.join(", ")}]))`;
|
|
3142
|
+
}
|
|
3143
|
+
__name(canRoleGate, "canRoleGate");
|
|
3144
|
+
function canEntries(def, alias) {
|
|
3145
|
+
const policies = def.policies ?? [];
|
|
3146
|
+
const dropped = [];
|
|
3147
|
+
const restrictive = [];
|
|
3148
|
+
for (const p of policies) {
|
|
3149
|
+
if (p.permissive !== false || p.command !== "update" && p.command !== "all") continue;
|
|
3150
|
+
const r = renderCanUsing(p, alias, def.name);
|
|
3151
|
+
if (r === null) {
|
|
3152
|
+
return {
|
|
3153
|
+
entries: [],
|
|
3154
|
+
dropped: policies.filter((q) => q.command === "update" && q.permissive !== false).map((q) => ({
|
|
3155
|
+
name: q.name,
|
|
3156
|
+
reason: `restrictive policy "${p.name}" cannot be evaluated as a column`
|
|
3157
|
+
}))
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
3160
|
+
const rGate = canRoleGate(p.roles ?? []);
|
|
3161
|
+
restrictive.push(rGate === null ? r : `((NOT ${rGate}) OR ${r})`);
|
|
3162
|
+
}
|
|
3163
|
+
const entries = [];
|
|
3164
|
+
for (const p of policies) {
|
|
3165
|
+
if (p.command !== "update" || p.permissive === false) continue;
|
|
3166
|
+
if (Buffer.byteLength(CAN_PREFIX + p.name, "utf8") > 63) {
|
|
3167
|
+
dropped.push({
|
|
3168
|
+
name: p.name,
|
|
3169
|
+
reason: "its name is longer than 59 bytes and Postgres would truncate the column alias"
|
|
3170
|
+
});
|
|
3171
|
+
continue;
|
|
3172
|
+
}
|
|
3173
|
+
const using = renderCanUsing(p, alias, def.name);
|
|
3174
|
+
if (using === null) {
|
|
3175
|
+
dropped.push({
|
|
3176
|
+
name: p.name,
|
|
3177
|
+
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)"
|
|
3178
|
+
});
|
|
3179
|
+
continue;
|
|
3180
|
+
}
|
|
3181
|
+
const gate = canRoleGate(p.roles ?? []);
|
|
3182
|
+
const parts = [
|
|
3183
|
+
...gate === null ? [] : [
|
|
3184
|
+
gate
|
|
3185
|
+
],
|
|
3186
|
+
using,
|
|
3187
|
+
...restrictive
|
|
3188
|
+
];
|
|
3189
|
+
entries.push({
|
|
3190
|
+
name: p.name,
|
|
3191
|
+
using: parts.length === 1 ? using : `(${parts.join(" AND ")})`
|
|
3192
|
+
});
|
|
3193
|
+
}
|
|
3194
|
+
return {
|
|
3195
|
+
entries,
|
|
3196
|
+
dropped
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
__name(canEntries, "canEntries");
|
|
3200
|
+
var CanRenderError = class CanRenderError2 extends Error {
|
|
3201
|
+
static {
|
|
3202
|
+
__name(this, "CanRenderError");
|
|
3203
|
+
}
|
|
3204
|
+
};
|
|
3205
|
+
var ENGINE_ALIASES = /^(t|locked|w\d+|wj\d+|x|q|g|p|upd)$/;
|
|
3206
|
+
function renderCanUsing(p, qualifier, tableName) {
|
|
3207
|
+
if (p.using === null || p.using === void 0) return null;
|
|
3208
|
+
if (typeof p.using === "string") {
|
|
3209
|
+
const withoutLiterals = p.using.replace(/'(?:[^']|'')*'/g, "''");
|
|
3210
|
+
if (/--|\/\*/.test(withoutLiterals)) return null;
|
|
3211
|
+
if (withoutLiterals.replace(/''/g, "").includes("'")) return null;
|
|
3212
|
+
let depth = 0;
|
|
3213
|
+
for (const ch of withoutLiterals) {
|
|
3214
|
+
if (ch === "(") depth++;
|
|
3215
|
+
else if (ch === ")" && --depth < 0) return null;
|
|
3216
|
+
}
|
|
3217
|
+
if (depth !== 0) return null;
|
|
3218
|
+
const tbl = tableName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3219
|
+
const qualified = new RegExp(`(^|[^A-Za-z0-9_])(?:"?[A-Za-z_][A-Za-z0-9_]*"?\\s*\\.\\s*)?"?${tbl}"?\\s*\\.`);
|
|
3220
|
+
if (qualified.test(withoutLiterals)) return null;
|
|
3221
|
+
const calls = withoutLiterals.match(/auth\s*\.\s*"?has_permission"?\s*\(/gi) ?? [];
|
|
3222
|
+
const wrapped = withoutLiterals.match(/\(\s*select\s*[(\s]*auth\s*\.\s*"?has_permission"?\s*\(/gi) ?? [];
|
|
3223
|
+
if (calls.length !== wrapped.length) return null;
|
|
3224
|
+
return `(${p.using})`;
|
|
3225
|
+
}
|
|
3226
|
+
try {
|
|
3227
|
+
const rendered = canExprSql(p.using, qualifier);
|
|
3228
|
+
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";
|
|
3229
|
+
return selfParenthesized ? rendered : `(${rendered})`;
|
|
3230
|
+
} catch (err) {
|
|
3231
|
+
if (err instanceof CanRenderError) return null;
|
|
3232
|
+
throw err;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
__name(renderCanUsing, "renderCanUsing");
|
|
3236
|
+
var CAN_BINOPS = {
|
|
3237
|
+
eq: "=",
|
|
3238
|
+
neq: "<>",
|
|
3239
|
+
gt: ">",
|
|
3240
|
+
gte: ">=",
|
|
3241
|
+
lt: "<",
|
|
3242
|
+
lte: "<="
|
|
3243
|
+
};
|
|
3244
|
+
function canLiteral(v) {
|
|
3245
|
+
if (v === null) return "NULL";
|
|
3246
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
3247
|
+
if (typeof v === "number") {
|
|
3248
|
+
if (!Number.isFinite(v)) throw new CanRenderError("can: non-finite literal");
|
|
3249
|
+
return String(v);
|
|
3250
|
+
}
|
|
3251
|
+
return `'${v.replace(/'/g, "''")}'`;
|
|
3252
|
+
}
|
|
3253
|
+
__name(canLiteral, "canLiteral");
|
|
3254
|
+
function canExprSql(e, qualifier) {
|
|
3255
|
+
switch (e.kind) {
|
|
3256
|
+
case "col":
|
|
3257
|
+
return e.alias !== void 0 && e.alias !== "" ? `${quoteIdent(e.alias)}.${quoteIdent(e.name)}` : `${qualifier}.${quoteIdent(e.name)}`;
|
|
3258
|
+
case "authUid":
|
|
3259
|
+
return "(SELECT auth.uid())";
|
|
3260
|
+
case "lit":
|
|
3261
|
+
return canLiteral(e.value);
|
|
3262
|
+
case "binop": {
|
|
3263
|
+
const op = CAN_BINOPS[e.op];
|
|
3264
|
+
if (op === void 0) throw new CanRenderError(`can: unknown operator ${String(e.op)}`);
|
|
3265
|
+
return `(${canExprSql(e.left, qualifier)} ${op} ${canExprSql(e.right, qualifier)})`;
|
|
3266
|
+
}
|
|
3267
|
+
case "and":
|
|
3268
|
+
return e.parts.length === 0 ? "(true)" : `(${e.parts.map((p) => canExprSql(p, qualifier)).join(" AND ")})`;
|
|
3269
|
+
case "or":
|
|
3270
|
+
return e.parts.length === 0 ? "(false)" : `(${e.parts.map((p) => canExprSql(p, qualifier)).join(" OR ")})`;
|
|
3271
|
+
case "not":
|
|
3272
|
+
return `(NOT ${canExprSql(e.expr, qualifier)})`;
|
|
3273
|
+
case "isNull":
|
|
3274
|
+
return `(${canExprSql(e.expr, qualifier)} IS ${e.negated ? "NOT " : ""}NULL)`;
|
|
3275
|
+
case "exists":
|
|
3276
|
+
if (e.alias === "" || ENGINE_ALIASES.test(e.alias)) {
|
|
3277
|
+
throw new CanRenderError(`can: exists alias "${e.alias}" collides with the engine's own aliases \u2014 the correlation would silently collapse`);
|
|
3278
|
+
}
|
|
3279
|
+
return `EXISTS (SELECT 1 FROM ${quoteTable(e.table)} ${quoteIdent(e.alias)} WHERE ${canExprSql(e.predicate, qualifier)})`;
|
|
3280
|
+
default:
|
|
3281
|
+
throw new CanRenderError(`can: unknown policy expression kind ${String(e.kind)}`);
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
__name(canExprSql, "canExprSql");
|
|
3285
|
+
function canEntriesOf(table, alias) {
|
|
3286
|
+
const byAlias = memoTable(canMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3287
|
+
const hit = byAlias.get(alias);
|
|
3288
|
+
if (hit !== void 0) return hit;
|
|
3289
|
+
const def = tableDefOf(table);
|
|
3290
|
+
const warnOnce = /* @__PURE__ */ __name((msg) => {
|
|
3291
|
+
if (byAlias.has("__warned")) return;
|
|
3292
|
+
byAlias.set("__warned", []);
|
|
3293
|
+
console.warn(msg);
|
|
3294
|
+
}, "warnOnce");
|
|
3295
|
+
if (def === null || def.policies === void 0) {
|
|
3296
|
+
byAlias.set(alias, []);
|
|
3297
|
+
return [];
|
|
3298
|
+
}
|
|
3299
|
+
if (hasCanColumn(table)) {
|
|
3300
|
+
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`);
|
|
3301
|
+
byAlias.set(alias, []);
|
|
3302
|
+
return [];
|
|
3303
|
+
}
|
|
3304
|
+
const { entries, dropped } = canEntries(def, alias === "" ? quoteTable(table) : alias);
|
|
3305
|
+
if (dropped.length > 0) {
|
|
3306
|
+
warnOnce(dropped.map((d) => `can(${table}): policy "${d.name}" is not exposed in can \u2014 ${d.reason}; the database still enforces it`).join("\n"));
|
|
3307
|
+
}
|
|
3308
|
+
byAlias.set(alias, entries);
|
|
3309
|
+
return entries;
|
|
3310
|
+
}
|
|
3311
|
+
__name(canEntriesOf, "canEntriesOf");
|
|
3312
|
+
function canOuter(table) {
|
|
3313
|
+
const cols = canProjection(table, "q");
|
|
3314
|
+
return cols.length === 0 ? "" : `, ${cols.join(", ")}`;
|
|
3315
|
+
}
|
|
3316
|
+
__name(canOuter, "canOuter");
|
|
3317
|
+
function canProjection(table, alias) {
|
|
3318
|
+
return canEntriesOf(table, alias).map((e) => `${e.using} AS ${quoteIdent(CAN_PREFIX + e.name)}`);
|
|
3319
|
+
}
|
|
3320
|
+
__name(canProjection, "canProjection");
|
|
3321
|
+
function canJsonPairs(table, alias) {
|
|
3322
|
+
return canEntriesOf(table, alias).map((e) => `${quoteLiteral(CAN_PREFIX + e.name)}, ${e.using}`);
|
|
3323
|
+
}
|
|
3324
|
+
__name(canJsonPairs, "canJsonPairs");
|
|
3325
|
+
function foldCan(table, row, partial) {
|
|
3326
|
+
let can = null;
|
|
3327
|
+
for (const key2 of Object.keys(row)) {
|
|
3328
|
+
if (!key2.startsWith(CAN_PREFIX)) continue;
|
|
3329
|
+
can ??= [];
|
|
3330
|
+
if (row[key2] === true) can.push(key2.slice(CAN_PREFIX.length));
|
|
3331
|
+
delete row[key2];
|
|
3332
|
+
}
|
|
3333
|
+
if (hasCanColumn(table)) return row;
|
|
3334
|
+
if (can !== null) row.can = can;
|
|
3335
|
+
else if (!partial && tableDefOf(table) !== null) {
|
|
3336
|
+
if (canEntriesOf(table, "").length > 0) {
|
|
3337
|
+
const flags = memoTable(canMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3338
|
+
if (!flags.has("__missing_warned")) {
|
|
3339
|
+
flags.set("__missing_warned", []);
|
|
3340
|
+
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: []`);
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
row.can = [];
|
|
3344
|
+
}
|
|
3345
|
+
return row;
|
|
3346
|
+
}
|
|
3347
|
+
__name(foldCan, "foldCan");
|
|
3348
|
+
function hasCanColumn(table) {
|
|
3349
|
+
const cols = tableDefOf(table)?.columns;
|
|
3350
|
+
if (cols !== void 0 && Object.prototype.hasOwnProperty.call(cols, "can")) return true;
|
|
3351
|
+
return (currentRelations.get(table) ?? []).some((rel) => rel.name === "can");
|
|
3352
|
+
}
|
|
3353
|
+
__name(hasCanColumn, "hasCanColumn");
|
|
3132
3354
|
function rowProjection(table, alias, wildcard = alias ? `${alias}.*` : "*") {
|
|
3133
3355
|
const cols = schemaColumns(table);
|
|
3134
|
-
|
|
3356
|
+
let base = wildcard;
|
|
3357
|
+
if (cols && memoTable(timestampMemo, currentSchema, table, () => [
|
|
3135
3358
|
...cols
|
|
3136
|
-
].some((c) => columnTypeOf(table, c) === "timestamp")))
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3359
|
+
].some((c) => columnTypeOf(table, c) === "timestamp"))) {
|
|
3360
|
+
const projections = memoTable(projectionMemo, currentSchema, table, () => /* @__PURE__ */ new Map());
|
|
3361
|
+
const key2 = alias ?? "";
|
|
3362
|
+
let sql = projections.get(key2);
|
|
3363
|
+
if (sql === void 0) {
|
|
3364
|
+
sql = [
|
|
3365
|
+
...cols
|
|
3366
|
+
].map((c) => projectedColumn(table, c, alias)).join(", ");
|
|
3367
|
+
projections.set(key2, sql);
|
|
3368
|
+
}
|
|
3369
|
+
base = sql;
|
|
3370
|
+
}
|
|
3371
|
+
const can = canProjection(table, alias ?? "");
|
|
3372
|
+
if (can.length === 0) return base;
|
|
3373
|
+
const rows = base === "*" && alias !== void 0 ? `${alias}.*` : base;
|
|
3374
|
+
return `${rows}, ${can.join(", ")}`;
|
|
3147
3375
|
}
|
|
3148
3376
|
__name(rowProjection, "rowProjection");
|
|
3149
3377
|
function colRefSql(table, known, ref, caller, alias = "t") {
|
|
@@ -3564,7 +3792,10 @@ function withClauses(table, spec, add, caller, parentAlias = "t", depth = 0, cou
|
|
|
3564
3792
|
jsonPairs: []
|
|
3565
3793
|
} : withClauses(rel.to, nestedSpec, add, caller, childAlias, depth + 1, counter);
|
|
3566
3794
|
const nestedJoins = nested.joins.length ? ` ${nested.joins.join(" ")}` : "";
|
|
3567
|
-
const obj = jsonObjectFor(rel.to, childCols, childAlias,
|
|
3795
|
+
const obj = jsonObjectFor(rel.to, childCols, childAlias, [
|
|
3796
|
+
...nested.jsonPairs,
|
|
3797
|
+
...canJsonPairs(rel.to, childAlias)
|
|
3798
|
+
]);
|
|
3568
3799
|
const innerWhere = o["where"] !== void 0 && o["where"] !== null ? compileWhereBare(rel.to, childKnown, o["where"], add, caller, childAlias, i) : "";
|
|
3569
3800
|
const link = `${childAlias}.${quoteIdent(theirCol)} = ${parentAlias}.${quoteIdent(ourCol)}`;
|
|
3570
3801
|
const cond = innerWhere === "" ? link : `${link} AND ${innerWhere}`;
|
|
@@ -3957,8 +4188,8 @@ function createOps(tx, context = {}) {
|
|
|
3957
4188
|
const wantRows = opts?.returning !== false;
|
|
3958
4189
|
const tail = wantRows ? `RETURNING ${rowProjection(table, "t", "*")}` : "RETURNING 1";
|
|
3959
4190
|
const simple = isSinglePkEquality(where, pk) || pk === null;
|
|
3960
|
-
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
|
|
3961
|
-
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) `;
|
|
4191
|
+
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");
|
|
4192
|
+
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) `;
|
|
3962
4193
|
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`;
|
|
3963
4194
|
const out = await (await at()).unsafe(sql, params);
|
|
3964
4195
|
if (!wantRows) return Number(out[0]?.["n"] ?? 0);
|
|
@@ -4103,7 +4334,7 @@ function createOps(tx, context = {}) {
|
|
|
4103
4334
|
const compiled = ops.compileQuery(query);
|
|
4104
4335
|
return asTableRows(query.table, await (await at()).unsafe(compiled.sql, compiled.params, {
|
|
4105
4336
|
name: query.name
|
|
4106
|
-
}));
|
|
4337
|
+
}), void 0, true);
|
|
4107
4338
|
},
|
|
4108
4339
|
async explainQuery(query) {
|
|
4109
4340
|
const compiled = ops.compileQuery(query);
|
|
@@ -4113,7 +4344,7 @@ function createOps(tx, context = {}) {
|
|
|
4113
4344
|
},
|
|
4114
4345
|
async findMany(table, query = {}, opts = {}) {
|
|
4115
4346
|
const compiled = compileFindMany(table, query, opts);
|
|
4116
|
-
return asTableRows(table, await (await at()).unsafe(compiled.sql, compiled.params), opts.with);
|
|
4347
|
+
return asTableRows(table, await (await at()).unsafe(compiled.sql, compiled.params), opts.with, opts.select !== void 0);
|
|
4117
4348
|
},
|
|
4118
4349
|
async page(table, query = {}, opts = {}) {
|
|
4119
4350
|
const secret = cursorKey();
|
|
@@ -4181,7 +4412,7 @@ function createOps(tx, context = {}) {
|
|
|
4181
4412
|
other
|
|
4182
4413
|
]) delete row[name];
|
|
4183
4414
|
return {
|
|
4184
|
-
rows: asTableRows(table, rows, opts.with),
|
|
4415
|
+
rows: asTableRows(table, rows, opts.with, opts.select !== void 0),
|
|
4185
4416
|
pageInfo
|
|
4186
4417
|
};
|
|
4187
4418
|
},
|
|
@@ -4288,7 +4519,9 @@ function createOps(tx, context = {}) {
|
|
|
4288
4519
|
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}`;
|
|
4289
4520
|
}
|
|
4290
4521
|
const msC = params.minScore === void 0 ? "" : addC(params.minScore);
|
|
4291
|
-
const colListC =
|
|
4522
|
+
const colListC = [
|
|
4523
|
+
...cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`)
|
|
4524
|
+
].join(", ");
|
|
4292
4525
|
const scoreC = scoreMul === "" ? "g._score" : `(g._score)${scoreMul}`;
|
|
4293
4526
|
const assembleC = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
4294
4527
|
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
@@ -4301,7 +4534,7 @@ function createOps(tx, context = {}) {
|
|
|
4301
4534
|
fusedSrc = `WITH kw AS (${kwIn}), fused AS (SELECT kw.pid, kw.chunk_seq, (1.0/(${K2} + kw.r))::float8 AS cscore FROM kw)`;
|
|
4302
4535
|
}
|
|
4303
4536
|
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`;
|
|
4304
|
-
return msC === "" ? `SELECT
|
|
4537
|
+
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}`;
|
|
4305
4538
|
}, "assembleC");
|
|
4306
4539
|
let rowsC = await liveC.unsafe(assembleC(kwC, kwC !== ""), bindC);
|
|
4307
4540
|
if (wantTextC && kwC !== "" && (rowsC.length === 0 || rowsC[0]._kwn === 0)) {
|
|
@@ -4370,26 +4603,26 @@ function createOps(tx, context = {}) {
|
|
|
4370
4603
|
qpRef = qp;
|
|
4371
4604
|
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}`;
|
|
4372
4605
|
}
|
|
4373
|
-
const colList =
|
|
4606
|
+
const colList = [
|
|
4607
|
+
...cfg.cols.map((c) => columnTypeOf(table, c) === "timestamp" ? projectedColumn(table, c, "t") : `t.${quoteIdent(c)}`)
|
|
4608
|
+
].join(", ");
|
|
4374
4609
|
const msP = params.minScore === void 0 ? "" : add(params.minScore);
|
|
4375
4610
|
const hlCol = params.highlight === true && wantText ? `, ts_headline('simple', ${ftsExprOf(cfg.ftsCols)}, websearch_to_tsquery('simple', ${qpRef})) AS _highlight` : "";
|
|
4376
4611
|
const assemble = /* @__PURE__ */ __name((kwIn, withKwn) => {
|
|
4377
4612
|
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
4378
4613
|
let inner;
|
|
4379
|
-
let orderScore;
|
|
4380
4614
|
if (semSql !== "" && kwIn !== "") {
|
|
4381
4615
|
const score = scoreMul === "" ? "fused._score" : `(fused._score)${scoreMul}`;
|
|
4382
4616
|
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`;
|
|
4383
|
-
orderScore = scoreMul === "" ? "fused._score" : "_score";
|
|
4384
4617
|
} else {
|
|
4385
4618
|
const single = semSql !== "" ? `sem AS (${semSql})` : `kw AS (${kwIn})`;
|
|
4386
4619
|
const alias = semSql !== "" ? "sem" : "kw";
|
|
4387
4620
|
const base = `(1.0/(${K} + ${alias}.r))::float8`;
|
|
4388
4621
|
const score = scoreMul === "" ? base : `(${base})${scoreMul}`;
|
|
4389
4622
|
inner = `WITH ${single} SELECT ${colList}, ${score} AS _score${hlCol}${semSql !== "" ? "" : kwnCol} FROM ${alias} JOIN ${quoteTable(table)} t ON t.${quoteIdent(cfg.pk)} = ${alias}.id`;
|
|
4390
|
-
orderScore = "_score";
|
|
4391
4623
|
}
|
|
4392
|
-
|
|
4624
|
+
const ms = msP === "" ? "" : ` WHERE q._score >= ${msP}`;
|
|
4625
|
+
return `SELECT q.*${canOuter(table)} FROM (${inner}) q${ms} ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
|
|
4393
4626
|
}, "assemble");
|
|
4394
4627
|
let rows = await live.unsafe(assemble(kwSql, kwSql !== ""), bind);
|
|
4395
4628
|
if (wantText && kwSql !== "" && (rows.length === 0 || rows[0]._kwn === 0)) {
|
|
@@ -5225,7 +5458,7 @@ function compilePlanOp(op, results) {
|
|
|
5225
5458
|
const assignments = cols.map((c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols, opTransforms)}`);
|
|
5226
5459
|
const planned = planWhere(op.table, op.where, args, results);
|
|
5227
5460
|
const planPk = primaryKeyOf(op.table);
|
|
5228
|
-
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
|
|
5461
|
+
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")}`;
|
|
5229
5462
|
break;
|
|
5230
5463
|
}
|
|
5231
5464
|
case "delete": {
|
|
@@ -6344,13 +6577,25 @@ async function refuseUnlessAuthorized(client, spec, requestId, rolesOfCaller) {
|
|
|
6344
6577
|
spec.permission
|
|
6345
6578
|
]);
|
|
6346
6579
|
if (row?.ok !== true) {
|
|
6347
|
-
return envelope(
|
|
6580
|
+
return envelope(
|
|
6581
|
+
"forbidden",
|
|
6582
|
+
`This endpoint requires the "${spec.permission}" permission`,
|
|
6583
|
+
403,
|
|
6584
|
+
requestId,
|
|
6585
|
+
// FR-008 / D-7: reddin ÇARESİ gövdede — istemci `required`'ı görünce ilgili
|
|
6586
|
+
// nesneyi yeniden çeker (Entra CAE deseni); cümle değişmez.
|
|
6587
|
+
{
|
|
6588
|
+
required: spec.permission
|
|
6589
|
+
}
|
|
6590
|
+
);
|
|
6348
6591
|
}
|
|
6349
6592
|
}
|
|
6350
6593
|
if (spec.role !== void 0) {
|
|
6351
6594
|
const held = await rolesOfCaller();
|
|
6352
6595
|
if (!held.includes(spec.role)) {
|
|
6353
|
-
return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId
|
|
6596
|
+
return envelope("forbidden", `This endpoint requires the "${spec.role}" role`, 403, requestId, {
|
|
6597
|
+
required_role: spec.role
|
|
6598
|
+
});
|
|
6354
6599
|
}
|
|
6355
6600
|
}
|
|
6356
6601
|
return null;
|