@palbase/backend 22.0.1 → 22.1.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 +340 -22
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +2 -2
- package/dist/{chunk-QYOHMVUW.js → chunk-74XDEF5J.js} +338 -22
- package/dist/chunk-74XDEF5J.js.map +1 -0
- package/dist/{chunk-SSGAMC26.js → chunk-I3ON7MYF.js} +57 -5
- package/dist/chunk-I3ON7MYF.js.map +1 -0
- package/dist/{chunk-POYAFBLF.js → chunk-SQC5EIWY.js} +5 -3
- package/dist/chunk-SQC5EIWY.js.map +1 -0
- package/dist/db/index.cjs +60 -6
- 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 +7 -3
- package/dist/{endpoint-B0LpZixz.d.cts → endpoint-BVT6jcVW.d.cts} +39 -7
- package/dist/{endpoint-B0LpZixz.d.ts → endpoint-BVT6jcVW.d.ts} +39 -7
- package/dist/engine/index.cjs +340 -22
- 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 +2 -2
- package/dist/{index-B4W6d2VJ.d.cts → index-BS1gW4nV.d.cts} +24 -25
- package/dist/{index-BGSCWlUa.d.cts → index-BqCiHao8.d.cts} +97 -7
- package/dist/{index-BCNtlG1w.d.ts → index-CCZqzych.d.ts} +24 -25
- package/dist/{index-g-EzitI-.d.ts → index-vwHoS0l2.d.ts} +97 -7
- package/dist/index.cjs +244 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -9
- package/dist/index.d.ts +124 -9
- package/dist/index.js +186 -3
- 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-Cw0YEYCg.d.cts → registry-BWttGlaT.d.cts} +1 -1
- package/dist/{registry-3BLYv4si.d.ts → registry-Bsuf-orT.d.ts} +1 -1
- package/docs/endpoints.md +1 -1
- package/docs/errors.md +9 -0
- package/docs/llms-full.txt +10 -1
- package/package.json +2 -2
- package/dist/chunk-POYAFBLF.js.map +0 -1
- package/dist/chunk-QYOHMVUW.js.map +0 -1
- package/dist/chunk-SSGAMC26.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__requestALS,
|
|
3
3
|
__runWithRuntime
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SQC5EIWY.js";
|
|
5
5
|
import {
|
|
6
6
|
getRoutes,
|
|
7
7
|
isHttpError
|
|
@@ -382,6 +382,194 @@ function asWireRow(row) {
|
|
|
382
382
|
function asWireRows(rows) {
|
|
383
383
|
return rows.map((row) => asWireRow(row));
|
|
384
384
|
}
|
|
385
|
+
function toVectorLiteral(v) {
|
|
386
|
+
return `[${v.join(",")}]`;
|
|
387
|
+
}
|
|
388
|
+
function vectorColumnsOf(schema, table) {
|
|
389
|
+
const out = /* @__PURE__ */ new Set();
|
|
390
|
+
for (const [key, def] of Object.entries(schema.tables ?? {})) {
|
|
391
|
+
if ((def.name ?? key) !== table) continue;
|
|
392
|
+
for (const [col, c] of Object.entries(def.columns ?? {})) {
|
|
393
|
+
const d = c !== null && typeof c === "object" && "_def" in c ? c._def : c;
|
|
394
|
+
if (d !== null && typeof d === "object" && d.type === "vector") out.add(col);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return out;
|
|
398
|
+
}
|
|
399
|
+
function reviveVectors(row, vectorCols) {
|
|
400
|
+
if (row === null || typeof row !== "object" || vectorCols.size === 0) return row;
|
|
401
|
+
const out = row;
|
|
402
|
+
for (const col of vectorCols) {
|
|
403
|
+
const v = out[col];
|
|
404
|
+
if (typeof v === "string") out[col] = JSON.parse(v);
|
|
405
|
+
}
|
|
406
|
+
return row;
|
|
407
|
+
}
|
|
408
|
+
function asTableRow(table, row) {
|
|
409
|
+
return reviveVectors(asWireRow(row), vectorColumnsOf(currentSchema, table));
|
|
410
|
+
}
|
|
411
|
+
function asTableRows(table, rows) {
|
|
412
|
+
const vectorCols = vectorColumnsOf(currentSchema, table);
|
|
413
|
+
return rows.map((row) => reviveVectors(asWireRow(row), vectorCols));
|
|
414
|
+
}
|
|
415
|
+
function asBindParams(table, cols, data) {
|
|
416
|
+
const vectorCols = vectorColumnsOf(currentSchema, table);
|
|
417
|
+
return cols.map((c) => {
|
|
418
|
+
const v = data[c];
|
|
419
|
+
return Array.isArray(v) && vectorCols.has(c) ? toVectorLiteral(v) : v;
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
var SELECTIVITY_EXACT_THRESHOLD = 1e4;
|
|
423
|
+
var METRIC_OPERATOR = {
|
|
424
|
+
cosine: "<=>",
|
|
425
|
+
euclidean: "<->",
|
|
426
|
+
inner_product: "<#>"
|
|
427
|
+
};
|
|
428
|
+
function searchConfigFor(table) {
|
|
429
|
+
const t = currentSchema.tables?.[table];
|
|
430
|
+
if (!t) return null;
|
|
431
|
+
const columns = t.columns ?? {};
|
|
432
|
+
const defOf = (c) => c !== null && typeof c === "object" && "_def" in c ? c._def : c ?? {};
|
|
433
|
+
const cols = Object.keys(columns);
|
|
434
|
+
const vectorCols = cols.filter((c) => defOf(columns[c]).type === "vector");
|
|
435
|
+
const defOfFull = (c) => c !== null && typeof c === "object" && "_def" in c ? c._def : c ?? {};
|
|
436
|
+
const pkCols = cols.filter((c) => defOfFull(columns[c]).primaryKey === true);
|
|
437
|
+
const pk = pkCols.length === 1 ? pkCols[0] : cols.includes("id") ? "id" : null;
|
|
438
|
+
if (pk === null) {
|
|
439
|
+
throw new Error(
|
|
440
|
+
`search(${table}): tek-kolon primary key bulunamad\u0131 \u2014 arama s\u0131ralamas\u0131 ve sat\u0131r birle\u015Fimi PK ister (FR-020)`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
const search = t.search;
|
|
444
|
+
const ftsCols = search?.text ?? [];
|
|
445
|
+
const rawLegs = search?.vector === void 0 ? [] : Array.isArray(search.vector) ? search.vector : [search.vector];
|
|
446
|
+
let legs;
|
|
447
|
+
if (rawLegs.length > 0) {
|
|
448
|
+
legs = rawLegs.map((leg) => {
|
|
449
|
+
const l = leg;
|
|
450
|
+
const column = l.column ?? (vectorCols.length === 1 ? vectorCols[0] : void 0);
|
|
451
|
+
if (column === void 0) {
|
|
452
|
+
throw new Error(`search(${table}): birden \xE7ok vector kolonu var \u2014 beyanda 'column' zorunlu (FR-010)`);
|
|
453
|
+
}
|
|
454
|
+
const model = l.model;
|
|
455
|
+
return {
|
|
456
|
+
column,
|
|
457
|
+
metric: l.metric ?? "cosine",
|
|
458
|
+
...model !== void 0 ? { embed: {
|
|
459
|
+
model: model.model,
|
|
460
|
+
apiKeyName: model.apiKeyName ?? "OPENAI_API_KEY",
|
|
461
|
+
...model.baseURL !== void 0 ? { baseURL: model.baseURL } : {},
|
|
462
|
+
...model.dimensions !== void 0 ? { dimensions: model.dimensions } : {}
|
|
463
|
+
} } : {}
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
} else {
|
|
467
|
+
legs = vectorCols.map((column) => ({ column, metric: "cosine" }));
|
|
468
|
+
}
|
|
469
|
+
if (ftsCols.length === 0 && legs.length === 0) return null;
|
|
470
|
+
return { pk, cols, colSet: new Set(cols), ftsCols, legs };
|
|
471
|
+
}
|
|
472
|
+
function pickLeg(table, legs, using) {
|
|
473
|
+
if (legs.length === 0) return null;
|
|
474
|
+
if (using !== void 0) {
|
|
475
|
+
const hit = legs.find((l) => l.column === using);
|
|
476
|
+
if (!hit) {
|
|
477
|
+
throw new Error(
|
|
478
|
+
`search(${table}): using "${using}" bir vekt\xF6r kolunu adlam\u0131yor \u2014 mevcut: ${legs.map((l) => l.column).join(", ")}`
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
return hit;
|
|
482
|
+
}
|
|
483
|
+
if (legs.length === 1) return legs[0];
|
|
484
|
+
throw new Error(`search(${table}): birden \xE7ok vekt\xF6r kolu var \u2014 'using' ile se\xE7in (FR-013) \u2014 salt metin ar\u0131yorsan mode:"text" kullan`);
|
|
485
|
+
}
|
|
486
|
+
var WHERE_OPS = { gt: ">", gte: ">=", lt: "<", lte: "<=", neq: "<>" };
|
|
487
|
+
function compileWhere(table, colSet, where, add) {
|
|
488
|
+
const parts = [];
|
|
489
|
+
for (const [col, cond] of Object.entries(where)) {
|
|
490
|
+
if (!colSet.has(col)) {
|
|
491
|
+
throw new Error(`search(${table}): where kolonu "${col}" tabloda yok (FR-016)`);
|
|
492
|
+
}
|
|
493
|
+
const q = `t.${quoteIdent(col)}`;
|
|
494
|
+
if (cond !== null && typeof cond === "object" && !Array.isArray(cond)) {
|
|
495
|
+
for (const [op, v] of Object.entries(cond)) {
|
|
496
|
+
if (op === "in") {
|
|
497
|
+
if (!Array.isArray(v)) throw new Error(`search(${table}): where.${col}.in bir dizi olmal\u0131`);
|
|
498
|
+
if (v.length === 0) {
|
|
499
|
+
parts.push("false");
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
parts.push(`${q} IN (${v.map((x) => add(x)).join(", ")})`);
|
|
503
|
+
} else if (op in WHERE_OPS) {
|
|
504
|
+
parts.push(`${q} ${WHERE_OPS[op]} ${add(v)}`);
|
|
505
|
+
} else {
|
|
506
|
+
throw new Error(`search(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in)`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
parts.push(`${q} = ${add(cond)}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return parts.length === 0 ? "" : ` AND ${parts.join(" AND ")}`;
|
|
514
|
+
}
|
|
515
|
+
var cachedVectorSchema = null;
|
|
516
|
+
async function vectorSchemaWithGuc(runner) {
|
|
517
|
+
if (cachedVectorSchema !== null) {
|
|
518
|
+
await runner.unsafe(
|
|
519
|
+
"select set_config('hnsw.iterative_scan','relaxed_order',true), set_config('hnsw.max_scan_tuples','200000',true), set_config('hnsw.ef_search','200',true)"
|
|
520
|
+
);
|
|
521
|
+
return cachedVectorSchema;
|
|
522
|
+
}
|
|
523
|
+
const rows = await runner.unsafe(
|
|
524
|
+
"select set_config('hnsw.iterative_scan','relaxed_order',true), set_config('hnsw.max_scan_tuples','200000',true), set_config('hnsw.ef_search','200',true), (select n.nspname from pg_extension e join pg_namespace n on n.oid = e.extnamespace where e.extname = 'vector') as nspname"
|
|
525
|
+
);
|
|
526
|
+
const name = rows?.[0]?.nspname;
|
|
527
|
+
if (typeof name !== "string" || name === "") {
|
|
528
|
+
throw new Error("pgvector extension kurulu de\u011Fil \u2014 vector aramas\u0131 \xE7al\u0131\u015Famaz (extensions beyan\u0131 deploy'dan ge\xE7ti mi?)");
|
|
529
|
+
}
|
|
530
|
+
cachedVectorSchema = name;
|
|
531
|
+
return name;
|
|
532
|
+
}
|
|
533
|
+
var secretReader = null;
|
|
534
|
+
function setSecretReader(fn) {
|
|
535
|
+
secretReader = fn;
|
|
536
|
+
}
|
|
537
|
+
var embedFetch = (url, init) => fetch(url, init);
|
|
538
|
+
async function embedQuery(embed, text) {
|
|
539
|
+
if (secretReader === null) {
|
|
540
|
+
throw new Error(`query embed: secret reader ba\u011Flanmam\u0131\u015F \u2014 ${embed.apiKeyName} okunam\u0131yor`);
|
|
541
|
+
}
|
|
542
|
+
const key = await secretReader(embed.apiKeyName);
|
|
543
|
+
if (key === null || key === "") {
|
|
544
|
+
throw new Error(`query embed: vault'ta ${embed.apiKeyName} yok (FR-021/FR-025)`);
|
|
545
|
+
}
|
|
546
|
+
const url = (embed.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "") + "/embeddings";
|
|
547
|
+
const controller = new AbortController();
|
|
548
|
+
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
549
|
+
try {
|
|
550
|
+
const res = await embedFetch(url, {
|
|
551
|
+
method: "POST",
|
|
552
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
|
|
553
|
+
body: JSON.stringify({
|
|
554
|
+
model: embed.model,
|
|
555
|
+
input: [text],
|
|
556
|
+
...embed.dimensions !== void 0 ? { dimensions: embed.dimensions } : {}
|
|
557
|
+
}),
|
|
558
|
+
signal: controller.signal
|
|
559
|
+
});
|
|
560
|
+
if (!res.ok) {
|
|
561
|
+
throw new Error(`query embed: sa\u011Flay\u0131c\u0131 ${res.status} d\xF6nd\xFC (${embed.apiKeyName} ile) \u2014 anahtar/model do\u011Fru mu?`);
|
|
562
|
+
}
|
|
563
|
+
const data = await res.json();
|
|
564
|
+
const vec = data.data?.[0]?.embedding;
|
|
565
|
+
if (!Array.isArray(vec)) {
|
|
566
|
+
throw new Error("query embed: sa\u011Flay\u0131c\u0131 yan\u0131t\u0131nda data[0].embedding yok (CLAIM-N1 \u015Fekli)");
|
|
567
|
+
}
|
|
568
|
+
return vec;
|
|
569
|
+
} finally {
|
|
570
|
+
clearTimeout(timer);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
385
573
|
function createOps(tx) {
|
|
386
574
|
const at = () => resolveTx(tx);
|
|
387
575
|
const ops = {
|
|
@@ -393,22 +581,22 @@ function createOps(tx) {
|
|
|
393
581
|
if (cols.length === 0) throw new Error(`insert into ${table}: no columns given`);
|
|
394
582
|
const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
|
|
395
583
|
const sql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`;
|
|
396
|
-
const rows = await (await at()).unsafe(sql,
|
|
584
|
+
const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data));
|
|
397
585
|
const inserted = rows[0];
|
|
398
586
|
if (!inserted) {
|
|
399
587
|
throw new Error(
|
|
400
588
|
`insert into ${table} returned no row \u2014 the write was rejected (an RLS policy, most likely).`
|
|
401
589
|
);
|
|
402
590
|
}
|
|
403
|
-
return
|
|
591
|
+
return asTableRow(table, inserted);
|
|
404
592
|
},
|
|
405
593
|
async update(table, id, data) {
|
|
406
594
|
const cols = Object.keys(data);
|
|
407
595
|
if (cols.length === 0) return ops.findById(table, id);
|
|
408
596
|
const assignments = cols.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(", ");
|
|
409
597
|
const sql = `UPDATE ${quoteIdent(table)} SET ${assignments} WHERE id = $${cols.length + 1} RETURNING *`;
|
|
410
|
-
const rows = await (await at()).unsafe(sql, [...
|
|
411
|
-
return rows[0] ?
|
|
598
|
+
const rows = await (await at()).unsafe(sql, [...asBindParams(table, cols, data), id]);
|
|
599
|
+
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
412
600
|
},
|
|
413
601
|
async delete(table, id) {
|
|
414
602
|
await (await at()).unsafe(`DELETE FROM ${quoteIdent(table)} WHERE id = $1`, [id]);
|
|
@@ -418,16 +606,96 @@ function createOps(tx) {
|
|
|
418
606
|
`SELECT * FROM ${quoteIdent(table)} WHERE id = $1`,
|
|
419
607
|
[id]
|
|
420
608
|
);
|
|
421
|
-
return rows[0] ?
|
|
609
|
+
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
422
610
|
},
|
|
423
611
|
async findMany(table, query = {}) {
|
|
424
612
|
const cols = Object.keys(query);
|
|
425
613
|
const where = cols.length ? ` WHERE ${cols.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ")}` : "";
|
|
426
|
-
return
|
|
614
|
+
return asTableRows(table, await (await at()).unsafe(
|
|
427
615
|
`SELECT * FROM ${quoteIdent(table)}${where}`,
|
|
428
616
|
cols.map((c) => query[c])
|
|
429
617
|
));
|
|
430
618
|
},
|
|
619
|
+
/**
|
|
620
|
+
* Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
|
|
621
|
+
* (1/(50+rank), CLAIM-N4). Operatör şema-nitelikli (C-10, M-1); GUC
|
|
622
|
+
* hnsw.iterative_scan=relaxed_order aynı tx'te set_config ile (CLAIM-N3 —
|
|
623
|
+
* RLS/filtre altında LIMIT-altı dönüş açığını kapatır). Sorgu-anı embed
|
|
624
|
+
* T024'te gelir; o zamana dek vector kolu yalnız params.vector ile koşar.
|
|
625
|
+
*/
|
|
626
|
+
async search(table, params = {}) {
|
|
627
|
+
const cfg = searchConfigFor(table);
|
|
628
|
+
if (!cfg) {
|
|
629
|
+
throw new Error(`search(${table}): tablo aranabilir de\u011Fil \u2014 ne vector kolonu ne search beyan\u0131 var (FR-013)`);
|
|
630
|
+
}
|
|
631
|
+
const rawLimit = params.limit ?? 20;
|
|
632
|
+
if (typeof rawLimit !== "number" || !Number.isFinite(rawLimit)) {
|
|
633
|
+
throw new Error(`search(${table}): limit sonlu bir say\u0131 olmal\u0131, ${String(rawLimit)} verildi (FR-013)`);
|
|
634
|
+
}
|
|
635
|
+
const limit = Math.min(Math.max(1, Math.trunc(rawLimit)), 100);
|
|
636
|
+
const pool = Math.max(limit * 3, 30);
|
|
637
|
+
const wantText = params.mode !== "vector" && cfg.ftsCols.length > 0 && typeof params.query === "string" && params.query !== "";
|
|
638
|
+
const anyEmbed = cfg.legs.some((l) => l.embed !== void 0);
|
|
639
|
+
const vectorAsked = params.mode !== "text" && (Array.isArray(params.vector) || params.using !== void 0 || params.mode === "vector" || anyEmbed && typeof params.query === "string" && params.query !== "");
|
|
640
|
+
const leg = vectorAsked ? pickLeg(table, cfg.legs, params.using) : null;
|
|
641
|
+
let qv = Array.isArray(params.vector) ? params.vector : null;
|
|
642
|
+
if (qv === null && leg?.embed !== void 0 && typeof params.query === "string" && params.query !== "") {
|
|
643
|
+
try {
|
|
644
|
+
qv = await embedQuery(leg.embed, params.query);
|
|
645
|
+
} catch (e) {
|
|
646
|
+
if (!wantText) throw e;
|
|
647
|
+
qv = null;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
const wantVector = leg !== null && qv !== null;
|
|
651
|
+
if (!wantText && !wantVector) {
|
|
652
|
+
throw new Error(
|
|
653
|
+
`search(${table}): ko\u015Fulabilir kol yok \u2014 metin i\xE7in 'query' (FTS beyan\u0131 gerekir), semantik i\xE7in 'vector' verin (FR-015)`
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
const bind = [];
|
|
657
|
+
const add = (v) => {
|
|
658
|
+
bind.push(v);
|
|
659
|
+
return `$${bind.length}`;
|
|
660
|
+
};
|
|
661
|
+
const whereSql = compileWhere(table, cfg.colSet, params.where ?? {}, add);
|
|
662
|
+
const live = await at();
|
|
663
|
+
const K = 50;
|
|
664
|
+
let semSql = "";
|
|
665
|
+
let kwSql = "";
|
|
666
|
+
if (wantVector && leg) {
|
|
667
|
+
const sch = await vectorSchemaWithGuc(live);
|
|
668
|
+
const op = METRIC_OPERATOR[leg.metric] ?? METRIC_OPERATOR.cosine;
|
|
669
|
+
let exactOrder = "";
|
|
670
|
+
if (whereSql !== "") {
|
|
671
|
+
const probeRows = await live.unsafe(
|
|
672
|
+
`SELECT count(*)::int AS n FROM (SELECT 1 FROM ${quoteIdent(table)} t WHERE t.${quoteIdent(leg.column)} IS NOT NULL${whereSql} LIMIT ${SELECTIVITY_EXACT_THRESHOLD + 1}) s`,
|
|
673
|
+
bind.slice()
|
|
674
|
+
);
|
|
675
|
+
const n = probeRows?.[0]?.n;
|
|
676
|
+
if (typeof n === "number" && n <= SELECTIVITY_EXACT_THRESHOLD) {
|
|
677
|
+
exactOrder = " + 0.0";
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const vp = add(toVectorLiteral(qv));
|
|
681
|
+
semSql = `SELECT t.${quoteIdent(cfg.pk)} AS id, ROW_NUMBER() OVER (ORDER BY (t.${quoteIdent(leg.column)} OPERATOR(${quoteIdent(sch)}.${op}) ${vp}::${quoteIdent(sch)}.vector)${exactOrder}) AS r FROM ${quoteIdent(table)} t WHERE t.${quoteIdent(leg.column)} IS NOT NULL${whereSql} ORDER BY r LIMIT ${pool}`;
|
|
682
|
+
}
|
|
683
|
+
if (wantText) {
|
|
684
|
+
const qp = add(params.query);
|
|
685
|
+
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 ${quoteIdent(table)} t WHERE t.palbase_fts @@ websearch_to_tsquery('simple', ${qp})${whereSql} ORDER BY r LIMIT ${pool}`;
|
|
686
|
+
}
|
|
687
|
+
const colList = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
|
|
688
|
+
let sql;
|
|
689
|
+
if (semSql !== "" && kwSql !== "") {
|
|
690
|
+
sql = `WITH sem AS (${semSql}), kw AS (${kwSql}), 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}, fused._score AS _score FROM fused JOIN ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = fused.id ORDER BY fused._score DESC, t.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
|
|
691
|
+
} else {
|
|
692
|
+
const single = semSql !== "" ? `sem AS (${semSql})` : `kw AS (${kwSql})`;
|
|
693
|
+
const alias = semSql !== "" ? "sem" : "kw";
|
|
694
|
+
sql = `WITH ${single} SELECT ${colList}, (1.0/(${K} + ${alias}.r))::float8 AS _score FROM ${alias} JOIN ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = ${alias}.id ORDER BY _score DESC, t.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
|
|
695
|
+
}
|
|
696
|
+
const rows = await live.unsafe(sql, bind);
|
|
697
|
+
return asTableRows(table, rows);
|
|
698
|
+
},
|
|
431
699
|
/** A real SAVEPOINT inside the request's transaction. */
|
|
432
700
|
async transaction(cb) {
|
|
433
701
|
const live = await at();
|
|
@@ -456,7 +724,7 @@ function createOps(tx) {
|
|
|
456
724
|
return live.savepoint(async (sp) => {
|
|
457
725
|
const results = [];
|
|
458
726
|
for (const op of plan.ops) {
|
|
459
|
-
const rows = await runPlanOp(sp, op, results);
|
|
727
|
+
const rows = asTableRows(op.table, await runPlanOp(sp, op, results));
|
|
460
728
|
const result = { rows, rows_affected: rows.length };
|
|
461
729
|
results.push(result);
|
|
462
730
|
assertGuard(op, result);
|
|
@@ -469,6 +737,7 @@ function createOps(tx) {
|
|
|
469
737
|
}
|
|
470
738
|
var currentSchema = {};
|
|
471
739
|
function setSchema(schema) {
|
|
740
|
+
cachedVectorSchema = null;
|
|
472
741
|
const s = schema;
|
|
473
742
|
currentSchema = (s && "default" in s ? s.default : s) ?? {};
|
|
474
743
|
}
|
|
@@ -555,7 +824,7 @@ function isRef(v) {
|
|
|
555
824
|
function isExpr(v) {
|
|
556
825
|
return typeof v === "object" && v !== null && "$expr" in v;
|
|
557
826
|
}
|
|
558
|
-
function renderValue(value, column, args, results) {
|
|
827
|
+
function renderValue(value, column, args, results, vectorCols) {
|
|
559
828
|
if (isRef(value)) {
|
|
560
829
|
const source = results[value.$ref.op];
|
|
561
830
|
const row = source?.rows[0];
|
|
@@ -564,15 +833,15 @@ function renderValue(value, column, args, results) {
|
|
|
564
833
|
error_code: "tx_ref_unresolved"
|
|
565
834
|
});
|
|
566
835
|
}
|
|
567
|
-
return args
|
|
836
|
+
return bindMaybeVector(args, column, vectorCols, row[value.$ref.field]);
|
|
568
837
|
}
|
|
569
838
|
if (isExpr(value)) {
|
|
570
839
|
const fn = value.$expr;
|
|
571
840
|
if (fn.fn === "now") return "now()";
|
|
572
841
|
const operator = fn.fn === "inc" ? "+" : "-";
|
|
573
|
-
return `${quoteIdent(column)} ${operator} ${args
|
|
842
|
+
return `${quoteIdent(column)} ${operator} ${bindMaybeVector(args, column, vectorCols, fn.by)}`;
|
|
574
843
|
}
|
|
575
|
-
return args
|
|
844
|
+
return bindMaybeVector(args, column, vectorCols, value);
|
|
576
845
|
}
|
|
577
846
|
function renderWhere(where, args, results) {
|
|
578
847
|
const cols = Object.keys(where ?? {});
|
|
@@ -584,14 +853,21 @@ function renderWhere(where, args, results) {
|
|
|
584
853
|
});
|
|
585
854
|
return ` WHERE ${terms.join(" AND ")}`;
|
|
586
855
|
}
|
|
856
|
+
function bindMaybeVector(args, column, vectorCols, value) {
|
|
857
|
+
if (column !== void 0 && vectorCols?.has(column) && Array.isArray(value)) {
|
|
858
|
+
return args.bind(toVectorLiteral(value));
|
|
859
|
+
}
|
|
860
|
+
return args.bind(value);
|
|
861
|
+
}
|
|
587
862
|
async function runPlanOp(sp, op, results) {
|
|
863
|
+
const opVectorCols = vectorColumnsOf(currentSchema, op.table);
|
|
588
864
|
const args = new Args();
|
|
589
865
|
const table = quoteIdent(op.table);
|
|
590
866
|
let sql;
|
|
591
867
|
switch (op.op) {
|
|
592
868
|
case "insert": {
|
|
593
869
|
const cols = Object.keys(op.values ?? {});
|
|
594
|
-
const rendered = cols.map((c) => renderValue(op.values[c], c, args, results));
|
|
870
|
+
const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols));
|
|
595
871
|
sql = cols.length ? `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) RETURNING *` : `INSERT INTO ${table} DEFAULT VALUES RETURNING *`;
|
|
596
872
|
break;
|
|
597
873
|
}
|
|
@@ -600,7 +876,7 @@ async function runPlanOp(sp, op, results) {
|
|
|
600
876
|
if (rows.length === 0 || !rows[0]) return [];
|
|
601
877
|
const cols = Object.keys(rows[0]);
|
|
602
878
|
const tuples = rows.map(
|
|
603
|
-
(r) => `(${cols.map((c) => renderValue(r[c], c, args, results)).join(", ")})`
|
|
879
|
+
(r) => `(${cols.map((c) => renderValue(r[c], c, args, results, opVectorCols)).join(", ")})`
|
|
604
880
|
);
|
|
605
881
|
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")} RETURNING *`;
|
|
606
882
|
break;
|
|
@@ -609,7 +885,7 @@ async function runPlanOp(sp, op, results) {
|
|
|
609
885
|
const cols = Object.keys(op.set ?? {});
|
|
610
886
|
if (cols.length === 0) throw new Error(`update ${op.table}: nothing to set`);
|
|
611
887
|
const assignments = cols.map(
|
|
612
|
-
(c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results)}`
|
|
888
|
+
(c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols)}`
|
|
613
889
|
);
|
|
614
890
|
sql = `UPDATE ${table} SET ${assignments.join(", ")}${renderWhere(op.where, args, results)} RETURNING *`;
|
|
615
891
|
break;
|
|
@@ -720,7 +996,8 @@ function grantFor(entry, ctx, bucketLimits) {
|
|
|
720
996
|
filename: ctx.filename
|
|
721
997
|
}),
|
|
722
998
|
maxBytes: bucketLimits?.maxBytes ?? null,
|
|
723
|
-
mimeTypes: bucketLimits?.mimeTypes ?? null
|
|
999
|
+
mimeTypes: bucketLimits?.mimeTypes ?? null,
|
|
1000
|
+
ownerUid: ctx.userId ?? null
|
|
724
1001
|
};
|
|
725
1002
|
}
|
|
726
1003
|
var CompletionLedger = class {
|
|
@@ -817,6 +1094,23 @@ function installEgressFence(policy) {
|
|
|
817
1094
|
|
|
818
1095
|
// src/engine/index.ts
|
|
819
1096
|
var JSON_HEADERS = { "content-type": "application/json" };
|
|
1097
|
+
function fieldErrors(err) {
|
|
1098
|
+
return err.issues.map((i) => ({ field: i.path.join("."), message: i.message }));
|
|
1099
|
+
}
|
|
1100
|
+
function headersFor(raw, schema) {
|
|
1101
|
+
const shape = schema.shape;
|
|
1102
|
+
if (typeof shape !== "object" || shape === null) return raw;
|
|
1103
|
+
let aliased = null;
|
|
1104
|
+
for (const declared of Object.keys(shape)) {
|
|
1105
|
+
const lower = declared.toLowerCase();
|
|
1106
|
+
if (lower === declared) continue;
|
|
1107
|
+
const value = raw[lower];
|
|
1108
|
+
if (value === void 0) continue;
|
|
1109
|
+
aliased ??= { ...raw };
|
|
1110
|
+
aliased[declared] = value;
|
|
1111
|
+
}
|
|
1112
|
+
return aliased ?? raw;
|
|
1113
|
+
}
|
|
820
1114
|
function envelope(error, description, status, requestId, extra) {
|
|
821
1115
|
return new Response(
|
|
822
1116
|
JSON.stringify({ error, error_description: description, status, request_id: requestId, ...extra }),
|
|
@@ -850,6 +1144,7 @@ async function defaultSqlDriver(config) {
|
|
|
850
1144
|
async function createApp(opts) {
|
|
851
1145
|
const { config, controllers } = opts;
|
|
852
1146
|
setSchema(opts.schema ?? {});
|
|
1147
|
+
setSecretReader(opts.secretReader ?? null);
|
|
853
1148
|
const routes = buildRouteTable(controllers);
|
|
854
1149
|
if (routes.length === 0) {
|
|
855
1150
|
throw new BootRefused([], "boot refused: zero endpoints collected \u2014 nothing would answer.");
|
|
@@ -962,7 +1257,8 @@ async function createApp(opts) {
|
|
|
962
1257
|
error: "too_many_requests",
|
|
963
1258
|
error_description: "Rate limit exceeded for this endpoint",
|
|
964
1259
|
status: 429,
|
|
965
|
-
request_id: requestId
|
|
1260
|
+
request_id: requestId,
|
|
1261
|
+
data: { retryAfter }
|
|
966
1262
|
}),
|
|
967
1263
|
{ status: 429, headers: { ...JSON_HEADERS, "retry-after": String(retryAfter) } }
|
|
968
1264
|
);
|
|
@@ -981,7 +1277,7 @@ async function createApp(opts) {
|
|
|
981
1277
|
const r = p.schema.safeParse(parsedBody);
|
|
982
1278
|
if (!r.success) {
|
|
983
1279
|
return envelope("bad_request", "Request body failed validation", 400, requestId, {
|
|
984
|
-
|
|
1280
|
+
data: { fields: fieldErrors(r.error) }
|
|
985
1281
|
});
|
|
986
1282
|
}
|
|
987
1283
|
args[p.index] = r.data;
|
|
@@ -991,7 +1287,7 @@ async function createApp(opts) {
|
|
|
991
1287
|
const r = p.schema.safeParse(Object.fromEntries(url.searchParams));
|
|
992
1288
|
if (!r.success) {
|
|
993
1289
|
return envelope("bad_request", "Query parameters failed validation", 400, requestId, {
|
|
994
|
-
|
|
1290
|
+
data: { fields: fieldErrors(r.error) }
|
|
995
1291
|
});
|
|
996
1292
|
}
|
|
997
1293
|
args[p.index] = r.data;
|
|
@@ -1000,9 +1296,21 @@ async function createApp(opts) {
|
|
|
1000
1296
|
case "param":
|
|
1001
1297
|
args[p.index] = hit.params[p.name];
|
|
1002
1298
|
break;
|
|
1003
|
-
case "headers":
|
|
1004
|
-
|
|
1299
|
+
case "headers": {
|
|
1300
|
+
const raw = Object.fromEntries(req.headers);
|
|
1301
|
+
if (!p.schema) {
|
|
1302
|
+
args[p.index] = raw;
|
|
1303
|
+
break;
|
|
1304
|
+
}
|
|
1305
|
+
const r = p.schema.safeParse(headersFor(raw, p.schema));
|
|
1306
|
+
if (!r.success) {
|
|
1307
|
+
return envelope("bad_request", "Request headers failed validation", 400, requestId, {
|
|
1308
|
+
data: { fields: fieldErrors(r.error) }
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
args[p.index] = r.data;
|
|
1005
1312
|
break;
|
|
1313
|
+
}
|
|
1006
1314
|
case "user":
|
|
1007
1315
|
case "optionalUser":
|
|
1008
1316
|
args[p.index] = claims ? {
|
|
@@ -1051,6 +1359,14 @@ async function createApp(opts) {
|
|
|
1051
1359
|
case "traceId":
|
|
1052
1360
|
args[p.index] = requestId;
|
|
1053
1361
|
break;
|
|
1362
|
+
case "client":
|
|
1363
|
+
args[p.index] = {
|
|
1364
|
+
sdkVersion: req.headers.get("x-palbase-sdk-version"),
|
|
1365
|
+
appVersion: req.headers.get("x-palbase-client-version"),
|
|
1366
|
+
platform: req.headers.get("x-platform"),
|
|
1367
|
+
osVersion: req.headers.get("x-os-version")
|
|
1368
|
+
};
|
|
1369
|
+
break;
|
|
1054
1370
|
case "req":
|
|
1055
1371
|
args[p.index] = req;
|
|
1056
1372
|
break;
|
|
@@ -1155,4 +1471,4 @@ export {
|
|
|
1155
1471
|
installEgressFence,
|
|
1156
1472
|
createApp
|
|
1157
1473
|
};
|
|
1158
|
-
//# sourceMappingURL=chunk-
|
|
1474
|
+
//# sourceMappingURL=chunk-74XDEF5J.js.map
|