@atscript/db-mongo 0.1.101 → 0.1.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/agg.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_mongo_filter = require("./mongo-filter-AihWWQXp.cjs");
2
+ const require_mongo_filter = require("./mongo-filter-1EpqdD-T.cjs");
3
3
  let _atscript_db_agg = require("@atscript/db/agg");
4
4
  //#region src/agg.ts
5
5
  /** Simple accumulators that map directly to `{ $<fn>: '$field' }`. */
package/dist/agg.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as buildMongoFilter } from "./mongo-filter-BsocUQG3.mjs";
1
+ import { t as buildMongoFilter } from "./mongo-filter-DBYaF9aH.mjs";
2
2
  import { resolveAlias } from "@atscript/db/agg";
3
3
  //#region src/agg.ts
4
4
  /** Simple accumulators that map directly to `{ $<fn>: '$field' }`. */
@@ -0,0 +1,6 @@
1
+ import { TAtscriptPlugin } from "@atscript/core";
2
+
3
+ //#region src/plugin/index.d.ts
4
+ declare const MongoPlugin: () => TAtscriptPlugin;
5
+ //#endregion
6
+ export { MongoPlugin as t };
@@ -0,0 +1,6 @@
1
+ import { TAtscriptPlugin } from "@atscript/core";
2
+
3
+ //#region src/plugin/index.d.ts
4
+ declare const MongoPlugin: () => TAtscriptPlugin;
5
+ //#endregion
6
+ export { MongoPlugin as t };
package/dist/index.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_mongo_filter = require("./mongo-filter-AihWWQXp.cjs");
2
+ const require_mongo_filter = require("./mongo-filter-1EpqdD-T.cjs");
3
+ const require_plugin = require("./plugin-Bq6hZMBA.cjs");
3
4
  let _atscript_db = require("@atscript/db");
4
5
  let mongodb = require("mongodb");
5
6
  //#region src/lib/projection-dedupe.ts
