@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/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}`;
|
|
@@ -902,7 +915,7 @@ function search(options = {}) {
|
|
|
902
915
|
});
|
|
903
916
|
}
|
|
904
917
|
// packages/extensions/src/vector/index.ts
|
|
905
|
-
import { VibeError as VibeError3, assertIdentifierFits as assertIdentifierFits2, defineExtension as defineExtension2, fitDefaultName as fitDefaultName2, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
918
|
+
import { VibeError as VibeError3, assertExtensionModelPlacement as assertExtensionModelPlacement3, assertIdentifierFits as assertIdentifierFits2, defineExtension as defineExtension2, fitDefaultName as fitDefaultName2, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
906
919
|
import { ParamCollector as ParamCollector2, getSqlDialect as getSqlDialect2, renderExpr as renderExpr2 } from "@vibeorm/sql";
|
|
907
920
|
var VERSION2 = "2.0.0-alpha.0";
|
|
908
921
|
var METRIC_OPERATOR = {
|
|
@@ -1015,9 +1028,9 @@ function vectorArtifacts(ctx) {
|
|
|
1015
1028
|
id: `vector:${modelName}:${field}`,
|
|
1016
1029
|
owns: { kind: "index", table, name: indexName },
|
|
1017
1030
|
create: [
|
|
1018
|
-
`CREATE INDEX IF NOT EXISTS ${quote(indexName)} ON ${
|
|
1031
|
+
`CREATE INDEX IF NOT EXISTS ${quote(indexName)} ON ${extensionSqlName({ dialect, name: table })} USING ${kindRaw} (${quote(column)} ${METRIC_OPCLASS[metric]})${withClause};`
|
|
1019
1032
|
],
|
|
1020
|
-
drop: [`DROP INDEX IF EXISTS ${
|
|
1033
|
+
drop: [`DROP INDEX IF EXISTS ${extensionSqlName({ dialect, name: indexName })};`]
|
|
1021
1034
|
});
|
|
1022
1035
|
}
|
|
1023
1036
|
}
|
|
@@ -1051,6 +1064,8 @@ function resolveColumn(params) {
|
|
|
1051
1064
|
}
|
|
1052
1065
|
function createVectorNamespace(config) {
|
|
1053
1066
|
return (ctx) => {
|
|
1067
|
+
if (ctx.model !== null)
|
|
1068
|
+
assertExtensionModelPlacement3({ namespace: ctx.model.namespace, dialect: ctx.dialect.name });
|
|
1054
1069
|
const dialect = ctx.dialect;
|
|
1055
1070
|
const quote = (name) => dialect.quoteIdent({ name });
|
|
1056
1071
|
const t = quote("t");
|
|
@@ -1066,7 +1081,7 @@ function createVectorNamespace(config) {
|
|
|
1066
1081
|
const collector = new ParamCollector2(dialect);
|
|
1067
1082
|
const vector = dialect.name === "postgres" ? collector.add(literal) : undefined;
|
|
1068
1083
|
let text = `SELECT ${ctx.projection({ qualify: "t" })}, (${col} ${op} ${vector ?? collector.add(literal)}) AS ${quote(DISTANCE_ALIAS)}`;
|
|
1069
|
-
text += ` FROM ${
|
|
1084
|
+
text += ` FROM ${extensionSqlName({ dialect, name: config.table })} AS ${t}`;
|
|
1070
1085
|
text += ` WHERE ${col} IS NOT NULL`;
|
|
1071
1086
|
if (args.where !== undefined) {
|
|
1072
1087
|
const expr = ctx.compileWhere({ where: args.where, qualify: "t" });
|
|
@@ -1090,7 +1105,7 @@ function createVectorNamespace(config) {
|
|
|
1090
1105
|
set: async (args) => {
|
|
1091
1106
|
const { column, literal } = resolveColumn({ config, args });
|
|
1092
1107
|
const collector = new ParamCollector2(dialect);
|
|
1093
|
-
let text = `UPDATE ${
|
|
1108
|
+
let text = `UPDATE ${extensionSqlName({ dialect, name: config.table })} SET ${quote(column)} = ${collector.add(literal)}`;
|
|
1094
1109
|
const expr = ctx.compileWhere({ where: args.where });
|
|
1095
1110
|
if (expr !== undefined)
|
|
1096
1111
|
text += ` WHERE ${renderExpr2({ dialect, expr, collector })}`;
|
|
@@ -1153,7 +1168,7 @@ function vector(options = {}) {
|
|
|
1153
1168
|
}
|
|
1154
1169
|
if (fields.length !== 1) {
|
|
1155
1170
|
return {
|
|
1156
|
-
error: `indexName needs exactly one indexed field \u2014
|
|
1171
|
+
error: `indexName needs exactly one indexed field \u2014 one named index declaration per model is supported`
|
|
1157
1172
|
};
|
|
1158
1173
|
}
|
|
1159
1174
|
}
|
|
@@ -1260,9 +1275,9 @@ function trgmArtifacts(ctx) {
|
|
|
1260
1275
|
id: `trgm:${modelName}:${field}`,
|
|
1261
1276
|
owns: { kind: "index", table, name: indexName },
|
|
1262
1277
|
create: [
|
|
1263
|
-
`CREATE INDEX IF NOT EXISTS ${quote(indexName)} ON ${
|
|
1278
|
+
`CREATE INDEX IF NOT EXISTS ${quote(indexName)} ON ${extensionSqlName({ dialect, name: table })} USING gin (${quote(column)} gin_trgm_ops);`
|
|
1264
1279
|
],
|
|
1265
|
-
drop: [`DROP INDEX IF EXISTS ${
|
|
1280
|
+
drop: [`DROP INDEX IF EXISTS ${extensionSqlName({ dialect, name: indexName })};`]
|
|
1266
1281
|
});
|
|
1267
1282
|
}
|
|
1268
1283
|
}
|
|
@@ -1410,4 +1425,4 @@ export {
|
|
|
1410
1425
|
vector
|
|
1411
1426
|
};
|
|
1412
1427
|
|
|
1413
|
-
//# debugId=
|
|
1428
|
+
//# debugId=198B01543660222064756E2164756E21
|