@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.
@@ -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}`;
@@ -907,4 +920,4 @@ export {
907
920
  search
908
921
  };
909
922
 
910
- //# debugId=5A96240B33D83FB764756E2164756E21
923
+ //# debugId=231E8A4C6A813A6064756E2164756E21