@vibeorm/extensions 2.0.0-alpha.1 → 2.0.0-alpha.10

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.
@@ -74,7 +74,7 @@ async function pgExtensionCheck(params) {
74
74
  }
75
75
 
76
76
  // packages/extensions/src/search/index.ts
77
- var VERSION = "2.0.0-alpha.0";
77
+ var VERSION = "2.0.0-alpha.1";
78
78
  var SCORE_ALIAS = "_vibe_score";
79
79
  var HIGHLIGHT_ALIAS = "_vibe_highlight";
80
80
  var WEIGHT_LETTERS = ["A", "B", "C", "D"];
@@ -101,6 +101,10 @@ function resolveSearchEngine(params) {
101
101
  hint: `this dialect supports "${native}"${dialect === "postgres" ? ' and "bm25"' : ""}`
102
102
  });
103
103
  }
104
+ function declRank(declaration) {
105
+ const value = declaration["rank"];
106
+ return value === "ts_rank" || value === "ts_rank_cd" ? value : undefined;
107
+ }
104
108
  function readDeclaration(params) {
105
109
  const fields = declStringArray(params.declaration, "fields");
106
110
  if (fields === undefined || fields.length === 0)
@@ -109,7 +113,10 @@ function readDeclaration(params) {
109
113
  fields,
110
114
  weights: declNumberArray(params.declaration, "weights"),
111
115
  base: declString(params.declaration, "name") ?? `${tableNameOf({ model: params.model })}_search`,
112
- language: declString(params.declaration, "language")
116
+ language: declString(params.declaration, "language"),
117
+ columnName: declString(params.declaration, "columnName"),
118
+ indexName: declString(params.declaration, "indexName"),
119
+ rank: declRank(params.declaration)
113
120
  };
114
121
  }
