@palbase/backend 23.1.0 → 24.0.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/dist/bin/palbase-backend.cjs +400 -47
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +4 -5
- package/dist/bin/palbase-backend.js.map +1 -1
- package/dist/{chunk-OHALWEOG.js → chunk-7Z6MGMXQ.js} +57 -2
- package/dist/chunk-7Z6MGMXQ.js.map +1 -0
- package/dist/{chunk-RCLNBJCM.js → chunk-H3JAISUY.js} +136 -1
- package/dist/chunk-H3JAISUY.js.map +1 -0
- package/dist/{chunk-PY7YJDCT.js → chunk-LCL7TUAI.js} +32 -3
- package/dist/chunk-LCL7TUAI.js.map +1 -0
- package/dist/{chunk-NS5V43YQ.js → chunk-P2Q27SGP.js} +19 -3
- package/dist/chunk-P2Q27SGP.js.map +1 -0
- package/dist/{chunk-R3KN6RHD.js → chunk-T5IOSOE5.js} +7 -2
- package/dist/chunk-T5IOSOE5.js.map +1 -0
- package/dist/{chunk-M5MCBWJI.js → chunk-YSQBC2VL.js} +275 -31
- package/dist/chunk-YSQBC2VL.js.map +1 -0
- package/dist/db/index.cjs +48 -3
- 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-CVWXh6oG.d.ts → endpoint-0_DGBajf.d.ts} +100 -3
- package/dist/{endpoint-c9h5jriX.d.cts → endpoint-CcQ1a36a.d.cts} +100 -3
- package/dist/engine/index.cjs +389 -34
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +4 -4
- package/dist/engine/index.d.ts +4 -4
- package/dist/engine/index.js +4 -4
- package/dist/{index-CwAJ7HEe.d.ts → index-BOS_rFBO.d.ts} +125 -21
- package/dist/{index-CxeQSfJP.d.cts → index-C84bLgeO.d.cts} +134 -9
- package/dist/{index-By8Dle5U.d.cts → index-MoQ31B6M.d.cts} +125 -21
- package/dist/{index-BZrJXnVh.d.ts → index-dJNhDZ7j.d.ts} +134 -9
- package/dist/index.cjs +166 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -12
- package/dist/index.d.ts +54 -12
- package/dist/index.js +28 -11
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.cjs +16 -1
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.d.cts +6 -4
- package/dist/openapi/index.d.ts +6 -4
- package/dist/openapi/index.js +6 -7
- package/dist/openapi/index.js.map +1 -1
- package/dist/{registry-CqPK2Qby.d.cts → registry-1X-skBNu.d.cts} +1 -1
- package/dist/{registry-B3niOVYp.d.ts → registry-CEod_5sz.d.ts} +1 -1
- package/dist/test/index.cjs +27 -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 +27 -0
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +4 -4
- package/docs/database.md +115 -11
- package/docs/getting-started.md +5 -4
- package/docs/llms-full.txt +385 -89
- package/docs/migrations.md +81 -59
- package/docs/schema.md +82 -2
- package/docs/services.md +98 -9
- package/package.json +2 -2
- package/template/AGENTS.md +121 -41
- package/template/controllers/notes.controller.ts +64 -0
- package/template/package.json +1 -1
- package/template/services/note.service.ts +74 -0
- package/template/tsconfig.json +11 -1
- package/dist/chunk-HQRJDARQ.js +0 -90
- package/dist/chunk-HQRJDARQ.js.map +0 -1
- package/dist/chunk-M5MCBWJI.js.map +0 -1
- package/dist/chunk-NS5V43YQ.js.map +0 -1
- package/dist/chunk-OHALWEOG.js.map +0 -1
- package/dist/chunk-PY7YJDCT.js.map +0 -1
- package/dist/chunk-R3KN6RHD.js.map +0 -1
- package/dist/chunk-RCLNBJCM.js.map +0 -1
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__requestALS,
|
|
3
|
+
__runStartHooks,
|
|
3
4
|
__runWithRuntime
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-7Z6MGMXQ.js";
|
|
5
6
|
import {
|
|
7
|
+
UniqueViolation,
|
|
8
|
+
assertZeroArgConstructor,
|
|
6
9
|
getRoutes,
|
|
7
|
-
isHttpError
|
|
8
|
-
|
|
10
|
+
isHttpError,
|
|
11
|
+
resolveEffectiveAuth
|
|
12
|
+
} from "./chunk-H3JAISUY.js";
|
|
9
13
|
|
|
10
14
|
// src/engine/config.ts
|
|
11
15
|
var BootRefused = class extends Error {
|
|
@@ -413,18 +417,48 @@ function reviveVectors(row, vectorCols) {
|
|
|
413
417
|
}
|
|
414
418
|
return row;
|
|
415
419
|
}
|
|
420
|
+
function transformsOf(schema, table) {
|
|
421
|
+
const out = /* @__PURE__ */ new Map();
|
|
422
|
+
for (const [key, def] of Object.entries(schema.tables ?? {})) {
|
|
423
|
+
if ((def.name ?? key) !== table) continue;
|
|
424
|
+
for (const [col, c] of Object.entries(def.columns ?? {})) {
|
|
425
|
+
const d = c !== null && typeof c === "object" && "_def" in c ? c._def : c;
|
|
426
|
+
const t = d?.transform;
|
|
427
|
+
if (t !== null && typeof t === "object") {
|
|
428
|
+
out.set(col, t);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return out;
|
|
433
|
+
}
|
|
434
|
+
function applyFromDb(row, transforms) {
|
|
435
|
+
if (transforms.size === 0) return row;
|
|
436
|
+
for (const [col, t] of transforms) {
|
|
437
|
+
if (t.fromDb === void 0) continue;
|
|
438
|
+
const v = row[col];
|
|
439
|
+
if (v === null || v === void 0) continue;
|
|
440
|
+
row[col] = t.fromDb(v);
|
|
441
|
+
}
|
|
442
|
+
return row;
|
|
443
|
+
}
|
|
416
444
|
function asTableRow(table, row) {
|
|
417
|
-
|
|
445
|
+
const revived = reviveVectors(asWireRow(row), vectorColumnsOf(currentSchema, table));
|
|
446
|
+
return applyFromDb(revived, transformsOf(currentSchema, table));
|
|
418
447
|
}
|
|
419
448
|
function asTableRows(table, rows) {
|
|
420
449
|
const vectorCols = vectorColumnsOf(currentSchema, table);
|
|
421
|
-
|
|
450
|
+
const transforms = transformsOf(currentSchema, table);
|
|
451
|
+
return rows.map((row) => applyFromDb(reviveVectors(asWireRow(row), vectorCols), transforms));
|
|
422
452
|
}
|
|
423
453
|
function asBindParams(table, cols, data) {
|
|
424
454
|
const vectorCols = vectorColumnsOf(currentSchema, table);
|
|
455
|
+
const transforms = transformsOf(currentSchema, table);
|
|
425
456
|
return cols.map((c) => {
|
|
426
457
|
const v = data[c];
|
|
427
|
-
|
|
458
|
+
if (Array.isArray(v) && vectorCols.has(c)) return toVectorLiteral(v);
|
|
459
|
+
const t = transforms.get(c);
|
|
460
|
+
if (t?.toDb !== void 0 && v !== null && v !== void 0) return t.toDb(v);
|
|
461
|
+
return v;
|
|
428
462
|
});
|
|
429
463
|
}
|
|
430
464
|
var SELECTIVITY_EXACT_THRESHOLD = 1e4;
|
|
@@ -618,13 +652,33 @@ function limitClause(limit) {
|
|
|
618
652
|
}
|
|
619
653
|
return ` LIMIT ${limit}`;
|
|
620
654
|
}
|
|
655
|
+
function offsetClause(offset, limit) {
|
|
656
|
+
if (offset === void 0) return "";
|
|
657
|
+
if (!Number.isInteger(offset) || offset < 0) {
|
|
658
|
+
throw new Error(
|
|
659
|
+
`findMany: offset bir negatif olmayan tam say\u0131 olmal\u0131 (geldi: ${String(offset)})`
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
if (limit === void 0) {
|
|
663
|
+
throw new Error(
|
|
664
|
+
"findMany: offset yaln\u0131z limit ile birlikte verilir \u2014 limitsiz offset bir sayfa de\u011Fil, s\u0131n\u0131rs\u0131z bir kuyru\u011Fun kayd\u0131r\u0131lm\u0131\u015F\u0131d\u0131r"
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
return ` OFFSET ${offset}`;
|
|
668
|
+
}
|
|
621
669
|
function compileWhere(table, colSet, where, add, caller = "search") {
|
|
622
670
|
const parts = [];
|
|
671
|
+
const transforms = transformsOf(currentSchema, table);
|
|
672
|
+
const bindFor = (col) => {
|
|
673
|
+
const t = transforms.get(col);
|
|
674
|
+
return t?.toDb === void 0 ? add : (v) => add(v === null || v === void 0 ? v : t.toDb(v));
|
|
675
|
+
};
|
|
623
676
|
for (const [col, cond] of Object.entries(where)) {
|
|
624
677
|
if (colSet !== null && !colSet.has(col)) {
|
|
625
678
|
throw new Error(`${caller}(${table}): where kolonu "${col}" tabloda yok (FR-016)`);
|
|
626
679
|
}
|
|
627
680
|
const q = `t.${quoteIdent(col)}`;
|
|
681
|
+
const bind = bindFor(col);
|
|
628
682
|
if (cond !== null && typeof cond === "object" && !Array.isArray(cond)) {
|
|
629
683
|
for (const [op, v] of Object.entries(cond)) {
|
|
630
684
|
if (op === "in") {
|
|
@@ -633,19 +687,26 @@ function compileWhere(table, colSet, where, add, caller = "search") {
|
|
|
633
687
|
parts.push("false");
|
|
634
688
|
continue;
|
|
635
689
|
}
|
|
636
|
-
parts.push(`${q} IN (${v.map((x) =>
|
|
690
|
+
parts.push(`${q} IN (${v.map((x) => bind(x)).join(", ")})`);
|
|
637
691
|
} else if (op in WHERE_OPS) {
|
|
638
|
-
parts.push(`${q} ${WHERE_OPS[op]} ${
|
|
692
|
+
parts.push(`${q} ${WHERE_OPS[op]} ${bind(v)}`);
|
|
639
693
|
} else {
|
|
640
694
|
throw new Error(`${caller}(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in)`);
|
|
641
695
|
}
|
|
642
696
|
}
|
|
643
697
|
} else {
|
|
644
|
-
parts.push(`${q} = ${
|
|
698
|
+
parts.push(`${q} = ${bind(cond)}`);
|
|
645
699
|
}
|
|
646
700
|
}
|
|
647
701
|
return parts.length === 0 ? "" : ` AND ${parts.join(" AND ")}`;
|
|
648
702
|
}
|
|
703
|
+
function onConflictTail(cols, conflict, action) {
|
|
704
|
+
const target = `ON CONFLICT (${conflict.map(quoteIdent).join(", ")})`;
|
|
705
|
+
if (action === "ignore") return `${target} DO NOTHING`;
|
|
706
|
+
const conflictSet = new Set(conflict);
|
|
707
|
+
const sets = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
|
|
708
|
+
return sets.length ? `${target} DO UPDATE SET ${sets.join(", ")}` : `${target} DO UPDATE SET ${quoteIdent(conflict[0])} = EXCLUDED.${quoteIdent(conflict[0])}`;
|
|
709
|
+
}
|
|
649
710
|
var cachedVectorSchema = null;
|
|
650
711
|
var cachedTrgmSchema = null;
|
|
651
712
|
async function trgmSchemaOf(live) {
|
|
@@ -755,6 +816,39 @@ function createOps(tx) {
|
|
|
755
816
|
}
|
|
756
817
|
const cols = Object.keys(data);
|
|
757
818
|
if (cols.length === 0) throw new Error(`upsert into ${table}: no columns given`);
|
|
819
|
+
if (searchConfigFor(table)?.validity) {
|
|
820
|
+
const ph = cols.map((_, i) => `$${i + 1}`).join(", ");
|
|
821
|
+
const insertSql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${ph}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) WHERE "valid_to" IS NULL DO NOTHING RETURNING *`;
|
|
822
|
+
const live = await at();
|
|
823
|
+
const bindData = asBindParams(table, cols, data);
|
|
824
|
+
const first = await live.unsafe(insertSql, bindData);
|
|
825
|
+
if (first[0]) return asTableRow(table, first[0]);
|
|
826
|
+
const condSql = conflict.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ");
|
|
827
|
+
const condBind = conflict.map((c) => data[c]);
|
|
828
|
+
const cur = await live.unsafe(
|
|
829
|
+
`SELECT * FROM ${quoteIdent(table)} WHERE ${condSql} AND "valid_to" IS NULL`,
|
|
830
|
+
condBind
|
|
831
|
+
);
|
|
832
|
+
const old = cur[0];
|
|
833
|
+
if (old === void 0) {
|
|
834
|
+
throw new Error(`upsert into ${table}: e\u015Fzamanl\u0131 supersede yar\u0131\u015F\u0131 \u2014 ge\xE7erli sat\u0131r bulunamad\u0131, iste\u011Fi yineleyin (D-020)`);
|
|
835
|
+
}
|
|
836
|
+
const pk = Object.keys(old).includes("id") ? "id" : Object.keys(old)[0];
|
|
837
|
+
await live.unsafe(
|
|
838
|
+
`UPDATE ${quoteIdent(table)} SET "valid_to" = now() WHERE ${quoteIdent(pk)} = $1 AND "valid_to" IS NULL`,
|
|
839
|
+
[old[pk]]
|
|
840
|
+
);
|
|
841
|
+
const second = await live.unsafe(insertSql, bindData);
|
|
842
|
+
if (!second[0]) {
|
|
843
|
+
throw new Error(`upsert into ${table}: e\u015Fzamanl\u0131 supersede yar\u0131\u015F\u0131 \u2014 yeni sat\u0131r a\xE7\u0131lamad\u0131, iste\u011Fi yineleyin (D-020)`);
|
|
844
|
+
}
|
|
845
|
+
const fresh = second[0];
|
|
846
|
+
await live.unsafe(
|
|
847
|
+
`UPDATE ${quoteIdent(table)} SET "superseded_by" = $2 WHERE ${quoteIdent(pk)} = $1`,
|
|
848
|
+
[old[pk], fresh[pk]]
|
|
849
|
+
);
|
|
850
|
+
return asTableRow(table, second[0]);
|
|
851
|
+
}
|
|
758
852
|
const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
|
|
759
853
|
const conflictSet = new Set(conflict);
|
|
760
854
|
const assignments = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
|
|
@@ -780,6 +874,82 @@ function createOps(tx) {
|
|
|
780
874
|
async delete(table, id) {
|
|
781
875
|
await (await at()).unsafe(`DELETE FROM ${quoteIdent(table)} WHERE id = $1`, [id]);
|
|
782
876
|
},
|
|
877
|
+
/**
|
|
878
|
+
* Update every row the filter matches, in ONE statement.
|
|
879
|
+
*
|
|
880
|
+
* The capability was already here and only reachable from inside a
|
|
881
|
+
* transaction plan (`tx.tables.x.updateWhere`, since 11.0.0). Outside it the
|
|
882
|
+
* only door was `update(id, …)`, so "mark every unapproved row in this
|
|
883
|
+
* household" was an N+1 loop or hand-written SQL — and hand-written SQL is
|
|
884
|
+
* where the typed surface and RLS both stop helping.
|
|
885
|
+
*
|
|
886
|
+
* The filter language is `findMany`'s, compiled by the same `compileWhere`:
|
|
887
|
+
* one filter language, or the two spellings drift.
|
|
888
|
+
*
|
|
889
|
+
* AN EMPTY FILTER IS REFUSED. `UPDATE … WHERE true` is a whole-table write,
|
|
890
|
+
* and the shape that produces it by accident — a filter object built from
|
|
891
|
+
* request input that happened to come back empty — is exactly the shape that
|
|
892
|
+
* should not silently succeed. Callers who mean every row say so with a
|
|
893
|
+
* predicate that is true for every row.
|
|
894
|
+
*/
|
|
895
|
+
async updateMany(table, where, set) {
|
|
896
|
+
const cols = Object.keys(set);
|
|
897
|
+
if (cols.length === 0) {
|
|
898
|
+
throw new Error(
|
|
899
|
+
`updateMany(${table}): nothing to set. An update with no columns is not a no-op worth pretending happened \u2014 pass the columns to write.`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
const params = [];
|
|
903
|
+
const add = (v) => {
|
|
904
|
+
params.push(v);
|
|
905
|
+
return `$${params.length}`;
|
|
906
|
+
};
|
|
907
|
+
const assignments = cols.map((c) => `${quoteIdent(c)} = ${add(asBindParams(table, [c], set)[0])}`).join(", ");
|
|
908
|
+
const known = schemaColumns(table);
|
|
909
|
+
const whereSql = compileWhere(table, known, where, add, "updateMany");
|
|
910
|
+
assertHasPredicate(whereSql, "updateMany", table);
|
|
911
|
+
const sql = `UPDATE ${quoteIdent(table)} AS t SET ${assignments} WHERE true${whereSql} RETURNING *`;
|
|
912
|
+
return asTableRows(table, await (await at()).unsafe(sql, params));
|
|
913
|
+
},
|
|
914
|
+
/**
|
|
915
|
+
* Delete every row the filter matches, in ONE statement; resolves to how
|
|
916
|
+
* many went. Same filter language, same empty-filter refusal as
|
|
917
|
+
* {@link updateMany} — and here the accident is worse.
|
|
918
|
+
*/
|
|
919
|
+
async deleteMany(table, where) {
|
|
920
|
+
const params = [];
|
|
921
|
+
const add = (v) => {
|
|
922
|
+
params.push(v);
|
|
923
|
+
return `$${params.length}`;
|
|
924
|
+
};
|
|
925
|
+
const known = schemaColumns(table);
|
|
926
|
+
const whereSql = compileWhere(table, known, where, add, "deleteMany");
|
|
927
|
+
assertHasPredicate(whereSql, "deleteMany", table);
|
|
928
|
+
const sql = `DELETE FROM ${quoteIdent(table)} AS t WHERE true${whereSql} RETURNING 1`;
|
|
929
|
+
const rows = await (await at()).unsafe(sql, params);
|
|
930
|
+
return rows.length;
|
|
931
|
+
},
|
|
932
|
+
/**
|
|
933
|
+
* How many rows match — the half of pagination `limit`/`offset` cannot
|
|
934
|
+
* supply. Without it a page count is either a guess or "fetch everything and
|
|
935
|
+
* read .length", and the second one is the scale risk this surface exists to
|
|
936
|
+
* remove.
|
|
937
|
+
*
|
|
938
|
+
* An empty filter is legitimate HERE: counting a whole table is a read, and
|
|
939
|
+
* reads do not destroy anything.
|
|
940
|
+
*/
|
|
941
|
+
async count(table, where = {}) {
|
|
942
|
+
const params = [];
|
|
943
|
+
const add = (v) => {
|
|
944
|
+
params.push(v);
|
|
945
|
+
return `$${params.length}`;
|
|
946
|
+
};
|
|
947
|
+
const known = schemaColumns(table);
|
|
948
|
+
const whereSql = compileWhere(table, known, where, add, "count");
|
|
949
|
+
const sql = `SELECT count(*) AS n FROM ${quoteIdent(table)} t WHERE true${whereSql}`;
|
|
950
|
+
const rows = await (await at()).unsafe(sql, params);
|
|
951
|
+
return Number(rows[0]?.n ?? 0);
|
|
952
|
+
},
|
|
783
953
|
async findById(table, id) {
|
|
784
954
|
const rows = await (await at()).unsafe(
|
|
785
955
|
`SELECT * FROM ${quoteIdent(table)} WHERE id = $1`,
|
|
@@ -797,7 +967,8 @@ function createOps(tx) {
|
|
|
797
967
|
const whereSql = compileWhere(table, known, query, add, "findMany");
|
|
798
968
|
const order = orderClause(table, known, opts.orderBy);
|
|
799
969
|
const limit = limitClause(opts.limit);
|
|
800
|
-
const
|
|
970
|
+
const offset = offsetClause(opts.offset, opts.limit);
|
|
971
|
+
const sql = `SELECT * FROM ${quoteIdent(table)} t WHERE true${whereSql}${order}${limit}${offset}`;
|
|
801
972
|
return asTableRows(table, await (await at()).unsafe(sql, params));
|
|
802
973
|
},
|
|
803
974
|
/**
|
|
@@ -880,7 +1051,9 @@ function createOps(tx) {
|
|
|
880
1051
|
bindC.push(v);
|
|
881
1052
|
return `$${bindC.length}`;
|
|
882
1053
|
};
|
|
883
|
-
const
|
|
1054
|
+
const userWhereC = compileWhere(table, cfg.colSet, params.where ?? {}, addC);
|
|
1055
|
+
const whereC = userWhereC + excludeSql(cfg.pk, excl, addC) + validityFor(addC);
|
|
1056
|
+
const parentJoinC = ` JOIN ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = c.${pidQ} WHERE true${whereC} AND `;
|
|
884
1057
|
const liveC = await at();
|
|
885
1058
|
const K2 = 50;
|
|
886
1059
|
const cpool = Math.max(limit * 9, 90);
|
|
@@ -889,8 +1062,19 @@ function createOps(tx) {
|
|
|
889
1062
|
if (qvC !== null) {
|
|
890
1063
|
const sch = await vectorSchemaWithGuc(liveC);
|
|
891
1064
|
const op = METRIC_OPERATOR[ck.metric] ?? METRIC_OPERATOR.cosine;
|
|
1065
|
+
let exactOrderC = "";
|
|
1066
|
+
if (userWhereC !== "") {
|
|
1067
|
+
const probeRows = await liveC.unsafe(
|
|
1068
|
+
`SELECT count(*)::int AS n FROM (SELECT 1 FROM ${quoteIdent(ck.table)} c${parentJoinC}c.embedding IS NOT NULL LIMIT ${SELECTIVITY_EXACT_THRESHOLD + 1}) s`,
|
|
1069
|
+
bindC.slice()
|
|
1070
|
+
);
|
|
1071
|
+
const n = probeRows?.[0]?.n;
|
|
1072
|
+
if (typeof n === "number" && n <= SELECTIVITY_EXACT_THRESHOLD) {
|
|
1073
|
+
exactOrderC = " + 0.0";
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
892
1076
|
const vp = addC(toVectorLiteral(qvC));
|
|
893
|
-
semC = `SELECT c.${pidQ} AS pid, c.chunk_seq, ROW_NUMBER() OVER (ORDER BY (c.embedding OPERATOR(${quoteIdent(sch)}.${op}) ${vp}::${quoteIdent(sch)}.vector)) AS r FROM ${quoteIdent(ck.table)} c
|
|
1077
|
+
semC = `SELECT c.${pidQ} AS pid, c.chunk_seq, ROW_NUMBER() OVER (ORDER BY (c.embedding OPERATOR(${quoteIdent(sch)}.${op}) ${vp}::${quoteIdent(sch)}.vector)${exactOrderC}) AS r FROM ${quoteIdent(ck.table)} c${parentJoinC}c.embedding IS NOT NULL ORDER BY r LIMIT ${cpool}`;
|
|
894
1078
|
}
|
|
895
1079
|
let qpC = "";
|
|
896
1080
|
let qpCIdx = -1;
|
|
@@ -898,7 +1082,7 @@ function createOps(tx) {
|
|
|
898
1082
|
const qTextC = cfg.synonyms !== void 0 ? expandSynonyms(params.query, cfg.synonyms) : params.query;
|
|
899
1083
|
qpC = addC(qTextC);
|
|
900
1084
|
qpCIdx = bindC.length - 1;
|
|
901
|
-
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 ${quoteIdent(ck.table)} c
|
|
1085
|
+
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 ${quoteIdent(ck.table)} c${parentJoinC}c.palbase_fts @@ websearch_to_tsquery('simple', ${qpC}) ORDER BY r LIMIT ${cpool}`;
|
|
902
1086
|
}
|
|
903
1087
|
const msC = params.minScore === void 0 ? "" : addC(params.minScore);
|
|
904
1088
|
const colListC = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
|
|
@@ -913,13 +1097,13 @@ function createOps(tx) {
|
|
|
913
1097
|
} else {
|
|
914
1098
|
fusedSrc = `WITH kw AS (${kwIn}), fused AS (SELECT kw.pid, kw.chunk_seq, (1.0/(${K2} + kw.r))::float8 AS cscore FROM kw)`;
|
|
915
1099
|
}
|
|
916
|
-
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 ${quoteIdent(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 ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = g.pid
|
|
1100
|
+
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 ${quoteIdent(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 ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = g.pid`;
|
|
917
1101
|
return msC === "" ? `SELECT * FROM (${inner}) q ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}` : `SELECT * FROM (${inner}) q WHERE q._score >= ${msC} ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
|
|
918
1102
|
};
|
|
919
1103
|
let rowsC = await liveC.unsafe(assembleC(kwC, kwC !== ""), bindC);
|
|
920
1104
|
if (wantTextC && kwC !== "" && (rowsC.length === 0 || rowsC[0]._kwn === 0)) {
|
|
921
1105
|
const tsch = await trgmSchemaOf(liveC);
|
|
922
|
-
const trgmKwC = `SELECT c.${pidQ} AS pid, c.chunk_seq, ROW_NUMBER() OVER (ORDER BY ${quoteIdent(tsch)}.word_similarity(${qpC}, coalesce(c.content,'')) DESC) AS r FROM ${quoteIdent(ck.table)} c
|
|
1106
|
+
const trgmKwC = `SELECT c.${pidQ} AS pid, c.chunk_seq, ROW_NUMBER() OVER (ORDER BY ${quoteIdent(tsch)}.word_similarity(${qpC}, coalesce(c.content,'')) DESC) AS r FROM ${quoteIdent(ck.table)} c${parentJoinC}${quoteIdent(tsch)}.word_similarity(${qpC}, coalesce(c.content,'')) > 0.3 ORDER BY r LIMIT ${cpool}`;
|
|
923
1107
|
const retryBindC = bindC.slice();
|
|
924
1108
|
if (qpCIdx >= 0) retryBindC[qpCIdx] = params.query;
|
|
925
1109
|
rowsC = await liveC.unsafe(assembleC(trgmKwC, false), retryBindC);
|
|
@@ -1015,7 +1199,7 @@ function createOps(tx) {
|
|
|
1015
1199
|
if (wantText && kwSql !== "" && (rows.length === 0 || rows[0]._kwn === 0)) {
|
|
1016
1200
|
const tsch = await trgmSchemaOf(live);
|
|
1017
1201
|
const expr = ftsExprOf(cfg.ftsCols);
|
|
1018
|
-
const trgmKw = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY ${quoteIdent(tsch)}.word_similarity(${qpRef}, ${expr}) DESC) AS r FROM ${quoteIdent(table)} t WHERE ${
|
|
1202
|
+
const trgmKw = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY ${quoteIdent(tsch)}.word_similarity(${qpRef}, ${expr}) DESC) AS r FROM ${quoteIdent(table)} t WHERE ${quoteIdent(tsch)}.word_similarity(${qpRef}, ${expr}) > 0.3${whereSql} ORDER BY r LIMIT ${pool}`;
|
|
1019
1203
|
const retryBind = bind.slice();
|
|
1020
1204
|
if (qpIdx >= 0) retryBind[qpIdx] = params.query;
|
|
1021
1205
|
rows = await live.unsafe(assemble(trgmKw, false), retryBind);
|
|
@@ -1038,6 +1222,25 @@ function createOps(tx) {
|
|
|
1038
1222
|
* tek SQL'de "id yok" ile "0 komşu" ayrılamazdı; +1 küçük turla id-yokluğu
|
|
1039
1223
|
* adlandırılmış hataya çevrilir. Sonuç şekli search ile aynı (FR-015).
|
|
1040
1224
|
*/
|
|
1225
|
+
/** D-021 (FR-027 DX): sayaçlar BAĞIMSIZ op'la — search'ün dizi-üstü
|
|
1226
|
+
* `_facets` özelliği JSON.stringify'da kaybolur (dizi özelliği), tenant
|
|
1227
|
+
* yanıtına koyunca sessizce yok olurdu. Ayrı dönüş ciddi bir sözleşmedir;
|
|
1228
|
+
* search'teki alan geriye-uyum için DURUR. where + validity default'u
|
|
1229
|
+
* sayaçlara da uygulanır (sayaç, kullanıcının gördüğü kümeyi anlatır). */
|
|
1230
|
+
async facets(table, params) {
|
|
1231
|
+
const cfg = searchConfigFor(table);
|
|
1232
|
+
if (!cfg) {
|
|
1233
|
+
throw new Error(`facets(${table}): tablo aranabilir de\u011Fil \u2014 search beyan\u0131 yok (FR-027)`);
|
|
1234
|
+
}
|
|
1235
|
+
for (const col of params.facets) {
|
|
1236
|
+
if (!cfg.colSet.has(col)) {
|
|
1237
|
+
throw new Error(`facets(${table}): facets kolonu "${col}" tabloda yok (FR-027)`);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
const live = await at();
|
|
1241
|
+
const extra = cfg.validity ? (add) => validitySql(params.validity, add) : () => "";
|
|
1242
|
+
return fetchFacets(live, table, cfg.colSet, params.facets, params.where ?? {}, extra);
|
|
1243
|
+
},
|
|
1041
1244
|
async similar(table, id, opts = {}) {
|
|
1042
1245
|
const cfg = searchConfigFor(table);
|
|
1043
1246
|
if (!cfg) {
|
|
@@ -1269,17 +1472,41 @@ function withTables(ops, schema = currentSchema) {
|
|
|
1269
1472
|
}
|
|
1270
1473
|
var SERVICE_LOCK_TIMEOUT = "5s";
|
|
1271
1474
|
function isAbortedTransaction(e) {
|
|
1272
|
-
const code = e?.code;
|
|
1273
1475
|
const message = String(e?.message ?? "");
|
|
1274
|
-
return
|
|
1476
|
+
return sqlstateOf(e) === "25P02" || /current transaction is aborted/i.test(message);
|
|
1477
|
+
}
|
|
1478
|
+
function assertHasPredicate(whereSql, op, table) {
|
|
1479
|
+
if (whereSql.trim().length > 0) return;
|
|
1480
|
+
throw new Error(
|
|
1481
|
+
`${op}(${table}): the filter compiled to no condition, so this would have written EVERY row. An empty filter \u2014 or an operator object with no operators in it, like { col: {} } \u2014 is refused. Name a condition.`
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
function sqlstateOf(e) {
|
|
1485
|
+
const err = e;
|
|
1486
|
+
const isState = (v) => typeof v === "string" && /^[0-9A-Z]{5}$/.test(v);
|
|
1487
|
+
if (isState(err?.code)) return err.code;
|
|
1488
|
+
if (isState(err?.errno)) return err.errno;
|
|
1489
|
+
if (typeof err?.errno === "number") {
|
|
1490
|
+
const asText = String(err.errno);
|
|
1491
|
+
if (isState(asText)) return asText;
|
|
1492
|
+
}
|
|
1493
|
+
return void 0;
|
|
1275
1494
|
}
|
|
1276
1495
|
function isForeignKeyViolation(e) {
|
|
1277
|
-
return e
|
|
1496
|
+
return sqlstateOf(e) === "23503";
|
|
1497
|
+
}
|
|
1498
|
+
function isUniqueViolation(e) {
|
|
1499
|
+
return sqlstateOf(e) === "23505";
|
|
1500
|
+
}
|
|
1501
|
+
function constraintOf(e) {
|
|
1502
|
+
const named = e?.constraint;
|
|
1503
|
+
if (typeof named === "string" && named.length > 0) return named;
|
|
1504
|
+
const message = String(e?.message ?? "");
|
|
1505
|
+
return /violates unique constraint "([^"]+)"/.exec(message)?.[1] ?? "";
|
|
1278
1506
|
}
|
|
1279
1507
|
function isLockTimeout(e) {
|
|
1280
|
-
const code = e?.code;
|
|
1281
1508
|
const message = String(e?.message ?? "");
|
|
1282
|
-
return
|
|
1509
|
+
return sqlstateOf(e) === "55P03" || /lock timeout/i.test(message);
|
|
1283
1510
|
}
|
|
1284
1511
|
function diagnosingDriver(sql, surface = "service") {
|
|
1285
1512
|
const original = (e) => String(e?.message ?? e);
|
|
@@ -1289,6 +1516,9 @@ function diagnosingDriver(sql, surface = "service") {
|
|
|
1289
1516
|
`This request cannot write any more: an earlier write in it failed, and a request runs in ONE Postgres transaction, so every statement after the failure is refused. Nothing here is retryable in place. Wrap a write you expect to fail in \`Database.attempt(async (tx) => \u2026)\` \u2014 it takes a SAVEPOINT, so only that write rolls back \u2014 or use \`Database.tables.<t>.upsert(data, { onConflict: [...] })\` when the failure you were bracing for is a duplicate row. (${original(e)})`
|
|
1290
1517
|
);
|
|
1291
1518
|
}
|
|
1519
|
+
if (isUniqueViolation(e)) {
|
|
1520
|
+
return new UniqueViolation(constraintOf(e));
|
|
1521
|
+
}
|
|
1292
1522
|
if (surface === "service" && isForeignKeyViolation(e)) {
|
|
1293
1523
|
return new Error(
|
|
1294
1524
|
`Database.asService() hit a foreign key with nothing to point at. It runs in its OWN transaction on its OWN connection, so a row this request wrote through \`Database.*\` is not visible to it until the request commits \u2014 and the request cannot commit until the handler returns. If the row it needs is one this request just created, do both writes on ONE surface. (${original(e)})`
|
|
@@ -1417,10 +1647,7 @@ async function runPlanOp(sp, op, results) {
|
|
|
1417
1647
|
const rendered = cols.map(
|
|
1418
1648
|
(c) => renderValue(op.values[c], c, args, results, opVectorCols)
|
|
1419
1649
|
);
|
|
1420
|
-
|
|
1421
|
-
const sets = cols.filter((c) => !conflictSet.has(c)).map((c) => `${quoteIdent(c)} = EXCLUDED.${quoteIdent(c)}`);
|
|
1422
|
-
const action = sets.length ? `DO UPDATE SET ${sets.join(", ")}` : `DO UPDATE SET ${quoteIdent(conflict[0])} = EXCLUDED.${quoteIdent(conflict[0])}`;
|
|
1423
|
-
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ON CONFLICT (${conflict.map(quoteIdent).join(", ")}) ${action} RETURNING *`;
|
|
1650
|
+
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) ${onConflictTail(cols, conflict, "update")} RETURNING *`;
|
|
1424
1651
|
break;
|
|
1425
1652
|
}
|
|
1426
1653
|
case "insertMany": {
|
|
@@ -1430,7 +1657,9 @@ async function runPlanOp(sp, op, results) {
|
|
|
1430
1657
|
const tuples = rows.map(
|
|
1431
1658
|
(r) => `(${cols.map((c) => renderValue(r[c], c, args, results, opVectorCols)).join(", ")})`
|
|
1432
1659
|
);
|
|
1433
|
-
|
|
1660
|
+
const conflictCols = op.onConflict ?? [];
|
|
1661
|
+
const tail = op.action !== void 0 && conflictCols.length > 0 ? ` ${onConflictTail(cols, conflictCols, op.action)}` : "";
|
|
1662
|
+
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")}${tail} RETURNING *`;
|
|
1434
1663
|
break;
|
|
1435
1664
|
}
|
|
1436
1665
|
case "update": {
|
|
@@ -1487,6 +1716,7 @@ function buildRouteTable(controllers) {
|
|
|
1487
1716
|
`controller ${name} collected zero routes. Either it declares no @Get/@Post/\u2026 , or its decorator metadata was erased at build time \u2014 check that the bundle was compiled with experimentalDecorators enabled.`
|
|
1488
1717
|
);
|
|
1489
1718
|
}
|
|
1719
|
+
assertZeroArgConstructor(Ctrl, "controller");
|
|
1490
1720
|
const instance = new ctor();
|
|
1491
1721
|
for (const r of routes) {
|
|
1492
1722
|
const full = `${basePath}${r.subpath ?? ""}` || "/";
|
|
@@ -1496,7 +1726,10 @@ function buildRouteTable(controllers) {
|
|
|
1496
1726
|
meta: r,
|
|
1497
1727
|
instance,
|
|
1498
1728
|
id: `${r.method} ${full}`,
|
|
1499
|
-
|
|
1729
|
+
// The route's OWN spec is deliberately not folded in here: this field
|
|
1730
|
+
// is what applies when the route is silent, and `effectiveAuth` puts
|
|
1731
|
+
// the route level back on top per request.
|
|
1732
|
+
controllerAuth: resolveEffectiveAuth(void 0, meta?.defaultAuth)
|
|
1500
1733
|
});
|
|
1501
1734
|
}
|
|
1502
1735
|
}
|
|
@@ -2125,18 +2358,29 @@ async function createApp(opts) {
|
|
|
2125
2358
|
return envelope("internal_error", "The request could not be completed", 500, requestId);
|
|
2126
2359
|
}
|
|
2127
2360
|
}
|
|
2361
|
+
let runShutdownHooks;
|
|
2362
|
+
try {
|
|
2363
|
+
runShutdownHooks = await __runStartHooks();
|
|
2364
|
+
} catch (err) {
|
|
2365
|
+
await closeDriver(sql);
|
|
2366
|
+
throw err;
|
|
2367
|
+
}
|
|
2128
2368
|
return {
|
|
2129
2369
|
handle,
|
|
2130
2370
|
routes,
|
|
2131
2371
|
config,
|
|
2132
2372
|
runInServiceScope,
|
|
2133
2373
|
async shutdown() {
|
|
2134
|
-
|
|
2135
|
-
await
|
|
2136
|
-
await closable.end?.();
|
|
2374
|
+
await runShutdownHooks();
|
|
2375
|
+
await closeDriver(sql);
|
|
2137
2376
|
}
|
|
2138
2377
|
};
|
|
2139
2378
|
}
|
|
2379
|
+
async function closeDriver(sql) {
|
|
2380
|
+
const closable = sql;
|
|
2381
|
+
await closable.close?.();
|
|
2382
|
+
await closable.end?.();
|
|
2383
|
+
}
|
|
2140
2384
|
|
|
2141
2385
|
export {
|
|
2142
2386
|
BootRefused,
|
|
@@ -2157,4 +2401,4 @@ export {
|
|
|
2157
2401
|
installEgressFence,
|
|
2158
2402
|
createApp
|
|
2159
2403
|
};
|
|
2160
|
-
//# sourceMappingURL=chunk-
|
|
2404
|
+
//# sourceMappingURL=chunk-YSQBC2VL.js.map
|