@vibeorm/extensions 3.0.0 → 4.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/index.js +44 -29
- package/dist/index.js.map +6 -6
- package/dist/search/index.js +34 -21
- package/dist/search/index.js.map +4 -4
- package/dist/shared.d.ts +6 -0
- package/dist/trgm/index.js +10 -4
- package/dist/trgm/index.js.map +4 -4
- package/dist/vector/index.js +16 -8
- package/dist/vector/index.js.map +4 -4
- package/package.json +4 -4
package/dist/search/index.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/extensions/src/search/index.ts
|
|
3
|
-
import { VibeError as VibeError2, assertIdentifierFits, defineExtension, fitDefaultName, unsupportedCapability } from "@vibeorm/schema";
|
|
3
|
+
import { VibeError as VibeError2, assertExtensionModelPlacement as assertExtensionModelPlacement2, assertIdentifierFits, defineExtension, fitDefaultName, unsupportedCapability } from "@vibeorm/schema";
|
|
4
4
|
|
|
5
5
|
// packages/extensions/src/shared.ts
|
|
6
|
-
import { VibeError } from "@vibeorm/schema";
|
|
6
|
+
import { VibeError, assertExtensionModelPlacement, qualifiedSqlName } from "@vibeorm/schema";
|
|
7
|
+
function extensionSqlName(params) {
|
|
8
|
+
const { dialect, name } = params;
|
|
9
|
+
const physical = qualifiedSqlName({ name });
|
|
10
|
+
return dialect.name === "postgres" ? `${dialect.quoteIdent({ name: physical.namespace })}.${dialect.quoteIdent({ name: physical.name })}` : dialect.quoteIdent({ name });
|
|
11
|
+
}
|
|
7
12
|
function finiteNumber(params) {
|
|
8
13
|
const { value, name, integer, min } = params;
|
|
9
14
|
if (typeof value !== "number" || !Number.isFinite(value) || integer === true && !Number.isSafeInteger(value) || min !== undefined && value < min) {
|
|
@@ -16,6 +21,7 @@ function finiteNumber(params) {
|
|
|
16
21
|
return value;
|
|
17
22
|
}
|
|
18
23
|
function tableNameOf(params) {
|
|
24
|
+
assertExtensionModelPlacement({ namespace: params.model.namespace });
|
|
19
25
|
return params.model.dbName ?? params.model.name;
|
|
20
26
|
}
|
|
21
27
|
function columnNameOf(params) {
|
|
@@ -461,16 +467,16 @@ function searchArtifacts(ctx) {
|
|
|
461
467
|
id: `search:${modelName}:column`,
|
|
462
468
|
owns: { kind: "column", table, column: tsvColumn },
|
|
463
469
|
create: [
|
|
464
|
-
`ALTER TABLE ${
|
|
470
|
+
`ALTER TABLE ${extensionSqlName({ dialect, name: table })} ADD COLUMN IF NOT EXISTS ${quote(tsvColumn)} tsvector GENERATED ALWAYS AS (${expression}) STORED;`
|
|
465
471
|
],
|
|
466
|
-
drop: [`ALTER TABLE ${
|
|
472
|
+
drop: [`ALTER TABLE ${extensionSqlName({ dialect, name: table })} DROP COLUMN IF EXISTS ${quote(tsvColumn)};`]
|
|
467
473
|
}, {
|
|
468
474
|
id: `search:${modelName}:index`,
|
|
469
475
|
owns: { kind: "index", table, name: indexName },
|
|
470
476
|
create: [
|
|
471
|
-
`CREATE INDEX IF NOT EXISTS ${quote(indexName)} ON ${
|
|
477
|
+
`CREATE INDEX IF NOT EXISTS ${quote(indexName)} ON ${extensionSqlName({ dialect, name: table })} USING gin (${quote(tsvColumn)});`
|
|
472
478
|
],
|
|
473
|
-
drop: [`DROP INDEX IF EXISTS ${
|
|
479
|
+
drop: [`DROP INDEX IF EXISTS ${extensionSqlName({ dialect, name: indexName })};`]
|
|
474
480
|
});
|
|
475
481
|
continue;
|
|
476
482
|
}
|
|
@@ -490,9 +496,9 @@ function searchArtifacts(ctx) {
|
|
|
490
496
|
id: `search:${modelName}:index`,
|
|
491
497
|
owns: { kind: "index", table, name: bm25Names.indexName },
|
|
492
498
|
create: [
|
|
493
|
-
`CREATE INDEX IF NOT EXISTS ${quote(bm25Names.indexName)} ON ${
|
|
499
|
+
`CREATE INDEX IF NOT EXISTS ${quote(bm25Names.indexName)} ON ${extensionSqlName({ dialect, name: table })} USING bm25 (${indexed.join(", ")}) WITH (${withOptions.join(", ")});`
|
|
494
500
|
],
|
|
495
|
-
drop: [`DROP INDEX IF EXISTS ${
|
|
501
|
+
drop: [`DROP INDEX IF EXISTS ${extensionSqlName({ dialect, name: bm25Names.indexName })};`]
|
|
496
502
|
});
|
|
497
503
|
continue;
|
|
498
504
|
}
|
|
@@ -528,10 +534,10 @@ function searchArtifacts(ctx) {
|
|
|
528
534
|
id: `search:${modelName}:fts5-triggers`,
|
|
529
535
|
owns: { kind: "trigger", table, name: `${fts}_ai` },
|
|
530
536
|
create: [
|
|
531
|
-
`CREATE TRIGGER IF NOT EXISTS ${quote(`${fts}_ai`)} AFTER INSERT ON ${
|
|
537
|
+
`CREATE TRIGGER IF NOT EXISTS ${quote(`${fts}_ai`)} AFTER INSERT ON ${extensionSqlName({ dialect, name: table })} BEGIN
|
|
532
538
|
INSERT INTO ${quote(fts)}(rowid, ${columnList}) VALUES (new.rowid, ${newList});
|
|
533
539
|
END;`,
|
|
534
|
-
`CREATE TRIGGER IF NOT EXISTS ${quote(`${fts}_ad`)} AFTER DELETE ON ${
|
|
540
|
+
`CREATE TRIGGER IF NOT EXISTS ${quote(`${fts}_ad`)} AFTER DELETE ON ${extensionSqlName({ dialect, name: table })} BEGIN
|
|
535
541
|
INSERT INTO ${quote(fts)}(${quote(fts)}, rowid, ${columnList}) VALUES ('delete', old.rowid, ${oldList});
|
|
536
542
|
END;`,
|
|
537
543
|
updateTrigger.create,
|
|
@@ -561,9 +567,9 @@ END;`,
|
|
|
561
567
|
id: `search:${modelName}:index`,
|
|
562
568
|
owns: { kind: "index", table, name: fulltextNames.indexName },
|
|
563
569
|
create: [
|
|
564
|
-
`CREATE FULLTEXT INDEX ${quote(fulltextNames.indexName)} ON ${
|
|
570
|
+
`CREATE FULLTEXT INDEX ${quote(fulltextNames.indexName)} ON ${extensionSqlName({ dialect, name: table })} (${columns.map((column) => quote(column)).join(", ")});`
|
|
565
571
|
],
|
|
566
|
-
drop: [`DROP INDEX ${quote(fulltextNames.indexName)} ON ${
|
|
572
|
+
drop: [`DROP INDEX ${quote(fulltextNames.indexName)} ON ${extensionSqlName({ dialect, name: table })};`]
|
|
567
573
|
});
|
|
568
574
|
}
|
|
569
575
|
return artifacts;
|
|
@@ -596,9 +602,12 @@ function shapeHits(params) {
|
|
|
596
602
|
}
|
|
597
603
|
function createSearchDelegate(config) {
|
|
598
604
|
return (ctx) => {
|
|
605
|
+
if (ctx.model !== null)
|
|
606
|
+
assertExtensionModelPlacement2({ namespace: ctx.model.namespace, dialect: ctx.dialect.name });
|
|
599
607
|
const dialect = ctx.dialect;
|
|
600
608
|
const quote = (name) => dialect.quoteIdent({ name });
|
|
601
609
|
const t = quote("t");
|
|
610
|
+
const pkColumns = ctx.model === null ? config.pkColumn === undefined || config.pkColumn === "" ? [] : [config.pkColumn] : ctx.model.primaryKey.map((field) => field.columnName);
|
|
602
611
|
return async (args) => {
|
|
603
612
|
if (typeof args.query !== "string" || args.query.trim().length === 0) {
|
|
604
613
|
throw new VibeError2({
|
|
@@ -650,6 +659,13 @@ function createSearchDelegate(config) {
|
|
|
650
659
|
meta: { table: config.table }
|
|
651
660
|
});
|
|
652
661
|
}
|
|
662
|
+
if (pkColumns.length === 0) {
|
|
663
|
+
throw new VibeError2({
|
|
664
|
+
code: "VIBE_CONFIG",
|
|
665
|
+
message: `search needs primary-key metadata for deterministic ordering: ${config.table} has no model key, or the legacy mount has no explicit pkColumn`,
|
|
666
|
+
meta: { table: config.table }
|
|
667
|
+
});
|
|
668
|
+
}
|
|
653
669
|
const projection = idOnly ? `${t}.${quote(config.pkColumn ?? "")}` : ctx.projection({ qualify: "t" });
|
|
654
670
|
const collector = new ParamCollector(dialect);
|
|
655
671
|
const userWhere = (qualify) => {
|
|
@@ -670,14 +686,13 @@ function createSearchDelegate(config) {
|
|
|
670
686
|
if (hlColumn !== null) {
|
|
671
687
|
text += `, ts_headline('${language}', ${t}.${quote(hlColumn)}, ${tsquery()}) AS ${quote(HIGHLIGHT_ALIAS)}`;
|
|
672
688
|
}
|
|
673
|
-
text += ` FROM ${
|
|
689
|
+
text += ` FROM ${extensionSqlName({ dialect, name: config.table })} AS ${t} WHERE ${tsv} @@ ${tsquery()}`;
|
|
674
690
|
const where = userWhere("t");
|
|
675
691
|
if (where !== null)
|
|
676
692
|
text += ` AND (${where})`;
|
|
677
693
|
if (args.minScore !== undefined) {
|
|
678
694
|
text += ` AND ${ranker}(${tsv}, ${tsquery()}) >= ${collector.add(args.minScore)}`;
|
|
679
695
|
}
|
|
680
|
-
text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
|
|
681
696
|
} else if (config.engine === "fts5") {
|
|
682
697
|
const fts = quote(config.ftsTable ?? "");
|
|
683
698
|
text = `SELECT ${projection}, -bm25(${fts}) AS ${quote(SCORE_ALIAS)}`;
|
|
@@ -685,7 +700,7 @@ function createSearchDelegate(config) {
|
|
|
685
700
|
const index = config.fields.findIndex((field) => field.column === hlColumn);
|
|
686
701
|
text += `, snippet(${fts}, ${index}, '<b>', '</b>', '\u2026', 16) AS ${quote(HIGHLIGHT_ALIAS)}`;
|
|
687
702
|
}
|
|
688
|
-
text += ` FROM ${fts} JOIN ${
|
|
703
|
+
text += ` FROM ${fts} JOIN ${extensionSqlName({ dialect, name: config.table })} AS ${t} ON ${t}.rowid = ${fts}.rowid`;
|
|
689
704
|
text += ` WHERE ${fts} MATCH ${collector.add(matchText)}`;
|
|
690
705
|
const where = userWhere("t");
|
|
691
706
|
if (where !== null)
|
|
@@ -693,12 +708,11 @@ function createSearchDelegate(config) {
|
|
|
693
708
|
if (args.minScore !== undefined) {
|
|
694
709
|
text += ` AND -bm25(${fts}) >= ${collector.add(args.minScore)}`;
|
|
695
710
|
}
|
|
696
|
-
text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
|
|
697
711
|
} else if (config.engine === "fulltext") {
|
|
698
712
|
engineHasHighlights = false;
|
|
699
713
|
const mode = prefix ? "IN BOOLEAN MODE" : "IN NATURAL LANGUAGE MODE";
|
|
700
714
|
const match = () => `MATCH (${config.fields.map((field) => `${t}.${quote(field.column)}`).join(", ")}) AGAINST (${collector.add(matchText)} ${mode})`;
|
|
701
|
-
text = `SELECT ${projection}, ${match()} AS ${quote(SCORE_ALIAS)} FROM ${
|
|
715
|
+
text = `SELECT ${projection}, ${match()} AS ${quote(SCORE_ALIAS)} FROM ${extensionSqlName({ dialect, name: config.table })} AS ${t}`;
|
|
702
716
|
text += ` WHERE ${match()}`;
|
|
703
717
|
const where = userWhere("t");
|
|
704
718
|
if (where !== null)
|
|
@@ -706,7 +720,6 @@ function createSearchDelegate(config) {
|
|
|
706
720
|
if (args.minScore !== undefined) {
|
|
707
721
|
text += ` AND ${match()} >= ${collector.add(args.minScore)}`;
|
|
708
722
|
}
|
|
709
|
-
text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
|
|
710
723
|
} else {
|
|
711
724
|
const pk = `${t}.${quote(config.pkColumn ?? "")}`;
|
|
712
725
|
const operator = prefix || fuzzy !== undefined ? "&&&" : "@@@";
|
|
@@ -715,7 +728,7 @@ function createSearchDelegate(config) {
|
|
|
715
728
|
if (hlColumn !== null) {
|
|
716
729
|
text += `, paradedb.snippet(${t}.${quote(hlColumn)}) AS ${quote(HIGHLIGHT_ALIAS)}`;
|
|
717
730
|
}
|
|
718
|
-
text += ` FROM ${
|
|
731
|
+
text += ` FROM ${extensionSqlName({ dialect, name: config.table })} AS ${t} WHERE (${config.fields.map((field, index) => {
|
|
719
732
|
const boost = config.boosts?.[index];
|
|
720
733
|
const boostCast = boost === undefined || boost === 1 ? "" : `::pdb.boost(${boost})`;
|
|
721
734
|
return `${t}.${quote(field.column)} ${operator} ${collector.add(matchText)}${fuzzyCast}${boostCast}`;
|
|
@@ -726,8 +739,8 @@ function createSearchDelegate(config) {
|
|
|
726
739
|
if (args.minScore !== undefined) {
|
|
727
740
|
text += ` AND paradedb.score(${pk}) >= ${collector.add(args.minScore)}`;
|
|
728
741
|
}
|
|
729
|
-
text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
|
|
730
742
|
}
|
|
743
|
+
text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC, ${pkColumns.map((column) => `${t}.${quote(column)} ASC`).join(", ")}`;
|
|
731
744
|
text += ` LIMIT ${take}`;
|
|
732
745
|
if (skip !== null)
|
|
733
746
|
text += ` OFFSET ${skip}`;
|
|
@@ -907,4 +920,4 @@ export {
|
|
|
907
920
|
search
|
|
908
921
|
};
|
|
909
922
|
|
|
910
|
-
//# debugId=
|
|
923
|
+
//# debugId=231E8A4C6A813A6064756E2164756E21
|