115
122
  function weightLetters(params) {
@@ -119,6 +126,41 @@ function weightLetters(params) {
119
126
  const ranked = [...new Set(decl.weights)].sort((a, b) => b - a);
120
127
  return decl.weights.map((weight) => WEIGHT_LETTERS[Math.min(ranked.indexOf(weight), WEIGHT_LETTERS.length - 1)]);
121
128
  }
129
+ function resolveArtifactNames(params) {
130
+ const { decl, engine, dialect } = params;
131
+ if (engine === "fts5" && (decl.columnName !== undefined || decl.indexName !== undefined)) {
132
+ throw unsupportedCapability({
133
+ dialect,
134
+ feature: "@@search columnName/indexName overrides on the fts5 engine",
135
+ hint: "fts5 emits a shadow table + triggers \u2014 there is no column or index to rename; use name: to change the table base"
136
+ });
137
+ }
138
+ if (decl.columnName !== undefined && engine !== "tsvector") {
139
+ throw unsupportedCapability({
140
+ dialect,
141
+ feature: `@@search columnName on the ${engine} engine`,
142
+ hint: "only the tsvector engine materializes a generated column \u2014 drop columnName or target tsvector"
143
+ });
144
+ }
145
+ return {
146
+ tsvColumn: decl.columnName ?? `${decl.base}_tsv`,
147
+ indexName: decl.indexName ?? `${decl.base}_idx`
148
+ };
149
+ }
150
+ function resolveRank(params) {
151
+ const { decl, options, engine, dialect } = params;
152
+ if (decl.rank !== undefined && engine !== "tsvector") {
153
+ throw unsupportedCapability({
154
+ dialect,
155
+ feature: `@@search rank on the ${engine} engine`,
156
+ hint: "rank chooses between the postgres ranking functions ts_rank and ts_rank_cd \u2014 a tsvector-only concept"
157
+ });
158
+ }
159
+ if (engine !== "tsvector")
160
+ return;
161
+ const fallback = options["rank"];
162
+ return decl.rank ?? (fallback === "ts_rank" || fallback === "ts_rank_cd" ? fallback : undefined);
163
+ }
122
164
  function tsvectorExpression(params) {
123
165
  const { dialect, model, decl, language } = params;
124
166
  const quote = (name) => dialect.quoteIdent({ name });
@@ -152,9 +194,10 @@ function searchArtifacts(ctx) {
152
194
  continue;
153
195
  const table = tableNameOf({ model });
154
196
  const columns = decl.fields.map((field) => columnNameOf({ model, field }));
197
+ resolveRank({ decl, options: ctx.options, engine, dialect: ctx.dialect });
155
198
  if (engine === "tsvector") {
156
199
  const language = decl.language ?? ctx.options["language"] ?? "english";
157
- const tsvColumn = `${decl.base}_tsv`;
200
+ const { tsvColumn, indexName } = resolveArtifactNames({ decl, engine, dialect: ctx.dialect });
158
201
  const expression = tsvectorExpression({ dialect, model, decl, language });
159
202
  artifacts.push({
160
203
  id: `search:${modelName}:column`,
@@ -165,11 +208,11 @@ function searchArtifacts(ctx) {
165
208
  drop: [`ALTER TABLE ${quote(table)} DROP COLUMN IF EXISTS ${quote(tsvColumn)};`]
166
209
  }, {
167
210
  id: `search:${modelName}:index`,
168
- owns: { kind: "index", table, name: `${decl.base}_idx` },
211
+ owns: { kind: "index", table, name: indexName },
169
212
  create: [
170
- `CREATE INDEX IF NOT EXISTS ${quote(`${decl.base}_idx`)} ON ${quote(table)} USING gin (${quote(tsvColumn)});`
213
+ `CREATE INDEX IF NOT EXISTS ${quote(indexName)} ON ${quote(table)} USING gin (${quote(tsvColumn)});`
171
214
  ],
172
- drop: [`DROP INDEX IF EXISTS ${quote(`${decl.base}_idx`)};`]
215
+ drop: [`DROP INDEX IF EXISTS ${quote(indexName)};`]
173
216
  });
174
217
  continue;
175
218
  }
@@ -183,17 +226,19 @@ function searchArtifacts(ctx) {
183
226
  });
184
227
  }
185
228
  const pkColumn = columnNameOf({ model, field: pkField });
229
+ const bm25Names = resolveArtifactNames({ decl, engine, dialect: ctx.dialect });
186
230
  artifacts.push({
187
231
  id: `search:${modelName}:index`,
188
- owns: { kind: "index", table, name: `${decl.base}_idx` },
232
+ owns: { kind: "index", table, name: bm25Names.indexName },
189
233
  create: [
190
- `CREATE INDEX IF NOT EXISTS ${quote(`${decl.base}_idx`)} ON ${quote(table)} USING bm25 (${[pkColumn, ...columns].map((column) => quote(column)).join(", ")}) WITH (key_field='${pkColumn}');`
234
+ `CREATE INDEX IF NOT EXISTS ${quote(bm25Names.indexName)} ON ${quote(table)} USING bm25 (${[pkColumn, ...columns].map((column) => quote(column)).join(", ")}) WITH (key_field='${pkColumn}');`
191
235
  ],
192
- drop: [`DROP INDEX IF EXISTS ${quote(`${decl.base}_idx`)};`]
236
+ drop: [`DROP INDEX IF EXISTS ${quote(bm25Names.indexName)};`]
193
237
  });
194
238
  continue;
195
239
  }
196
240
  if (engine === "fts5") {
241
+ resolveArtifactNames({ decl, engine, dialect: ctx.dialect });
197
242
  const pkName = primaryKeyFieldOf({ model });
198
243
  const pkField = pkName === null ? undefined : model.fields.find((field) => field.name === pkName);
199
244
  const pkIsRowidAlias = pkField !== undefined && !pkField.isList && (pkField.type === "Int" || pkField.type === "BigInt");
@@ -242,13 +287,14 @@ END;`
242
287
  });
243
288
  continue;
244
289
  }
290
+ const fulltextNames = resolveArtifactNames({ decl, engine, dialect: ctx.dialect });
245
291
  artifacts.push({
246
292
  id: `search:${modelName}:index`,
247
- owns: { kind: "index", table, name: `${decl.base}_idx` },
293
+ owns: { kind: "index", table, name: fulltextNames.indexName },
248
294
  create: [
249
- `CREATE FULLTEXT INDEX ${quote(`${decl.base}_idx`)} ON ${quote(table)} (${columns.map((column) => quote(column)).join(", ")});`
295
+ `CREATE FULLTEXT INDEX ${quote(fulltextNames.indexName)} ON ${quote(table)} (${columns.map((column) => quote(column)).join(", ")});`
250
296
  ],
251
- drop: [`DROP INDEX ${quote(`${decl.base}_idx`)} ON ${quote(table)};`]
297
+ drop: [`DROP INDEX ${quote(fulltextNames.indexName)} ON ${quote(table)};`]
252
298
  });
253
299
  }
254
300
  return artifacts;
@@ -294,9 +340,33 @@ function createSearchDelegate(config) {
294
340
  meta: { table: config.table }
295
341
  });
296
342
  }
343
+ const prefix = args.prefix === true;
344
+ if (prefix && config.engine === "bm25") {
345
+ throw unsupportedCapability({
346
+ dialect: "postgres",
347
+ feature: 'search prefix mode on the "bm25" engine',
348
+ hint: "ParadeDB prefix behavior is decided by the index tokenizer (ngram/edge-ngram) \u2014 configure the index instead, or use the tsvector engine"
349
+ });
350
+ }
351
+ let matchText = args.query;
352
+ if (prefix) {
353
+ const tokens = args.query.match(/[\p{L}\p{N}]+/gu) ?? [];
354
+ if (tokens.length === 0)
355
+ return [];
356
+ matchText = config.engine === "tsvector" ? tokens.map((token) => `${token}:*`).join(" & ") : config.engine === "fts5" ? tokens.map((token) => `"${token}"*`).join(" ") : tokens.map((token) => `+${token}*`).join(" ");
357
+ }
297
358
  const take = Math.max(1, Math.trunc(args.take ?? 10));
298
359
  const skip = args.skip === undefined ? null : Math.max(0, Math.trunc(args.skip));
299
360
  const hlColumn = highlightColumn({ config, args });
361
+ const idOnly = args.select === "id";
362
+ if (idOnly && config.pkColumn === undefined) {
363
+ throw new VibeError2({
364
+ code: "VIBE_VALIDATION",
365
+ message: `select: "id" needs a single-column primary key baked into the client \u2014 ${config.table} has none, or the client predates the projection (run vibeorm generate)`,
366
+ meta: { table: config.table }
367
+ });
368
+ }
369
+ const projection = idOnly ? `${t}.${quote(config.pkColumn ?? "")}` : `${t}.*`;
300
370
  const collector = new ParamCollector(dialect);
301
371
  const userWhere = (qualify) => {
302
372
  if (args.where === undefined)
@@ -309,9 +379,10 @@ function createSearchDelegate(config) {
309
379
  let engineHasHighlights = true;
310
380
  if (config.engine === "tsvector") {
311
381
  const language = config.language ?? "english";
382
+ const ranker = config.rank ?? "ts_rank_cd";
312
383
  const tsv = `${t}.${quote(config.tsvColumn ?? "")}`;
313
- const tsquery = () => `websearch_to_tsquery('${language}', ${collector.add(args.query)})`;
314
- text = `SELECT ${t}.*, ts_rank_cd(${tsv}, ${tsquery()}) AS ${quote(SCORE_ALIAS)}`;
384
+ const tsquery = () => prefix ? `to_tsquery('${language}', ${collector.add(matchText)})` : `websearch_to_tsquery('${language}', ${collector.add(matchText)})`;
385
+ text = `SELECT ${projection}, ${ranker}(${tsv}, ${tsquery()}) AS ${quote(SCORE_ALIAS)}`;
315
386
  if (hlColumn !== null) {
316
387
  text += `, ts_headline('${language}', ${t}.${quote(hlColumn)}, ${tsquery()}) AS ${quote(HIGHLIGHT_ALIAS)}`;
317
388
  }
@@ -320,19 +391,19 @@ function createSearchDelegate(config) {
320
391
  if (where !== null)
321
392
  text += ` AND (${where})`;
322
393
  if (args.minScore !== undefined) {
323
- text += ` AND ts_rank_cd(${tsv}, ${tsquery()}) >= ${collector.add(args.minScore)}`;
394
+ text += ` AND ${ranker}(${tsv}, ${tsquery()}) >= ${collector.add(args.minScore)}`;
324
395
  }
325
396
  text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
326
- strip = [config.tsvColumn ?? ""];
397
+ strip = idOnly ? [] : [config.tsvColumn ?? ""];
327
398
  } else if (config.engine === "fts5") {
328
399
  const fts = quote(config.ftsTable ?? "");
329
- text = `SELECT ${t}.*, -bm25(${fts}) AS ${quote(SCORE_ALIAS)}`;
400
+ text = `SELECT ${projection}, -bm25(${fts}) AS ${quote(SCORE_ALIAS)}`;
330
401
  if (hlColumn !== null) {
331
402
  const index = config.fields.findIndex((field) => field.column === hlColumn);
332
403
  text += `, snippet(${fts}, ${index}, '<b>', '</b>', '\u2026', 16) AS ${quote(HIGHLIGHT_ALIAS)}`;
333
404
  }
334
405
  text += ` FROM ${fts} JOIN ${quote(config.table)} AS ${t} ON ${t}.rowid = ${fts}.rowid`;
335
- text += ` WHERE ${fts} MATCH ${collector.add(args.query)}`;
406
+ text += ` WHERE ${fts} MATCH ${collector.add(matchText)}`;
336
407
  const where = userWhere("t");
337
408
  if (where !== null)
338
409
  text += ` AND (${where})`;
@@ -342,8 +413,9 @@ function createSearchDelegate(config) {
342
413
  text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
343
414
  } else if (config.engine === "fulltext") {
344
415
  engineHasHighlights = false;
345
- const match = () => `MATCH (${config.fields.map((field) => `${t}.${quote(field.column)}`).join(", ")}) AGAINST (${collector.add(args.query)} IN NATURAL LANGUAGE MODE)`;
346
- text = `SELECT ${t}.*, ${match()} AS ${quote(SCORE_ALIAS)} FROM ${quote(config.table)} AS ${t}`;
416
+ const mode = prefix ? "IN BOOLEAN MODE" : "IN NATURAL LANGUAGE MODE";
417
+ const match = () => `MATCH (${config.fields.map((field) => `${t}.${quote(field.column)}`).join(", ")}) AGAINST (${collector.add(matchText)} ${mode})`;
418
+ text = `SELECT ${projection}, ${match()} AS ${quote(SCORE_ALIAS)} FROM ${quote(config.table)} AS ${t}`;
347
419
  text += ` WHERE ${match()}`;
348
420
  const where = userWhere("t");
349
421
  if (where !== null)
@@ -354,7 +426,7 @@ function createSearchDelegate(config) {
354
426
  text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;
355
427
  } else {
356
428
  const pk = `${t}.${quote(config.pkColumn ?? "")}`;
357
- text = `SELECT ${t}.*, paradedb.score(${pk}) AS ${quote(SCORE_ALIAS)}`;
429
+ text = `SELECT ${projection}, paradedb.score(${pk}) AS ${quote(SCORE_ALIAS)}`;
358
430
  if (hlColumn !== null) {
359
431
  text += `, paradedb.snippet(${t}.${quote(hlColumn)}) AS ${quote(HIGHLIGHT_ALIAS)}`;
360
432
  }
@@ -415,6 +487,16 @@ function search(options = {}) {
415
487
  if (language !== undefined && (typeof language !== "string" || !isSafeSqlName({ name: language }))) {
416
488
  return { error: `language must be a plain text-search config name` };
417
489
  }
490
+ for (const key of ["columnName", "indexName"]) {
491
+ const value = args.named[key];
492
+ if (value !== undefined && (typeof value !== "string" || !isSafeSqlName({ name: value }))) {
493
+ return { error: `${key} must be a plain identifier` };
494
+ }
495
+ }
496
+ const rank = args.named["rank"];
497
+ if (rank !== undefined && rank !== "ts_rank" && rank !== "ts_rank_cd") {
498
+ return { error: `rank must be "ts_rank" or "ts_rank_cd"` };
499
+ }
418
500
  return { declaration: { fields, ...args.named } };
419
501
  }
420
502
  }
@@ -438,6 +520,8 @@ function search(options = {}) {
438
520
  });
439
521
  }
440
522
  const pkField = primaryKeyFieldOf({ model });
523
+ const rank = resolveRank({ decl, options: extOptions, engine, dialect });
524
+ const names = resolveArtifactNames({ decl, engine, dialect });
441
525
  const config = {
442
526
  engine,
443
527
  table: tableNameOf({ model }),
@@ -445,26 +529,42 @@ function search(options = {}) {
445
529
  name: field,
446
530
  column: columnNameOf({ model, field })
447
531
  })),
448
- ...engine === "tsvector" ? { tsvColumn: `${decl.base}_tsv`, language } : {},
532
+ ...engine === "tsvector" ? { tsvColumn: names.tsvColumn, language, ...rank === undefined ? {} : { rank } } : {},
449
533
  ...engine === "fts5" ? { ftsTable: `${decl.base}_fts` } : {},
450
- ...engine === "bm25" && pkField !== null ? { pkColumn: columnNameOf({ model, field: pkField }) } : {}
534
+ ...pkField !== null ? { pkColumn: columnNameOf({ model, field: pkField }) } : {}
451
535
  };
452
536
  const fieldUnion = decl.fields.map((field) => JSON.stringify(field)).join(" | ");
537
+ const argLines = [
538
+ ` readonly query: string;`,
539
+ ` readonly where?: ${typeNames.whereInput};`,
540
+ ` readonly take?: number;`,
541
+ ` readonly skip?: number;`,
542
+ ` readonly minScore?: number;`,
543
+ ` readonly highlight?: { readonly field: ${fieldUnion} };`,
544
+ ` readonly prefix?: boolean;`
545
+ ];
546
+ const dts = pkField === null ? [
547
+ `readonly search: (args: {`,
548
+ ...argLines,
549
+ `}) => Promise<SearchHit<${typeNames.payload}>[]>;`
550
+ ] : [
551
+ `readonly search: {`,
552
+ ` (args: {`,
553
+ ...argLines.map((line) => ` ${line}`),
554
+ ` readonly select: "id";`,
555
+ ` }): Promise<SearchHit<Pick<${typeNames.payload}, ${JSON.stringify(pkField)}>>[]>;`,
556
+ ` (args: {`,
557
+ ...argLines.map((line) => ` ${line}`),
558
+ ` readonly select?: "row";`,
559
+ ` }): Promise<SearchHit<${typeNames.payload}>[]>;`,
560
+ `};`
561
+ ];
453
562
  return {
454
563
  kind: "method",
455
564
  runtimeGlue: `createSearchDelegate(${JSON.stringify(config)})`,
456
565
  imports: [{ from: "@vibeorm/extensions/search", names: ["createSearchDelegate"] }],
457
566
  dtsImports: [{ from: "@vibeorm/extensions/search", names: ["SearchHit"] }],
458
- dts: [
459
- `readonly search: (args: {`,
460
- ` readonly query: string;`,
461
- ` readonly where?: ${typeNames.whereInput};`,
462
- ` readonly take?: number;`,
463
- ` readonly skip?: number;`,
464
- ` readonly minScore?: number;`,
465
- ` readonly highlight?: { readonly field: ${fieldUnion} };`,
466
- `}) => Promise<SearchHit<${typeNames.payload}>[]>;`
467
- ]
567
+ dts
468
568
  };
469
569
  }
470
570
  },
@@ -508,4 +608,4 @@ export {
508
608
  createSearchDelegate
509
609
  };
510
610
 
511
- //# debugId=CE3DBDCF2300506B64756E2164756E21
611
+ //# debugId=8D730D54A357293664756E2164756E21
@@ -2,10 +2,10 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/search/index.ts", "../src/shared.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * `search` — unified full-text search (extensions.md §4).\n *\n * One API, four engines resolved per dialect at GENERATE time:\n * - `tsvector` (postgres/pglite): generated STORED tsvector column + GIN,\n * websearch_to_tsquery / ts_rank_cd / ts_headline.\n * - `bm25` (postgres + ParadeDB pg_search, opt-in via `engine: \"bm25\"`):\n * bm25 index + `@@@` + paradedb.score/snippet.\n * - `fts5` (sqlite): external-content virtual table + sync triggers,\n * MATCH / bm25() / snippet().\n * - `fulltext` (mysql): FULLTEXT index + MATCH … AGAINST; highlights are\n * typed `null` (mysql has none).\n *\n * `engine: \"auto\"` picks the dialect's native engine (never bm25 — ParadeDB\n * availability is unknowable at generate time; opt in explicitly).\n */\n\nimport { VibeError, defineExtension, unsupportedCapability } from \"@vibeorm/schema\";\nimport type {\n Dialect,\n ExtensionArtifact,\n ExtensionSchemaContext,\n ModelIR,\n Provider,\n VibeExtension,\n} from \"@vibeorm/schema\";\nimport { ParamCollector, getSqlDialect, renderExpr } from \"@vibeorm/sql\";\nimport type { SqlDialect } from \"@vibeorm/sql\";\nimport type { ExtensionMountContext } from \"@vibeorm/runtime\";\nimport {\n columnNameOf,\n declNumberArray,\n declString,\n declStringArray,\n isSafeSqlName,\n pgExtensionCheck,\n primaryKeyFieldOf,\n tableNameOf,\n} from \"../shared.ts\";\n\n// ─── Options & types ──────────────────────────────────────────────\n\nexport type SearchEngine = \"bm25\" | \"tsvector\" | \"fts5\" | \"fulltext\";\n\nexport type SearchOptions = {\n /** Engine choice; `\"auto\"` (default) resolves per dialect. */\n readonly engine?: SearchEngine | \"auto\";\n /** Default text-search language for the tsvector engine (default \"english\"). */\n readonly language?: string;\n};\n\n/** One search hit. `highlights` is `null` on engines without highlighting. */\nexport type SearchHit<T> = {\n readonly row: T;\n readonly score: number;\n readonly highlights: readonly string[] | null;\n};\n\nconst VERSION = \"2.0.0-alpha.0\";\nconst SCORE_ALIAS = \"_vibe_score\";\nconst HIGHLIGHT_ALIAS = \"_vibe_highlight\";\nconst WEIGHT_LETTERS = [\"A\", \"B\", \"C\", \"D\"] as const;\n\n// ─── Engine resolution (generate/migrate time) ────────────────────\n\n/** Resolve the engine for a dialect; throws on impossible explicit choices. */\nexport function resolveSearchEngine(params: {\n dialect: Dialect;\n provider: Provider;\n requested: SearchEngine | \"auto\";\n}): SearchEngine {\n const { dialect, provider, requested } = params;\n const native: SearchEngine = dialect === \"postgres\" ? \"tsvector\" : dialect === \"sqlite\" ? \"fts5\" : \"fulltext\";\n if (requested === \"auto\") return native;\n if (requested === native) return native;\n if (requested === \"bm25\" && dialect === \"postgres\") {\n if (provider === \"pglite\") {\n throw unsupportedCapability({\n dialect,\n feature: 'the search engine \"bm25\" (ParadeDB pg_search)',\n hint: 'pglite does not bundle pg_search — use engine \"auto\"/\"tsvector\"',\n });\n }\n return \"bm25\";\n }\n throw unsupportedCapability({\n dialect,\n feature: `the search engine \"${requested}\"`,\n hint: `this dialect supports \"${native}\"${dialect === \"postgres\" ? ' and \"bm25\"' : \"\"}`,\n });\n}\n\n// ─── Declaration ──────────────────────────────────────────────────\n\ntype SearchDecl = {\n readonly fields: readonly string[];\n readonly weights: readonly number[] | undefined;\n readonly base: string;\n readonly language: string | undefined;\n};\n\nfunction readDeclaration(params: {\n model: ModelIR;\n declaration: Readonly<Record<string, unknown>>;\n}): SearchDecl | null {\n const fields = declStringArray(params.declaration, \"fields\");\n if (fields === undefined || fields.length === 0) return null;\n return {\n fields,\n weights: declNumberArray(params.declaration, \"weights\"),\n base: declString(params.declaration, \"name\") ?? `${tableNameOf({ model: params.model })}_search`,\n language: declString(params.declaration, \"language\"),\n };\n}\n\n/** Weight letter per field: highest weight → 'A', capped at 'D'. */\nfunction weightLetters(params: { decl: SearchDecl }): readonly string[] | null {\n const { decl } = params;\n if (decl.weights === undefined || decl.weights.length !== decl.fields.length) return null;\n const ranked = [...new Set(decl.weights)].sort((a, b) => b - a);\n return decl.weights.map(\n (weight) => WEIGHT_LETTERS[Math.min(ranked.indexOf(weight), WEIGHT_LETTERS.length - 1)] as string,\n );\n}\n\nfunction tsvectorExpression(params: {\n dialect: SqlDialect;\n model: ModelIR;\n decl: SearchDecl;\n language: string;\n}): string {\n const { dialect, model, decl, language } = params;\n const quote = (name: string): string => dialect.quoteIdent({ name });\n const letters = weightLetters({ decl });\n const parts = decl.fields.map((field, index) => {\n const vector = `to_tsvector('${language}', coalesce(${quote(columnNameOf({ model, field }))}, ''))`;\n return letters === null ? vector : `setweight(${vector}, '${letters[index]}')`;\n });\n return letters === null && parts.length > 1\n ? `to_tsvector('${language}', ${decl.fields\n .map((field) => `coalesce(${quote(columnNameOf({ model, field }))}, '')`)\n .join(\" || ' ' || \")})`\n : parts.join(\" || \");\n}\n\n// ─── Artifacts ────────────────────────────────────────────────────\n\nfunction searchArtifacts(ctx: ExtensionSchemaContext): readonly ExtensionArtifact[] {\n const requested = (ctx.options[\"engine\"] as SearchEngine | \"auto\" | undefined) ?? \"auto\";\n const engine = resolveSearchEngine({ dialect: ctx.dialect, provider: ctx.provider, requested });\n const dialect = getSqlDialect({ dialect: ctx.dialect });\n const quote = (name: string): string => dialect.quoteIdent({ name });\n const artifacts: ExtensionArtifact[] = [];\n\n if (engine === \"bm25\") {\n artifacts.push({\n id: \"search:extension:pg_search\",\n owns: { kind: \"postgresExtension\", name: \"pg_search\" },\n create: [\"CREATE EXTENSION IF NOT EXISTS pg_search;\"],\n drop: [\"DROP EXTENSION IF EXISTS pg_search;\"],\n });\n }\n\n for (const [modelName, declaration] of Object.entries(ctx.declarations)) {\n const model = ctx.schema.models.find((candidate) => candidate.name === modelName);\n if (model === undefined) continue;\n const decl = readDeclaration({ model, declaration });\n if (decl === null) continue;\n const table = tableNameOf({ model });\n const columns = decl.fields.map((field) => columnNameOf({ model, field }));\n\n if (engine === \"tsvector\") {\n const language = decl.language ?? (ctx.options[\"language\"] as string | undefined) ?? \"english\";\n const tsvColumn = `${decl.base}_tsv`;\n const expression = tsvectorExpression({ dialect, model, decl, language });\n artifacts.push(\n {\n id: `search:${modelName}:column`,\n owns: { kind: \"column\", table, column: tsvColumn },\n create: [\n `ALTER TABLE ${quote(table)} ADD COLUMN IF NOT EXISTS ${quote(tsvColumn)} tsvector GENERATED ALWAYS AS (${expression}) STORED;`,\n ],\n drop: [`ALTER TABLE ${quote(table)} DROP COLUMN IF EXISTS ${quote(tsvColumn)};`],\n },\n {\n id: `search:${modelName}:index`,\n owns: { kind: \"index\", table, name: `${decl.base}_idx` },\n create: [\n `CREATE INDEX IF NOT EXISTS ${quote(`${decl.base}_idx`)} ON ${quote(table)} USING gin (${quote(tsvColumn)});`,\n ],\n drop: [`DROP INDEX IF EXISTS ${quote(`${decl.base}_idx`)};`],\n },\n );\n continue;\n }\n\n if (engine === \"bm25\") {\n const pkField = primaryKeyFieldOf({ model });\n if (pkField === null) {\n throw new VibeError({\n code: \"VIBE_SCHEMA_INVALID\",\n message: `@@search with engine \"bm25\" needs a single-column primary key on \"${modelName}\" (key_field)`,\n meta: { model: modelName },\n });\n }\n const pkColumn = columnNameOf({ model, field: pkField });\n artifacts.push({\n id: `search:${modelName}:index`,\n owns: { kind: \"index\", table, name: `${decl.base}_idx` },\n create: [\n `CREATE INDEX IF NOT EXISTS ${quote(`${decl.base}_idx`)} ON ${quote(table)} USING bm25 (${[pkColumn, ...columns]\n .map((column) => quote(column))\n .join(\", \")}) WITH (key_field='${pkColumn}');`,\n ],\n drop: [`DROP INDEX IF EXISTS ${quote(`${decl.base}_idx`)};`],\n });\n continue;\n }\n\n if (engine === \"fts5\") {\n // N7 (SQL review): the external-content table and its sync triggers key\n // on the host's ROWID. Only a single Int/BigInt @id aliases (and pins)\n // the rowid; any other primary key leaves an IMPLICIT rowid that VACUUM\n // may renumber — a documented sqlite non-guarantee — after which the\n // shadow table silently points at the WRONG rows. Refuse at generate\n // time (earliest-moment rule); recovery for a database that already\n // drifted is `INSERT INTO <fts>(<fts>) VALUES('rebuild')`.\n const pkName = primaryKeyFieldOf({ model });\n const pkField = pkName === null ? undefined : model.fields.find((field) => field.name === pkName);\n const pkIsRowidAlias =\n pkField !== undefined && !pkField.isList && (pkField.type === \"Int\" || pkField.type === \"BigInt\");\n if (!pkIsRowidAlias) {\n throw unsupportedCapability({\n dialect: \"sqlite\",\n feature: `@@search (fts5) on \"${modelName}\", whose primary key is not a single INTEGER column`,\n hint: \"fts5 external-content tables key on the rowid; only an Int/BigInt @id aliases and PINS it — any other key's implicit rowid can be renumbered by VACUUM, silently mis-linking search hits. Give the searched model an Int/BigInt @id.\",\n });\n }\n const fts = `${decl.base}_fts`;\n const columnList = columns.map((column) => quote(column)).join(\", \");\n const newList = columns.map((column) => `new.${quote(column)}`).join(\", \");\n const oldList = columns.map((column) => `old.${quote(column)}`).join(\", \");\n artifacts.push(\n {\n id: `search:${modelName}:fts5-table`,\n owns: {\n kind: \"table\",\n name: fts,\n auxiliaryPrefixes: [`${fts}_`],\n },\n create: [\n `CREATE VIRTUAL TABLE IF NOT EXISTS ${quote(fts)} USING fts5(${columnList}, content='${table}');`,\n ],\n drop: [`DROP TABLE IF EXISTS ${quote(fts)};`],\n },\n {\n id: `search:${modelName}:fts5-triggers`,\n owns: { kind: \"trigger\", table, name: `${fts}_ai` },\n create: [\n `CREATE TRIGGER IF NOT EXISTS ${quote(`${fts}_ai`)} AFTER INSERT ON ${quote(table)} BEGIN\\n INSERT INTO ${quote(fts)}(rowid, ${columnList}) VALUES (new.rowid, ${newList});\\nEND;`,\n `CREATE TRIGGER IF NOT EXISTS ${quote(`${fts}_ad`)} AFTER DELETE ON ${quote(table)} BEGIN\\n INSERT INTO ${quote(fts)}(${quote(fts)}, rowid, ${columnList}) VALUES ('delete', old.rowid, ${oldList});\\nEND;`,\n `CREATE TRIGGER IF NOT EXISTS ${quote(`${fts}_au`)} AFTER UPDATE ON ${quote(table)} BEGIN\\n INSERT INTO ${quote(fts)}(${quote(fts)}, rowid, ${columnList}) VALUES ('delete', old.rowid, ${oldList});\\n INSERT INTO ${quote(fts)}(rowid, ${columnList}) VALUES (new.rowid, ${newList});\\nEND;`,\n ],\n drop: [\n `DROP TRIGGER IF EXISTS ${quote(`${fts}_ai`)};`,\n `DROP TRIGGER IF EXISTS ${quote(`${fts}_ad`)};`,\n `DROP TRIGGER IF EXISTS ${quote(`${fts}_au`)};`,\n ],\n },\n );\n continue;\n }\n\n // fulltext (mysql): no IF NOT EXISTS form exists — migrate's presence\n // check (introspected index name) decides whether the create runs.\n artifacts.push({\n id: `search:${modelName}:index`,\n owns: { kind: \"index\", table, name: `${decl.base}_idx` },\n create: [\n `CREATE FULLTEXT INDEX ${quote(`${decl.base}_idx`)} ON ${quote(table)} (${columns\n .map((column) => quote(column))\n .join(\", \")});`,\n ],\n drop: [`DROP INDEX ${quote(`${decl.base}_idx`)} ON ${quote(table)};`],\n });\n }\n return artifacts;\n}\n\n// ─── Runtime delegate (generated-glue target) ─────────────────────\n\n/** Baked per-model config the generator writes into the client. */\nexport type SearchDelegateConfig = {\n readonly engine: SearchEngine;\n readonly table: string;\n /** Searched fields in declaration order → column names. */\n readonly fields: readonly { readonly name: string; readonly column: string }[];\n /** tsvector engine: the generated column. */\n readonly tsvColumn?: string;\n /** fts5 engine: the shadow table. */\n readonly ftsTable?: string;\n /** tsvector engine: regconfig name (validated identifier). */\n readonly language?: string;\n /** bm25 engine: key_field column. */\n readonly pkColumn?: string;\n};\n\nexport type SearchArgs = {\n readonly query: string;\n readonly where?: Record<string, unknown>;\n readonly take?: number;\n readonly skip?: number;\n readonly minScore?: number;\n readonly highlight?: { readonly field: string };\n};\n\nexport type SearchMethod = (args: SearchArgs) => Promise<SearchHit<Record<string, unknown>>[]>;\n\nfunction highlightColumn(params: { config: SearchDelegateConfig; args: SearchArgs }): string | null {\n const { config, args } = params;\n if (args.highlight === undefined) return null;\n const field = config.fields.find((candidate) => candidate.name === args.highlight?.field);\n if (field === undefined) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `highlight.field \"${args.highlight.field}\" is not searched — declared: ${config.fields\n .map((candidate) => candidate.name)\n .join(\", \")}`,\n meta: { field: args.highlight.field },\n });\n }\n return field.column;\n}\n\nfunction shapeHits(params: {\n rows: readonly Record<string, unknown>[];\n strip: readonly string[];\n highlightsRequested: boolean;\n engineHasHighlights: boolean;\n}): SearchHit<Record<string, unknown>>[] {\n const { rows, strip, highlightsRequested, engineHasHighlights } = params;\n return rows.map((raw) => {\n const row: Record<string, unknown> = { ...raw };\n const score = Number(row[SCORE_ALIAS]);\n const highlight = row[HIGHLIGHT_ALIAS];\n delete row[SCORE_ALIAS];\n delete row[HIGHLIGHT_ALIAS];\n for (const name of strip) delete row[name];\n const highlights =\n engineHasHighlights && highlightsRequested && highlight !== null && highlight !== undefined\n ? [String(highlight)]\n : null;\n return { row, score, highlights };\n });\n}\n\n/**\n * The generated client's glue target: bakes engine + names at generate time,\n * mounts as the `search` method at runtime (D9).\n */\nexport function createSearchDelegate(\n config: SearchDelegateConfig,\n): (ctx: ExtensionMountContext) => SearchMethod {\n return (ctx: ExtensionMountContext): SearchMethod => {\n const dialect = ctx.dialect;\n const quote = (name: string): string => dialect.quoteIdent({ name });\n const t = quote(\"t\");\n\n return async (args: SearchArgs): Promise<SearchHit<Record<string, unknown>>[]> => {\n if (typeof args.query !== \"string\" || args.query.trim().length === 0) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: \"search requires a non-empty query string\",\n meta: { table: config.table },\n });\n }\n const take = Math.max(1, Math.trunc(args.take ?? 10));\n const skip = args.skip === undefined ? null : Math.max(0, Math.trunc(args.skip));\n const hlColumn = highlightColumn({ config, args });\n const collector = new ParamCollector(dialect);\n\n const userWhere = (qualify: string): string | null => {\n if (args.where === undefined) return null;\n const expr = ctx.compileWhere({ where: args.where, qualify });\n return expr === undefined ? null : renderExpr({ dialect, expr, collector });\n };\n\n let text: string;\n let strip: readonly string[] = [];\n let engineHasHighlights = true;\n\n if (config.engine === \"tsvector\") {\n const language = config.language ?? \"english\";\n const tsv = `${t}.${quote(config.tsvColumn ?? \"\")}`;\n const tsquery = (): string => `websearch_to_tsquery('${language}', ${collector.add(args.query)})`;\n text = `SELECT ${t}.*, ts_rank_cd(${tsv}, ${tsquery()}) AS ${quote(SCORE_ALIAS)}`;\n if (hlColumn !== null) {\n text += `, ts_headline('${language}', ${t}.${quote(hlColumn)}, ${tsquery()}) AS ${quote(HIGHLIGHT_ALIAS)}`;\n }\n text += ` FROM ${quote(config.table)} AS ${t} WHERE ${tsv} @@ ${tsquery()}`;\n const where = userWhere(\"t\");\n if (where !== null) text += ` AND (${where})`;\n if (args.minScore !== undefined) {\n text += ` AND ts_rank_cd(${tsv}, ${tsquery()}) >= ${collector.add(args.minScore)}`;\n }\n text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;\n strip = [config.tsvColumn ?? \"\"];\n } else if (config.engine === \"fts5\") {\n const fts = quote(config.ftsTable ?? \"\");\n text = `SELECT ${t}.*, -bm25(${fts}) AS ${quote(SCORE_ALIAS)}`;\n if (hlColumn !== null) {\n const index = config.fields.findIndex((field) => field.column === hlColumn);\n text += `, snippet(${fts}, ${index}, '<b>', '</b>', '…', 16) AS ${quote(HIGHLIGHT_ALIAS)}`;\n }\n text += ` FROM ${fts} JOIN ${quote(config.table)} AS ${t} ON ${t}.rowid = ${fts}.rowid`;\n text += ` WHERE ${fts} MATCH ${collector.add(args.query)}`;\n const where = userWhere(\"t\");\n if (where !== null) text += ` AND (${where})`;\n if (args.minScore !== undefined) {\n text += ` AND -bm25(${fts}) >= ${collector.add(args.minScore)}`;\n }\n text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;\n } else if (config.engine === \"fulltext\") {\n engineHasHighlights = false;\n const match = (): string =>\n `MATCH (${config.fields.map((field) => `${t}.${quote(field.column)}`).join(\", \")}) AGAINST (${collector.add(args.query)} IN NATURAL LANGUAGE MODE)`;\n text = `SELECT ${t}.*, ${match()} AS ${quote(SCORE_ALIAS)} FROM ${quote(config.table)} AS ${t}`;\n text += ` WHERE ${match()}`;\n const where = userWhere(\"t\");\n if (where !== null) text += ` AND (${where})`;\n if (args.minScore !== undefined) {\n text += ` AND ${match()} >= ${collector.add(args.minScore)}`;\n }\n text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;\n } else {\n // bm25 (ParadeDB). minScore filters client-side (paradedb.score is not\n // reliably WHERE-safe); highlights via paradedb.snippet.\n const pk = `${t}.${quote(config.pkColumn ?? \"\")}`;\n text = `SELECT ${t}.*, paradedb.score(${pk}) AS ${quote(SCORE_ALIAS)}`;\n if (hlColumn !== null) {\n text += `, paradedb.snippet(${t}.${quote(hlColumn)}) AS ${quote(HIGHLIGHT_ALIAS)}`;\n }\n text += ` FROM ${quote(config.table)} AS ${t} WHERE (${config.fields\n .map((field) => `${t}.${quote(field.column)} @@@ ${collector.add(args.query)}`)\n .join(\" OR \")})`;\n const where = userWhere(\"t\");\n if (where !== null) text += ` AND (${where})`;\n text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;\n }\n\n text += ` LIMIT ${take}`;\n if (skip !== null) text += ` OFFSET ${skip}`;\n\n const rows = await ctx.executeRows({ text, values: collector.values, method: \"search\" });\n let hits = shapeHits({\n rows,\n strip,\n highlightsRequested: args.highlight !== undefined,\n engineHasHighlights,\n });\n if (config.engine === \"bm25\" && args.minScore !== undefined) {\n const min = args.minScore;\n hits = hits.filter((hit) => hit.score >= min);\n }\n return hits;\n };\n };\n}\n\n// ─── Factory ──────────────────────────────────────────────────────\n\n/**\n * Unified full-text search.\n *\n * @example schema\n * // @@search([title, body], name: \"post_search\", weights: [2, 1])\n * const hits = await db.post.search({ query: \"sqlite full text\", take: 20 });\n * // → [{ row, score, highlights }]\n */\nexport function search(options: SearchOptions = {}): VibeExtension {\n return defineExtension({\n name: \"search\",\n version: VERSION,\n options: { ...options },\n requires: { postgresql: null, pglite: null, sqlite: null, mysql: null },\n\n schema: {\n modelAttributes: [\n {\n name: \"search\",\n parse: ({ args, fieldNames }) => {\n const fields = args.fields ?? [];\n if (fields.length === 0) {\n return { error: \"@@search requires a field list, e.g. @@search([title, body])\" };\n }\n const missing = fields.find((name) => !fieldNames.includes(name));\n if (missing !== undefined) {\n return { error: `@@search references unknown field \"${missing}\"` };\n }\n const name = args.named[\"name\"];\n if (name !== undefined && (typeof name !== \"string\" || !isSafeSqlName({ name }))) {\n return { error: `name must be a plain identifier` };\n }\n const weights = args.named[\"weights\"];\n if (weights !== undefined) {\n if (\n !Array.isArray(weights) ||\n weights.length !== fields.length ||\n weights.some((weight) => typeof weight !== \"number\")\n ) {\n return { error: `weights must be a number list matching the field list` };\n }\n }\n const language = args.named[\"language\"];\n if (language !== undefined && (typeof language !== \"string\" || !isSafeSqlName({ name: language }))) {\n return { error: `language must be a plain text-search config name` };\n }\n return { declaration: { fields, ...args.named } };\n },\n },\n ],\n indexKinds: [\"bm25\", \"fts5\", \"fulltext\", \"gin\"],\n },\n\n migrate: { artifacts: searchArtifacts },\n\n client: {\n model: ({ model, dialect, provider, options: extOptions, declaration, typeNames }) => {\n const decl = readDeclaration({ model, declaration });\n if (decl === null) return null;\n const requested = (extOptions[\"engine\"] as SearchEngine | \"auto\" | undefined) ?? \"auto\";\n const engine = resolveSearchEngine({ dialect, provider, requested });\n const language =\n decl.language ?? (extOptions[\"language\"] as string | undefined) ?? \"english\";\n if (!isSafeSqlName({ name: language })) {\n throw new VibeError({\n code: \"VIBE_SCHEMA_INVALID\",\n message: `search language \"${language}\" is not a plain text-search config name`,\n meta: { model: model.name, language },\n });\n }\n\n const pkField = primaryKeyFieldOf({ model });\n const config: SearchDelegateConfig = {\n engine,\n table: tableNameOf({ model }),\n fields: decl.fields.map((field) => ({\n name: field,\n column: columnNameOf({ model, field }),\n })),\n ...(engine === \"tsvector\" ? { tsvColumn: `${decl.base}_tsv`, language } : {}),\n ...(engine === \"fts5\" ? { ftsTable: `${decl.base}_fts` } : {}),\n ...(engine === \"bm25\" && pkField !== null\n ? { pkColumn: columnNameOf({ model, field: pkField }) }\n : {}),\n };\n\n const fieldUnion = decl.fields.map((field) => JSON.stringify(field)).join(\" | \");\n return {\n kind: \"method\",\n runtimeGlue: `createSearchDelegate(${JSON.stringify(config)})`,\n imports: [{ from: \"@vibeorm/extensions/search\", names: [\"createSearchDelegate\"] }],\n dtsImports: [{ from: \"@vibeorm/extensions/search\", names: [\"SearchHit\"] }],\n dts: [\n `readonly search: (args: {`,\n ` readonly query: string;`,\n ` readonly where?: ${typeNames.whereInput};`,\n ` readonly take?: number;`,\n ` readonly skip?: number;`,\n ` readonly minScore?: number;`,\n ` readonly highlight?: { readonly field: ${fieldUnion} };`,\n `}) => Promise<SearchHit<${typeNames.payload}>[]>;`,\n ],\n };\n },\n },\n\n doctor: async ({ execute, dialect, options: extOptions }) => {\n if (dialect === \"postgres\") {\n const requested = (extOptions[\"engine\"] as SearchEngine | \"auto\" | undefined) ?? \"auto\";\n if (requested === \"bm25\") {\n return { checks: [await pgExtensionCheck({ execute, name: \"pg_search\" })] };\n }\n return { checks: [{ name: \"tsvector\", ok: true, note: \"built in\" }] };\n }\n if (dialect === \"sqlite\") {\n try {\n const rows = await execute({\n text: \"SELECT 1 AS ok FROM pragma_compile_options WHERE compile_options = 'ENABLE_FTS5'\",\n });\n return {\n checks: [\n rows.length > 0\n ? { name: \"fts5\", ok: true, note: \"compiled in\" }\n : { name: \"fts5\", ok: false, note: \"sqlite built without FTS5\" },\n ],\n };\n } catch (error) {\n return {\n checks: [\n {\n name: \"fts5\",\n ok: false,\n note: `check failed: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n };\n }\n }\n return { checks: [{ name: \"fulltext\", ok: true, note: \"built in (InnoDB FULLTEXT)\" }] };\n },\n });\n}\n",
5
+ "/**\n * `search` — unified full-text search (extensions.md §4).\n *\n * One API, four engines resolved per dialect at GENERATE time:\n * - `tsvector` (postgres/pglite): generated STORED tsvector column + GIN,\n * websearch_to_tsquery / ts_rank_cd / ts_headline.\n * - `bm25` (postgres + ParadeDB pg_search, opt-in via `engine: \"bm25\"`):\n * bm25 index + `@@@` + paradedb.score/snippet.\n * - `fts5` (sqlite): external-content virtual table + sync triggers,\n * MATCH / bm25() / snippet().\n * - `fulltext` (mysql): FULLTEXT index + MATCH … AGAINST; highlights are\n * typed `null` (mysql has none).\n *\n * `engine: \"auto\"` picks the dialect's native engine (never bm25 — ParadeDB\n * availability is unknowable at generate time; opt in explicitly).\n *\n * `prefix: true` (per query) turns the text into per-token prefix matching\n * for type-ahead boxes: unicode letter/number tokens, each matched as a\n * prefix (tsvector `tok:*` via to_tsquery, fts5 `\"tok\"*`, fulltext BOOLEAN\n * MODE `+tok*`). Refused on bm25 — prefix behavior there is decided by the\n * index tokenizer, which this extension does not configure.\n */\n\nimport { VibeError, defineExtension, unsupportedCapability } from \"@vibeorm/schema\";\nimport type {\n Dialect,\n ExtensionArtifact,\n ExtensionSchemaContext,\n ModelIR,\n Provider,\n VibeExtension,\n} from \"@vibeorm/schema\";\nimport { ParamCollector, getSqlDialect, renderExpr } from \"@vibeorm/sql\";\nimport type { SqlDialect } from \"@vibeorm/sql\";\nimport type { ExtensionMountContext } from \"@vibeorm/runtime\";\nimport {\n columnNameOf,\n declNumberArray,\n declString,\n declStringArray,\n isSafeSqlName,\n pgExtensionCheck,\n primaryKeyFieldOf,\n tableNameOf,\n} from \"../shared.ts\";\n\n// ─── Options & types ──────────────────────────────────────────────\n\nexport type SearchEngine = \"bm25\" | \"tsvector\" | \"fts5\" | \"fulltext\";\n\nexport type SearchOptions = {\n /** Engine choice; `\"auto\"` (default) resolves per dialect. */\n readonly engine?: SearchEngine | \"auto\";\n /** Default text-search language for the tsvector engine (default \"english\"). */\n readonly language?: string;\n /**\n * Ranking function for the tsvector engine (default `\"ts_rank_cd\"`).\n * `ts_rank` ignores cover density, so the two order results differently —\n * consumers migrating hand-written `ts_rank` queries keep their ordering by\n * setting this. A per-model `rank:` declaration overrides the default.\n */\n readonly rank?: \"ts_rank\" | \"ts_rank_cd\";\n};\n\n/** One search hit. `highlights` is `null` on engines without highlighting. */\nexport type SearchHit<T> = {\n readonly row: T;\n readonly score: number;\n readonly highlights: readonly string[] | null;\n};\n\nconst VERSION = \"2.0.0-alpha.1\";\nconst SCORE_ALIAS = \"_vibe_score\";\nconst HIGHLIGHT_ALIAS = \"_vibe_highlight\";\nconst WEIGHT_LETTERS = [\"A\", \"B\", \"C\", \"D\"] as const;\n\n// ─── Engine resolution (generate/migrate time) ────────────────────\n\n/** Resolve the engine for a dialect; throws on impossible explicit choices. */\nexport function resolveSearchEngine(params: {\n dialect: Dialect;\n provider: Provider;\n requested: SearchEngine | \"auto\";\n}): SearchEngine {\n const { dialect, provider, requested } = params;\n const native: SearchEngine = dialect === \"postgres\" ? \"tsvector\" : dialect === \"sqlite\" ? \"fts5\" : \"fulltext\";\n if (requested === \"auto\") return native;\n if (requested === native) return native;\n if (requested === \"bm25\" && dialect === \"postgres\") {\n if (provider === \"pglite\") {\n throw unsupportedCapability({\n dialect,\n feature: 'the search engine \"bm25\" (ParadeDB pg_search)',\n hint: 'pglite does not bundle pg_search — use engine \"auto\"/\"tsvector\"',\n });\n }\n return \"bm25\";\n }\n throw unsupportedCapability({\n dialect,\n feature: `the search engine \"${requested}\"`,\n hint: `this dialect supports \"${native}\"${dialect === \"postgres\" ? ' and \"bm25\"' : \"\"}`,\n });\n}\n\n// ─── Declaration ──────────────────────────────────────────────────\n\ntype SearchDecl = {\n readonly fields: readonly string[];\n readonly weights: readonly number[] | undefined;\n readonly base: string;\n readonly language: string | undefined;\n /** Override for the tsvector column name (adopt-existing; tsvector engine only). */\n readonly columnName: string | undefined;\n /** Override for the index name (tsvector / bm25 / fulltext engines). */\n readonly indexName: string | undefined;\n /** Ranking function override (tsvector engine only). */\n readonly rank: \"ts_rank\" | \"ts_rank_cd\" | undefined;\n};\n\nfunction declRank(\n declaration: Readonly<Record<string, unknown>>,\n): \"ts_rank\" | \"ts_rank_cd\" | undefined {\n const value = declaration[\"rank\"];\n return value === \"ts_rank\" || value === \"ts_rank_cd\" ? value : undefined;\n}\n\nfunction readDeclaration(params: {\n model: ModelIR;\n declaration: Readonly<Record<string, unknown>>;\n}): SearchDecl | null {\n const fields = declStringArray(params.declaration, \"fields\");\n if (fields === undefined || fields.length === 0) return null;\n return {\n fields,\n weights: declNumberArray(params.declaration, \"weights\"),\n base: declString(params.declaration, \"name\") ?? `${tableNameOf({ model: params.model })}_search`,\n language: declString(params.declaration, \"language\"),\n columnName: declString(params.declaration, \"columnName\"),\n indexName: declString(params.declaration, \"indexName\"),\n rank: declRank(params.declaration),\n };\n}\n\n/** Weight letter per field: highest weight → 'A', capped at 'D'. */\nfunction weightLetters(params: { decl: SearchDecl }): readonly string[] | null {\n const { decl } = params;\n if (decl.weights === undefined || decl.weights.length !== decl.fields.length) return null;\n const ranked = [...new Set(decl.weights)].sort((a, b) => b - a);\n return decl.weights.map(\n (weight) => WEIGHT_LETTERS[Math.min(ranked.indexOf(weight), WEIGHT_LETTERS.length - 1)] as string,\n );\n}\n\n/**\n * Artifact names with the adoption overrides applied (`columnName:` /\n * `indexName:` — declared independently so a consumer with LIVE objects can\n * adopt them without a rename migration). Engines without the named object\n * refuse the override instead of silently ignoring it.\n */\nfunction resolveArtifactNames(params: {\n decl: SearchDecl;\n engine: SearchEngine;\n dialect: Dialect;\n}): { tsvColumn: string; indexName: string } {\n const { decl, engine, dialect } = params;\n if (engine === \"fts5\" && (decl.columnName !== undefined || decl.indexName !== undefined)) {\n throw unsupportedCapability({\n dialect,\n feature: \"@@search columnName/indexName overrides on the fts5 engine\",\n hint: \"fts5 emits a shadow table + triggers — there is no column or index to rename; use name: to change the table base\",\n });\n }\n if (decl.columnName !== undefined && engine !== \"tsvector\") {\n throw unsupportedCapability({\n dialect,\n feature: `@@search columnName on the ${engine} engine`,\n hint: \"only the tsvector engine materializes a generated column — drop columnName or target tsvector\",\n });\n }\n return {\n tsvColumn: decl.columnName ?? `${decl.base}_tsv`,\n indexName: decl.indexName ?? `${decl.base}_idx`,\n };\n}\n\n/**\n * Ranking function for the tsvector engine: the model's `rank:` declaration\n * wins over the factory-level default. A DECLARED rank on any other engine is\n * refused (it names postgres ranking functions); the factory default is just\n * a default and stays inert off tsvector.\n */\nfunction resolveRank(params: {\n decl: SearchDecl;\n options: Readonly<Record<string, unknown>>;\n engine: SearchEngine;\n dialect: Dialect;\n}): \"ts_rank\" | \"ts_rank_cd\" | undefined {\n const { decl, options, engine, dialect } = params;\n if (decl.rank !== undefined && engine !== \"tsvector\") {\n throw unsupportedCapability({\n dialect,\n feature: `@@search rank on the ${engine} engine`,\n hint: \"rank chooses between the postgres ranking functions ts_rank and ts_rank_cd — a tsvector-only concept\",\n });\n }\n if (engine !== \"tsvector\") return undefined;\n const fallback = options[\"rank\"];\n return decl.rank ?? (fallback === \"ts_rank\" || fallback === \"ts_rank_cd\" ? fallback : undefined);\n}\n\nfunction tsvectorExpression(params: {\n dialect: SqlDialect;\n model: ModelIR;\n decl: SearchDecl;\n language: string;\n}): string {\n const { dialect, model, decl, language } = params;\n const quote = (name: string): string => dialect.quoteIdent({ name });\n const letters = weightLetters({ decl });\n const parts = decl.fields.map((field, index) => {\n const vector = `to_tsvector('${language}', coalesce(${quote(columnNameOf({ model, field }))}, ''))`;\n return letters === null ? vector : `setweight(${vector}, '${letters[index]}')`;\n });\n return letters === null && parts.length > 1\n ? `to_tsvector('${language}', ${decl.fields\n .map((field) => `coalesce(${quote(columnNameOf({ model, field }))}, '')`)\n .join(\" || ' ' || \")})`\n : parts.join(\" || \");\n}\n\n// ─── Artifacts ────────────────────────────────────────────────────\n\nfunction searchArtifacts(ctx: ExtensionSchemaContext): readonly ExtensionArtifact[] {\n const requested = (ctx.options[\"engine\"] as SearchEngine | \"auto\" | undefined) ?? \"auto\";\n const engine = resolveSearchEngine({ dialect: ctx.dialect, provider: ctx.provider, requested });\n const dialect = getSqlDialect({ dialect: ctx.dialect });\n const quote = (name: string): string => dialect.quoteIdent({ name });\n const artifacts: ExtensionArtifact[] = [];\n\n if (engine === \"bm25\") {\n artifacts.push({\n id: \"search:extension:pg_search\",\n owns: { kind: \"postgresExtension\", name: \"pg_search\" },\n create: [\"CREATE EXTENSION IF NOT EXISTS pg_search;\"],\n drop: [\"DROP EXTENSION IF EXISTS pg_search;\"],\n });\n }\n\n for (const [modelName, declaration] of Object.entries(ctx.declarations)) {\n const model = ctx.schema.models.find((candidate) => candidate.name === modelName);\n if (model === undefined) continue;\n const decl = readDeclaration({ model, declaration });\n if (decl === null) continue;\n const table = tableNameOf({ model });\n const columns = decl.fields.map((field) => columnNameOf({ model, field }));\n resolveRank({ decl, options: ctx.options, engine, dialect: ctx.dialect });\n\n if (engine === \"tsvector\") {\n const language = decl.language ?? (ctx.options[\"language\"] as string | undefined) ?? \"english\";\n const { tsvColumn, indexName } = resolveArtifactNames({ decl, engine, dialect: ctx.dialect });\n const expression = tsvectorExpression({ dialect, model, decl, language });\n artifacts.push(\n {\n id: `search:${modelName}:column`,\n owns: { kind: \"column\", table, column: tsvColumn },\n create: [\n `ALTER TABLE ${quote(table)} ADD COLUMN IF NOT EXISTS ${quote(tsvColumn)} tsvector GENERATED ALWAYS AS (${expression}) STORED;`,\n ],\n drop: [`ALTER TABLE ${quote(table)} DROP COLUMN IF EXISTS ${quote(tsvColumn)};`],\n },\n {\n id: `search:${modelName}:index`,\n owns: { kind: \"index\", table, name: indexName },\n create: [\n `CREATE INDEX IF NOT EXISTS ${quote(indexName)} ON ${quote(table)} USING gin (${quote(tsvColumn)});`,\n ],\n drop: [`DROP INDEX IF EXISTS ${quote(indexName)};`],\n },\n );\n continue;\n }\n\n if (engine === \"bm25\") {\n const pkField = primaryKeyFieldOf({ model });\n if (pkField === null) {\n throw new VibeError({\n code: \"VIBE_SCHEMA_INVALID\",\n message: `@@search with engine \"bm25\" needs a single-column primary key on \"${modelName}\" (key_field)`,\n meta: { model: modelName },\n });\n }\n const pkColumn = columnNameOf({ model, field: pkField });\n const bm25Names = resolveArtifactNames({ decl, engine, dialect: ctx.dialect });\n artifacts.push({\n id: `search:${modelName}:index`,\n owns: { kind: \"index\", table, name: bm25Names.indexName },\n create: [\n `CREATE INDEX IF NOT EXISTS ${quote(bm25Names.indexName)} ON ${quote(table)} USING bm25 (${[pkColumn, ...columns]\n .map((column) => quote(column))\n .join(\", \")}) WITH (key_field='${pkColumn}');`,\n ],\n drop: [`DROP INDEX IF EXISTS ${quote(bm25Names.indexName)};`],\n });\n continue;\n }\n\n if (engine === \"fts5\") {\n resolveArtifactNames({ decl, engine, dialect: ctx.dialect });\n // N7 (SQL review): the external-content table and its sync triggers key\n // on the host's ROWID. Only a single Int/BigInt @id aliases (and pins)\n // the rowid; any other primary key leaves an IMPLICIT rowid that VACUUM\n // may renumber — a documented sqlite non-guarantee — after which the\n // shadow table silently points at the WRONG rows. Refuse at generate\n // time (earliest-moment rule); recovery for a database that already\n // drifted is `INSERT INTO <fts>(<fts>) VALUES('rebuild')`.\n const pkName = primaryKeyFieldOf({ model });\n const pkField = pkName === null ? undefined : model.fields.find((field) => field.name === pkName);\n const pkIsRowidAlias =\n pkField !== undefined && !pkField.isList && (pkField.type === \"Int\" || pkField.type === \"BigInt\");\n if (!pkIsRowidAlias) {\n throw unsupportedCapability({\n dialect: \"sqlite\",\n feature: `@@search (fts5) on \"${modelName}\", whose primary key is not a single INTEGER column`,\n hint: \"fts5 external-content tables key on the rowid; only an Int/BigInt @id aliases and PINS it — any other key's implicit rowid can be renumbered by VACUUM, silently mis-linking search hits. Give the searched model an Int/BigInt @id.\",\n });\n }\n const fts = `${decl.base}_fts`;\n const columnList = columns.map((column) => quote(column)).join(\", \");\n const newList = columns.map((column) => `new.${quote(column)}`).join(\", \");\n const oldList = columns.map((column) => `old.${quote(column)}`).join(\", \");\n artifacts.push(\n {\n id: `search:${modelName}:fts5-table`,\n owns: {\n kind: \"table\",\n name: fts,\n auxiliaryPrefixes: [`${fts}_`],\n },\n create: [\n `CREATE VIRTUAL TABLE IF NOT EXISTS ${quote(fts)} USING fts5(${columnList}, content='${table}');`,\n ],\n drop: [`DROP TABLE IF EXISTS ${quote(fts)};`],\n },\n {\n id: `search:${modelName}:fts5-triggers`,\n owns: { kind: \"trigger\", table, name: `${fts}_ai` },\n create: [\n `CREATE TRIGGER IF NOT EXISTS ${quote(`${fts}_ai`)} AFTER INSERT ON ${quote(table)} BEGIN\\n INSERT INTO ${quote(fts)}(rowid, ${columnList}) VALUES (new.rowid, ${newList});\\nEND;`,\n `CREATE TRIGGER IF NOT EXISTS ${quote(`${fts}_ad`)} AFTER DELETE ON ${quote(table)} BEGIN\\n INSERT INTO ${quote(fts)}(${quote(fts)}, rowid, ${columnList}) VALUES ('delete', old.rowid, ${oldList});\\nEND;`,\n `CREATE TRIGGER IF NOT EXISTS ${quote(`${fts}_au`)} AFTER UPDATE ON ${quote(table)} BEGIN\\n INSERT INTO ${quote(fts)}(${quote(fts)}, rowid, ${columnList}) VALUES ('delete', old.rowid, ${oldList});\\n INSERT INTO ${quote(fts)}(rowid, ${columnList}) VALUES (new.rowid, ${newList});\\nEND;`,\n ],\n drop: [\n `DROP TRIGGER IF EXISTS ${quote(`${fts}_ai`)};`,\n `DROP TRIGGER IF EXISTS ${quote(`${fts}_ad`)};`,\n `DROP TRIGGER IF EXISTS ${quote(`${fts}_au`)};`,\n ],\n },\n );\n continue;\n }\n\n // fulltext (mysql): no IF NOT EXISTS form exists — migrate's presence\n // check (introspected index name) decides whether the create runs.\n const fulltextNames = resolveArtifactNames({ decl, engine, dialect: ctx.dialect });\n artifacts.push({\n id: `search:${modelName}:index`,\n owns: { kind: \"index\", table, name: fulltextNames.indexName },\n create: [\n `CREATE FULLTEXT INDEX ${quote(fulltextNames.indexName)} ON ${quote(table)} (${columns\n .map((column) => quote(column))\n .join(\", \")});`,\n ],\n drop: [`DROP INDEX ${quote(fulltextNames.indexName)} ON ${quote(table)};`],\n });\n }\n return artifacts;\n}\n\n// ─── Runtime delegate (generated-glue target) ─────────────────────\n\n/** Baked per-model config the generator writes into the client. */\nexport type SearchDelegateConfig = {\n readonly engine: SearchEngine;\n readonly table: string;\n /** Searched fields in declaration order → column names. */\n readonly fields: readonly { readonly name: string; readonly column: string }[];\n /** tsvector engine: the generated column. */\n readonly tsvColumn?: string;\n /** fts5 engine: the shadow table. */\n readonly ftsTable?: string;\n /** tsvector engine: regconfig name (validated identifier). */\n readonly language?: string;\n /** tsvector engine: ranking function (default `ts_rank_cd`). */\n readonly rank?: \"ts_rank\" | \"ts_rank_cd\";\n /** Single-column primary key: bm25's key_field and the `select: \"id\"` projection. */\n readonly pkColumn?: string;\n};\n\nexport type SearchArgs = {\n readonly query: string;\n readonly where?: Record<string, unknown>;\n readonly take?: number;\n readonly skip?: number;\n readonly minScore?: number;\n readonly highlight?: { readonly field: string };\n /**\n * Type-ahead prefix matching. The query is split into unicode\n * letter/number tokens (punctuation separates and never matches) and every\n * token matches as a prefix — \"piz\" finds \"Pizza\". Per engine: tsvector\n * builds `to_tsquery('lang', 'piz:*')` (lexemes are STEMMED, so a stemming\n * `language` can miss mid-stem prefixes — declare `language: \"simple\"` for\n * type-ahead columns), fts5 `\"piz\"*`, fulltext `+piz*` in BOOLEAN MODE\n * (InnoDB ignores tokens under `innodb_ft_min_token_size`, default 3). A\n * query with no tokens resolves to `[]` without touching the database.\n * Refused on the bm25 engine (`VIBE_UNSUPPORTED_CAPABILITY`) — ParadeDB\n * prefix behavior is index-tokenizer-dependent.\n */\n readonly prefix?: boolean;\n /**\n * Projection: `\"row\"` (default) returns full rows; `\"id\"` returns only the\n * primary key — for repositories that re-read hits through their own typed\n * queries (full rows are wasted bandwidth there).\n */\n readonly select?: \"row\" | \"id\";\n};\n\nexport type SearchMethod = (args: SearchArgs) => Promise<SearchHit<Record<string, unknown>>[]>;\n\nfunction highlightColumn(params: { config: SearchDelegateConfig; args: SearchArgs }): string | null {\n const { config, args } = params;\n if (args.highlight === undefined) return null;\n const field = config.fields.find((candidate) => candidate.name === args.highlight?.field);\n if (field === undefined) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `highlight.field \"${args.highlight.field}\" is not searched — declared: ${config.fields\n .map((candidate) => candidate.name)\n .join(\", \")}`,\n meta: { field: args.highlight.field },\n });\n }\n return field.column;\n}\n\nfunction shapeHits(params: {\n rows: readonly Record<string, unknown>[];\n strip: readonly string[];\n highlightsRequested: boolean;\n engineHasHighlights: boolean;\n}): SearchHit<Record<string, unknown>>[] {\n const { rows, strip, highlightsRequested, engineHasHighlights } = params;\n return rows.map((raw) => {\n const row: Record<string, unknown> = { ...raw };\n const score = Number(row[SCORE_ALIAS]);\n const highlight = row[HIGHLIGHT_ALIAS];\n delete row[SCORE_ALIAS];\n delete row[HIGHLIGHT_ALIAS];\n for (const name of strip) delete row[name];\n const highlights =\n engineHasHighlights && highlightsRequested && highlight !== null && highlight !== undefined\n ? [String(highlight)]\n : null;\n return { row, score, highlights };\n });\n}\n\n/**\n * The generated client's glue target: bakes engine + names at generate time,\n * mounts as the `search` method at runtime (D9).\n */\nexport function createSearchDelegate(\n config: SearchDelegateConfig,\n): (ctx: ExtensionMountContext) => SearchMethod {\n return (ctx: ExtensionMountContext): SearchMethod => {\n const dialect = ctx.dialect;\n const quote = (name: string): string => dialect.quoteIdent({ name });\n const t = quote(\"t\");\n\n return async (args: SearchArgs): Promise<SearchHit<Record<string, unknown>>[]> => {\n if (typeof args.query !== \"string\" || args.query.trim().length === 0) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: \"search requires a non-empty query string\",\n meta: { table: config.table },\n });\n }\n const prefix = args.prefix === true;\n if (prefix && config.engine === \"bm25\") {\n throw unsupportedCapability({\n dialect: \"postgres\",\n feature: 'search prefix mode on the \"bm25\" engine',\n hint: \"ParadeDB prefix behavior is decided by the index tokenizer (ngram/edge-ngram) — configure the index instead, or use the tsvector engine\",\n });\n }\n let matchText = args.query;\n if (prefix) {\n const tokens = args.query.match(/[\\p{L}\\p{N}]+/gu) ?? [];\n if (tokens.length === 0) return [];\n matchText =\n config.engine === \"tsvector\"\n ? tokens.map((token) => `${token}:*`).join(\" & \")\n : config.engine === \"fts5\"\n ? tokens.map((token) => `\"${token}\"*`).join(\" \")\n : tokens.map((token) => `+${token}*`).join(\" \");\n }\n const take = Math.max(1, Math.trunc(args.take ?? 10));\n const skip = args.skip === undefined ? null : Math.max(0, Math.trunc(args.skip));\n const hlColumn = highlightColumn({ config, args });\n const idOnly = args.select === \"id\";\n if (idOnly && config.pkColumn === undefined) {\n throw new VibeError({\n code: \"VIBE_VALIDATION\",\n message: `select: \"id\" needs a single-column primary key baked into the client — ${config.table} has none, or the client predates the projection (run vibeorm generate)`,\n meta: { table: config.table },\n });\n }\n const projection = idOnly ? `${t}.${quote(config.pkColumn ?? \"\")}` : `${t}.*`;\n const collector = new ParamCollector(dialect);\n\n const userWhere = (qualify: string): string | null => {\n if (args.where === undefined) return null;\n const expr = ctx.compileWhere({ where: args.where, qualify });\n return expr === undefined ? null : renderExpr({ dialect, expr, collector });\n };\n\n let text: string;\n let strip: readonly string[] = [];\n let engineHasHighlights = true;\n\n if (config.engine === \"tsvector\") {\n const language = config.language ?? \"english\";\n const ranker: \"ts_rank\" | \"ts_rank_cd\" = config.rank ?? \"ts_rank_cd\";\n const tsv = `${t}.${quote(config.tsvColumn ?? \"\")}`;\n const tsquery = (): string =>\n prefix\n ? `to_tsquery('${language}', ${collector.add(matchText)})`\n : `websearch_to_tsquery('${language}', ${collector.add(matchText)})`;\n text = `SELECT ${projection}, ${ranker}(${tsv}, ${tsquery()}) AS ${quote(SCORE_ALIAS)}`;\n if (hlColumn !== null) {\n text += `, ts_headline('${language}', ${t}.${quote(hlColumn)}, ${tsquery()}) AS ${quote(HIGHLIGHT_ALIAS)}`;\n }\n text += ` FROM ${quote(config.table)} AS ${t} WHERE ${tsv} @@ ${tsquery()}`;\n const where = userWhere(\"t\");\n if (where !== null) text += ` AND (${where})`;\n if (args.minScore !== undefined) {\n text += ` AND ${ranker}(${tsv}, ${tsquery()}) >= ${collector.add(args.minScore)}`;\n }\n text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;\n strip = idOnly ? [] : [config.tsvColumn ?? \"\"];\n } else if (config.engine === \"fts5\") {\n const fts = quote(config.ftsTable ?? \"\");\n text = `SELECT ${projection}, -bm25(${fts}) AS ${quote(SCORE_ALIAS)}`;\n if (hlColumn !== null) {\n const index = config.fields.findIndex((field) => field.column === hlColumn);\n text += `, snippet(${fts}, ${index}, '<b>', '</b>', '…', 16) AS ${quote(HIGHLIGHT_ALIAS)}`;\n }\n text += ` FROM ${fts} JOIN ${quote(config.table)} AS ${t} ON ${t}.rowid = ${fts}.rowid`;\n text += ` WHERE ${fts} MATCH ${collector.add(matchText)}`;\n const where = userWhere(\"t\");\n if (where !== null) text += ` AND (${where})`;\n if (args.minScore !== undefined) {\n text += ` AND -bm25(${fts}) >= ${collector.add(args.minScore)}`;\n }\n text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;\n } else if (config.engine === \"fulltext\") {\n engineHasHighlights = false;\n const mode = prefix ? \"IN BOOLEAN MODE\" : \"IN NATURAL LANGUAGE MODE\";\n const match = (): string =>\n `MATCH (${config.fields.map((field) => `${t}.${quote(field.column)}`).join(\", \")}) AGAINST (${collector.add(matchText)} ${mode})`;\n text = `SELECT ${projection}, ${match()} AS ${quote(SCORE_ALIAS)} FROM ${quote(config.table)} AS ${t}`;\n text += ` WHERE ${match()}`;\n const where = userWhere(\"t\");\n if (where !== null) text += ` AND (${where})`;\n if (args.minScore !== undefined) {\n text += ` AND ${match()} >= ${collector.add(args.minScore)}`;\n }\n text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;\n } else {\n // bm25 (ParadeDB). minScore filters client-side (paradedb.score is not\n // reliably WHERE-safe); highlights via paradedb.snippet.\n const pk = `${t}.${quote(config.pkColumn ?? \"\")}`;\n text = `SELECT ${projection}, paradedb.score(${pk}) AS ${quote(SCORE_ALIAS)}`;\n if (hlColumn !== null) {\n text += `, paradedb.snippet(${t}.${quote(hlColumn)}) AS ${quote(HIGHLIGHT_ALIAS)}`;\n }\n text += ` FROM ${quote(config.table)} AS ${t} WHERE (${config.fields\n .map((field) => `${t}.${quote(field.column)} @@@ ${collector.add(args.query)}`)\n .join(\" OR \")})`;\n const where = userWhere(\"t\");\n if (where !== null) text += ` AND (${where})`;\n text += ` ORDER BY ${quote(SCORE_ALIAS)} DESC`;\n }\n\n text += ` LIMIT ${take}`;\n if (skip !== null) text += ` OFFSET ${skip}`;\n\n const rows = await ctx.executeRows({ text, values: collector.values, method: \"search\" });\n let hits = shapeHits({\n rows,\n strip,\n highlightsRequested: args.highlight !== undefined,\n engineHasHighlights,\n });\n if (config.engine === \"bm25\" && args.minScore !== undefined) {\n const min = args.minScore;\n hits = hits.filter((hit) => hit.score >= min);\n }\n return hits;\n };\n };\n}\n\n// ─── Factory ──────────────────────────────────────────────────────\n\n/**\n * Unified full-text search.\n *\n * @example schema\n * // @@search([title, body], name: \"post_search\", weights: [2, 1])\n * const hits = await db.post.search({ query: \"sqlite full text\", take: 20 });\n * // → [{ row, score, highlights }]\n */\nexport function search(options: SearchOptions = {}): VibeExtension {\n return defineExtension({\n name: \"search\",\n version: VERSION,\n options: { ...options },\n requires: { postgresql: null, pglite: null, sqlite: null, mysql: null },\n\n schema: {\n modelAttributes: [\n {\n name: \"search\",\n parse: ({ args, fieldNames }) => {\n const fields = args.fields ?? [];\n if (fields.length === 0) {\n return { error: \"@@search requires a field list, e.g. @@search([title, body])\" };\n }\n const missing = fields.find((name) => !fieldNames.includes(name));\n if (missing !== undefined) {\n return { error: `@@search references unknown field \"${missing}\"` };\n }\n const name = args.named[\"name\"];\n if (name !== undefined && (typeof name !== \"string\" || !isSafeSqlName({ name }))) {\n return { error: `name must be a plain identifier` };\n }\n const weights = args.named[\"weights\"];\n if (weights !== undefined) {\n if (\n !Array.isArray(weights) ||\n weights.length !== fields.length ||\n weights.some((weight) => typeof weight !== \"number\")\n ) {\n return { error: `weights must be a number list matching the field list` };\n }\n }\n const language = args.named[\"language\"];\n if (language !== undefined && (typeof language !== \"string\" || !isSafeSqlName({ name: language }))) {\n return { error: `language must be a plain text-search config name` };\n }\n for (const key of [\"columnName\", \"indexName\"] as const) {\n const value = args.named[key];\n if (value !== undefined && (typeof value !== \"string\" || !isSafeSqlName({ name: value }))) {\n return { error: `${key} must be a plain identifier` };\n }\n }\n const rank = args.named[\"rank\"];\n if (rank !== undefined && rank !== \"ts_rank\" && rank !== \"ts_rank_cd\") {\n return { error: `rank must be \"ts_rank\" or \"ts_rank_cd\"` };\n }\n return { declaration: { fields, ...args.named } };\n },\n },\n ],\n indexKinds: [\"bm25\", \"fts5\", \"fulltext\", \"gin\"],\n },\n\n migrate: { artifacts: searchArtifacts },\n\n client: {\n model: ({ model, dialect, provider, options: extOptions, declaration, typeNames }) => {\n const decl = readDeclaration({ model, declaration });\n if (decl === null) return null;\n const requested = (extOptions[\"engine\"] as SearchEngine | \"auto\" | undefined) ?? \"auto\";\n const engine = resolveSearchEngine({ dialect, provider, requested });\n const language =\n decl.language ?? (extOptions[\"language\"] as string | undefined) ?? \"english\";\n if (!isSafeSqlName({ name: language })) {\n throw new VibeError({\n code: \"VIBE_SCHEMA_INVALID\",\n message: `search language \"${language}\" is not a plain text-search config name`,\n meta: { model: model.name, language },\n });\n }\n\n const pkField = primaryKeyFieldOf({ model });\n const rank = resolveRank({ decl, options: extOptions, engine, dialect });\n const names = resolveArtifactNames({ decl, engine, dialect });\n const config: SearchDelegateConfig = {\n engine,\n table: tableNameOf({ model }),\n fields: decl.fields.map((field) => ({\n name: field,\n column: columnNameOf({ model, field }),\n })),\n ...(engine === \"tsvector\"\n ? { tsvColumn: names.tsvColumn, language, ...(rank === undefined ? {} : { rank }) }\n : {}),\n ...(engine === \"fts5\" ? { ftsTable: `${decl.base}_fts` } : {}),\n ...(pkField !== null ? { pkColumn: columnNameOf({ model, field: pkField }) } : {}),\n };\n\n const fieldUnion = decl.fields.map((field) => JSON.stringify(field)).join(\" | \");\n const argLines: string[] = [\n ` readonly query: string;`,\n ` readonly where?: ${typeNames.whereInput};`,\n ` readonly take?: number;`,\n ` readonly skip?: number;`,\n ` readonly minScore?: number;`,\n ` readonly highlight?: { readonly field: ${fieldUnion} };`,\n ` readonly prefix?: boolean;`,\n ];\n // With a single-column pk the id projection is offered as an overload:\n // `select: \"id\"` narrows the hit row to the pk.\n const dts: string[] =\n pkField === null\n ? [\n `readonly search: (args: {`,\n ...argLines,\n `}) => Promise<SearchHit<${typeNames.payload}>[]>;`,\n ]\n : [\n `readonly search: {`,\n ` (args: {`,\n ...argLines.map((line) => ` ${line}`),\n ` readonly select: \"id\";`,\n ` }): Promise<SearchHit<Pick<${typeNames.payload}, ${JSON.stringify(pkField)}>>[]>;`,\n ` (args: {`,\n ...argLines.map((line) => ` ${line}`),\n ` readonly select?: \"row\";`,\n ` }): Promise<SearchHit<${typeNames.payload}>[]>;`,\n `};`,\n ];\n return {\n kind: \"method\",\n runtimeGlue: `createSearchDelegate(${JSON.stringify(config)})`,\n imports: [{ from: \"@vibeorm/extensions/search\", names: [\"createSearchDelegate\"] }],\n dtsImports: [{ from: \"@vibeorm/extensions/search\", names: [\"SearchHit\"] }],\n dts,\n };\n },\n },\n\n doctor: async ({ execute, dialect, options: extOptions }) => {\n if (dialect === \"postgres\") {\n const requested = (extOptions[\"engine\"] as SearchEngine | \"auto\" | undefined) ?? \"auto\";\n if (requested === \"bm25\") {\n return { checks: [await pgExtensionCheck({ execute, name: \"pg_search\" })] };\n }\n return { checks: [{ name: \"tsvector\", ok: true, note: \"built in\" }] };\n }\n if (dialect === \"sqlite\") {\n try {\n const rows = await execute({\n text: \"SELECT 1 AS ok FROM pragma_compile_options WHERE compile_options = 'ENABLE_FTS5'\",\n });\n return {\n checks: [\n rows.length > 0\n ? { name: \"fts5\", ok: true, note: \"compiled in\" }\n : { name: \"fts5\", ok: false, note: \"sqlite built without FTS5\" },\n ],\n };\n } catch (error) {\n return {\n checks: [\n {\n name: \"fts5\",\n ok: false,\n note: `check failed: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n };\n }\n }\n return { checks: [{ name: \"fulltext\", ok: true, note: \"built in (InnoDB FULLTEXT)\" }] };\n },\n });\n}\n",
6
6
  "/**\n * Helpers shared by the tier-1 extensions. Nothing here is privileged —\n * userland extensions can do everything these do with the same public hooks.\n */\n\nimport { VibeError } from \"@vibeorm/schema\";\nimport type {\n ExtensionDoctorCheck,\n ExtensionSqlExecutor,\n ModelIR,\n} from \"@vibeorm/schema\";\n\n// ─── IR lookups ───────────────────────────────────────────────────\n\n/** Table name of a model (`dbName ?? name`). */\nexport function tableNameOf(params: { model: ModelIR }): string {\n return params.model.dbName ?? params.model.name;\n}\n\n/** Column name of a field (`dbName ?? name`); the field must exist. */\nexport function columnNameOf(params: { model: ModelIR; field: string }): string {\n const found = params.model.fields.find((candidate) => candidate.name === params.field);\n if (found === undefined) {\n throw new VibeError({\n code: \"VIBE_INTERNAL\",\n message: `extension referenced unknown field \"${params.field}\" on model \"${params.model.name}\"`,\n meta: { model: params.model.name, field: params.field },\n });\n }\n return found.dbName ?? found.name;\n}\n\n/** The model's single primary-key field name, for key_field-style options. */\nexport function primaryKeyFieldOf(params: { model: ModelIR }): string | null {\n const { model } = params;\n if (model.primaryKey !== undefined && model.primaryKey.fields.length === 1) {\n return model.primaryKey.fields[0] ?? null;\n }\n const idField = model.fields.find((field) => field.isId);\n return idField?.name ?? null;\n}\n\n// ─── Declaration readers (ExtensionDeclIR.options are untyped JSON) ─\n\nexport function declString(decl: Readonly<Record<string, unknown>>, key: string): string | undefined {\n const value = decl[key];\n return typeof value === \"string\" ? value : undefined;\n}\n\nexport function declStringArray(\n decl: Readonly<Record<string, unknown>>,\n key: string,\n): readonly string[] | undefined {\n const value = decl[key];\n if (!Array.isArray(value)) return undefined;\n return value.every((entry): entry is string => typeof entry === \"string\") ? value : undefined;\n}\n\nexport function declNumberArray(\n decl: Readonly<Record<string, unknown>>,\n key: string,\n): readonly number[] | undefined {\n const value = decl[key];\n if (!Array.isArray(value)) return undefined;\n return value.every((entry): entry is number => typeof entry === \"number\") ? value : undefined;\n}\n\n// ─── Identifier safety ────────────────────────────────────────────\n\nconst SQL_LITERAL_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Names an extension splices as SQL LITERALS (regconfig names, key_field\n * options) must be plain identifiers — everything else is rejected at parse\n * time so no schema-borne text ever reaches SQL unquoted.\n */\nexport function isSafeSqlName(params: { name: string }): boolean {\n return SQL_LITERAL_NAME.test(params.name);\n}\n\n// ─── Doctor helpers ───────────────────────────────────────────────\n\n/** Availability of a postgres extension, per pg_available_extensions. */\nexport async function pgExtensionCheck(params: {\n execute: ExtensionSqlExecutor;\n name: string;\n}): Promise<ExtensionDoctorCheck> {\n const { execute, name } = params;\n try {\n const rows = await execute({\n text: \"SELECT name, default_version, installed_version FROM pg_available_extensions WHERE name = $1\",\n values: [name],\n });\n const row = rows[0];\n if (row === undefined) {\n return { name: `extension ${name}`, ok: false, note: \"not available on this server\" };\n }\n const installed = row[\"installed_version\"];\n return {\n name: `extension ${name}`,\n ok: true,\n note: installed === null || installed === undefined ? \"available, not installed\" : `installed ${String(installed)}`,\n };\n } catch (error) {\n return {\n name: `extension ${name}`,\n ok: false,\n note: `check failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n}\n"
7
7
  ],
8
- "mappings": ";;AAiBA,sBAAS;AAST;;;ACrBA;AAUO,SAAS,WAAW,CAAC,QAAoC;AAAA,EAC9D,OAAO,OAAO,MAAM,UAAU,OAAO,MAAM;AAAA;AAItC,SAAS,YAAY,CAAC,QAAmD;AAAA,EAC9E,MAAM,QAAQ,OAAO,MAAM,OAAO,KAAK,CAAC,cAAc,UAAU,SAAS,OAAO,KAAK;AAAA,EACrF,IAAI,UAAU,WAAW;AAAA,IACvB,MAAM,IAAI,UAAU;AAAA,MAClB,MAAM;AAAA,MACN,SAAS,uCAAuC,OAAO,oBAAoB,OAAO,MAAM;AAAA,MACxF,MAAM,EAAE,OAAO,OAAO,MAAM,MAAM,OAAO,OAAO,MAAM;AAAA,IACxD,CAAC;AAAA,EACH;AAAA,EACA,OAAO,MAAM,UAAU,MAAM;AAAA;AAIxB,SAAS,iBAAiB,CAAC,QAA2C;AAAA,EAC3E,QAAQ,UAAU;AAAA,EAClB,IAAI,MAAM,eAAe,aAAa,MAAM,WAAW,OAAO,WAAW,GAAG;AAAA,IAC1E,OAAO,MAAM,WAAW,OAAO,MAAM;AAAA,EACvC;AAAA,EACA,MAAM,UAAU,MAAM,OAAO,KAAK,CAAC,UAAU,MAAM,IAAI;AAAA,EACvD,OAAO,SAAS,QAAQ;AAAA;AAKnB,SAAS,UAAU,CAAC,MAAyC,KAAiC;AAAA,EACnG,MAAM,QAAQ,KAAK;AAAA,EACnB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA;AAGtC,SAAS,eAAe,CAC7B,MACA,KAC+B;AAAA,EAC/B,MAAM,QAAQ,KAAK;AAAA,EACnB,IAAI,CAAC,MAAM,QAAQ,KAAK;AAAA,IAAG;AAAA,EAC3B,OAAO,MAAM,MAAM,CAAC,UAA2B,OAAO,UAAU,QAAQ,IAAI,QAAQ;AAAA;AAG/E,SAAS,eAAe,CAC7B,MACA,KAC+B;AAAA,EAC/B,MAAM,QAAQ,KAAK;AAAA,EACnB,IAAI,CAAC,MAAM,QAAQ,KAAK;AAAA,IAAG;AAAA,EAC3B,OAAO,MAAM,MAAM,CAAC,UAA2B,OAAO,UAAU,QAAQ,IAAI,QAAQ;AAAA;AAKtF,IAAM,mBAAmB;AAOlB,SAAS,aAAa,CAAC,QAAmC;AAAA,EAC/D,OAAO,iBAAiB,KAAK,OAAO,IAAI;AAAA;AAM1C,eAAsB,gBAAgB,CAAC,QAGL;AAAA,EAChC,QAAQ,SAAS,SAAS;AAAA,EAC1B,IAAI;AAAA,IACF,MAAM,OAAO,MAAM,QAAQ;AAAA,MACzB,MAAM;AAAA,MACN,QAAQ,CAAC,IAAI;AAAA,IACf,CAAC;AAAA,IACD,MAAM,MAAM,KAAK;AAAA,IACjB,IAAI,QAAQ,WAAW;AAAA,MACrB,OAAO,EAAE,MAAM,aAAa,QAAQ,IAAI,OAAO,MAAM,+BAA+B;AAAA,IACtF;AAAA,IACA,MAAM,YAAY,IAAI;AAAA,IACtB,OAAO;AAAA,MACL,MAAM,aAAa;AAAA,MACnB,IAAI;AAAA,MACJ,MAAM,cAAc,QAAQ,cAAc,YAAY,6BAA6B,aAAa,OAAO,SAAS;AAAA,IAClH;AAAA,IACA,OAAO,OAAO;AAAA,IACd,OAAO;AAAA,MACL,MAAM,aAAa;AAAA,MACnB,IAAI;AAAA,MACJ,MAAM,iBAAiB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAC9E;AAAA;AAAA;;;ADlDJ,IAAM,UAAU;AAChB,IAAM,cAAc;AACpB,IAAM,kBAAkB;AACxB,IAAM,iBAAiB,CAAC,KAAK,KAAK,KAAK,GAAG;AAKnC,SAAS,mBAAmB,CAAC,QAInB;AAAA,EACf,QAAQ,SAAS,UAAU,cAAc;AAAA,EACzC,MAAM,SAAuB,YAAY,aAAa,aAAa,YAAY,WAAW,SAAS;AAAA,EACnG,IAAI,cAAc;AAAA,IAAQ,OAAO;AAAA,EACjC,IAAI,cAAc;AAAA,IAAQ,OAAO;AAAA,EACjC,IAAI,cAAc,UAAU,YAAY,YAAY;AAAA,IAClD,IAAI,aAAa,UAAU;AAAA,MACzB,MAAM,sBAAsB;AAAA,QAC1B;AAAA,QACA,SAAS;AAAA,QACT,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,IACA,OAAO;AAAA,EACT;AAAA,EACA,MAAM,sBAAsB;AAAA,IAC1B;AAAA,IACA,SAAS,sBAAsB;AAAA,IAC/B,MAAM,0BAA0B,UAAU,YAAY,aAAa,gBAAgB;AAAA,EACrF,CAAC;AAAA;AAYH,SAAS,eAAe,CAAC,QAGH;AAAA,EACpB,MAAM,SAAS,gBAAgB,OAAO,aAAa,QAAQ;AAAA,EAC3D,IAAI,WAAW,aAAa,OAAO,WAAW;AAAA,IAAG,OAAO;AAAA,EACxD,OAAO;AAAA,IACL;AAAA,IACA,SAAS,gBAAgB,OAAO,aAAa,SAAS;AAAA,IACtD,MAAM,WAAW,OAAO,aAAa,MAAM,KAAK,GAAG,YAAY,EAAE,OAAO,OAAO,MAAM,CAAC;AAAA,IACtF,UAAU,WAAW,OAAO,aAAa,UAAU;AAAA,EACrD;AAAA;AAIF,SAAS,aAAa,CAAC,QAAwD;AAAA,EAC7E,QAAQ,SAAS;AAAA,EACjB,IAAI,KAAK,YAAY,aAAa,KAAK,QAAQ,WAAW,KAAK,OAAO;AAAA,IAAQ,OAAO;AAAA,EACrF,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,KAAK,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAAA,EAC9D,OAAO,KAAK,QAAQ,IAClB,CAAC,WAAW,eAAe,KAAK,IAAI,OAAO,QAAQ,MAAM,GAAG,eAAe,SAAS,CAAC,EACvF;AAAA;AAGF,SAAS,kBAAkB,CAAC,QAKjB;AAAA,EACT,QAAQ,SAAS,OAAO,MAAM,aAAa;AAAA,EAC3C,MAAM,QAAQ,CAAC,SAAyB,QAAQ,WAAW,EAAE,KAAK,CAAC;AAAA,EACnE,MAAM,UAAU,cAAc,EAAE,KAAK,CAAC;AAAA,EACtC,MAAM,QAAQ,KAAK,OAAO,IAAI,CAAC,OAAO,UAAU;AAAA,IAC9C,MAAM,SAAS,gBAAgB,uBAAuB,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC,CAAC;AAAA,IAC1F,OAAO,YAAY,OAAO,SAAS,aAAa,YAAY,QAAQ;AAAA,GACrE;AAAA,EACD,OAAO,YAAY,QAAQ,MAAM,SAAS,IACtC,gBAAgB,cAAc,KAAK,OAChC,IAAI,CAAC,UAAU,YAAY,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC,CAAC,QAAQ,EACvE,KAAK,aAAa,OACrB,MAAM,KAAK,MAAM;AAAA;AAKvB,SAAS,eAAe,CAAC,KAA2D;AAAA,EAClF,MAAM,YAAa,IAAI,QAAQ,aAAmD;AAAA,EAClF,MAAM,SAAS,oBAAoB,EAAE,SAAS,IAAI,SAAS,UAAU,IAAI,UAAU,UAAU,CAAC;AAAA,EAC9F,MAAM,UAAU,cAAc,EAAE,SAAS,IAAI,QAAQ,CAAC;AAAA,EACtD,MAAM,QAAQ,CAAC,SAAyB,QAAQ,WAAW,EAAE,KAAK,CAAC;AAAA,EACnE,MAAM,YAAiC,CAAC;AAAA,EAExC,IAAI,WAAW,QAAQ;AAAA,IACrB,UAAU,KAAK;AAAA,MACb,IAAI;AAAA,MACJ,MAAM,EAAE,MAAM,qBAAqB,MAAM,YAAY;AAAA,MACrD,QAAQ,CAAC,2CAA2C;AAAA,MACpD,MAAM,CAAC,qCAAqC;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA,EAEA,YAAY,WAAW,gBAAgB,OAAO,QAAQ,IAAI,YAAY,GAAG;AAAA,IACvE,MAAM,QAAQ,IAAI,OAAO,OAAO,KAAK,CAAC,cAAc,UAAU,SAAS,SAAS;AAAA,IAChF,IAAI,UAAU;AAAA,MAAW;AAAA,IACzB,MAAM,OAAO,gBAAgB,EAAE,OAAO,YAAY,CAAC;AAAA,IACnD,IAAI,SAAS;AAAA,MAAM;AAAA,IACnB,MAAM,QAAQ,YAAY,EAAE,MAAM,CAAC;AAAA,IACnC,MAAM,UAAU,KAAK,OAAO,IAAI,CAAC,UAAU,aAAa,EAAE,OAAO,MAAM,CAAC,CAAC;AAAA,IAEzE,IAAI,WAAW,YAAY;AAAA,MACzB,MAAM,WAAW,KAAK,YAAa,IAAI,QAAQ,eAAsC;AAAA,MACrF,MAAM,YAAY,GAAG,KAAK;AAAA,MAC1B,MAAM,aAAa,mBAAmB,EAAE,SAAS,OAAO,MAAM,SAAS,CAAC;AAAA,MACxE,UAAU,KACR;AAAA,QACE,IAAI,UAAU;AAAA,QACd,MAAM,EAAE,MAAM,UAAU,OAAO,QAAQ,UAAU;AAAA,QACjD,QAAQ;AAAA,UACN,eAAe,MAAM,KAAK,8BAA8B,MAAM,SAAS,mCAAmC;AAAA,QAC5G;AAAA,QACA,MAAM,CAAC,eAAe,MAAM,KAAK,2BAA2B,MAAM,SAAS,IAAI;AAAA,MACjF,GACA;AAAA,QACE,IAAI,UAAU;AAAA,QACd,MAAM,EAAE,MAAM,SAAS,OAAO,MAAM,GAAG,KAAK,WAAW;AAAA,QACvD,QAAQ;AAAA,UACN,8BAA8B,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,KAAK,gBAAgB,MAAM,SAAS;AAAA,QAC1G;AAAA,QACA,MAAM,CAAC,wBAAwB,MAAM,GAAG,KAAK,UAAU,IAAI;AAAA,MAC7D,CACF;AAAA,MACA;AAAA,IACF;AAAA,IAEA,IAAI,WAAW,QAAQ;AAAA,MACrB,MAAM,UAAU,kBAAkB,EAAE,MAAM,CAAC;AAAA,MAC3C,IAAI,YAAY,MAAM;AAAA,QACpB,MAAM,IAAI,WAAU;AAAA,UAClB,MAAM;AAAA,UACN,SAAS,qEAAqE;AAAA,UAC9E,MAAM,EAAE,OAAO,UAAU;AAAA,QAC3B,CAAC;AAAA,MACH;AAAA,MACA,MAAM,WAAW,aAAa,EAAE,OAAO,OAAO,QAAQ,CAAC;AAAA,MACvD,UAAU,KAAK;AAAA,QACb,IAAI,UAAU;AAAA,QACd,MAAM,EAAE,MAAM,SAAS,OAAO,MAAM,GAAG,KAAK,WAAW;AAAA,QACvD,QAAQ;AAAA,UACN,8BAA8B,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,KAAK,iBAAiB,CAAC,UAAU,GAAG,OAAO,EAC5G,IAAI,CAAC,WAAW,MAAM,MAAM,CAAC,EAC7B,KAAK,IAAI,uBAAuB;AAAA,QACrC;AAAA,QACA,MAAM,CAAC,wBAAwB,MAAM,GAAG,KAAK,UAAU,IAAI;AAAA,MAC7D,CAAC;AAAA,MACD;AAAA,IACF;AAAA,IAEA,IAAI,WAAW,QAAQ;AAAA,MAQrB,MAAM,SAAS,kBAAkB,EAAE,MAAM,CAAC;AAAA,MAC1C,MAAM,UAAU,WAAW,OAAO,YAAY,MAAM,OAAO,KAAK,CAAC,UAAU,MAAM,SAAS,MAAM;AAAA,MAChG,MAAM,iBACJ,YAAY,aAAa,CAAC,QAAQ,WAAW,QAAQ,SAAS,SAAS,QAAQ,SAAS;AAAA,MAC1F,IAAI,CAAC,gBAAgB;AAAA,QACnB,MAAM,sBAAsB;AAAA,UAC1B,SAAS;AAAA,UACT,SAAS,uBAAuB;AAAA,UAChC,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,MACA,MAAM,MAAM,GAAG,KAAK;AAAA,MACpB,MAAM,aAAa,QAAQ,IAAI,CAAC,WAAW,MAAM,MAAM,CAAC,EAAE,KAAK,IAAI;AAAA,MACnE,MAAM,UAAU,QAAQ,IAAI,CAAC,WAAW,OAAO,MAAM,MAAM,GAAG,EAAE,KAAK,IAAI;AAAA,MACzE,MAAM,UAAU,QAAQ,IAAI,CAAC,WAAW,OAAO,MAAM,MAAM,GAAG,EAAE,KAAK,IAAI;AAAA,MACzE,UAAU,KACR;AAAA,QACE,IAAI,UAAU;AAAA,QACd,MAAM;AAAA,UACJ,MAAM;AAAA,UACN,MAAM;AAAA,UACN,mBAAmB,CAAC,GAAG,MAAM;AAAA,QAC/B;AAAA,QACA,QAAQ;AAAA,UACN,sCAAsC,MAAM,GAAG,gBAAgB,wBAAwB;AAAA,QACzF;AAAA,QACA,MAAM,CAAC,wBAAwB,MAAM,GAAG,IAAI;AAAA,MAC9C,GACA;AAAA,QACE,IAAI,UAAU;AAAA,QACd,MAAM,EAAE,MAAM,WAAW,OAAO,MAAM,GAAG,SAAS;AAAA,QAClD,QAAQ;AAAA,UACN,gCAAgC,MAAM,GAAG,QAAQ,qBAAqB,MAAM,KAAK;AAAA,gBAA0B,MAAM,GAAG,YAAY,kCAAkC;AAAA;AAAA,UAClK,gCAAgC,MAAM,GAAG,QAAQ,qBAAqB,MAAM,KAAK;AAAA,gBAA0B,MAAM,GAAG,KAAK,MAAM,GAAG,aAAa,4CAA4C;AAAA;AAAA,UAC3L,gCAAgC,MAAM,GAAG,QAAQ,qBAAqB,MAAM,KAAK;AAAA,gBAA0B,MAAM,GAAG,KAAK,MAAM,GAAG,aAAa,4CAA4C;AAAA,gBAA4B,MAAM,GAAG,YAAY,kCAAkC;AAAA;AAAA,QAChR;AAAA,QACA,MAAM;AAAA,UACJ,0BAA0B,MAAM,GAAG,QAAQ;AAAA,UAC3C,0BAA0B,MAAM,GAAG,QAAQ;AAAA,UAC3C,0BAA0B,MAAM,GAAG,QAAQ;AAAA,QAC7C;AAAA,MACF,CACF;AAAA,MACA;AAAA,IACF;AAAA,IAIA,UAAU,KAAK;AAAA,MACb,IAAI,UAAU;AAAA,MACd,MAAM,EAAE,MAAM,SAAS,OAAO,MAAM,GAAG,KAAK,WAAW;AAAA,MACvD,QAAQ;AAAA,QACN,yBAAyB,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,KAAK,MAAM,QACvE,IAAI,CAAC,WAAW,MAAM,MAAM,CAAC,EAC7B,KAAK,IAAI;AAAA,MACd;AAAA,MACA,MAAM,CAAC,cAAc,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,KAAK,IAAI;AAAA,IACtE,CAAC;AAAA,EACH;AAAA,EACA,OAAO;AAAA;AAgCT,SAAS,eAAe,CAAC,QAA2E;AAAA,EAClG,QAAQ,QAAQ,SAAS;AAAA,EACzB,IAAI,KAAK,cAAc;AAAA,IAAW,OAAO;AAAA,EACzC,MAAM,QAAQ,OAAO,OAAO,KAAK,CAAC,cAAc,UAAU,SAAS,KAAK,WAAW,KAAK;AAAA,EACxF,IAAI,UAAU,WAAW;AAAA,IACvB,MAAM,IAAI,WAAU;AAAA,MAClB,MAAM;AAAA,MACN,SAAS,oBAAoB,KAAK,UAAU,2CAAqC,OAAO,OACrF,IAAI,CAAC,cAAc,UAAU,IAAI,EACjC,KAAK,IAAI;AAAA,MACZ,MAAM,EAAE,OAAO,KAAK,UAAU,MAAM;AAAA,IACtC,CAAC;AAAA,EACH;AAAA,EACA,OAAO,MAAM;AAAA;AAGf,SAAS,SAAS,CAAC,QAKsB;AAAA,EACvC,QAAQ,MAAM,OAAO,qBAAqB,wBAAwB;AAAA,EAClE,OAAO,KAAK,IAAI,CAAC,QAAQ;AAAA,IACvB,MAAM,MAA+B,KAAK,IAAI;AAAA,IAC9C,MAAM,QAAQ,OAAO,IAAI,YAAY;AAAA,IACrC,MAAM,YAAY,IAAI;AAAA,IACtB,OAAO,IAAI;AAAA,IACX,OAAO,IAAI;AAAA,IACX,WAAW,QAAQ;AAAA,MAAO,OAAO,IAAI;AAAA,IACrC,MAAM,aACJ,uBAAuB,uBAAuB,cAAc,QAAQ,cAAc,YAC9E,CAAC,OAAO,SAAS,CAAC,IAClB;AAAA,IACN,OAAO,EAAE,KAAK,OAAO,WAAW;AAAA,GACjC;AAAA;AAOI,SAAS,oBAAoB,CAClC,QAC8C;AAAA,EAC9C,OAAO,CAAC,QAA6C;AAAA,IACnD,MAAM,UAAU,IAAI;AAAA,IACpB,MAAM,QAAQ,CAAC,SAAyB,QAAQ,WAAW,EAAE,KAAK,CAAC;AAAA,IACnE,MAAM,IAAI,MAAM,GAAG;AAAA,IAEnB,OAAO,OAAO,SAAoE;AAAA,MAChF,IAAI,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,EAAE,WAAW,GAAG;AAAA,QACpE,MAAM,IAAI,WAAU;AAAA,UAClB,MAAM;AAAA,UACN,SAAS;AAAA,UACT,MAAM,EAAE,OAAO,OAAO,MAAM;AAAA,QAC9B,CAAC;AAAA,MACH;AAAA,MACA,MAAM,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,QAAQ,EAAE,CAAC;AAAA,MACpD,MAAM,OAAO,KAAK,SAAS,YAAY,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,IAAI,CAAC;AAAA,MAC/E,MAAM,WAAW,gBAAgB,EAAE,QAAQ,KAAK,CAAC;AAAA,MACjD,MAAM,YAAY,IAAI,eAAe,OAAO;AAAA,MAE5C,MAAM,YAAY,CAAC,YAAmC;AAAA,QACpD,IAAI,KAAK,UAAU;AAAA,UAAW,OAAO;AAAA,QACrC,MAAM,OAAO,IAAI,aAAa,EAAE,OAAO,KAAK,OAAO,QAAQ,CAAC;AAAA,QAC5D,OAAO,SAAS,YAAY,OAAO,WAAW,EAAE,SAAS,MAAM,UAAU,CAAC;AAAA;AAAA,MAG5E,IAAI;AAAA,MACJ,IAAI,QAA2B,CAAC;AAAA,MAChC,IAAI,sBAAsB;AAAA,MAE1B,IAAI,OAAO,WAAW,YAAY;AAAA,QAChC,MAAM,WAAW,OAAO,YAAY;AAAA,QACpC,MAAM,MAAM,GAAG,KAAK,MAAM,OAAO,aAAa,EAAE;AAAA,QAChD,MAAM,UAAU,MAAc,yBAAyB,cAAc,UAAU,IAAI,KAAK,KAAK;AAAA,QAC7F,OAAO,UAAU,mBAAmB,QAAQ,QAAQ,SAAS,MAAM,WAAW;AAAA,QAC9E,IAAI,aAAa,MAAM;AAAA,UACrB,QAAQ,kBAAkB,cAAc,KAAK,MAAM,QAAQ,MAAM,QAAQ,SAAS,MAAM,eAAe;AAAA,QACzG;AAAA,QACA,QAAQ,SAAS,MAAM,OAAO,KAAK,QAAQ,WAAW,UAAU,QAAQ;AAAA,QACxE,MAAM,QAAQ,UAAU,GAAG;AAAA,QAC3B,IAAI,UAAU;AAAA,UAAM,QAAQ,SAAS;AAAA,QACrC,IAAI,KAAK,aAAa,WAAW;AAAA,UAC/B,QAAQ,mBAAmB,QAAQ,QAAQ,SAAS,UAAU,IAAI,KAAK,QAAQ;AAAA,QACjF;AAAA,QACA,QAAQ,aAAa,MAAM,WAAW;AAAA,QACtC,QAAQ,CAAC,OAAO,aAAa,EAAE;AAAA,MACjC,EAAO,SAAI,OAAO,WAAW,QAAQ;AAAA,QACnC,MAAM,MAAM,MAAM,OAAO,YAAY,EAAE;AAAA,QACvC,OAAO,UAAU,cAAc,WAAW,MAAM,WAAW;AAAA,QAC3D,IAAI,aAAa,MAAM;AAAA,UACrB,MAAM,QAAQ,OAAO,OAAO,UAAU,CAAC,UAAU,MAAM,WAAW,QAAQ;AAAA,UAC1E,QAAQ,aAAa,QAAQ,0CAAoC,MAAM,eAAe;AAAA,QACxF;AAAA,QACA,QAAQ,SAAS,YAAY,MAAM,OAAO,KAAK,QAAQ,QAAQ,aAAa;AAAA,QAC5E,QAAQ,UAAU,aAAa,UAAU,IAAI,KAAK,KAAK;AAAA,QACvD,MAAM,QAAQ,UAAU,GAAG;AAAA,QAC3B,IAAI,UAAU;AAAA,UAAM,QAAQ,SAAS;AAAA,QACrC,IAAI,KAAK,aAAa,WAAW;AAAA,UAC/B,QAAQ,cAAc,WAAW,UAAU,IAAI,KAAK,QAAQ;AAAA,QAC9D;AAAA,QACA,QAAQ,aAAa,MAAM,WAAW;AAAA,MACxC,EAAO,SAAI,OAAO,WAAW,YAAY;AAAA,QACvC,sBAAsB;AAAA,QACtB,MAAM,QAAQ,MACZ,UAAU,OAAO,OAAO,IAAI,CAAC,UAAU,GAAG,KAAK,MAAM,MAAM,MAAM,GAAG,EAAE,KAAK,IAAI,eAAe,UAAU,IAAI,KAAK,KAAK;AAAA,QACxH,OAAO,UAAU,QAAQ,MAAM,QAAQ,MAAM,WAAW,UAAU,MAAM,OAAO,KAAK,QAAQ;AAAA,QAC5F,QAAQ,UAAU,MAAM;AAAA,QACxB,MAAM,QAAQ,UAAU,GAAG;AAAA,QAC3B,IAAI,UAAU;AAAA,UAAM,QAAQ,SAAS;AAAA,QACrC,IAAI,KAAK,aAAa,WAAW;AAAA,UAC/B,QAAQ,QAAQ,MAAM,QAAQ,UAAU,IAAI,KAAK,QAAQ;AAAA,QAC3D;AAAA,QACA,QAAQ,aAAa,MAAM,WAAW;AAAA,MACxC,EAAO;AAAA,QAGL,MAAM,KAAK,GAAG,KAAK,MAAM,OAAO,YAAY,EAAE;AAAA,QAC9C,OAAO,UAAU,uBAAuB,UAAU,MAAM,WAAW;AAAA,QACnE,IAAI,aAAa,MAAM;AAAA,UACrB,QAAQ,sBAAsB,KAAK,MAAM,QAAQ,SAAS,MAAM,eAAe;AAAA,QACjF;AAAA,QACA,QAAQ,SAAS,MAAM,OAAO,KAAK,QAAQ,YAAY,OAAO,OAC3D,IAAI,CAAC,UAAU,GAAG,KAAK,MAAM,MAAM,MAAM,SAAS,UAAU,IAAI,KAAK,KAAK,GAAG,EAC7E,KAAK,MAAM;AAAA,QACd,MAAM,QAAQ,UAAU,GAAG;AAAA,QAC3B,IAAI,UAAU;AAAA,UAAM,QAAQ,SAAS;AAAA,QACrC,QAAQ,aAAa,MAAM,WAAW;AAAA;AAAA,MAGxC,QAAQ,UAAU;AAAA,MAClB,IAAI,SAAS;AAAA,QAAM,QAAQ,WAAW;AAAA,MAEtC,MAAM,OAAO,MAAM,IAAI,YAAY,EAAE,MAAM,QAAQ,UAAU,QAAQ,QAAQ,SAAS,CAAC;AAAA,MACvF,IAAI,OAAO,UAAU;AAAA,QACnB;AAAA,QACA;AAAA,QACA,qBAAqB,KAAK,cAAc;AAAA,QACxC;AAAA,MACF,CAAC;AAAA,MACD,IAAI,OAAO,WAAW,UAAU,KAAK,aAAa,WAAW;AAAA,QAC3D,MAAM,MAAM,KAAK;AAAA,QACjB,OAAO,KAAK,OAAO,CAAC,QAAQ,IAAI,SAAS,GAAG;AAAA,MAC9C;AAAA,MACA,OAAO;AAAA;AAAA;AAAA;AAeN,SAAS,MAAM,CAAC,UAAyB,CAAC,GAAkB;AAAA,EACjE,OAAO,gBAAgB;AAAA,IACrB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,KAAK,QAAQ;AAAA,IACtB,UAAU,EAAE,YAAY,MAAM,QAAQ,MAAM,QAAQ,MAAM,OAAO,KAAK;AAAA,IAEtE,QAAQ;AAAA,MACN,iBAAiB;AAAA,QACf;AAAA,UACE,MAAM;AAAA,UACN,OAAO,GAAG,MAAM,iBAAiB;AAAA,YAC/B,MAAM,SAAS,KAAK,UAAU,CAAC;AAAA,YAC/B,IAAI,OAAO,WAAW,GAAG;AAAA,cACvB,OAAO,EAAE,OAAO,+DAA+D;AAAA,YACjF;AAAA,YACA,MAAM,UAAU,OAAO,KAAK,CAAC,UAAS,CAAC,WAAW,SAAS,KAAI,CAAC;AAAA,YAChE,IAAI,YAAY,WAAW;AAAA,cACzB,OAAO,EAAE,OAAO,sCAAsC,WAAW;AAAA,YACnE;AAAA,YACA,MAAM,OAAO,KAAK,MAAM;AAAA,YACxB,IAAI,SAAS,cAAc,OAAO,SAAS,YAAY,CAAC,cAAc,EAAE,KAAK,CAAC,IAAI;AAAA,cAChF,OAAO,EAAE,OAAO,kCAAkC;AAAA,YACpD;AAAA,YACA,MAAM,UAAU,KAAK,MAAM;AAAA,YAC3B,IAAI,YAAY,WAAW;AAAA,cACzB,IACE,CAAC,MAAM,QAAQ,OAAO,KACtB,QAAQ,WAAW,OAAO,UAC1B,QAAQ,KAAK,CAAC,WAAW,OAAO,WAAW,QAAQ,GACnD;AAAA,gBACA,OAAO,EAAE,OAAO,wDAAwD;AAAA,cAC1E;AAAA,YACF;AAAA,YACA,MAAM,WAAW,KAAK,MAAM;AAAA,YAC5B,IAAI,aAAa,cAAc,OAAO,aAAa,YAAY,CAAC,cAAc,EAAE,MAAM,SAAS,CAAC,IAAI;AAAA,cAClG,OAAO,EAAE,OAAO,mDAAmD;AAAA,YACrE;AAAA,YACA,OAAO,EAAE,aAAa,EAAE,WAAW,KAAK,MAAM,EAAE;AAAA;AAAA,QAEpD;AAAA,MACF;AAAA,MACA,YAAY,CAAC,QAAQ,QAAQ,YAAY,KAAK;AAAA,IAChD;AAAA,IAEA,SAAS,EAAE,WAAW,gBAAgB;AAAA,IAEtC,QAAQ;AAAA,MACN,OAAO,GAAG,OAAO,SAAS,UAAU,SAAS,YAAY,aAAa,gBAAgB;AAAA,QACpF,MAAM,OAAO,gBAAgB,EAAE,OAAO,YAAY,CAAC;AAAA,QACnD,IAAI,SAAS;AAAA,UAAM,OAAO;AAAA,QAC1B,MAAM,YAAa,WAAW,aAAmD;AAAA,QACjF,MAAM,SAAS,oBAAoB,EAAE,SAAS,UAAU,UAAU,CAAC;AAAA,QACnE,MAAM,WACJ,KAAK,YAAa,WAAW,eAAsC;AAAA,QACrE,IAAI,CAAC,cAAc,EAAE,MAAM,SAAS,CAAC,GAAG;AAAA,UACtC,MAAM,IAAI,WAAU;AAAA,YAClB,MAAM;AAAA,YACN,SAAS,oBAAoB;AAAA,YAC7B,MAAM,EAAE,OAAO,MAAM,MAAM,SAAS;AAAA,UACtC,CAAC;AAAA,QACH;AAAA,QAEA,MAAM,UAAU,kBAAkB,EAAE,MAAM,CAAC;AAAA,QAC3C,MAAM,SAA+B;AAAA,UACnC;AAAA,UACA,OAAO,YAAY,EAAE,MAAM,CAAC;AAAA,UAC5B,QAAQ,KAAK,OAAO,IAAI,CAAC,WAAW;AAAA,YAClC,MAAM;AAAA,YACN,QAAQ,aAAa,EAAE,OAAO,MAAM,CAAC;AAAA,UACvC,EAAE;AAAA,aACE,WAAW,aAAa,EAAE,WAAW,GAAG,KAAK,YAAY,SAAS,IAAI,CAAC;AAAA,aACvE,WAAW,SAAS,EAAE,UAAU,GAAG,KAAK,WAAW,IAAI,CAAC;AAAA,aACxD,WAAW,UAAU,YAAY,OACjC,EAAE,UAAU,aAAa,EAAE,OAAO,OAAO,QAAQ,CAAC,EAAE,IACpD,CAAC;AAAA,QACP;AAAA,QAEA,MAAM,aAAa,KAAK,OAAO,IAAI,CAAC,UAAU,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK,KAAK;AAAA,QAC/E,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aAAa,wBAAwB,KAAK,UAAU,MAAM;AAAA,UAC1D,SAAS,CAAC,EAAE,MAAM,8BAA8B,OAAO,CAAC,sBAAsB,EAAE,CAAC;AAAA,UACjF,YAAY,CAAC,EAAE,MAAM,8BAA8B,OAAO,CAAC,WAAW,EAAE,CAAC;AAAA,UACzE,KAAK;AAAA,YACH;AAAA,YACA;AAAA,YACA,sBAAsB,UAAU;AAAA,YAChC;AAAA,YACA;AAAA,YACA;AAAA,YACA,4CAA4C;AAAA,YAC5C,2BAA2B,UAAU;AAAA,UACvC;AAAA,QACF;AAAA;AAAA,IAEJ;AAAA,IAEA,QAAQ,SAAS,SAAS,SAAS,SAAS,iBAAiB;AAAA,MAC3D,IAAI,YAAY,YAAY;AAAA,QAC1B,MAAM,YAAa,WAAW,aAAmD;AAAA,QACjF,IAAI,cAAc,QAAQ;AAAA,UACxB,OAAO,EAAE,QAAQ,CAAC,MAAM,iBAAiB,EAAE,SAAS,MAAM,YAAY,CAAC,CAAC,EAAE;AAAA,QAC5E;AAAA,QACA,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,YAAY,IAAI,MAAM,MAAM,WAAW,CAAC,EAAE;AAAA,MACtE;AAAA,MACA,IAAI,YAAY,UAAU;AAAA,QACxB,IAAI;AAAA,UACF,MAAM,OAAO,MAAM,QAAQ;AAAA,YACzB,MAAM;AAAA,UACR,CAAC;AAAA,UACD,OAAO;AAAA,YACL,QAAQ;AAAA,cACN,KAAK,SAAS,IACV,EAAE,MAAM,QAAQ,IAAI,MAAM,MAAM,cAAc,IAC9C,EAAE,MAAM,QAAQ,IAAI,OAAO,MAAM,4BAA4B;AAAA,YACnE;AAAA,UACF;AAAA,UACA,OAAO,OAAO;AAAA,UACd,OAAO;AAAA,YACL,QAAQ;AAAA,cACN;AAAA,gBACE,MAAM;AAAA,gBACN,IAAI;AAAA,gBACJ,MAAM,iBAAiB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,cAC9E;AAAA,YACF;AAAA,UACF;AAAA;AAAA,MAEJ;AAAA,MACA,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,YAAY,IAAI,MAAM,MAAM,6BAA6B,CAAC,EAAE;AAAA;AAAA,EAE1F,CAAC;AAAA;",
9
- "debugId": "CE3DBDCF2300506B64756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "8D730D54A357293664756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -4,8 +4,10 @@
4
4
  * Schema: `embedding Vector(1536)` (field type) + `@@vector([embedding],
5
5
  * metric: "cosine", index: "hnsw")` (index declaration). Client surface is a
6
6
  * namespace (D9): `db.doc.vector.nearest({ by, to, take })` and
7
- * `db.doc.vector.set({ where, by, to })`. Postgres AND pglite (pglite bundles
8
- * pgvector); sqlite/mysql → VIBE_UNSUPPORTED_CAPABILITY at generate time.
7
+ * `db.doc.vector.set({ where, by, to })`. Postgres AND pglite (on pglite’s
8
+ * current 0.5 line pgvector loads from @electric-sql/pglite-pgvector — the
9
+ * 0.3 line bundled it as @electric-sql/pglite/vector); sqlite/mysql →
10
+ * VIBE_UNSUPPORTED_CAPABILITY at generate time.
9
11
  */
10
12
  import type { VibeExtension } from "@vibeorm/schema";
11
13
  import type { ExtensionMountContext } from "@vibeorm/runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAGV,aAAa,EACd,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAY9D,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,iCAAiC;AACjC,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACzB,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;IAChB,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AAsBF,KAAK,gBAAgB,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAqF3E,mEAAmE;AACnE,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC7D,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7F,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACxD,CAAC;AAgCF;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,qBAAqB,GAC5B,CAAC,GAAG,EAAE,qBAAqB,KAAK,eAAe,CAiDjD;AAID;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,aAAa,CAoHjE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAGV,aAAa,EACd,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAa9D,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,iCAAiC;AACjC,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACzB,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;IAChB,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AAsBF,KAAK,gBAAgB,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAwF3E,mEAAmE;AACnE,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC7D,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7F,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACxD,CAAC;AAgCF;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,qBAAqB,GAC5B,CAAC,GAAG,EAAE,qBAAqB,KAAK,eAAe,CAiDjD;AAID;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,aAAa,CA+HjE"}