@@ -72,6 +73,9 @@ function containsAggregationExpr(v) {
72
73
  * `$set` map.
73
74
  */
74
75
  var CollectionPatcher = class {
76
+ collection;
77
+ payload;
78
+ ops;
75
79
  constructor(collection, payload, ops) {
76
80
  this.collection = collection;
77
81
  this.payload = payload;
@@ -630,13 +634,13 @@ function isVectorSearchableImpl(host) {
630
634
  }
631
635
  /** Text search via $search aggregation stage. */
632
636
  async function searchImpl(host, text, query, indexName) {
633
- const plan = buildSearchStage(host, text, indexName);
637
+ const plan = buildSearchStage(host, text, indexName, query.controls);
634
638
  if (!plan) throw new Error(indexName ? `Search index "${indexName}" not found` : "No search index available");
635
639
  return runSearchPipeline(host, plan.stage, query, "search", void 0, plan.classicText);
636
640
  }
637
641
  /** Text search with faceted count. */
638
642
  async function searchWithCountImpl(host, text, query, indexName) {
639
- const plan = buildSearchStage(host, text, indexName);
643
+ const plan = buildSearchStage(host, text, indexName, query.controls);
640
644
  if (!plan) throw new Error(indexName ? `Search index "${indexName}" not found` : "No search index available");
641
645
  return runSearchWithCountPipeline(host, plan.stage, query, "searchWithCount", void 0, plan.classicText);
642
646
  }
@@ -672,7 +676,7 @@ function resolveThreshold(host, controls, indexName) {
672
676
  * MongoDB the stage is unsupported entirely — even though the table reports
673
677
  * `searchable: true`. The discriminator is the resolved index's `type`.
674
678
  */
675
- function buildSearchStage(host, text, indexName) {
679
+ function buildSearchStage(host, text, indexName, controls) {
676
680
  const index = host.getMongoSearchIndex(indexName);
677
681
  if (!index) return;
678
682
  if (index.type === "vector") throw new Error("Vector indexes cannot be used with text search. Use vectorSearch() instead.");
@@ -680,17 +684,58 @@ function buildSearchStage(host, text, indexName) {
680
684
  stage: { $match: { $text: { $search: text } } },
681
685
  classicText: true
682
686
  };
687
+ const searchIndex = index;
688
+ const fuzzy = resolveSearchFuzzy(searchIndex, controls);
689
+ const strategy = searchIndex.strategy ?? "compound";
690
+ const autocompletePaths = autocompleteFieldPaths(searchIndex);
691
+ const textClause = () => ({ text: {
692
+ query: text,
693
+ path: { wildcard: "*" },
694
+ ...fuzzy ? { fuzzy } : {}
695
+ } });
696
+ const autocompleteClause = (path) => ({ autocomplete: {
697
+ query: text,
698
+ path,
699
+ ...fuzzy ? { fuzzy } : {}
700
+ } });
701
+ let body;
702
+ if (strategy === "text" || autocompletePaths.length === 0) body = textClause();
703
+ else if (strategy === "autocomplete") {
704
+ const clauses = autocompletePaths.map(autocompleteClause);
705
+ body = clauses.length === 1 ? clauses[0] : { compound: {
706
+ should: clauses,
707
+ minimumShouldMatch: 1
708
+ } };
709
+ } else body = { compound: {
710
+ should: [textClause(), ...autocompletePaths.map(autocompleteClause)],
711
+ minimumShouldMatch: 1
712
+ } };
683
713
  return {
684
714
  stage: { $search: {
685
715
  index: index.key,
686
- text: {
687
- query: text,
688
- path: { wildcard: "*" }
689
- }
716
+ ...body
690
717
  } },
691
718
  classicText: false
692
719
  };
693
720
  }
721
+ /**
722
+ * Resolves query-time fuzzy (typo tolerance): the `$fuzzy` request control
723
+ * overrides the schema-declared `@db.mongo.search.*` fuzzy. Only an edit distance
724
+ * of 1 or 2 is emitted (Atlas rejects 0) — anything else means "no fuzzy".
725
+ */
726
+ function resolveSearchFuzzy(index, controls) {
727
+ const override = controls?.$fuzzy;
728
+ const maxEdits = override === void 0 ? index.fuzzy?.maxEdits : Number(override);
729
+ return maxEdits === 1 || maxEdits === 2 ? { maxEdits } : void 0;
730
+ }
731
+ /** Field paths in this index that carry an `autocomplete` mapping. */
732
+ function autocompleteFieldPaths(index) {
733
+ const fields = index.definition.mappings?.fields;
734
+ if (!fields) return [];
735
+ const paths = [];
736
+ for (const [path, mapping] of Object.entries(fields)) if ((Array.isArray(mapping) ? mapping : [mapping]).some((m) => m.type === "autocomplete")) paths.push(path);
737
+ return paths;
738
+ }
694
739
  /** Builds a $vectorSearch aggregation stage from a pre-computed vector. */
695
740
  function buildVectorSearchStage(host, vector, indexName, limit) {
696
741
  let index;
@@ -1289,10 +1334,26 @@ function fieldsMatch(left, right) {
1289
1334
  if (leftKeys.length !== rightKeys.length) return false;
1290
1335
  for (const key of leftKeys) {
1291
1336
  if (!(key in right)) return false;
1292
- if (left[key].type !== right[key].type || left[key].analyzer !== right[key].analyzer) return false;
1337
+ if (!fieldMappingEqual(left[key], right[key])) return false;
1293
1338
  }
1294
1339
  return true;
1295
1340
  }
1341
+ /** Order-independent structural compare of a field's Atlas type mapping(s). */
1342
+ function fieldMappingEqual(a, b) {
1343
+ const am = mappingsByType(a);
1344
+ const bm = mappingsByType(b);
1345
+ if (am.size !== bm.size) return false;
1346
+ for (const [type, av] of am) {
1347
+ const bv = bm.get(type);
1348
+ if (!bv || av.analyzer !== bv.analyzer || av.tokenization !== bv.tokenization || av.minGrams !== bv.minGrams || av.maxGrams !== bv.maxGrams || av.foldDiacritics !== bv.foldDiacritics) return false;
1349
+ }
1350
+ return true;
1351
+ }
1352
+ function mappingsByType(m) {
1353
+ const map = /* @__PURE__ */ new Map();
1354
+ for (const x of Array.isArray(m) ? m : [m]) map.set(x.type, x);
1355
+ return map;
1356
+ }
1296
1357
  function vectorFieldsMatch(left, right) {
1297
1358
  if (left.length !== (right || []).length) return false;
1298
1359
  const rightMap = /* @__PURE__ */ new Map();
@@ -1318,6 +1379,8 @@ const validateMongoIdPlugin = (ctx, def, value) => {
1318
1379
  //#endregion
1319
1380
  //#region src/lib/mongo-adapter.ts
1320
1381
  var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
1382
+ db;
1383
+ client;
1321
1384
  _collection;
1322
1385
  /** MongoDB-specific indexes (search, vector) — separate from table.indexes. */
1323
1386
  _mongoIndexes = /* @__PURE__ */ new Map();
@@ -1508,13 +1571,14 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
1508
1571
  const dynamicText = typeMeta.get("db.mongo.search.dynamic");
1509
1572
  if (dynamicText) this._setSearchIndex("dynamic_text", "_", {
1510
1573
  mappings: { dynamic: true },
1511
- analyzer: dynamicText.analyzer,
1512
- text: { fuzzy: { maxEdits: dynamicText.fuzzy || 0 } }
1513
- });
1574
+ analyzer: dynamicText.analyzer
1575
+ }, { fuzzy: normalizeSearchFuzzy(dynamicText.fuzzy) });
1514
1576
  for (const textSearch of typeMeta.get("db.mongo.search.static") || []) this._setSearchIndex("search_text", textSearch.indexName, {
1515
1577
  mappings: { fields: {} },
1516
- analyzer: textSearch.analyzer,
1517
- text: { fuzzy: { maxEdits: textSearch.fuzzy || 0 } }
1578
+ analyzer: textSearch.analyzer
1579
+ }, {
1580
+ fuzzy: normalizeSearchFuzzy(textSearch.fuzzy),
1581
+ strategy: normalizeSearchStrategy(textSearch.strategy)
1518
1582
  });
1519
1583
  }
1520
1584
  onFieldScanned(field, _type, metadata) {
@@ -1528,7 +1592,24 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
1528
1592
  const startValue = metadata.get("db.default.increment");
1529
1593
  this._incrementFields.set(physicalName, typeof startValue === "number" ? startValue : void 0);
1530
1594
  }
1531
- for (const index of metadata.get("db.mongo.search.text") || []) this._addFieldToSearchIndex("search_text", index.indexName, field, index.analyzer);
1595
+ for (const index of metadata.get("db.mongo.search.text") || []) this._addFieldToSearchIndex("search_text", index.indexName, field, [index.analyzer ? {
1596
+ type: "string",
1597
+ analyzer: index.analyzer
1598
+ } : { type: "string" }]);
1599
+ for (const ac of metadata.get("db.mongo.search.autocomplete") || []) {
1600
+ const autocomplete = {
1601
+ type: "autocomplete",
1602
+ tokenization: ac.tokenization || "edgeGram",
1603
+ minGrams: ac.minGrams ?? 2,
1604
+ maxGrams: ac.maxGrams ?? 15,
1605
+ foldDiacritics: ac.foldDiacritics ?? true
1606
+ };
1607
+ const companion = ac.analyzer ? {
1608
+ type: "string",
1609
+ analyzer: ac.analyzer
1610
+ } : { type: "string" };
1611
+ this._addFieldToSearchIndex("search_text", ac.indexName, field, [autocomplete, companion]);
1612
+ }
1532
1613
  const vectorIndex = metadata.get("db.search.vector");
1533
1614
  if (vectorIndex) {
1534
1615
  const indexName = vectorIndex.indexName || field;
@@ -1989,32 +2070,68 @@ var MongoAdapter = class MongoAdapter extends _atscript_db.BaseDbAdapter {
1989
2070
  }
1990
2071
  if (weight) index.weights[field] = weight;
1991
2072
  }
1992
- _setSearchIndex(type, name, definition) {
2073
+ _setSearchIndex(type, name, definition, meta) {
1993
2074
  const key = mongoIndexKey(type, name || "DEFAULT");
1994
- this._mongoIndexes.set(key, {
2075
+ const index = {
1995
2076
  key,
1996
2077
  name: name || "DEFAULT",
1997
2078
  type,
1998
- definition
1999
- });
2079
+ definition,
2080
+ fuzzy: meta?.fuzzy,
2081
+ strategy: meta?.strategy
2082
+ };
2083
+ this._mongoIndexes.set(key, index);
2084
+ return index;
2000
2085
  }
2001
- _addFieldToSearchIndex(type, _name, fieldName, analyzer) {
2086
+ /**
2087
+ * Adds (and merges, by mapping `type`) one or more Atlas field-type mappings to
2088
+ * a `search_text` index. Multiple annotations on the same field — e.g.
2089
+ * `@db.mongo.search.text` (string) plus `@db.mongo.search.autocomplete`
2090
+ * (autocomplete + string) — accumulate into a single multi-type mapping
2091
+ * instead of overwriting one another.
2092
+ */
2093
+ _addFieldToSearchIndex(type, _name, fieldName, mappings) {
2002
2094
  const name = _name || "DEFAULT";
2003
2095
  let index = this._mongoIndexes.get(mongoIndexKey(type, name));
2004
- if (!index && type === "search_text") {
2005
- this._setSearchIndex(type, name, {
2006
- mappings: { fields: {} },
2007
- text: { fuzzy: { maxEdits: 0 } }
2008
- });
2009
- index = this._mongoIndexes.get(mongoIndexKey(type, name));
2010
- }
2096
+ if (!index && type === "search_text") index = this._setSearchIndex(type, name, { mappings: { fields: {} } });
2011
2097
  if (index) {
2012
- index.definition.mappings.fields[fieldName] = { type: "string" };
2013
- if (analyzer) index.definition.mappings.fields[fieldName].analyzer = analyzer;
2098
+ const fields = index.definition.mappings.fields;
2099
+ fields[fieldName] = mergeFieldMappings(fields[fieldName], mappings);
2014
2100
  }
2015
2101
  }
2016
2102
  };
2017
2103
  /**
2104
+ * Normalizes a declared `fuzzy` arg (`0-2`) into the query-time metadata Atlas
2105
+ * accepts. Atlas only honors an edit distance of `1` or `2`; `0`/undefined means
2106
+ * "no fuzzy", returned as `undefined` so no `fuzzy` clause is ever emitted.
2107
+ */
2108
+ function normalizeSearchFuzzy(fuzzy) {
2109
+ return fuzzy === 1 || fuzzy === 2 ? { maxEdits: fuzzy } : void 0;
2110
+ }
2111
+ /** Narrows the declared `strategy` arg to a known value (undefined → query layer defaults to "compound"). */
2112
+ function normalizeSearchStrategy(strategy) {
2113
+ return strategy === "compound" || strategy === "autocomplete" || strategy === "text" ? strategy : void 0;
2114
+ }
2115
+ /**
2116
+ * Merges incoming Atlas field-type mappings into a field's existing mapping,
2117
+ * keyed by `type` (a later mapping of the same type replaces the earlier one).
2118
+ * Collapses to a single object when one type remains, else an array (Atlas's
2119
+ * multi-type field form).
2120
+ */
2121
+ function mergeFieldMappings(existing, incoming) {
2122
+ const byType = /* @__PURE__ */ new Map();
2123
+ const order = [];
2124
+ const add = (m) => {
2125
+ if (!byType.has(m.type)) order.push(m.type);
2126
+ byType.set(m.type, m);
2127
+ };
2128
+ if (Array.isArray(existing)) existing.forEach(add);
2129
+ else if (existing) add(existing);
2130
+ incoming.forEach(add);
2131
+ const merged = order.map((t) => byType.get(t));
2132
+ return merged.length === 1 ? merged[0] : merged;
2133
+ }
2134
+ /**
2018
2135
  * Builds a MongoDB update document from a data object that may contain
2019
2136
  * field ops (`{ $inc: N }`, `{ $dec: N }`, `{ $mul: N }`).
2020
2137
  * Regular fields go into `$set`, ops go into `$inc` / `$mul`. When
@@ -2047,6 +2164,7 @@ function createAdapter(connection, _options) {
2047
2164
  //#endregion
2048
2165
  exports.CollectionPatcher = CollectionPatcher;
2049
2166
  exports.MongoAdapter = MongoAdapter;
2167
+ exports.MongoPlugin = require_plugin.MongoPlugin;
2050
2168
  exports.buildMongoFilter = require_mongo_filter.buildMongoFilter;
2051
2169
  exports.createAdapter = createAdapter;
2052
2170
  exports.validateMongoIdPlugin = validateMongoIdPlugin;
package/dist/index.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ import { t as MongoPlugin } from "./index-Bl_O47fp.cjs";
1
2
  import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbForeignKey, TDbInsertManyResult, TDbInsertResult, TDbRelation, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TSearchIndexInfo, TSyncColumnResult, TTableResolver, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
2
3
  import { AggregationCursor, ClientSession, Collection, Db, Document, Filter, MongoClient, ObjectId, UpdateFilter, UpdateOptions } from "mongodb";
3
4
  import { TAtscriptAnnotatedType, TMetadataMap, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
@@ -165,16 +166,45 @@ interface TSearchIndex {
165
166
  name: string;
166
167
  type: "dynamic_text" | "search_text" | "vector";
167
168
  definition: TMongoSearchIndexDefinition;
169
+ /**
170
+ * Query-time fuzzy (typo tolerance) declared via `@db.mongo.search.dynamic` /
171
+ * `@db.mongo.search.static`. Carried as index metadata — NOT part of the Atlas
172
+ * index `definition` (Atlas applies fuzzy at query time on the `text`/
173
+ * `autocomplete` operator, not in the index schema). `buildSearchStage` reads
174
+ * this and attaches it to the emitted operator.
175
+ */
176
+ fuzzy?: {
177
+ maxEdits: number;
178
+ };
179
+ /**
180
+ * The match strategy declared via `@db.mongo.search.static`, locking this
181
+ * index's query shape. `buildSearchStage` reads it; undefined → `"compound"`.
182
+ * - `compound` — wildcard `text` + per-field `autocomplete` (exact ranks above prefix).
183
+ * - `autocomplete` — autocomplete fields only (pure typeahead, no word clause).
184
+ * - `text` — single `text` operator over all string-mapped fields.
185
+ */
186
+ strategy?: "compound" | "autocomplete" | "text";
168
187
  }
169
188
  type TMongoIndex = TPlainIndex | TSearchIndex;
170
189
  type TVectorSimilarity = "cosine" | "euclidean" | "dotProduct";
190
+ /**
191
+ * One Atlas Search field-type mapping. `type: "string"` is plain word matching;
192
+ * `type: "autocomplete"` enables prefix/typeahead (edgeGram) or substring (nGram).
193
+ * A field may carry several mappings at once (an array of these) — e.g. an
194
+ * autocomplete field double-mapped as `string` so exact-word hits still rank.
195
+ */
196
+ interface TSearchFieldMapping {
197
+ type: string;
198
+ analyzer?: string;
199
+ tokenization?: "edgeGram" | "rightEdgeGram" | "nGram";
200
+ minGrams?: number;
201
+ maxGrams?: number;
202
+ foldDiacritics?: boolean;
203
+ }
171
204
  interface TMongoSearchIndexDefinition {
172
205
  mappings?: {
173
206
  dynamic?: boolean;
174
- fields?: Record<string, {
175
- type: string;
176
- analyzer?: string;
177
- }>;
207
+ fields?: Record<string, TSearchFieldMapping | TSearchFieldMapping[]>;
178
208
  };
179
209
  fields?: Array<{
180
210
  path: string;
@@ -183,11 +213,6 @@ interface TMongoSearchIndexDefinition {
183
213
  numDimensions?: number;
184
214
  }>;
185
215
  analyzer?: string;
186
- text?: {
187
- fuzzy?: {
188
- maxEdits: number;
189
- };
190
- };
191
216
  }
192
217
  //#endregion
193
218
  //#region src/lib/mongo-adapter.d.ts
@@ -351,8 +376,20 @@ declare class MongoAdapter extends BaseDbAdapter {
351
376
  */
352
377
  private _getCollationOpts;
353
378
  protected _addMongoIndexField(type: TPlainIndex["type"], name: string, field: string, weight?: number): void;
354
- protected _setSearchIndex(type: TSearchIndex["type"], name: string | undefined, definition: TMongoSearchIndexDefinition): void;
355
- protected _addFieldToSearchIndex(type: TSearchIndex["type"], _name: string | undefined, fieldName: string, analyzer?: string): void;
379
+ protected _setSearchIndex(type: TSearchIndex["type"], name: string | undefined, definition: TMongoSearchIndexDefinition, meta?: {
380
+ fuzzy?: {
381
+ maxEdits: number;
382
+ };
383
+ strategy?: TSearchIndex["strategy"];
384
+ }): TSearchIndex;
385
+ /**
386
+ * Adds (and merges, by mapping `type`) one or more Atlas field-type mappings to
387
+ * a `search_text` index. Multiple annotations on the same field — e.g.
388
+ * `@db.mongo.search.text` (string) plus `@db.mongo.search.autocomplete`
389
+ * (autocomplete + string) — accumulate into a single multi-type mapping
390
+ * instead of overwriting one another.
391
+ */
392
+ protected _addFieldToSearchIndex(type: TSearchIndex["type"], _name: string | undefined, fieldName: string, mappings: TSearchFieldMapping[]): void;
356
393
  }
357
394
  //#endregion
358
395
  //#region src/lib/mongo-filter.d.ts
@@ -371,4 +408,4 @@ declare const validateMongoIdPlugin: TValidatorPlugin;
371
408
  //#region src/lib/index.d.ts
372
409
  declare function createAdapter(connection: string, _options?: Record<string, unknown>): DbSpace;
373
410
  //#endregion
374
- export { CollectionPatcher, MongoAdapter, TCollectionPatcherContext, type TMongoIndex, type TMongoSearchIndexDefinition, type TPlainIndex, type TSearchIndex, buildMongoFilter, createAdapter, validateMongoIdPlugin };
411
+ export { CollectionPatcher, MongoAdapter, MongoPlugin, TCollectionPatcherContext, type TMongoIndex, type TMongoSearchIndexDefinition, type TPlainIndex, type TSearchIndex, buildMongoFilter, createAdapter, validateMongoIdPlugin };
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { t as MongoPlugin } from "./index-Bl_O47fp.mjs";
1
2
  import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbForeignKey, TDbInsertManyResult, TDbInsertResult, TDbRelation, TDbUpdateResult, TExistingTableOption, TFieldOps, TMetadataOverrides, TSearchIndexInfo, TSyncColumnResult, TTableResolver, TableMetadata, WithRelation, getKeyProps } from "@atscript/db";
2
3
  import { AggregationCursor, ClientSession, Collection, Db, Document, Filter, MongoClient, ObjectId, UpdateFilter, UpdateOptions } from "mongodb";
3
4
  import { TAtscriptAnnotatedType, TMetadataMap, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
@@ -165,16 +166,45 @@ interface TSearchIndex {
165
166
  name: string;
166
167
  type: "dynamic_text" | "search_text" | "vector";
167
168
  definition: TMongoSearchIndexDefinition;
169
+ /**
170
+ * Query-time fuzzy (typo tolerance) declared via `@db.mongo.search.dynamic` /
171
+ * `@db.mongo.search.static`. Carried as index metadata — NOT part of the Atlas
172
+ * index `definition` (Atlas applies fuzzy at query time on the `text`/
173
+ * `autocomplete` operator, not in the index schema). `buildSearchStage` reads
174
+ * this and attaches it to the emitted operator.
175
+ */
176
+ fuzzy?: {
177
+ maxEdits: number;
178
+ };
179
+ /**
180
+ * The match strategy declared via `@db.mongo.search.static`, locking this
181
+ * index's query shape. `buildSearchStage` reads it; undefined → `"compound"`.
182
+ * - `compound` — wildcard `text` + per-field `autocomplete` (exact ranks above prefix).
183
+ * - `autocomplete` — autocomplete fields only (pure typeahead, no word clause).
184
+ * - `text` — single `text` operator over all string-mapped fields.
185
+ */
186
+ strategy?: "compound" | "autocomplete" | "text";
168
187
  }
169
188
  type TMongoIndex = TPlainIndex | TSearchIndex;
170
189
  type TVectorSimilarity = "cosine" | "euclidean" | "dotProduct";
190
+ /**
191
+ * One Atlas Search field-type mapping. `type: "string"` is plain word matching;
192
+ * `type: "autocomplete"` enables prefix/typeahead (edgeGram) or substring (nGram).
193
+ * A field may carry several mappings at once (an array of these) — e.g. an
194
+ * autocomplete field double-mapped as `string` so exact-word hits still rank.
195
+ */
196
+ interface TSearchFieldMapping {
197
+ type: string;
198
+ analyzer?: string;
199
+ tokenization?: "edgeGram" | "rightEdgeGram" | "nGram";
200
+ minGrams?: number;
201
+ maxGrams?: number;
202
+ foldDiacritics?: boolean;
203
+ }
171
204
  interface TMongoSearchIndexDefinition {
172
205
  mappings?: {
173
206
  dynamic?: boolean;
174
- fields?: Record<string, {
175
- type: string;
176
- analyzer?: string;
177
- }>;
207
+ fields?: Record<string, TSearchFieldMapping | TSearchFieldMapping[]>;
178
208
  };
179
209
  fields?: Array<{
180
210
  path: string;
@@ -183,11 +213,6 @@ interface TMongoSearchIndexDefinition {
183
213
  numDimensions?: number;
184
214
  }>;
185
215
  analyzer?: string;
186
- text?: {
187
- fuzzy?: {
188
- maxEdits: number;
189
- };
190
- };
191
216
  }
192
217
  //#endregion
193
218
  //#region src/lib/mongo-adapter.d.ts
@@ -351,8 +376,20 @@ declare class MongoAdapter extends BaseDbAdapter {
351
376
  */
352
377
  private _getCollationOpts;
353
378
  protected _addMongoIndexField(type: TPlainIndex["type"], name: string, field: string, weight?: number): void;
354
- protected _setSearchIndex(type: TSearchIndex["type"], name: string | undefined, definition: TMongoSearchIndexDefinition): void;
355
- protected _addFieldToSearchIndex(type: TSearchIndex["type"], _name: string | undefined, fieldName: string, analyzer?: string): void;
379
+ protected _setSearchIndex(type: TSearchIndex["type"], name: string | undefined, definition: TMongoSearchIndexDefinition, meta?: {
380
+ fuzzy?: {
381
+ maxEdits: number;
382
+ };
383
+ strategy?: TSearchIndex["strategy"];
384
+ }): TSearchIndex;
385
+ /**
386
+ * Adds (and merges, by mapping `type`) one or more Atlas field-type mappings to
387
+ * a `search_text` index. Multiple annotations on the same field — e.g.
388
+ * `@db.mongo.search.text` (string) plus `@db.mongo.search.autocomplete`
389
+ * (autocomplete + string) — accumulate into a single multi-type mapping
390
+ * instead of overwriting one another.
391
+ */
392
+ protected _addFieldToSearchIndex(type: TSearchIndex["type"], _name: string | undefined, fieldName: string, mappings: TSearchFieldMapping[]): void;
356
393
  }
357
394
  //#endregion
358
395
  //#region src/lib/mongo-filter.d.ts
@@ -371,4 +408,4 @@ declare const validateMongoIdPlugin: TValidatorPlugin;
371
408
  //#region src/lib/index.d.ts
372
409
  declare function createAdapter(connection: string, _options?: Record<string, unknown>): DbSpace;
373
410
  //#endregion
374
- export { CollectionPatcher, MongoAdapter, TCollectionPatcherContext, type TMongoIndex, type TMongoSearchIndexDefinition, type TPlainIndex, type TSearchIndex, buildMongoFilter, createAdapter, validateMongoIdPlugin };
411
+ export { CollectionPatcher, MongoAdapter, MongoPlugin, TCollectionPatcherContext, type TMongoIndex, type TMongoSearchIndexDefinition, type TPlainIndex, type TSearchIndex, buildMongoFilter, createAdapter, validateMongoIdPlugin };