@palbase/backend 22.0.1 → 23.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/palbase-backend.cjs +764 -25
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +4 -4
- package/dist/{chunk-POYAFBLF.js → chunk-FSGSB42K.js} +13 -6
- package/dist/chunk-FSGSB42K.js.map +1 -0
- package/dist/{chunk-SSGAMC26.js → chunk-HAF67F2H.js} +114 -5
- package/dist/chunk-HAF67F2H.js.map +1 -0
- package/dist/{chunk-QMVK4X3V.js → chunk-OMRTHM4X.js} +98 -98
- package/dist/chunk-OMRTHM4X.js.map +1 -0
- package/dist/{chunk-QYOHMVUW.js → chunk-REZU6UKT.js} +755 -24
- package/dist/chunk-REZU6UKT.js.map +1 -0
- package/dist/{chunk-YL4C5NRY.js → chunk-Y5HXVUMP.js} +2 -2
- package/dist/{chunk-N32VDWKH.js → chunk-ZC6Q2BRD.js} +4 -59
- package/dist/chunk-ZC6Q2BRD.js.map +1 -0
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +3 -21
- package/dist/db/env.d.ts +3 -21
- package/dist/db/index.cjs +117 -6
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +3 -2
- package/dist/db/index.d.ts +3 -2
- package/dist/db/index.js +7 -3
- package/dist/{endpoint-B0LpZixz.d.cts → endpoint-BavvbW4P.d.ts} +116 -14
- package/dist/{endpoint-B0LpZixz.d.ts → endpoint-i8TTCohk.d.cts} +116 -14
- package/dist/engine/index.cjs +764 -25
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +6 -5
- package/dist/engine/index.d.ts +6 -5
- package/dist/engine/index.js +3 -3
- package/dist/{index-g-EzitI-.d.ts → index-B3jmmItD.d.ts} +203 -7
- package/dist/{index-BCNtlG1w.d.ts → index-B7YBEG5w.d.ts} +117 -93
- package/dist/{index-BGSCWlUa.d.cts → index-Bmvx1EvJ.d.cts} +203 -7
- package/dist/{index-B4W6d2VJ.d.cts → index-E7OscPJT.d.cts} +117 -93
- package/dist/index.cjs +509 -1085
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +203 -1133
- package/dist/index.d.ts +203 -1133
- package/dist/index.js +288 -900
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.cjs +3 -57
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.d.cts +3 -2
- package/dist/openapi/index.d.ts +3 -2
- package/dist/openapi/index.js +5 -22
- package/dist/openapi/index.js.map +1 -1
- package/dist/{registry-3BLYv4si.d.ts → registry-C3H2uPeZ.d.cts} +13 -169
- package/dist/{registry-Cw0YEYCg.d.cts → registry-DY3d9l1k.d.ts} +13 -169
- package/dist/{purchases/keys.cjs → stack.cjs} +4 -4
- package/dist/stack.cjs.map +1 -0
- package/dist/stack.d.cts +76 -0
- package/dist/stack.d.ts +76 -0
- package/dist/stack.js +1 -0
- package/docs/README.md +7 -6
- package/docs/endpoints.md +1 -1
- package/docs/errors.md +9 -0
- package/docs/llms-full.txt +17 -261
- package/docs/llms.txt +0 -2
- package/package.json +7 -7
- package/template/package.json +1 -1
- package/dist/chunk-N32VDWKH.js.map +0 -1
- package/dist/chunk-POYAFBLF.js.map +0 -1
- package/dist/chunk-QMVK4X3V.js.map +0 -1
- package/dist/chunk-QYOHMVUW.js.map +0 -1
- package/dist/chunk-SSGAMC26.js.map +0 -1
- package/dist/purchases/keys.cjs.map +0 -1
- package/dist/purchases/keys.d.cts +0 -42
- package/dist/purchases/keys.d.ts +0 -42
- package/dist/purchases/keys.js +0 -1
- package/docs/config.md +0 -147
- package/docs/resources.md +0 -97
- package/template/config/secrets.ts +0 -24
- /package/dist/{chunk-YL4C5NRY.js.map → chunk-Y5HXVUMP.js.map} +0 -0
- /package/dist/{purchases/keys.js.map → stack.js.map} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__requestALS,
|
|
3
3
|
__runWithRuntime
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FSGSB42K.js";
|
|
5
5
|
import {
|
|
6
6
|
getRoutes,
|
|
7
7
|
isHttpError
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OMRTHM4X.js";
|
|
9
9
|
|
|
10
10
|
// src/engine/config.ts
|
|
11
11
|
var BootRefused = class extends Error {
|
|
@@ -382,6 +382,305 @@ 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 synonyms = search?.synonyms !== void 0 && Object.keys(search.synonyms).length > 0 ? { synonyms: search.synonyms } : {};
|
|
445
|
+
const validity = search?.validity === true ? { validity: true } : {};
|
|
446
|
+
if (search?.from !== void 0 && search.model !== void 0) {
|
|
447
|
+
const m = search.model;
|
|
448
|
+
const embed = {
|
|
449
|
+
model: m.model,
|
|
450
|
+
apiKeyName: m.apiKeyName ?? "OPENAI_API_KEY",
|
|
451
|
+
...m.baseURL !== void 0 ? { baseURL: m.baseURL } : {},
|
|
452
|
+
...m.dimensions !== void 0 ? { dimensions: m.dimensions } : {}
|
|
453
|
+
};
|
|
454
|
+
const metric = search.metric ?? "cosine";
|
|
455
|
+
const ftsOn = search.text !== false;
|
|
456
|
+
const ftsColsNew = ftsOn ? Array.isArray(search.text) && search.text.length > 0 ? search.text : search.from : [];
|
|
457
|
+
if (vectorCols.length > 0) {
|
|
458
|
+
return {
|
|
459
|
+
pk,
|
|
460
|
+
cols,
|
|
461
|
+
colSet: new Set(cols),
|
|
462
|
+
ftsCols: ftsColsNew,
|
|
463
|
+
legs: [{ column: vectorCols[0], metric, embed }],
|
|
464
|
+
...synonyms,
|
|
465
|
+
...validity
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
pk,
|
|
470
|
+
cols,
|
|
471
|
+
colSet: new Set(cols),
|
|
472
|
+
ftsCols: ftsColsNew,
|
|
473
|
+
legs: [],
|
|
474
|
+
chunk: { table: `${table}__palbase_chunks`, metric, embed, fts: ftsOn },
|
|
475
|
+
...synonyms,
|
|
476
|
+
...validity
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const ftsCols = (Array.isArray(search?.text) ? search.text : void 0) ?? [];
|
|
480
|
+
const rawLegs = search?.vector === void 0 ? [] : Array.isArray(search.vector) ? search.vector : [search.vector];
|
|
481
|
+
let legs;
|
|
482
|
+
if (rawLegs.length > 0) {
|
|
483
|
+
legs = rawLegs.map((leg) => {
|
|
484
|
+
const l = leg;
|
|
485
|
+
const column = l.column ?? (vectorCols.length === 1 ? vectorCols[0] : void 0);
|
|
486
|
+
if (column === void 0) {
|
|
487
|
+
throw new Error(`search(${table}): birden \xE7ok vector kolonu var \u2014 beyanda 'column' zorunlu (FR-010)`);
|
|
488
|
+
}
|
|
489
|
+
const model = l.model;
|
|
490
|
+
return {
|
|
491
|
+
column,
|
|
492
|
+
metric: l.metric ?? "cosine",
|
|
493
|
+
...model !== void 0 ? { embed: {
|
|
494
|
+
model: model.model,
|
|
495
|
+
apiKeyName: model.apiKeyName ?? "OPENAI_API_KEY",
|
|
496
|
+
...model.baseURL !== void 0 ? { baseURL: model.baseURL } : {},
|
|
497
|
+
...model.dimensions !== void 0 ? { dimensions: model.dimensions } : {}
|
|
498
|
+
} } : {}
|
|
499
|
+
};
|
|
500
|
+
});
|
|
501
|
+
} else {
|
|
502
|
+
legs = vectorCols.map((column) => ({ column, metric: "cosine" }));
|
|
503
|
+
}
|
|
504
|
+
if (ftsCols.length === 0 && legs.length === 0) return null;
|
|
505
|
+
return { pk, cols, colSet: new Set(cols), ftsCols, legs, ...synonyms, ...validity };
|
|
506
|
+
}
|
|
507
|
+
function pickLeg(table, legs, using) {
|
|
508
|
+
if (legs.length === 0) return null;
|
|
509
|
+
if (using !== void 0) {
|
|
510
|
+
const hit = legs.find((l) => l.column === using);
|
|
511
|
+
if (!hit) {
|
|
512
|
+
throw new Error(
|
|
513
|
+
`search(${table}): using "${using}" bir vekt\xF6r kolunu adlam\u0131yor \u2014 mevcut: ${legs.map((l) => l.column).join(", ")}`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
return hit;
|
|
517
|
+
}
|
|
518
|
+
if (legs.length === 1) return legs[0];
|
|
519
|
+
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`);
|
|
520
|
+
}
|
|
521
|
+
var HALF_LIFE_UNIT_SECONDS = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
522
|
+
function parseHalfLife(s) {
|
|
523
|
+
const m = /^(\d+)(s|m|h|d)$/.exec(s);
|
|
524
|
+
const sec = m === null ? 0 : Number(m[1]) * HALF_LIFE_UNIT_SECONDS[m[2]];
|
|
525
|
+
if (!Number.isFinite(sec) || sec <= 0) {
|
|
526
|
+
throw new Error(`recency.halfLife "${s}" \xE7\xF6z\xFCmlenemedi \u2014 beklenen: pozitif <say\u0131>+(s|m|h|d), \xF6rn. "30d" (FR-004)`);
|
|
527
|
+
}
|
|
528
|
+
return sec;
|
|
529
|
+
}
|
|
530
|
+
var WHERE_OPS = { gt: ">", gte: ">=", lt: "<", lte: "<=", neq: "<>" };
|
|
531
|
+
function ftsExprOf(ftsCols) {
|
|
532
|
+
return [...ftsCols].sort().map((c) => `coalesce(t.${quoteIdent(c)},'')`).join(" || ' ' || ");
|
|
533
|
+
}
|
|
534
|
+
function expandSynonyms(query, map) {
|
|
535
|
+
const lower = {};
|
|
536
|
+
for (const [word, alts] of Object.entries(map)) lower[word.toLowerCase()] = alts;
|
|
537
|
+
return query.split(/("[^"]*")/).map((seg) => {
|
|
538
|
+
if (seg.startsWith('"')) return seg;
|
|
539
|
+
return seg.split(/(\s+)/).map((tok) => {
|
|
540
|
+
if (tok === "" || /^\s+$/.test(tok)) return tok;
|
|
541
|
+
const alts = lower[tok.toLowerCase()];
|
|
542
|
+
return alts !== void 0 && alts.length > 0 ? `(${tok} OR ${alts.join(" OR ")})` : tok;
|
|
543
|
+
}).join("");
|
|
544
|
+
}).join("");
|
|
545
|
+
}
|
|
546
|
+
var searchLogger = null;
|
|
547
|
+
function logNoResults(table, query, mode, n) {
|
|
548
|
+
if (n === 0) {
|
|
549
|
+
searchLogger?.("palbase.search.no_results", {
|
|
550
|
+
table,
|
|
551
|
+
query: (query ?? "").slice(0, 200),
|
|
552
|
+
mode: mode ?? "hybrid"
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async function fetchFacets(live, table, colSet, facets, where, extraWhere = () => "") {
|
|
557
|
+
const bind = [];
|
|
558
|
+
const add = (v) => {
|
|
559
|
+
bind.push(v);
|
|
560
|
+
return `$${bind.length}`;
|
|
561
|
+
};
|
|
562
|
+
const whereSql = compileWhere(table, colSet, where, add) + extraWhere(add);
|
|
563
|
+
const parts = facets.map(
|
|
564
|
+
(col) => `(SELECT '${col.replace(/'/g, "''")}' AS f, t.${quoteIdent(col)}::text AS v, count(*) AS n FROM ${quoteIdent(table)} t WHERE true${whereSql} GROUP BY 2 ORDER BY 3 DESC LIMIT 20)`
|
|
565
|
+
);
|
|
566
|
+
const rows = await live.unsafe(parts.join(" UNION ALL "), bind);
|
|
567
|
+
const out = {};
|
|
568
|
+
for (const col of facets) out[col] = [];
|
|
569
|
+
for (const r of rows) {
|
|
570
|
+
if (typeof r.f === "string" && out[r.f] !== void 0) {
|
|
571
|
+
out[r.f].push({ value: r.v ?? null, count: Number(r.n) });
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return out;
|
|
575
|
+
}
|
|
576
|
+
function validitySql(v, add) {
|
|
577
|
+
if (v === "all") return "";
|
|
578
|
+
if (v !== void 0) {
|
|
579
|
+
const p = add(v.asOf);
|
|
580
|
+
return ` AND t."valid_from" <= ${p} AND (t."valid_to" IS NULL OR t."valid_to" > ${p})`;
|
|
581
|
+
}
|
|
582
|
+
return ` AND t."valid_to" IS NULL`;
|
|
583
|
+
}
|
|
584
|
+
function excludeSql(pk, ids, add) {
|
|
585
|
+
if (ids.length === 0) return "";
|
|
586
|
+
if (ids.length === 1) return ` AND t.${quoteIdent(pk)} <> ${add(ids[0])}`;
|
|
587
|
+
return ` AND t.${quoteIdent(pk)} <> ALL(ARRAY[${ids.map((x) => add(x)).join(", ")}])`;
|
|
588
|
+
}
|
|
589
|
+
function compileWhere(table, colSet, where, add) {
|
|
590
|
+
const parts = [];
|
|
591
|
+
for (const [col, cond] of Object.entries(where)) {
|
|
592
|
+
if (!colSet.has(col)) {
|
|
593
|
+
throw new Error(`search(${table}): where kolonu "${col}" tabloda yok (FR-016)`);
|
|
594
|
+
}
|
|
595
|
+
const q = `t.${quoteIdent(col)}`;
|
|
596
|
+
if (cond !== null && typeof cond === "object" && !Array.isArray(cond)) {
|
|
597
|
+
for (const [op, v] of Object.entries(cond)) {
|
|
598
|
+
if (op === "in") {
|
|
599
|
+
if (!Array.isArray(v)) throw new Error(`search(${table}): where.${col}.in bir dizi olmal\u0131`);
|
|
600
|
+
if (v.length === 0) {
|
|
601
|
+
parts.push("false");
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
parts.push(`${q} IN (${v.map((x) => add(x)).join(", ")})`);
|
|
605
|
+
} else if (op in WHERE_OPS) {
|
|
606
|
+
parts.push(`${q} ${WHERE_OPS[op]} ${add(v)}`);
|
|
607
|
+
} else {
|
|
608
|
+
throw new Error(`search(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in)`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
parts.push(`${q} = ${add(cond)}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return parts.length === 0 ? "" : ` AND ${parts.join(" AND ")}`;
|
|
616
|
+
}
|
|
617
|
+
var cachedVectorSchema = null;
|
|
618
|
+
var cachedTrgmSchema = null;
|
|
619
|
+
async function trgmSchemaOf(live) {
|
|
620
|
+
if (cachedTrgmSchema !== null) return cachedTrgmSchema;
|
|
621
|
+
const rows = await live.unsafe(
|
|
622
|
+
"select n.nspname from pg_extension e join pg_namespace n on n.oid = e.extnamespace where e.extname = 'pg_trgm'"
|
|
623
|
+
);
|
|
624
|
+
cachedTrgmSchema = rows?.[0]?.nspname ?? "public";
|
|
625
|
+
return cachedTrgmSchema;
|
|
626
|
+
}
|
|
627
|
+
async function vectorSchemaWithGuc(runner) {
|
|
628
|
+
if (cachedVectorSchema !== null) {
|
|
629
|
+
await runner.unsafe(
|
|
630
|
+
"select set_config('hnsw.iterative_scan','relaxed_order',true), set_config('hnsw.max_scan_tuples','200000',true), set_config('hnsw.ef_search','200',true)"
|
|
631
|
+
);
|
|
632
|
+
return cachedVectorSchema;
|
|
633
|
+
}
|
|
634
|
+
const rows = await runner.unsafe(
|
|
635
|
+
"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"
|
|
636
|
+
);
|
|
637
|
+
const name = rows?.[0]?.nspname;
|
|
638
|
+
if (typeof name !== "string" || name === "") {
|
|
639
|
+
throw new Error("pgvector extension kurulu de\u011Fil \u2014 vector aramas\u0131 \xE7al\u0131\u015Famaz (extensions beyan\u0131 deploy'dan ge\xE7ti mi?)");
|
|
640
|
+
}
|
|
641
|
+
cachedVectorSchema = name;
|
|
642
|
+
return name;
|
|
643
|
+
}
|
|
644
|
+
var secretReader = null;
|
|
645
|
+
function setSecretReader(fn) {
|
|
646
|
+
secretReader = fn;
|
|
647
|
+
}
|
|
648
|
+
var embedFetch = (url, init) => fetch(url, init);
|
|
649
|
+
async function embedQuery(embed, text) {
|
|
650
|
+
if (secretReader === null) {
|
|
651
|
+
throw new Error(`query embed: secret reader ba\u011Flanmam\u0131\u015F \u2014 ${embed.apiKeyName} okunam\u0131yor`);
|
|
652
|
+
}
|
|
653
|
+
const key = await secretReader(embed.apiKeyName);
|
|
654
|
+
if (key === null || key === "") {
|
|
655
|
+
throw new Error(`query embed: vault'ta ${embed.apiKeyName} yok (FR-021/FR-025)`);
|
|
656
|
+
}
|
|
657
|
+
const url = (embed.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "") + "/embeddings";
|
|
658
|
+
const controller = new AbortController();
|
|
659
|
+
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
660
|
+
try {
|
|
661
|
+
const res = await embedFetch(url, {
|
|
662
|
+
method: "POST",
|
|
663
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
|
|
664
|
+
body: JSON.stringify({
|
|
665
|
+
model: embed.model,
|
|
666
|
+
input: [text],
|
|
667
|
+
...embed.dimensions !== void 0 ? { dimensions: embed.dimensions } : {}
|
|
668
|
+
}),
|
|
669
|
+
signal: controller.signal
|
|
670
|
+
});
|
|
671
|
+
if (!res.ok) {
|
|
672
|
+
throw new Error(`query embed: sa\u011Flay\u0131c\u0131 ${res.status} d\xF6nd\xFC (${embed.apiKeyName} ile) \u2014 anahtar/model do\u011Fru mu?`);
|
|
673
|
+
}
|
|
674
|
+
const data = await res.json();
|
|
675
|
+
const vec = data.data?.[0]?.embedding;
|
|
676
|
+
if (!Array.isArray(vec)) {
|
|
677
|
+
throw new Error("query embed: sa\u011Flay\u0131c\u0131 yan\u0131t\u0131nda data[0].embedding yok (CLAIM-N1 \u015Fekli)");
|
|
678
|
+
}
|
|
679
|
+
return vec;
|
|
680
|
+
} finally {
|
|
681
|
+
clearTimeout(timer);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
385
684
|
function createOps(tx) {
|
|
386
685
|
const at = () => resolveTx(tx);
|
|
387
686
|
const ops = {
|
|
@@ -393,22 +692,22 @@ function createOps(tx) {
|
|
|
393
692
|
if (cols.length === 0) throw new Error(`insert into ${table}: no columns given`);
|
|
394
693
|
const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
|
|
395
694
|
const sql = `INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`;
|
|
396
|
-
const rows = await (await at()).unsafe(sql,
|
|
695
|
+
const rows = await (await at()).unsafe(sql, asBindParams(table, cols, data));
|
|
397
696
|
const inserted = rows[0];
|
|
398
697
|
if (!inserted) {
|
|
399
698
|
throw new Error(
|
|
400
699
|
`insert into ${table} returned no row \u2014 the write was rejected (an RLS policy, most likely).`
|
|
401
700
|
);
|
|
402
701
|
}
|
|
403
|
-
return
|
|
702
|
+
return asTableRow(table, inserted);
|
|
404
703
|
},
|
|
405
704
|
async update(table, id, data) {
|
|
406
705
|
const cols = Object.keys(data);
|
|
407
706
|
if (cols.length === 0) return ops.findById(table, id);
|
|
408
707
|
const assignments = cols.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(", ");
|
|
409
708
|
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] ?
|
|
709
|
+
const rows = await (await at()).unsafe(sql, [...asBindParams(table, cols, data), id]);
|
|
710
|
+
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
412
711
|
},
|
|
413
712
|
async delete(table, id) {
|
|
414
713
|
await (await at()).unsafe(`DELETE FROM ${quoteIdent(table)} WHERE id = $1`, [id]);
|
|
@@ -418,16 +717,400 @@ function createOps(tx) {
|
|
|
418
717
|
`SELECT * FROM ${quoteIdent(table)} WHERE id = $1`,
|
|
419
718
|
[id]
|
|
420
719
|
);
|
|
421
|
-
return rows[0] ?
|
|
720
|
+
return rows[0] ? asTableRow(table, rows[0]) : null;
|
|
422
721
|
},
|
|
423
722
|
async findMany(table, query = {}) {
|
|
424
723
|
const cols = Object.keys(query);
|
|
425
724
|
const where = cols.length ? ` WHERE ${cols.map((c, i) => `${quoteIdent(c)} = $${i + 1}`).join(" AND ")}` : "";
|
|
426
|
-
return
|
|
725
|
+
return asTableRows(table, await (await at()).unsafe(
|
|
427
726
|
`SELECT * FROM ${quoteIdent(table)}${where}`,
|
|
428
727
|
cols.map((c) => query[c])
|
|
429
728
|
));
|
|
430
729
|
},
|
|
730
|
+
/**
|
|
731
|
+
* Tek-SQL hibrit arama (FR-014): iki kol CTE + FULL OUTER JOIN + RRF
|
|
732
|
+
* (1/(50+rank), CLAIM-N4). Operatör şema-nitelikli (C-10, M-1); GUC
|
|
733
|
+
* hnsw.iterative_scan=relaxed_order aynı tx'te set_config ile (CLAIM-N3 —
|
|
734
|
+
* RLS/filtre altında LIMIT-altı dönüş açığını kapatır). Sorgu-anı embed
|
|
735
|
+
* T024'te gelir; o zamana dek vector kolu yalnız params.vector ile koşar.
|
|
736
|
+
*/
|
|
737
|
+
async search(table, params = {}) {
|
|
738
|
+
const cfg = searchConfigFor(table);
|
|
739
|
+
if (!cfg) {
|
|
740
|
+
throw new Error(`search(${table}): tablo aranabilir de\u011Fil \u2014 ne vector kolonu ne search beyan\u0131 var (FR-013)`);
|
|
741
|
+
}
|
|
742
|
+
const excl = params.__excludeIds ?? [];
|
|
743
|
+
if (params.facets !== void 0) {
|
|
744
|
+
for (const col of params.facets) {
|
|
745
|
+
if (!cfg.colSet.has(col)) {
|
|
746
|
+
throw new Error(`search(${table}): facets kolonu "${col}" tabloda yok (FR-027)`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (params.validity !== void 0 && cfg.validity !== true) {
|
|
751
|
+
throw new Error(
|
|
752
|
+
`search(${table}): validity parametresi verildi ama tablo validity beyan\u0131 ta\u015F\u0131m\u0131yor (FR-029)`
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
const validityFor = (adder) => cfg.validity === true ? validitySql(params.validity, adder) : "";
|
|
756
|
+
const rawLimit = params.limit ?? 20;
|
|
757
|
+
if (typeof rawLimit !== "number" || !Number.isFinite(rawLimit)) {
|
|
758
|
+
throw new Error(`search(${table}): limit sonlu bir say\u0131 olmal\u0131, ${String(rawLimit)} verildi (FR-013)`);
|
|
759
|
+
}
|
|
760
|
+
const limit = Math.min(Math.max(1, Math.trunc(rawLimit)), 100);
|
|
761
|
+
const pool = Math.max(limit * 3, 30);
|
|
762
|
+
if (params.minScore !== void 0 && (typeof params.minScore !== "number" || !Number.isFinite(params.minScore))) {
|
|
763
|
+
throw new Error(`search(${table}): minScore sonlu bir say\u0131 olmal\u0131, ${String(params.minScore)} verildi (FR-001)`);
|
|
764
|
+
}
|
|
765
|
+
let recencyMul = "";
|
|
766
|
+
if (params.recency !== void 0) {
|
|
767
|
+
const { field, halfLife } = params.recency;
|
|
768
|
+
if (!cfg.colSet.has(field)) {
|
|
769
|
+
throw new Error(`search(${table}): recency.field "${field}" tabloda yok (FR-004)`);
|
|
770
|
+
}
|
|
771
|
+
recencyMul = ` * exp(-ln(2) * extract(epoch from (now() - t.${quoteIdent(field)})) / ${parseHalfLife(halfLife)})`;
|
|
772
|
+
}
|
|
773
|
+
let boostMul = "";
|
|
774
|
+
if (params.boost !== void 0) {
|
|
775
|
+
const { field, weight } = params.boost;
|
|
776
|
+
if (!cfg.colSet.has(field)) {
|
|
777
|
+
throw new Error(`search(${table}): boost.field "${field}" tabloda yok (FR-030)`);
|
|
778
|
+
}
|
|
779
|
+
if (typeof weight !== "number" || !Number.isFinite(weight)) {
|
|
780
|
+
throw new Error(`search(${table}): boost.weight sonlu bir say\u0131 olmal\u0131, ${String(weight)} verildi (FR-030)`);
|
|
781
|
+
}
|
|
782
|
+
const g = `greatest(t.${quoteIdent(field)},0)`;
|
|
783
|
+
boostMul = ` * (1 + ${weight} * ${g}/(1+${g}))`;
|
|
784
|
+
}
|
|
785
|
+
const scoreMul = boostMul + recencyMul;
|
|
786
|
+
if (cfg.chunk !== void 0) {
|
|
787
|
+
const ck = cfg.chunk;
|
|
788
|
+
const pidQ = quoteIdent(`parent_${cfg.pk}`);
|
|
789
|
+
const bpr = Math.min(Math.max(1, Math.trunc(params.blocksPerRow ?? 3)), 10);
|
|
790
|
+
const wantTextC = params.mode !== "vector" && ck.fts && typeof params.query === "string" && params.query !== "";
|
|
791
|
+
let qvC = Array.isArray(params.vector) ? params.vector : null;
|
|
792
|
+
if (qvC === null && ck.embed !== void 0 && params.mode !== "text" && typeof params.query === "string" && params.query !== "") {
|
|
793
|
+
try {
|
|
794
|
+
qvC = await embedQuery(ck.embed, params.query);
|
|
795
|
+
} catch (e) {
|
|
796
|
+
if (!wantTextC) throw e;
|
|
797
|
+
qvC = null;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (!wantTextC && qvC === null) {
|
|
801
|
+
throw new Error(
|
|
802
|
+
`search(${table}): ko\u015Fulabilir kol yok \u2014 metin i\xE7in 'query' (FTS beyan\u0131 gerekir), semantik i\xE7in 'vector' verin (FR-015)`
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
const bindC = [];
|
|
806
|
+
const addC = (v) => {
|
|
807
|
+
bindC.push(v);
|
|
808
|
+
return `$${bindC.length}`;
|
|
809
|
+
};
|
|
810
|
+
const whereC = compileWhere(table, cfg.colSet, params.where ?? {}, addC) + excludeSql(cfg.pk, excl, addC) + validityFor(addC);
|
|
811
|
+
const liveC = await at();
|
|
812
|
+
const K2 = 50;
|
|
813
|
+
const cpool = Math.max(limit * 9, 90);
|
|
814
|
+
let semC = "";
|
|
815
|
+
let kwC = "";
|
|
816
|
+
if (qvC !== null) {
|
|
817
|
+
const sch = await vectorSchemaWithGuc(liveC);
|
|
818
|
+
const op = METRIC_OPERATOR[ck.metric] ?? METRIC_OPERATOR.cosine;
|
|
819
|
+
const vp = addC(toVectorLiteral(qvC));
|
|
820
|
+
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 WHERE c.embedding IS NOT NULL ORDER BY r LIMIT ${cpool}`;
|
|
821
|
+
}
|
|
822
|
+
let qpC = "";
|
|
823
|
+
let qpCIdx = -1;
|
|
824
|
+
if (wantTextC) {
|
|
825
|
+
const qTextC = cfg.synonyms !== void 0 ? expandSynonyms(params.query, cfg.synonyms) : params.query;
|
|
826
|
+
qpC = addC(qTextC);
|
|
827
|
+
qpCIdx = bindC.length - 1;
|
|
828
|
+
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 WHERE c.palbase_fts @@ websearch_to_tsquery('simple', ${qpC}) ORDER BY r LIMIT ${cpool}`;
|
|
829
|
+
}
|
|
830
|
+
const msC = params.minScore === void 0 ? "" : addC(params.minScore);
|
|
831
|
+
const colListC = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
|
|
832
|
+
const scoreC = scoreMul === "" ? "g._score" : `(g._score)${scoreMul}`;
|
|
833
|
+
const assembleC = (kwIn, withKwn) => {
|
|
834
|
+
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
835
|
+
let fusedSrc;
|
|
836
|
+
if (semC !== "" && kwIn !== "") {
|
|
837
|
+
fusedSrc = `WITH sem AS (${semC}), kw AS (${kwIn}), fused AS (SELECT COALESCE(sem.pid, kw.pid) AS pid, COALESCE(sem.chunk_seq, kw.chunk_seq) AS chunk_seq, (COALESCE(1.0/(${K2} + sem.r), 0) + COALESCE(1.0/(${K2} + kw.r), 0))::float8 AS cscore FROM sem FULL OUTER JOIN kw ON sem.pid = kw.pid AND sem.chunk_seq = kw.chunk_seq)`;
|
|
838
|
+
} else if (semC !== "") {
|
|
839
|
+
fusedSrc = `WITH sem AS (${semC}), fused AS (SELECT sem.pid, sem.chunk_seq, (1.0/(${K2} + sem.r))::float8 AS cscore FROM sem)`;
|
|
840
|
+
} else {
|
|
841
|
+
fusedSrc = `WITH kw AS (${kwIn}), fused AS (SELECT kw.pid, kw.chunk_seq, (1.0/(${K2} + kw.r))::float8 AS cscore FROM kw)`;
|
|
842
|
+
}
|
|
843
|
+
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 WHERE true${whereC}`;
|
|
844
|
+
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}`;
|
|
845
|
+
};
|
|
846
|
+
let rowsC = await liveC.unsafe(assembleC(kwC, kwC !== ""), bindC);
|
|
847
|
+
if (wantTextC && kwC !== "" && (rowsC.length === 0 || rowsC[0]._kwn === 0)) {
|
|
848
|
+
const tsch = await trgmSchemaOf(liveC);
|
|
849
|
+
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 WHERE ${qpC} OPERATOR(${quoteIdent(tsch)}.<%) coalesce(c.content,'') ORDER BY r LIMIT ${cpool}`;
|
|
850
|
+
const retryBindC = bindC.slice();
|
|
851
|
+
if (qpCIdx >= 0) retryBindC[qpCIdx] = params.query;
|
|
852
|
+
rowsC = await liveC.unsafe(assembleC(trgmKwC, false), retryBindC);
|
|
853
|
+
}
|
|
854
|
+
for (const r of rowsC) delete r._kwn;
|
|
855
|
+
const outC = asTableRows(table, rowsC).map((r) => ({ ...r, blocks: r.blocks ?? [] }));
|
|
856
|
+
if (params.facets !== void 0 && params.facets.length > 0) {
|
|
857
|
+
Object.assign(outC, {
|
|
858
|
+
_facets: await fetchFacets(liveC, table, cfg.colSet, params.facets, params.where ?? {}, validityFor)
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
logNoResults(table, params.query, params.mode, outC.length);
|
|
862
|
+
return outC;
|
|
863
|
+
}
|
|
864
|
+
const wantText = params.mode !== "vector" && cfg.ftsCols.length > 0 && typeof params.query === "string" && params.query !== "";
|
|
865
|
+
const anyEmbed = cfg.legs.some((l) => l.embed !== void 0);
|
|
866
|
+
const vectorAsked = params.mode !== "text" && (Array.isArray(params.vector) || params.using !== void 0 || params.mode === "vector" || anyEmbed && typeof params.query === "string" && params.query !== "");
|
|
867
|
+
const leg = vectorAsked ? pickLeg(table, cfg.legs, params.using) : null;
|
|
868
|
+
let qv = Array.isArray(params.vector) ? params.vector : null;
|
|
869
|
+
if (qv === null && leg?.embed !== void 0 && typeof params.query === "string" && params.query !== "") {
|
|
870
|
+
try {
|
|
871
|
+
qv = await embedQuery(leg.embed, params.query);
|
|
872
|
+
} catch (e) {
|
|
873
|
+
if (!wantText) throw e;
|
|
874
|
+
qv = null;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const wantVector = leg !== null && qv !== null;
|
|
878
|
+
if (!wantText && !wantVector) {
|
|
879
|
+
throw new Error(
|
|
880
|
+
`search(${table}): ko\u015Fulabilir kol yok \u2014 metin i\xE7in 'query' (FTS beyan\u0131 gerekir), semantik i\xE7in 'vector' verin (FR-015)`
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
const bind = [];
|
|
884
|
+
const add = (v) => {
|
|
885
|
+
bind.push(v);
|
|
886
|
+
return `$${bind.length}`;
|
|
887
|
+
};
|
|
888
|
+
const userWhere = compileWhere(table, cfg.colSet, params.where ?? {}, add);
|
|
889
|
+
const whereSql = userWhere + excludeSql(cfg.pk, excl, add) + validityFor(add);
|
|
890
|
+
const live = await at();
|
|
891
|
+
const K = 50;
|
|
892
|
+
let semSql = "";
|
|
893
|
+
let kwSql = "";
|
|
894
|
+
if (wantVector && leg) {
|
|
895
|
+
const sch = await vectorSchemaWithGuc(live);
|
|
896
|
+
const op = METRIC_OPERATOR[leg.metric] ?? METRIC_OPERATOR.cosine;
|
|
897
|
+
let exactOrder = "";
|
|
898
|
+
if (userWhere !== "") {
|
|
899
|
+
const probeRows = await live.unsafe(
|
|
900
|
+
`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`,
|
|
901
|
+
bind.slice()
|
|
902
|
+
);
|
|
903
|
+
const n = probeRows?.[0]?.n;
|
|
904
|
+
if (typeof n === "number" && n <= SELECTIVITY_EXACT_THRESHOLD) {
|
|
905
|
+
exactOrder = " + 0.0";
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const vp = add(toVectorLiteral(qv));
|
|
909
|
+
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}`;
|
|
910
|
+
}
|
|
911
|
+
let qpRef = "";
|
|
912
|
+
let qpIdx = -1;
|
|
913
|
+
if (wantText) {
|
|
914
|
+
const qText = cfg.synonyms !== void 0 ? expandSynonyms(params.query, cfg.synonyms) : params.query;
|
|
915
|
+
const qp = add(qText);
|
|
916
|
+
qpIdx = bind.length - 1;
|
|
917
|
+
qpRef = qp;
|
|
918
|
+
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}`;
|
|
919
|
+
}
|
|
920
|
+
const colList = cfg.cols.map((c) => `t.${quoteIdent(c)}`).join(", ");
|
|
921
|
+
const msP = params.minScore === void 0 ? "" : add(params.minScore);
|
|
922
|
+
const hlCol = params.highlight === true && wantText ? `, ts_headline('simple', ${ftsExprOf(cfg.ftsCols)}, websearch_to_tsquery('simple', ${qpRef})) AS _highlight` : "";
|
|
923
|
+
const assemble = (kwIn, withKwn) => {
|
|
924
|
+
const kwnCol = withKwn ? `, (SELECT count(*) FROM kw)::int AS _kwn` : "";
|
|
925
|
+
let inner;
|
|
926
|
+
let orderScore;
|
|
927
|
+
if (semSql !== "" && kwIn !== "") {
|
|
928
|
+
const score = scoreMul === "" ? "fused._score" : `(fused._score)${scoreMul}`;
|
|
929
|
+
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 ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = fused.id`;
|
|
930
|
+
orderScore = scoreMul === "" ? "fused._score" : "_score";
|
|
931
|
+
} else {
|
|
932
|
+
const single = semSql !== "" ? `sem AS (${semSql})` : `kw AS (${kwIn})`;
|
|
933
|
+
const alias = semSql !== "" ? "sem" : "kw";
|
|
934
|
+
const base = `(1.0/(${K} + ${alias}.r))::float8`;
|
|
935
|
+
const score = scoreMul === "" ? base : `(${base})${scoreMul}`;
|
|
936
|
+
inner = `WITH ${single} SELECT ${colList}, ${score} AS _score${hlCol}${semSql !== "" ? "" : kwnCol} FROM ${alias} JOIN ${quoteIdent(table)} t ON t.${quoteIdent(cfg.pk)} = ${alias}.id`;
|
|
937
|
+
orderScore = "_score";
|
|
938
|
+
}
|
|
939
|
+
return msP === "" ? `${inner} ORDER BY ${orderScore} DESC, t.${quoteIdent(cfg.pk)} LIMIT ${limit}` : `SELECT * FROM (${inner}) q WHERE q._score >= ${msP} ORDER BY q._score DESC, q.${quoteIdent(cfg.pk)} LIMIT ${limit}`;
|
|
940
|
+
};
|
|
941
|
+
let rows = await live.unsafe(assemble(kwSql, kwSql !== ""), bind);
|
|
942
|
+
if (wantText && kwSql !== "" && (rows.length === 0 || rows[0]._kwn === 0)) {
|
|
943
|
+
const tsch = await trgmSchemaOf(live);
|
|
944
|
+
const expr = ftsExprOf(cfg.ftsCols);
|
|
945
|
+
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 ${qpRef} OPERATOR(${quoteIdent(tsch)}.<%) ${expr}${whereSql} ORDER BY r LIMIT ${pool}`;
|
|
946
|
+
const retryBind = bind.slice();
|
|
947
|
+
if (qpIdx >= 0) retryBind[qpIdx] = params.query;
|
|
948
|
+
rows = await live.unsafe(assemble(trgmKw, false), retryBind);
|
|
949
|
+
}
|
|
950
|
+
for (const r of rows) delete r._kwn;
|
|
951
|
+
const out = asTableRows(table, rows).map((r) => ({ ...r, blocks: [] }));
|
|
952
|
+
if (params.facets !== void 0 && params.facets.length > 0) {
|
|
953
|
+
Object.assign(out, {
|
|
954
|
+
_facets: await fetchFacets(live, table, cfg.colSet, params.facets, params.where ?? {}, validityFor)
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
logNoResults(table, params.query, params.mode, out.length);
|
|
958
|
+
return out;
|
|
959
|
+
},
|
|
960
|
+
/**
|
|
961
|
+
* similar (T018, FR-022): "bu satıra benzeyenler". Hedef vektör DB'den
|
|
962
|
+
* okunur — satır-modu satırın kendi kolonu, chunk-modu parent chunk'larının
|
|
963
|
+
* şema-nitelikli avg'ı — ve ana arama search'ün vector yoluyla koşar;
|
|
964
|
+
* kaynak satır sonuçtan düşer. İKİ tur BİLİNÇLİ (plan T018): target-CTE'li
|
|
965
|
+
* tek SQL'de "id yok" ile "0 komşu" ayrılamazdı; +1 küçük turla id-yokluğu
|
|
966
|
+
* adlandırılmış hataya çevrilir. Sonuç şekli search ile aynı (FR-015).
|
|
967
|
+
*/
|
|
968
|
+
async similar(table, id, opts = {}) {
|
|
969
|
+
const cfg = searchConfigFor(table);
|
|
970
|
+
if (!cfg) {
|
|
971
|
+
throw new Error(`similar(${table}): tablo aranabilir de\u011Fil \u2014 ne vector kolonu ne search beyan\u0131 var (FR-013)`);
|
|
972
|
+
}
|
|
973
|
+
const live = await at();
|
|
974
|
+
let v;
|
|
975
|
+
if (cfg.chunk !== void 0) {
|
|
976
|
+
const sch = await vectorSchemaWithGuc(live);
|
|
977
|
+
const rows = await live.unsafe(
|
|
978
|
+
`SELECT ${quoteIdent(sch)}.avg(c.embedding) AS v FROM ${quoteIdent(cfg.chunk.table)} c WHERE c.${quoteIdent(`parent_${cfg.pk}`)} = $1 AND c.embedding IS NOT NULL`,
|
|
979
|
+
[id]
|
|
980
|
+
);
|
|
981
|
+
v = rows?.[0]?.v;
|
|
982
|
+
} else {
|
|
983
|
+
const leg = pickLeg(table, cfg.legs, opts.using);
|
|
984
|
+
if (leg === null) {
|
|
985
|
+
throw new Error(`similar(${table}): tabloda vekt\xF6r kolonu yok \u2014 benzerlik semantik vekt\xF6r ister (FR-022)`);
|
|
986
|
+
}
|
|
987
|
+
const rows = await live.unsafe(
|
|
988
|
+
`SELECT t.${quoteIdent(leg.column)} AS v FROM ${quoteIdent(table)} t WHERE t.${quoteIdent(cfg.pk)} = $1`,
|
|
989
|
+
[id]
|
|
990
|
+
);
|
|
991
|
+
v = rows?.[0]?.v;
|
|
992
|
+
}
|
|
993
|
+
if (typeof v !== "string") {
|
|
994
|
+
throw new Error(`similar(${table}): id "${String(id)}" bulunamad\u0131 ya da embedding'i yok (FR-022)`);
|
|
995
|
+
}
|
|
996
|
+
const p = {
|
|
997
|
+
vector: JSON.parse(v),
|
|
998
|
+
mode: "vector",
|
|
999
|
+
where: opts.where,
|
|
1000
|
+
limit: opts.limit,
|
|
1001
|
+
using: opts.using,
|
|
1002
|
+
minScore: opts.minScore,
|
|
1003
|
+
recency: opts.recency,
|
|
1004
|
+
blocksPerRow: opts.blocksPerRow,
|
|
1005
|
+
validity: opts.validity,
|
|
1006
|
+
boost: opts.boost,
|
|
1007
|
+
__excludeIds: [id]
|
|
1008
|
+
};
|
|
1009
|
+
return ops.search(table, p);
|
|
1010
|
+
},
|
|
1011
|
+
/**
|
|
1012
|
+
* recommend (T018, FR-023): positive/negative id kümelerinden öneri.
|
|
1013
|
+
* Hedef vektör DB-İÇİ CTE'lerle türer: pos = avg(embedding of positives),
|
|
1014
|
+
* negative varsa hedef = pos.v + (pos.v - neg.v) — pgvector avg agregası
|
|
1015
|
+
* ve +/- operatörleri ŞEMA-NİTELİKLİ (M-1: search_path'e güvenilmez).
|
|
1016
|
+
* Vektör matematiği istemciye inmez; bulunamayan positive/negative
|
|
1017
|
+
* adlandırılmış hatadır ve kaynak id'ler sonuçtan düşer.
|
|
1018
|
+
*/
|
|
1019
|
+
async recommend(table, opts) {
|
|
1020
|
+
const positive = opts?.positive;
|
|
1021
|
+
if (!Array.isArray(positive) || positive.length === 0) {
|
|
1022
|
+
throw new Error(`recommend(${table}): positive bo\u015F olamaz \u2014 en az bir kaynak id gerekir (FR-023)`);
|
|
1023
|
+
}
|
|
1024
|
+
const negative = Array.isArray(opts.negative) ? opts.negative : [];
|
|
1025
|
+
const cfg = searchConfigFor(table);
|
|
1026
|
+
if (!cfg) {
|
|
1027
|
+
throw new Error(`recommend(${table}): tablo aranabilir de\u011Fil \u2014 ne vector kolonu ne search beyan\u0131 var (FR-013)`);
|
|
1028
|
+
}
|
|
1029
|
+
let leg = null;
|
|
1030
|
+
if (cfg.chunk === void 0) {
|
|
1031
|
+
leg = pickLeg(table, cfg.legs, opts.using);
|
|
1032
|
+
if (leg === null) {
|
|
1033
|
+
throw new Error(`recommend(${table}): tabloda vekt\xF6r kolonu yok \u2014 \xF6neri semantik vekt\xF6r ister (FR-023)`);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
const live = await at();
|
|
1037
|
+
const sch = await vectorSchemaWithGuc(live);
|
|
1038
|
+
const bind = [];
|
|
1039
|
+
const add = (x) => {
|
|
1040
|
+
bind.push(x);
|
|
1041
|
+
return `$${bind.length}`;
|
|
1042
|
+
};
|
|
1043
|
+
const avgSel = (ids) => {
|
|
1044
|
+
const inList = ids.map((x) => add(x)).join(", ");
|
|
1045
|
+
return cfg.chunk !== void 0 ? `SELECT ${quoteIdent(sch)}.avg(c.embedding) AS v FROM ${quoteIdent(cfg.chunk.table)} c WHERE c.${quoteIdent(`parent_${cfg.pk}`)} IN (${inList}) AND c.embedding IS NOT NULL` : `SELECT ${quoteIdent(sch)}.avg(t.${quoteIdent(leg.column)}) AS v FROM ${quoteIdent(table)} t WHERE t.${quoteIdent(cfg.pk)} IN (${inList}) AND t.${quoteIdent(leg.column)} IS NOT NULL`;
|
|
1046
|
+
};
|
|
1047
|
+
const targetSql = negative.length === 0 ? `WITH pos AS (${avgSel(positive)}) SELECT pos.v AS v, (pos.v IS NULL) AS pos_missing FROM pos` : `WITH pos AS (${avgSel(positive)}), neg AS (${avgSel(negative)}) SELECT (pos.v OPERATOR(${quoteIdent(sch)}.+) (pos.v OPERATOR(${quoteIdent(sch)}.-) neg.v)) AS v, (pos.v IS NULL) AS pos_missing, (neg.v IS NULL) AS neg_missing FROM pos, neg`;
|
|
1048
|
+
const rows = await live.unsafe(targetSql, bind);
|
|
1049
|
+
const r0 = rows?.[0];
|
|
1050
|
+
if (r0 === void 0 || r0.pos_missing === true) {
|
|
1051
|
+
throw new Error(`recommend(${table}): positive id'lerin hi\xE7biri bulunamad\u0131 ya da embedding'i yok (FR-023)`);
|
|
1052
|
+
}
|
|
1053
|
+
if (negative.length > 0 && r0.neg_missing === true) {
|
|
1054
|
+
throw new Error(`recommend(${table}): negative id'leri bulunamad\u0131 ya da embedding'i yok (FR-023)`);
|
|
1055
|
+
}
|
|
1056
|
+
if (typeof r0.v !== "string") {
|
|
1057
|
+
throw new Error(`recommend(${table}): hedef vekt\xF6r hesaplanamad\u0131 (FR-023)`);
|
|
1058
|
+
}
|
|
1059
|
+
const p = {
|
|
1060
|
+
vector: JSON.parse(r0.v),
|
|
1061
|
+
mode: "vector",
|
|
1062
|
+
where: opts.where,
|
|
1063
|
+
limit: opts.limit,
|
|
1064
|
+
using: opts.using,
|
|
1065
|
+
minScore: opts.minScore,
|
|
1066
|
+
recency: opts.recency,
|
|
1067
|
+
blocksPerRow: opts.blocksPerRow,
|
|
1068
|
+
validity: opts.validity,
|
|
1069
|
+
boost: opts.boost,
|
|
1070
|
+
__excludeIds: [...positive, ...negative]
|
|
1071
|
+
};
|
|
1072
|
+
return ops.search(table, p);
|
|
1073
|
+
},
|
|
1074
|
+
/**
|
|
1075
|
+
* supersede (T021, FR-029, C-9): validity'li tabloda satırın YENİ
|
|
1076
|
+
* versiyonunu TEK savepoint'te yazar — eski satır kapatılır
|
|
1077
|
+
* (valid_to = now(), superseded_by = yeni pk), yeni satır eklenir, dönüş
|
|
1078
|
+
* yeni satırdır. Yeni pk CLIENT'ta üretilir (row'da verilmemişse
|
|
1079
|
+
* randomUUID — declared şemaların uuid().defaultRandom() standardı) ki
|
|
1080
|
+
* kapatma UPDATE'i INSERT'ten ÖNCE koşabilsin: 0 satır = zaten superseded
|
|
1081
|
+
* ya da yok → INSERT hiç denenmez; INSERT hatası ise savepoint'le
|
|
1082
|
+
* kapatmayı da geri sarar — yarım supersede diye bir durum yoktur.
|
|
1083
|
+
*/
|
|
1084
|
+
async supersede(table, id, row) {
|
|
1085
|
+
const cfg = searchConfigFor(table);
|
|
1086
|
+
if (cfg === null || cfg.validity !== true) {
|
|
1087
|
+
throw new Error(`supersede(${table}): tablo validity beyan\u0131 ta\u015F\u0131m\u0131yor (FR-029)`);
|
|
1088
|
+
}
|
|
1089
|
+
const live = await at();
|
|
1090
|
+
return live.savepoint(async (sp) => {
|
|
1091
|
+
const newId = row[cfg.pk] ?? crypto.randomUUID();
|
|
1092
|
+
const closed = await sp.unsafe(
|
|
1093
|
+
`UPDATE ${quoteIdent(table)} SET "valid_to" = now(), "superseded_by" = $1 WHERE ${quoteIdent(cfg.pk)} = $2 AND "valid_to" IS NULL RETURNING ${quoteIdent(cfg.pk)}`,
|
|
1094
|
+
[newId, id]
|
|
1095
|
+
);
|
|
1096
|
+
if (closed.length === 0) {
|
|
1097
|
+
throw new Error(`supersede(${table}): id "${String(id)}" zaten superseded ya da yok (FR-029)`);
|
|
1098
|
+
}
|
|
1099
|
+
const data = { ...row, [cfg.pk]: newId };
|
|
1100
|
+
const cols = Object.keys(data);
|
|
1101
|
+
const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
|
|
1102
|
+
const rows = await sp.unsafe(
|
|
1103
|
+
`INSERT INTO ${quoteIdent(table)} (${cols.map(quoteIdent).join(", ")}) VALUES (${placeholders}) RETURNING *`,
|
|
1104
|
+
asBindParams(table, cols, data)
|
|
1105
|
+
);
|
|
1106
|
+
if (!rows[0]) {
|
|
1107
|
+
throw new Error(
|
|
1108
|
+
`supersede(${table}): INSERT sat\u0131r d\xF6nd\xFCrmedi \u2014 yazma reddedildi (b\xFCy\xFCk olas\u0131l\u0131kla bir RLS policy'si)`
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
return asTableRow(table, rows[0]);
|
|
1112
|
+
});
|
|
1113
|
+
},
|
|
431
1114
|
/** A real SAVEPOINT inside the request's transaction. */
|
|
432
1115
|
async transaction(cb) {
|
|
433
1116
|
const live = await at();
|
|
@@ -456,7 +1139,7 @@ function createOps(tx) {
|
|
|
456
1139
|
return live.savepoint(async (sp) => {
|
|
457
1140
|
const results = [];
|
|
458
1141
|
for (const op of plan.ops) {
|
|
459
|
-
const rows = await runPlanOp(sp, op, results);
|
|
1142
|
+
const rows = asTableRows(op.table, await runPlanOp(sp, op, results));
|
|
460
1143
|
const result = { rows, rows_affected: rows.length };
|
|
461
1144
|
results.push(result);
|
|
462
1145
|
assertGuard(op, result);
|
|
@@ -469,6 +1152,8 @@ function createOps(tx) {
|
|
|
469
1152
|
}
|
|
470
1153
|
var currentSchema = {};
|
|
471
1154
|
function setSchema(schema) {
|
|
1155
|
+
cachedVectorSchema = null;
|
|
1156
|
+
cachedTrgmSchema = null;
|
|
472
1157
|
const s = schema;
|
|
473
1158
|
currentSchema = (s && "default" in s ? s.default : s) ?? {};
|
|
474
1159
|
}
|
|
@@ -555,7 +1240,7 @@ function isRef(v) {
|
|
|
555
1240
|
function isExpr(v) {
|
|
556
1241
|
return typeof v === "object" && v !== null && "$expr" in v;
|
|
557
1242
|
}
|
|
558
|
-
function renderValue(value, column, args, results) {
|
|
1243
|
+
function renderValue(value, column, args, results, vectorCols) {
|
|
559
1244
|
if (isRef(value)) {
|
|
560
1245
|
const source = results[value.$ref.op];
|
|
561
1246
|
const row = source?.rows[0];
|
|
@@ -564,15 +1249,15 @@ function renderValue(value, column, args, results) {
|
|
|
564
1249
|
error_code: "tx_ref_unresolved"
|
|
565
1250
|
});
|
|
566
1251
|
}
|
|
567
|
-
return args
|
|
1252
|
+
return bindMaybeVector(args, column, vectorCols, row[value.$ref.field]);
|
|
568
1253
|
}
|
|
569
1254
|
if (isExpr(value)) {
|
|
570
1255
|
const fn = value.$expr;
|
|
571
1256
|
if (fn.fn === "now") return "now()";
|
|
572
1257
|
const operator = fn.fn === "inc" ? "+" : "-";
|
|
573
|
-
return `${quoteIdent(column)} ${operator} ${args
|
|
1258
|
+
return `${quoteIdent(column)} ${operator} ${bindMaybeVector(args, column, vectorCols, fn.by)}`;
|
|
574
1259
|
}
|
|
575
|
-
return args
|
|
1260
|
+
return bindMaybeVector(args, column, vectorCols, value);
|
|
576
1261
|
}
|
|
577
1262
|
function renderWhere(where, args, results) {
|
|
578
1263
|
const cols = Object.keys(where ?? {});
|
|
@@ -584,14 +1269,21 @@ function renderWhere(where, args, results) {
|
|
|
584
1269
|
});
|
|
585
1270
|
return ` WHERE ${terms.join(" AND ")}`;
|
|
586
1271
|
}
|
|
1272
|
+
function bindMaybeVector(args, column, vectorCols, value) {
|
|
1273
|
+
if (column !== void 0 && vectorCols?.has(column) && Array.isArray(value)) {
|
|
1274
|
+
return args.bind(toVectorLiteral(value));
|
|
1275
|
+
}
|
|
1276
|
+
return args.bind(value);
|
|
1277
|
+
}
|
|
587
1278
|
async function runPlanOp(sp, op, results) {
|
|
1279
|
+
const opVectorCols = vectorColumnsOf(currentSchema, op.table);
|
|
588
1280
|
const args = new Args();
|
|
589
1281
|
const table = quoteIdent(op.table);
|
|
590
1282
|
let sql;
|
|
591
1283
|
switch (op.op) {
|
|
592
1284
|
case "insert": {
|
|
593
1285
|
const cols = Object.keys(op.values ?? {});
|
|
594
|
-
const rendered = cols.map((c) => renderValue(op.values[c], c, args, results));
|
|
1286
|
+
const rendered = cols.map((c) => renderValue(op.values[c], c, args, results, opVectorCols));
|
|
595
1287
|
sql = cols.length ? `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES (${rendered.join(", ")}) RETURNING *` : `INSERT INTO ${table} DEFAULT VALUES RETURNING *`;
|
|
596
1288
|
break;
|
|
597
1289
|
}
|
|
@@ -600,7 +1292,7 @@ async function runPlanOp(sp, op, results) {
|
|
|
600
1292
|
if (rows.length === 0 || !rows[0]) return [];
|
|
601
1293
|
const cols = Object.keys(rows[0]);
|
|
602
1294
|
const tuples = rows.map(
|
|
603
|
-
(r) => `(${cols.map((c) => renderValue(r[c], c, args, results)).join(", ")})`
|
|
1295
|
+
(r) => `(${cols.map((c) => renderValue(r[c], c, args, results, opVectorCols)).join(", ")})`
|
|
604
1296
|
);
|
|
605
1297
|
sql = `INSERT INTO ${table} (${cols.map(quoteIdent).join(", ")}) VALUES ${tuples.join(", ")} RETURNING *`;
|
|
606
1298
|
break;
|
|
@@ -609,7 +1301,7 @@ async function runPlanOp(sp, op, results) {
|
|
|
609
1301
|
const cols = Object.keys(op.set ?? {});
|
|
610
1302
|
if (cols.length === 0) throw new Error(`update ${op.table}: nothing to set`);
|
|
611
1303
|
const assignments = cols.map(
|
|
612
|
-
(c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results)}`
|
|
1304
|
+
(c) => `${quoteIdent(c)} = ${renderValue(op.set[c], c, args, results, opVectorCols)}`
|
|
613
1305
|
);
|
|
614
1306
|
sql = `UPDATE ${table} SET ${assignments.join(", ")}${renderWhere(op.where, args, results)} RETURNING *`;
|
|
615
1307
|
break;
|
|
@@ -720,7 +1412,8 @@ function grantFor(entry, ctx, bucketLimits) {
|
|
|
720
1412
|
filename: ctx.filename
|
|
721
1413
|
}),
|
|
722
1414
|
maxBytes: bucketLimits?.maxBytes ?? null,
|
|
723
|
-
mimeTypes: bucketLimits?.mimeTypes ?? null
|
|
1415
|
+
mimeTypes: bucketLimits?.mimeTypes ?? null,
|
|
1416
|
+
ownerUid: ctx.userId ?? null
|
|
724
1417
|
};
|
|
725
1418
|
}
|
|
726
1419
|
var CompletionLedger = class {
|
|
@@ -817,6 +1510,23 @@ function installEgressFence(policy) {
|
|
|
817
1510
|
|
|
818
1511
|
// src/engine/index.ts
|
|
819
1512
|
var JSON_HEADERS = { "content-type": "application/json" };
|
|
1513
|
+
function fieldErrors(err) {
|
|
1514
|
+
return err.issues.map((i) => ({ field: i.path.join("."), message: i.message }));
|
|
1515
|
+
}
|
|
1516
|
+
function headersFor(raw, schema) {
|
|
1517
|
+
const shape = schema.shape;
|
|
1518
|
+
if (typeof shape !== "object" || shape === null) return raw;
|
|
1519
|
+
let aliased = null;
|
|
1520
|
+
for (const declared of Object.keys(shape)) {
|
|
1521
|
+
const lower = declared.toLowerCase();
|
|
1522
|
+
if (lower === declared) continue;
|
|
1523
|
+
const value = raw[lower];
|
|
1524
|
+
if (value === void 0) continue;
|
|
1525
|
+
aliased ??= { ...raw };
|
|
1526
|
+
aliased[declared] = value;
|
|
1527
|
+
}
|
|
1528
|
+
return aliased ?? raw;
|
|
1529
|
+
}
|
|
820
1530
|
function envelope(error, description, status, requestId, extra) {
|
|
821
1531
|
return new Response(
|
|
822
1532
|
JSON.stringify({ error, error_description: description, status, request_id: requestId, ...extra }),
|
|
@@ -850,6 +1560,7 @@ async function defaultSqlDriver(config) {
|
|
|
850
1560
|
async function createApp(opts) {
|
|
851
1561
|
const { config, controllers } = opts;
|
|
852
1562
|
setSchema(opts.schema ?? {});
|
|
1563
|
+
setSecretReader(opts.secretReader ?? null);
|
|
853
1564
|
const routes = buildRouteTable(controllers);
|
|
854
1565
|
if (routes.length === 0) {
|
|
855
1566
|
throw new BootRefused([], "boot refused: zero endpoints collected \u2014 nothing would answer.");
|
|
@@ -875,7 +1586,6 @@ async function createApp(opts) {
|
|
|
875
1586
|
Notifications: modules.Notifications ?? stubModule("Notifications"),
|
|
876
1587
|
Flags: modules.Flags ?? stubModule("Flags"),
|
|
877
1588
|
Realtime: modules.Realtime ?? stubModule("Realtime"),
|
|
878
|
-
Purchases: modules.Purchases ?? stubModule("Purchases"),
|
|
879
1589
|
// Named, never undefined. A backend started without a secrets client
|
|
880
1590
|
// that returned `undefined` here would fail inside the handler as
|
|
881
1591
|
// "Cannot read properties of undefined", which says nothing about what
|
|
@@ -962,7 +1672,8 @@ async function createApp(opts) {
|
|
|
962
1672
|
error: "too_many_requests",
|
|
963
1673
|
error_description: "Rate limit exceeded for this endpoint",
|
|
964
1674
|
status: 429,
|
|
965
|
-
request_id: requestId
|
|
1675
|
+
request_id: requestId,
|
|
1676
|
+
data: { retryAfter }
|
|
966
1677
|
}),
|
|
967
1678
|
{ status: 429, headers: { ...JSON_HEADERS, "retry-after": String(retryAfter) } }
|
|
968
1679
|
);
|
|
@@ -981,7 +1692,7 @@ async function createApp(opts) {
|
|
|
981
1692
|
const r = p.schema.safeParse(parsedBody);
|
|
982
1693
|
if (!r.success) {
|
|
983
1694
|
return envelope("bad_request", "Request body failed validation", 400, requestId, {
|
|
984
|
-
|
|
1695
|
+
data: { fields: fieldErrors(r.error) }
|
|
985
1696
|
});
|
|
986
1697
|
}
|
|
987
1698
|
args[p.index] = r.data;
|
|
@@ -991,7 +1702,7 @@ async function createApp(opts) {
|
|
|
991
1702
|
const r = p.schema.safeParse(Object.fromEntries(url.searchParams));
|
|
992
1703
|
if (!r.success) {
|
|
993
1704
|
return envelope("bad_request", "Query parameters failed validation", 400, requestId, {
|
|
994
|
-
|
|
1705
|
+
data: { fields: fieldErrors(r.error) }
|
|
995
1706
|
});
|
|
996
1707
|
}
|
|
997
1708
|
args[p.index] = r.data;
|
|
@@ -1000,9 +1711,21 @@ async function createApp(opts) {
|
|
|
1000
1711
|
case "param":
|
|
1001
1712
|
args[p.index] = hit.params[p.name];
|
|
1002
1713
|
break;
|
|
1003
|
-
case "headers":
|
|
1004
|
-
|
|
1714
|
+
case "headers": {
|
|
1715
|
+
const raw = Object.fromEntries(req.headers);
|
|
1716
|
+
if (!p.schema) {
|
|
1717
|
+
args[p.index] = raw;
|
|
1718
|
+
break;
|
|
1719
|
+
}
|
|
1720
|
+
const r = p.schema.safeParse(headersFor(raw, p.schema));
|
|
1721
|
+
if (!r.success) {
|
|
1722
|
+
return envelope("bad_request", "Request headers failed validation", 400, requestId, {
|
|
1723
|
+
data: { fields: fieldErrors(r.error) }
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
args[p.index] = r.data;
|
|
1005
1727
|
break;
|
|
1728
|
+
}
|
|
1006
1729
|
case "user":
|
|
1007
1730
|
case "optionalUser":
|
|
1008
1731
|
args[p.index] = claims ? {
|
|
@@ -1051,6 +1774,14 @@ async function createApp(opts) {
|
|
|
1051
1774
|
case "traceId":
|
|
1052
1775
|
args[p.index] = requestId;
|
|
1053
1776
|
break;
|
|
1777
|
+
case "client":
|
|
1778
|
+
args[p.index] = {
|
|
1779
|
+
sdkVersion: req.headers.get("x-palbase-sdk-version"),
|
|
1780
|
+
appVersion: req.headers.get("x-palbase-client-version"),
|
|
1781
|
+
platform: req.headers.get("x-platform"),
|
|
1782
|
+
osVersion: req.headers.get("x-os-version")
|
|
1783
|
+
};
|
|
1784
|
+
break;
|
|
1054
1785
|
case "req":
|
|
1055
1786
|
args[p.index] = req;
|
|
1056
1787
|
break;
|
|
@@ -1155,4 +1886,4 @@ export {
|
|
|
1155
1886
|
installEgressFence,
|
|
1156
1887
|
createApp
|
|
1157
1888
|
};
|
|
1158
|
-
//# sourceMappingURL=chunk-
|
|
1889
|
+
//# sourceMappingURL=chunk-REZU6UKT.js.map
|