@@ -139,7 +139,8 @@ function vectorArtifacts(ctx) {
139
139
  });
140
140
  }
141
141
  const column = columnNameOf({ model, field });
142
- const indexName = `${table}_${column}_${kindRaw}_idx`;
142
+ const nameOverride = indexFields.length === 1 ? declString(declaration, "indexName") : undefined;
143
+ const indexName = nameOverride ?? `${table}_${column}_${kindRaw}_idx`;
143
144
  const withOptions = [];
144
145
  const m = declaration["m"];
145
146
  const efConstruction = declaration["efConstruction"];
@@ -287,6 +288,17 @@ function vector(options = {}) {
287
288
  return { error: `${key} must be a number` };
288
289
  }
289
290
  }
291
+ const indexName = args.named["indexName"];
292
+ if (indexName !== undefined) {
293
+ if (typeof indexName !== "string" || !isSafeSqlName({ name: indexName })) {
294
+ return { error: `indexName must be a plain identifier` };
295
+ }
296
+ if (fields.length !== 1) {
297
+ return {
298
+ error: `indexName needs exactly one indexed field \u2014 split @@vector declarations to name each index`
299
+ };
300
+ }
301
+ }
290
302
  return { declaration: { fields, ...args.named } };
291
303
  }
292
304
  }
@@ -356,4 +368,4 @@ export {
356
368
  createVectorNamespace
357
369
  };
358
370
 
359
- //# debugId=27645C2EFD94B7BF64756E2164756E21
371
+ //# debugId=60FD13536C92091B64756E2164756E21