@vibeorm/extensions 2.3.1 → 3.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 +36 -26
- package/dist/index.js.map +4 -4
- package/dist/search/index.js +32 -23
- 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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VibeORM contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -73,6 +73,8 @@ const hits = await db.post.search({
|
|
|
73
73
|
|
|
74
74
|
`highlights` is `readonly string[] | null`; it is `null` when no highlight was requested and on engines without highlighting.
|
|
75
75
|
|
|
76
|
+
`row` holds the model's columns, read exactly as `findMany` reads them, for `search` and for vector `nearest`. Generated artifact columns (the tsvector column) and `Unsupported(...)` columns such as the vector itself are not model fields and are not returned.
|
|
77
|
+
|
|
76
78
|
### Engines per dialect
|
|
77
79
|
|
|
78
80
|
`engine: "auto"` (the default) picks the dialect's native engine. `resolveSearchEngine({ dialect, provider, requested })` is the exported resolution rule.
|
package/dist/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
|
});
|
|
@@ -632,7 +650,7 @@ function createSearchDelegate(config) {
|
|
|
632
650
|
meta: { table: config.table }
|
|
633
651
|
});
|
|
634
652
|
}
|
|
635
|
-
const projection = idOnly ? `${t}.${quote(config.pkColumn ?? "")}` :
|
|
653
|
+
const projection = idOnly ? `${t}.${quote(config.pkColumn ?? "")}` : ctx.projection({ qualify: "t" });
|
|
636
654
|
const collector = new ParamCollector(dialect);
|
|
637
655
|
const userWhere = (qualify) => {
|
|
638
656
|
if (args.where === undefined)
|
|
@@ -641,13 +659,13 @@ function createSearchDelegate(config) {
|
|
|
641
659
|
return expr === undefined ? null : renderExpr({ dialect, expr, collector });
|
|
642
660
|
};
|
|
643
661
|
let text;
|
|
644
|
-
let strip = [];
|
|
645
662
|
let engineHasHighlights = true;
|
|
646
663
|
if (config.engine === "tsvector") {
|
|
647
664
|
const language = config.language ?? "english";
|
|
648
665
|
const ranker = config.rank ?? "ts_rank_cd";
|
|
649
666
|
const tsv = `${t}.${quote(config.tsvColumn ?? "")}`;
|
|
650
|
-
const
|
|
667
|
+
const queryParameter = dialect.name === "postgres" ? collector.add(matchText) : undefined;
|
|
668
|
+
const tsquery = () => prefix ? `to_tsquery('${language}', ${queryParameter ?? collector.add(matchText)})` : `websearch_to_tsquery('${language}', ${queryParameter ?? collector.add(matchText)})`;
|
|
651
669
|
text = `SELECT ${projection}, ${ranker}(${tsv}, ${tsquery()}) AS ${quote(SCORE_ALIAS)}`;
|
|
652
670
|
if (hlColumn !== null) {
|
|
653
671
|
text += `, ts_headline('${language}', ${t}.${quote(hlColumn)}, ${tsquery()}) AS ${quote(HIGHLIGHT_ALIAS)}`;
|
|
@@ -660,7 +678,6 @@ function createSearchDelegate(config) {
|
|
|
660
678
|
text += ` AND ${ranker}(${tsv}, ${tsquery()}) >= ${collector.add(args.minScore)}`;
|
|
661
679
|
}
|
|
662
680
|
text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
|
|
663
|
-
strip = idOnly ? [] : [config.tsvColumn ?? ""];
|
|
664
681
|
} else if (config.engine === "fts5") {
|
|
665
682
|
const fts = quote(config.ftsTable ?? "");
|
|
666
683
|
text = `SELECT ${projection}, -bm25(${fts}) AS ${quote(SCORE_ALIAS)}`;
|
|
@@ -717,7 +734,6 @@ function createSearchDelegate(config) {
|
|
|
717
734
|
const rows = await ctx.executeRows({ text, values: collector.values, method: "search" });
|
|
718
735
|
return shapeHits({
|
|
719
736
|
rows,
|
|
720
|
-
strip,
|
|
721
737
|
highlightsRequested: args.highlight !== undefined,
|
|
722
738
|
engineHasHighlights
|
|
723
739
|
});
|
|
@@ -794,14 +810,7 @@ function search(options = {}) {
|
|
|
794
810
|
return null;
|
|
795
811
|
const requested = extOptions["engine"] ?? "auto";
|
|
796
812
|
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
|
-
}
|
|
813
|
+
const language = resolveLanguage({ model, decl, options: extOptions });
|
|
805
814
|
const pkField = primaryKeyFieldOf({ model });
|
|
806
815
|
const rank = resolveRank({ decl, options: extOptions, engine, dialect });
|
|
807
816
|
assertBm25Declarations({ decl, engine, dialect });
|
|
@@ -1055,7 +1064,8 @@ function createVectorNamespace(config) {
|
|
|
1055
1064
|
const op = METRIC_OPERATOR[metric];
|
|
1056
1065
|
const col = `${t}.${quote(column)}`;
|
|
1057
1066
|
const collector = new ParamCollector2(dialect);
|
|
1058
|
-
|
|
1067
|
+
const vector = dialect.name === "postgres" ? collector.add(literal) : undefined;
|
|
1068
|
+
let text = `SELECT ${ctx.projection({ qualify: "t" })}, (${col} ${op} ${vector ?? collector.add(literal)}) AS ${quote(DISTANCE_ALIAS)}`;
|
|
1059
1069
|
text += ` FROM ${quote(config.table)} AS ${t}`;
|
|
1060
1070
|
text += ` WHERE ${col} IS NOT NULL`;
|
|
1061
1071
|
if (args.where !== undefined) {
|
|
@@ -1064,9 +1074,9 @@ function createVectorNamespace(config) {
|
|
|
1064
1074
|
text += ` AND (${renderExpr2({ dialect, expr, collector })})`;
|
|
1065
1075
|
}
|
|
1066
1076
|
if (args.maxDistance !== undefined) {
|
|
1067
|
-
text += ` AND (${col} ${op} ${collector.add(literal)}) < ${collector.add(args.maxDistance)}`;
|
|
1077
|
+
text += ` AND (${col} ${op} ${vector ?? collector.add(literal)}) < ${collector.add(args.maxDistance)}`;
|
|
1068
1078
|
}
|
|
1069
|
-
text += ` ORDER BY ${col} ${op} ${collector.add(literal)} LIMIT ${take}`;
|
|
1079
|
+
text += ` ORDER BY ${col} ${op} ${vector ?? collector.add(literal)} LIMIT ${take}`;
|
|
1070
1080
|
const rows = await ctx.executeRows({
|
|
1071
1081
|
text,
|
|
1072
1082
|
values: collector.values,
|
|
@@ -1400,4 +1410,4 @@ export {
|
|
|
1400
1410
|
vector
|
|
1401
1411
|
};
|
|
1402
1412
|
|
|
1403
|
-
//# debugId=
|
|
1413
|
+
//# debugId=6C7F38EC9C7882FB64756E2164756E21
|