@vibeorm/extensions 2.3.2 → 4.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/LICENSE +21 -0
- package/README.md +2 -0
- package/dist/index.js +45 -30
- package/dist/index.js.map +4 -4
- package/dist/search/index.js +41 -27
- package/dist/search/index.js.map +3 -3
- package/dist/vector/index.js +5 -4
- package/dist/vector/index.js.map +3 -3
- package/package.json +7 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/search/index.d.ts.map +0 -1
- package/dist/shared.d.ts.map +0 -1
- package/dist/trgm/index.d.ts.map +0 -1
- package/dist/vector/index.d.ts.map +0 -1
package/dist/search/index.js
CHANGED
|
@@ -84,7 +84,7 @@ async function pgExtensionCheck(params) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// packages/extensions/src/search/index.ts
|
|
87
|
-
import { ParamCollector, getSqlDialect, renderExpr } from "@vibeorm/sql";
|
|
87
|
+
import { ParamCollector, getSqlDialect, renderExpr, renderSqliteFtsUpdateTrigger } from "@vibeorm/sql";
|
|
88
88
|
var VERSION = "2.0.0-alpha.2";
|
|
89
89
|
var SCORE_ALIAS = "_vibe_score";
|
|
90
90
|
var HIGHLIGHT_ALIAS = "_vibe_highlight";
|
|
@@ -327,6 +327,17 @@ function readDeclaration(params) {
|
|
|
327
327
|
filterFields: declStringArray(params.declaration, "filterFields")
|
|
328
328
|
};
|
|
329
329
|
}
|
|
330
|
+
function resolveLanguage(params) {
|
|
331
|
+
const language = params.decl.language ?? params.options["language"] ?? "english";
|
|
332
|
+
if (!isSafeSqlName({ name: language })) {
|
|
333
|
+
throw new VibeError2({
|
|
334
|
+
code: "VIBE_SCHEMA_INVALID",
|
|
335
|
+
message: `search language "${language}" is not a plain text-search config name`,
|
|
336
|
+
meta: { model: params.model.name, language }
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
return language;
|
|
340
|
+
}
|
|
330
341
|
function weightLetters(params) {
|
|
331
342
|
const { decl } = params;
|
|
332
343
|
if (decl.weights === undefined || decl.weights.length !== decl.fields.length)
|
|
@@ -438,12 +449,12 @@ function searchArtifacts(ctx) {
|
|
|
438
449
|
const decl = readDeclaration({ model, declaration });
|
|
439
450
|
if (decl === null)
|
|
440
451
|
continue;
|
|
452
|
+
const language = resolveLanguage({ model, decl, options: ctx.options });
|
|
441
453
|
const table = tableNameOf({ model });
|
|
442
454
|
const columns = decl.fields.map((field) => columnNameOf({ model, field }));
|
|
443
455
|
resolveRank({ decl, options: ctx.options, engine, dialect: ctx.dialect });
|
|
444
456
|
assertBm25Declarations({ decl, engine, dialect: ctx.dialect });
|
|
445
457
|
if (engine === "tsvector") {
|
|
446
|
-
const language = decl.language ?? ctx.options["language"] ?? "english";
|
|
447
458
|
const { tsvColumn, indexName } = resolveArtifactNames({ decl, engine, dialect: ctx.dialect });
|
|
448
459
|
const expression = tsvectorExpression({ dialect, model, decl, language });
|
|
449
460
|
artifacts.push({
|
|
@@ -501,6 +512,7 @@ function searchArtifacts(ctx) {
|
|
|
501
512
|
const columnList = columns.map((column) => quote(column)).join(", ");
|
|
502
513
|
const newList = columns.map((column) => `new.${quote(column)}`).join(", ");
|
|
503
514
|
const oldList = columns.map((column) => `old.${quote(column)}`).join(", ");
|
|
515
|
+
const updateTrigger = renderSqliteFtsUpdateTrigger({ table, ftsTable: fts, columns });
|
|
504
516
|
artifacts.push({
|
|
505
517
|
id: `search:${modelName}:fts5-table`,
|
|
506
518
|
owns: {
|
|
@@ -522,10 +534,7 @@ END;`,
|
|
|
522
534
|
`CREATE TRIGGER IF NOT EXISTS ${quote(`${fts}_ad`)} AFTER DELETE ON ${quote(table)} BEGIN
|
|
523
535
|
INSERT INTO ${quote(fts)}(${quote(fts)}, rowid, ${columnList}) VALUES ('delete', old.rowid, ${oldList});
|
|
524
536
|
END;`,
|
|
525
|
-
|
|
526
|
-
INSERT INTO ${quote(fts)}(${quote(fts)}, rowid, ${columnList}) VALUES ('delete', old.rowid, ${oldList});
|
|
527
|
-
INSERT INTO ${quote(fts)}(rowid, ${columnList}) VALUES (new.rowid, ${newList});
|
|
528
|
-
END;`,
|
|
537
|
+
updateTrigger.create,
|
|
529
538
|
`INSERT INTO ${quote(fts)}(${quote(fts)}) VALUES ('rebuild');`
|
|
530
539
|
],
|
|
531
540
|
drop: [
|
|
@@ -533,6 +542,17 @@ END;`,
|
|
|
533
542
|
`DROP TRIGGER IF EXISTS ${quote(`${fts}_ad`)};`,
|
|
534
543
|
`DROP TRIGGER IF EXISTS ${quote(`${fts}_au`)};`
|
|
535
544
|
]
|
|
545
|
+
}, {
|
|
546
|
+
id: `search:${modelName}:fts5-update-trigger`,
|
|
547
|
+
owns: { kind: "trigger", table, name: `${fts}_au` },
|
|
548
|
+
expected: {
|
|
549
|
+
identity: { kind: "trigger", table: { namespace: "main", name: table }, name: `${fts}_au` },
|
|
550
|
+
facts: { definition: updateTrigger.definition, type: "trigger" },
|
|
551
|
+
dependencies: [],
|
|
552
|
+
format: "catalog-v2"
|
|
553
|
+
},
|
|
554
|
+
create: [],
|
|
555
|
+
drop: []
|
|
536
556
|
});
|
|
537
557
|
continue;
|
|
538
558
|
}
|
|
@@ -563,15 +583,13 @@ function highlightColumn(params) {
|
|
|
563
583
|
return field.column;
|
|
564
584
|
}
|
|
565
585
|
function shapeHits(params) {
|
|
566
|
-
const { rows,
|
|
586
|
+
const { rows, highlightsRequested, engineHasHighlights } = params;
|
|
567
587
|
return rows.map((raw) => {
|
|
568
588
|
const row = { ...raw };
|
|
569
589
|
const score = Number(row[SCORE_ALIAS]);
|
|
570
590
|
const highlight = row[HIGHLIGHT_ALIAS];
|
|
571
591
|
delete row[SCORE_ALIAS];
|
|
572
592
|
delete row[HIGHLIGHT_ALIAS];
|
|
573
|
-
for (const name of strip)
|
|
574
|
-
delete row[name];
|
|
575
593
|
const highlights = engineHasHighlights && highlightsRequested && highlight !== null && highlight !== undefined ? [String(highlight)] : null;
|
|
576
594
|
return { row, score, highlights };
|
|
577
595
|
});
|
|
@@ -581,6 +599,7 @@ function createSearchDelegate(config) {
|
|
|
581
599
|
const dialect = ctx.dialect;
|
|
582
600
|
const quote = (name) => dialect.quoteIdent({ name });
|
|
583
601
|
const t = quote("t");
|
|
602
|
+
const pkColumns = ctx.model === null ? config.pkColumn === undefined || config.pkColumn === "" ? [] : [config.pkColumn] : ctx.model.primaryKey.map((field) => field.columnName);
|
|
584
603
|
return async (args) => {
|
|
585
604
|
if (typeof args.query !== "string" || args.query.trim().length === 0) {
|
|
586
605
|
throw new VibeError2({
|
|
@@ -632,7 +651,14 @@ function createSearchDelegate(config) {
|
|
|
632
651
|
meta: { table: config.table }
|
|
633
652
|
});
|
|
634
653
|
}
|
|
635
|
-
|
|
654
|
+
if (pkColumns.length === 0) {
|
|
655
|
+
throw new VibeError2({
|
|
656
|
+
code: "VIBE_CONFIG",
|
|
657
|
+
message: `search needs primary-key metadata for deterministic ordering: ${config.table} has no model key, or the legacy mount has no explicit pkColumn`,
|
|
658
|
+
meta: { table: config.table }
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
const projection = idOnly ? `${t}.${quote(config.pkColumn ?? "")}` : ctx.projection({ qualify: "t" });
|
|
636
662
|
const collector = new ParamCollector(dialect);
|
|
637
663
|
const userWhere = (qualify) => {
|
|
638
664
|
if (args.where === undefined)
|
|
@@ -641,13 +667,13 @@ function createSearchDelegate(config) {
|
|
|
641
667
|
return expr === undefined ? null : renderExpr({ dialect, expr, collector });
|
|
642
668
|
};
|
|
643
669
|
let text;
|
|
644
|
-
let strip = [];
|
|
645
670
|
let engineHasHighlights = true;
|
|
646
671
|
if (config.engine === "tsvector") {
|
|
647
672
|
const language = config.language ?? "english";
|
|
648
673
|
const ranker = config.rank ?? "ts_rank_cd";
|
|
649
674
|
const tsv = `${t}.${quote(config.tsvColumn ?? "")}`;
|
|
650
|
-
const
|
|
675
|
+
const queryParameter = dialect.name === "postgres" ? collector.add(matchText) : undefined;
|
|
676
|
+
const tsquery = () => prefix ? `to_tsquery('${language}', ${queryParameter ?? collector.add(matchText)})` : `websearch_to_tsquery('${language}', ${queryParameter ?? collector.add(matchText)})`;
|
|
651
677
|
text = `SELECT ${projection}, ${ranker}(${tsv}, ${tsquery()}) AS ${quote(SCORE_ALIAS)}`;
|
|
652
678
|
if (hlColumn !== null) {
|
|
653
679
|
text += `, ts_headline('${language}', ${t}.${quote(hlColumn)}, ${tsquery()}) AS ${quote(HIGHLIGHT_ALIAS)}`;
|
|
@@ -659,8 +685,6 @@ function createSearchDelegate(config) {
|
|
|
659
685
|
if (args.minScore !== undefined) {
|
|
660
686
|
text += ` AND ${ranker}(${tsv}, ${tsquery()}) >= ${collector.add(args.minScore)}`;
|
|
661
687
|
}
|
|
662
|
-
text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
|
|
663
|
-
strip = idOnly ? [] : [config.tsvColumn ?? ""];
|
|
664
688
|
} else if (config.engine === "fts5") {
|
|
665
689
|
const fts = quote(config.ftsTable ?? "");
|
|
666
690
|
text = `SELECT ${projection}, -bm25(${fts}) AS ${quote(SCORE_ALIAS)}`;
|
|
@@ -676,7 +700,6 @@ function createSearchDelegate(config) {
|
|
|
676
700
|
if (args.minScore !== undefined) {
|
|
677
701
|
text += ` AND -bm25(${fts}) >= ${collector.add(args.minScore)}`;
|
|
678
702
|
}
|
|
679
|
-
text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
|
|
680
703
|
} else if (config.engine === "fulltext") {
|
|
681
704
|
engineHasHighlights = false;
|
|
682
705
|
const mode = prefix ? "IN BOOLEAN MODE" : "IN NATURAL LANGUAGE MODE";
|
|
@@ -689,7 +712,6 @@ function createSearchDelegate(config) {
|
|
|
689
712
|
if (args.minScore !== undefined) {
|
|
690
713
|
text += ` AND ${match()} >= ${collector.add(args.minScore)}`;
|
|
691
714
|
}
|
|
692
|
-
text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
|
|
693
715
|
} else {
|
|
694
716
|
const pk = `${t}.${quote(config.pkColumn ?? "")}`;
|
|
695
717
|
const operator = prefix || fuzzy !== undefined ? "&&&" : "@@@";
|
|
@@ -709,15 +731,14 @@ function createSearchDelegate(config) {
|
|
|
709
731
|
if (args.minScore !== undefined) {
|
|
710
732
|
text += ` AND paradedb.score(${pk}) >= ${collector.add(args.minScore)}`;
|
|
711
733
|
}
|
|
712
|
-
text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
|
|
713
734
|
}
|
|
735
|
+
text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC, ${pkColumns.map((column) => `${t}.${quote(column)} ASC`).join(", ")}`;
|
|
714
736
|
text += ` LIMIT ${take}`;
|
|
715
737
|
if (skip !== null)
|
|
716
738
|
text += ` OFFSET ${skip}`;
|
|
717
739
|
const rows = await ctx.executeRows({ text, values: collector.values, method: "search" });
|
|
718
740
|
return shapeHits({
|
|
719
741
|
rows,
|
|
720
|
-
strip,
|
|
721
742
|
highlightsRequested: args.highlight !== undefined,
|
|
722
743
|
engineHasHighlights
|
|
723
744
|
});
|
|
@@ -794,14 +815,7 @@ function search(options = {}) {
|
|
|
794
815
|
return null;
|
|
795
816
|
const requested = extOptions["engine"] ?? "auto";
|
|
796
817
|
const engine = resolveSearchEngine({ dialect, provider, requested });
|
|
797
|
-
const language = decl
|
|
798
|
-
if (!isSafeSqlName({ name: language })) {
|
|
799
|
-
throw new VibeError2({
|
|
800
|
-
code: "VIBE_SCHEMA_INVALID",
|
|
801
|
-
message: `search language "${language}" is not a plain text-search config name`,
|
|
802
|
-
meta: { model: model.name, language }
|
|
803
|
-
});
|
|
804
|
-
}
|
|
818
|
+
const language = resolveLanguage({ model, decl, options: extOptions });
|
|
805
819
|
const pkField = primaryKeyFieldOf({ model });
|
|
806
820
|
const rank = resolveRank({ decl, options: extOptions, engine, dialect });
|
|
807
821
|
assertBm25Declarations({ decl, engine, dialect });
|
|
@@ -898,4 +912,4 @@ export {
|
|
|
898
912
|
search
|
|
899
913
|
};
|
|
900
914
|
|
|
901
|
-
//# debugId=
|
|
915
|
+
//# debugId=8DD793DC3166C4C964756E2164756E21
|