@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 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 ${quote(table)} ADD COLUMN IF NOT EXISTS ${quote(tsvColumn)} tsvector GENERATED ALWAYS AS (${expression}) STORED;`
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 ${quote(table)} DROP COLUMN IF EXISTS ${quote(tsvColumn)};`]
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 ${quote(table)} USING gin (${quote(tsvColumn)});`
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 ${quote(indexName)};`]
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 ${quote(table)} USING bm25 (${indexed.join(", ")}) WITH (${withOptions.join(", ")});`
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 ${quote(bm25Names.indexName)};`]
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 ${quote(table)} BEGIN
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 ${quote(table)} BEGIN
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 ${quote(table)} (${columns.map((column) => quote(column)).join(", ")});`
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 ${quote(table)};`]
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 ${quote(config.table)} AS ${t} WHERE ${tsv} @@ ${tsquery()}`;
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 ${quote(config.table)} AS ${t} ON ${t}.rowid = ${fts}.rowid`;
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 ${quote(config.table)} AS ${t}`;
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 ${quote(config.table)} AS ${t} WHERE (${config.fields.map((field, index) => {
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 ${quote(table)} USING ${kindRaw} (${quote(column)} ${METRIC_OPCLASS[metric]})${withClause};`
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 ${quote(indexName)};`]
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 ${quote(config.table)} AS ${t}`;
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 ${quote(config.table)} SET ${quote(column)} = ${collector.add(literal)}`;
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 split @@vector declarations to name each index`
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 ${quote(table)} USING gin (${quote(column)} gin_trgm_ops);`
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 ${quote(indexName)};`]
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=6C7F38EC9C7882FB64756E2164756E21
1428
+ //# debugId=198B01543660222064756E2164756E21