@classytic/mongokit 3.42.0 → 3.43.1
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/README.md +28 -0
- package/dist/{Repository-DlhPbXlG.mjs → Repository-Dj6xqPxJ.mjs} +148 -1
- package/dist/{Repository-pnLw1rG5.d.mts → Repository-DnL4xk5w.d.mts} +10 -2
- package/dist/actions/index.mjs +2 -12
- package/dist/actions-ChYfkPYP.mjs +12 -0
- package/dist/adapter/index.mjs +1 -2
- package/dist/ai/index.d.mts +1 -1
- package/dist/better-auth/index.d.mts +1 -13
- package/dist/better-auth/index.mjs +1 -1
- package/dist/{create-repository-BUx9vfG9.d.mts → create-repository-CJtVdwig.d.mts} +4 -4
- package/dist/{create-repository-Bg0H5rte.mjs → create-repository-UjwPWR1z.mjs} +2 -2
- package/dist/derive-DRBiegRH.mjs +375 -0
- package/dist/index-C0AjVqX3.d.mts +169 -0
- package/dist/index.d.mts +37 -8
- package/dist/index.mjs +54 -6
- package/dist/{multi-tenant.plugin-a3UhQARj.d.mts → multi-tenant.plugin-B_q9NUHc.d.mts} +1 -1
- package/dist/outbox/index.d.mts +1 -1
- package/dist/outbox/index.mjs +1 -1
- package/dist/pagination/PaginationEngine.mjs +1 -2
- package/dist/{plugin-options-CRhIvSzx.d.mts → plugin-options-Cirmb4Kp.d.mts} +1 -1
- package/dist/plugins/index.d.mts +4 -4
- package/dist/plugins/index.mjs +4 -3
- package/dist/read-model/index.d.mts +1 -1
- package/dist/{repository-DNHfjAZX.d.mts → repository-MqAP409e.d.mts} +14 -3
- package/dist/search/index.d.mts +2 -0
- package/dist/search/index.mjs +1 -0
- package/dist/testkit/index.d.mts +2 -2
- package/dist/testkit/index.mjs +1 -1
- package/dist/{timestamp.plugin-DvZlnKEc.mjs → timestamp.plugin-DpOWavVI.mjs} +1 -1
- package/dist/{to-plain-CjgEInWV.d.mts → to-plain-o5tUY2nV.d.mts} +2 -2
- package/dist/utils/index.d.mts +1 -1
- package/dist/{validation-chain.plugin-CTcNJCmV.d.mts → validation-chain.plugin-BAeC5eVT.d.mts} +12 -3
- package/dist/{validation-chain.plugin-D1WsGCYr.mjs → validation-chain.plugin-DRApTmkN.mjs} +3 -2
- package/package.json +8 -4
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
//#region src/search/normalize.ts
|
|
2
|
+
/**
|
|
3
|
+
* Search-as-you-type text rules, shared by the WRITER (the search keys stored on
|
|
4
|
+
* every document) and the READER (the query typed into a search box).
|
|
5
|
+
*
|
|
6
|
+
* Both sides MUST run the same function, otherwise a key written as `mohammed`
|
|
7
|
+
* is never found by a query normalised to `Mohammed`. That is also why this
|
|
8
|
+
* module is pure (no mongoose, no driver): a portal or mobile client imports it
|
|
9
|
+
* from `@classytic/mongokit/search` to highlight matches with the exact rules
|
|
10
|
+
* the server indexed with.
|
|
11
|
+
*
|
|
12
|
+
* Rules, in order:
|
|
13
|
+
* 1. lower-case (before NFKD: `'İ'.toLowerCase()` yields a combining dot the
|
|
14
|
+
* next step removes);
|
|
15
|
+
* 2. Unicode NFKD, then every combining mark (`\p{M}`) dropped: `é → e`,
|
|
16
|
+
* `fi → fi`, and Arabic tashkeel (fatha, damma, kasra, shadda, sukun,
|
|
17
|
+
* tanween, superscript alef) disappear;
|
|
18
|
+
* 3. Arabic folding: tatweel removed; `أ إ آ ٱ → ا` (the hamza/madda forms
|
|
19
|
+
* decompose to bare alef in step 2, wasla is folded here); `ى → ي`;
|
|
20
|
+
* `ة → ه`; Arabic-Indic and Persian digits → ASCII digits;
|
|
21
|
+
* 4. format characters (`\p{Cf}`: ZWJ, ZWNJ, bidi marks) removed so a pasted
|
|
22
|
+
* name does not split or miss;
|
|
23
|
+
* 5. split on anything that is not a letter or a digit (whitespace and
|
|
24
|
+
* punctuation: `al-harbi` → `al`, `harbi`; `noura.saad@x.io` → `noura`,
|
|
25
|
+
* `saad`, `x`, `io`).
|
|
26
|
+
*/
|
|
27
|
+
/** Characters folded after the mark strip (step 3). */
|
|
28
|
+
const FOLD = {
|
|
29
|
+
ٱ: "ا",
|
|
30
|
+
ى: "ي",
|
|
31
|
+
ة: "ه"
|
|
32
|
+
};
|
|
33
|
+
const MARKS = /\p{M}+/gu;
|
|
34
|
+
/** Tatweel (kashida) and format characters (ZWJ/ZWNJ, bidi controls). */
|
|
35
|
+
const DROPPED = /[ـ\p{Cf}]+/gu;
|
|
36
|
+
const FOLDED = /[ٱىة٠-٩۰-۹]/gu;
|
|
37
|
+
const SEPARATORS = /[^\p{L}\p{N}]+/u;
|
|
38
|
+
function foldChar(ch) {
|
|
39
|
+
const code = ch.charCodeAt(0);
|
|
40
|
+
if (code >= 1632 && code <= 1641) return String(code - 1632);
|
|
41
|
+
if (code >= 1776 && code <= 1785) return String(code - 1776);
|
|
42
|
+
return FOLD[ch] ?? ch;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The search tokens of `text`, in order, duplicates kept. Empty for blank text
|
|
46
|
+
* or text with no letters/digits.
|
|
47
|
+
*
|
|
48
|
+
* @example tokenizeSearchText(' Mohammed AL-Harbi ') // ['mohammed', 'al', 'harbi']
|
|
49
|
+
* @example tokenizeSearchText('مُحَمَّد أحمد') // ['محمد', 'احمد']
|
|
50
|
+
*/
|
|
51
|
+
function tokenizeSearchText(text) {
|
|
52
|
+
if (typeof text !== "string" || text.length === 0) return [];
|
|
53
|
+
return text.toLowerCase().normalize("NFKD").replace(MARKS, "").replace(DROPPED, "").replace(FOLDED, foldChar).split(SEPARATORS).filter((t) => t.length > 0);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `text` normalised to its tokens joined by single spaces: the canonical form to
|
|
57
|
+
* compare or display ("what the index sees").
|
|
58
|
+
*
|
|
59
|
+
* @example normalizeSearchText('Café Crème') // 'cafe creme'
|
|
60
|
+
*/
|
|
61
|
+
function normalizeSearchText(text) {
|
|
62
|
+
return tokenizeSearchText(text).join(" ");
|
|
63
|
+
}
|
|
64
|
+
/** A run of the original text that can hold a token (letters, digits, and the marks/tatweel/format chars the normaliser drops). */
|
|
65
|
+
const WORD = /[\p{L}\p{N}\p{M}ـ\p{Cf}]+/gu;
|
|
66
|
+
/**
|
|
67
|
+
* Where `query` matches `text` under the prefix-search rules: for every word of
|
|
68
|
+
* `text` whose normalised form starts with one of the query's tokens, the
|
|
69
|
+
* shortest leading run of the ORIGINAL characters that covers that token.
|
|
70
|
+
* Offsets index the original string, so a client wraps them in `<mark>` with no
|
|
71
|
+
* re-normalisation of its own.
|
|
72
|
+
*
|
|
73
|
+
* @example findSearchMatches('Mohammed Al-Harbi', 'moh har')
|
|
74
|
+
* // [{ start: 0, end: 3 }, { start: 12, end: 15 }]
|
|
75
|
+
*/
|
|
76
|
+
function findSearchMatches(text, query) {
|
|
77
|
+
const terms = [...new Set(tokenizeSearchText(query))];
|
|
78
|
+
if (terms.length === 0 || typeof text !== "string") return [];
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const m of text.matchAll(WORD)) {
|
|
81
|
+
const word = m[0];
|
|
82
|
+
const start = m.index ?? 0;
|
|
83
|
+
const normalized = tokenizeSearchText(word).join("");
|
|
84
|
+
let best = 0;
|
|
85
|
+
for (const term of terms) {
|
|
86
|
+
if (!normalized.startsWith(term)) continue;
|
|
87
|
+
for (let len = 1; len <= word.length; len += 1) if (tokenizeSearchText(word.slice(0, len)).join("").length >= term.length) {
|
|
88
|
+
while (len < word.length && tokenizeSearchText(word.slice(len, len + 1)).length === 0) len += 1;
|
|
89
|
+
best = Math.max(best, len);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (best > 0) out.push({
|
|
94
|
+
start,
|
|
95
|
+
end: start + best
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/search/prefix.ts
|
|
102
|
+
/**
|
|
103
|
+
* Indexed search-as-you-type (`searchMode: 'prefix'`): the stored keys, the
|
|
104
|
+
* query filter, and the index/schema declarations. Pure: no mongoose import.
|
|
105
|
+
*
|
|
106
|
+
* ## Storage choice: word tokens + anchored regex (not edge n-grams)
|
|
107
|
+
*
|
|
108
|
+
* Each document stores the DISTINCT normalised word tokens of its search fields
|
|
109
|
+
* in one array field (`_searchKeys` by default), covered by a multikey index. A
|
|
110
|
+
* query token `moh` becomes `{ _searchKeys: { $regex: '^moh' } }`: a
|
|
111
|
+
* case-sensitive regex with a literal prefix, which the planner turns into the
|
|
112
|
+
* index range `["moh", "moi")` (IXSCAN, keys examined ≈ matches). Several query
|
|
113
|
+
* tokens AND together, one `$and` arm each; the planner bounds one arm and
|
|
114
|
+
* filters the rest on the fetched documents.
|
|
115
|
+
*
|
|
116
|
+
* The alternative, edge n-grams (`m, mo, moh, ..., mohammed` stored, matched by
|
|
117
|
+
* an exact `$all`), gives the SAME index-range query plan, only with point
|
|
118
|
+
* bounds, while storing one key per prefix length instead of one per word.
|
|
119
|
+
* Measured on 2,000 two-to-three-word names (mongod 8, WiredTiger; see
|
|
120
|
+
* `tests/integration/prefix-search.test.ts`): tokens 6,000 keys / 60 KiB index,
|
|
121
|
+
* edge n-grams (N=20) 34,140 keys / 256 KiB index, ~5.7x the keys and ~4x the
|
|
122
|
+
* index bytes. N-grams also cost a write amplification on every
|
|
123
|
+
* rename, and an arbitrary cut-off N past which longer prefixes stop matching.
|
|
124
|
+
* Tokens keep the index small, have no length cliff, and make the backfill a
|
|
125
|
+
* pure function of the source fields. So tokens are the standard here.
|
|
126
|
+
*/
|
|
127
|
+
/** Default name of the field that stores the search keys. */
|
|
128
|
+
const DEFAULT_SEARCH_KEYS_FIELD = "_searchKeys";
|
|
129
|
+
/** Query tokens beyond this many are ignored (each one is an `$and` arm). */
|
|
130
|
+
const MAX_PREFIX_SEARCH_TERMS = 8;
|
|
131
|
+
/** Every value at a dotted `path` of `doc`, arrays flattened (`contacts.name` over an array of contacts). */
|
|
132
|
+
function valuesAtPath(doc, path) {
|
|
133
|
+
const parts = path.split(".");
|
|
134
|
+
const out = [];
|
|
135
|
+
const walk = (value, i) => {
|
|
136
|
+
if (value === void 0 || value === null) return;
|
|
137
|
+
if (Array.isArray(value)) {
|
|
138
|
+
for (const item of value) walk(item, i);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (i === parts.length) {
|
|
142
|
+
out.push(value);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (typeof value !== "object") return;
|
|
146
|
+
walk(value[parts[i]], i + 1);
|
|
147
|
+
};
|
|
148
|
+
walk(doc, 0);
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Distinct tokens of `values` (arrays flattened, nested arrays too; strings and
|
|
153
|
+
* numbers tokenised, every other value ignored), in first-seen order.
|
|
154
|
+
*/
|
|
155
|
+
function collectSearchTokens(values) {
|
|
156
|
+
const keys = /* @__PURE__ */ new Set();
|
|
157
|
+
const add = (v) => {
|
|
158
|
+
if (Array.isArray(v)) {
|
|
159
|
+
for (const item of v) add(item);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const text = typeof v === "string" ? v : typeof v === "number" || typeof v === "bigint" ? String(v) : void 0;
|
|
163
|
+
if (text === void 0) return;
|
|
164
|
+
for (const token of tokenizeSearchText(text)) keys.add(token);
|
|
165
|
+
};
|
|
166
|
+
for (const v of values) add(v);
|
|
167
|
+
return [...keys];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The search keys of `doc`: the distinct normalised tokens of every value at
|
|
171
|
+
* every `fields` path (strings and numbers; arrays flattened; other values
|
|
172
|
+
* ignored), in first-seen order. This is exactly what the write paths store.
|
|
173
|
+
*
|
|
174
|
+
* @example buildSearchKeys({ name: 'Ahmed bin Ali', email: 'a.ali@x.io' }, ['name'])
|
|
175
|
+
* // ['ahmed', 'bin', 'ali']
|
|
176
|
+
*/
|
|
177
|
+
function buildSearchKeys(doc, fields) {
|
|
178
|
+
return collectSearchTokens(fields.map((f) => valuesAtPath(doc, f)));
|
|
179
|
+
}
|
|
180
|
+
const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
181
|
+
/**
|
|
182
|
+
* The MongoDB filter for a prefix search: every query token must be the start of
|
|
183
|
+
* some stored key (AND across words, any order). `null` when `query` has no
|
|
184
|
+
* tokens (blank or punctuation only): the caller decides whether that means "no
|
|
185
|
+
* search" (the repository's choice) or "match nothing".
|
|
186
|
+
*
|
|
187
|
+
* Uses `{ $regex: '^…' }` strings, never RegExp objects, so the filter survives
|
|
188
|
+
* JSON (cache keys, logs, wire) unchanged.
|
|
189
|
+
*
|
|
190
|
+
* @example prefixSearchFilter('Moh Ali')
|
|
191
|
+
* // { $and: [{ _searchKeys: { $regex: '^moh' } }, { _searchKeys: { $regex: '^ali' } }] }
|
|
192
|
+
*/
|
|
193
|
+
function prefixSearchFilter(query, options = {}) {
|
|
194
|
+
const field = options.keysField ?? "_searchKeys";
|
|
195
|
+
const max = options.maxTerms ?? 8;
|
|
196
|
+
const terms = [...new Set(tokenizeSearchText(query))].slice(0, Math.max(1, max));
|
|
197
|
+
if (terms.length === 0) return null;
|
|
198
|
+
const arms = terms.map((t) => ({ [field]: { $regex: `^${escapeRegex(t)}` } }));
|
|
199
|
+
return arms.length === 1 ? arms[0] : { $and: arms };
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Mongoose schema path for the keys field: an array of strings, never selected
|
|
203
|
+
* by default (`select: false`), absent until written (so a backfill can find
|
|
204
|
+
* rows without it).
|
|
205
|
+
*
|
|
206
|
+
* @example schema.add({ _searchKeys: searchKeysPath() });
|
|
207
|
+
*/
|
|
208
|
+
function searchKeysPath() {
|
|
209
|
+
return {
|
|
210
|
+
type: [String],
|
|
211
|
+
select: false,
|
|
212
|
+
default: void 0
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Index declaration for the keys field (multikey), in the
|
|
217
|
+
* `{ fields, options }` shape `schema.index(fields, options)` and the Better
|
|
218
|
+
* Auth overlay's `indexes` take. Name defaults to `<field>_1`.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* // Serves getAll({ search }) prefix search (searchMode: 'prefix').
|
|
222
|
+
* const idx = searchKeysIndex();
|
|
223
|
+
* schema.index(idx.fields, idx.options);
|
|
224
|
+
*/
|
|
225
|
+
function searchKeysIndex(field = DEFAULT_SEARCH_KEYS_FIELD, options = {}) {
|
|
226
|
+
return {
|
|
227
|
+
fields: { [field]: 1 },
|
|
228
|
+
options: { name: options.name ?? `${field}_1` }
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/search/derive.ts
|
|
233
|
+
/**
|
|
234
|
+
* How a write changes the search fields: the pure analysis behind the
|
|
235
|
+
* search-keys plugin. Given one write (a document, a replacement, or an update
|
|
236
|
+
* with its filter), it says which search-field values the write determines and
|
|
237
|
+
* which it leaves to the stored document, so the plugin can derive the keys in
|
|
238
|
+
* the SAME write (no second round-trip, no window where keys are stale).
|
|
239
|
+
*/
|
|
240
|
+
/** Thrown (as `status: 400` with a `code`) for a write the keys cannot follow. */
|
|
241
|
+
const SEARCH_KEYS_ERROR_CODES = {
|
|
242
|
+
/** An operator or path the plugin cannot evaluate touches a search field. */
|
|
243
|
+
UNSUPPORTED_UPDATE: "SEARCH_KEYS_UNSUPPORTED_UPDATE",
|
|
244
|
+
/** A multi-document update sets SOME search fields; the rest differ per document. */
|
|
245
|
+
PARTIAL_MULTI_UPDATE: "SEARCH_KEYS_PARTIAL_MULTI_UPDATE"
|
|
246
|
+
};
|
|
247
|
+
var SearchKeysWriteError = class extends Error {
|
|
248
|
+
code;
|
|
249
|
+
status = 400;
|
|
250
|
+
constructor(message, code) {
|
|
251
|
+
super(message);
|
|
252
|
+
this.code = code;
|
|
253
|
+
this.name = "SearchKeysWriteError";
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
|
|
257
|
+
/** Operators whose effect on a search field the plugin evaluates. */
|
|
258
|
+
const EVALUATED = /* @__PURE__ */ new Set([
|
|
259
|
+
"$set",
|
|
260
|
+
"$setOnInsert",
|
|
261
|
+
"$unset"
|
|
262
|
+
]);
|
|
263
|
+
/**
|
|
264
|
+
* Resolve each search field against one `path → value` bag (`$set`, a plain
|
|
265
|
+
* patch): an exact key, or an ANCESTOR key (`profile` for `profile.name`). A
|
|
266
|
+
* DESCENDANT key (`tags.0` for `tags`) changes part of the value the plugin
|
|
267
|
+
* cannot see, so it is refused.
|
|
268
|
+
*/
|
|
269
|
+
function resolveBag(bag, fields, op, out, unset = false) {
|
|
270
|
+
for (const key of Object.keys(bag)) for (const field of fields) if (key === field) out[field] = unset ? void 0 : bag[key];
|
|
271
|
+
else if (field.startsWith(`${key}.`)) out[field] = unset ? void 0 : valuesAtPath(bag[key], field.slice(key.length + 1));
|
|
272
|
+
else if (key.startsWith(`${field}.`)) throw new SearchKeysWriteError(`[mongokit] search keys: '${op}' on '${key}' changes part of search field '${field}'; set the whole field ('${field}') so its search keys can be derived`, SEARCH_KEYS_ERROR_CODES.UNSUPPORTED_UPDATE);
|
|
273
|
+
}
|
|
274
|
+
const touches = (key, fields) => fields.some((f) => key === f || f.startsWith(`${key}.`) || key.startsWith(`${f}.`));
|
|
275
|
+
/** Equalities in a filter's top level (and top-level `$and` arms) on search fields. */
|
|
276
|
+
function pinnedValues(filter, fields, out) {
|
|
277
|
+
if (!isPlainObject(filter)) return;
|
|
278
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
279
|
+
if (key === "$and" && Array.isArray(value)) {
|
|
280
|
+
for (const arm of value) pinnedValues(arm, fields, out);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (!fields.includes(key)) continue;
|
|
284
|
+
if (typeof value === "string" || typeof value === "number") out[key] = value;
|
|
285
|
+
else if (isPlainObject(value) && Object.keys(value).length === 1 && "$eq" in value) {
|
|
286
|
+
const eq = value.$eq;
|
|
287
|
+
if (typeof eq === "string" || typeof eq === "number") out[key] = eq;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Analyse an update (plain patch, operator update, or pipeline) against the
|
|
293
|
+
* search fields. Strips any caller-supplied value for `keysField` (the keys
|
|
294
|
+
* are derived, never client-settable). Throws {@link SearchKeysWriteError} for
|
|
295
|
+
* an operator other than `$set`/`$setOnInsert`/`$unset` (or a pipeline stage)
|
|
296
|
+
* that touches a search field.
|
|
297
|
+
*/
|
|
298
|
+
function analyzeUpdate(update, filter, fields, keysField) {
|
|
299
|
+
const effect = {
|
|
300
|
+
updated: {},
|
|
301
|
+
onInsert: {},
|
|
302
|
+
pinned: {}
|
|
303
|
+
};
|
|
304
|
+
pinnedValues(filter, fields, effect.pinned);
|
|
305
|
+
if (Array.isArray(update)) {
|
|
306
|
+
for (const stage of update) {
|
|
307
|
+
if (!isPlainObject(stage)) continue;
|
|
308
|
+
for (const [name, body] of Object.entries(stage)) {
|
|
309
|
+
const keys = name === "$unset" ? [].concat(body).map(String) : isPlainObject(body) ? Object.keys(body) : [];
|
|
310
|
+
if (name === "$replaceRoot" || name === "$replaceWith" || name === "$project" || keys.some((k) => touches(k, fields))) throw new SearchKeysWriteError(`[mongokit] search keys: pipeline stage '${name}' touches a search field (${fields.join(", ")}); use an operator update (\$set / \$unset) so the keys can be derived`, SEARCH_KEYS_ERROR_CODES.UNSUPPORTED_UPDATE);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return effect;
|
|
314
|
+
}
|
|
315
|
+
if (!isPlainObject(update)) return effect;
|
|
316
|
+
const keys = Object.keys(update);
|
|
317
|
+
if (!keys.some((k) => k.startsWith("$"))) {
|
|
318
|
+
delete update[keysField];
|
|
319
|
+
resolveBag(update, fields, "$set", effect.updated);
|
|
320
|
+
return effect;
|
|
321
|
+
}
|
|
322
|
+
for (const op of keys) {
|
|
323
|
+
const body = update[op];
|
|
324
|
+
if (!isPlainObject(body)) continue;
|
|
325
|
+
if (op === "$set" || op === "$setOnInsert") delete body[keysField];
|
|
326
|
+
if (op === "$unset") delete body[keysField];
|
|
327
|
+
if (!EVALUATED.has(op)) {
|
|
328
|
+
const hit = Object.keys(body).find((k) => touches(k, fields));
|
|
329
|
+
if (hit) throw new SearchKeysWriteError(`[mongokit] search keys: '${op}' on '${hit}' cannot be followed by the search keys; set the whole value with $set (or call rebuildSearchKeys afterwards)`, SEARCH_KEYS_ERROR_CODES.UNSUPPORTED_UPDATE);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (op === "$set") resolveBag(body, fields, op, effect.updated);
|
|
333
|
+
else if (op === "$unset") resolveBag(body, fields, op, effect.updated, true);
|
|
334
|
+
else resolveBag(body, fields, op, effect.onInsert);
|
|
335
|
+
}
|
|
336
|
+
return effect;
|
|
337
|
+
}
|
|
338
|
+
/** Fields the effect leaves unknown for the update path (neither updated nor pinned). */
|
|
339
|
+
function unresolvedForUpdate(effect, fields) {
|
|
340
|
+
return fields.filter((f) => !(f in effect.updated) && !(f in effect.pinned));
|
|
341
|
+
}
|
|
342
|
+
/** The full value set an INSERT through this update would store. */
|
|
343
|
+
function insertValues(effect) {
|
|
344
|
+
return {
|
|
345
|
+
...effect.pinned,
|
|
346
|
+
...effect.onInsert,
|
|
347
|
+
...effect.updated
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Put `keys` into an update in place: a plain patch gets the key directly
|
|
352
|
+
* (mongoose wraps it in `$set`), an operator update under `$set` (or
|
|
353
|
+
* `$setOnInsert` for an insert-only derivation).
|
|
354
|
+
*/
|
|
355
|
+
function writeKeys(update, keysField, keys, slot = "$set") {
|
|
356
|
+
if (!Object.keys(update).some((k) => k.startsWith("$"))) {
|
|
357
|
+
update[keysField] = keys;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const bag = isPlainObject(update[slot]) ? update[slot] : {};
|
|
361
|
+
bag[keysField] = keys;
|
|
362
|
+
update[slot] = bag;
|
|
363
|
+
}
|
|
364
|
+
/** The values of `fields` read from a stored (or to-be-inserted) document. */
|
|
365
|
+
function fieldValuesOf(doc, fields) {
|
|
366
|
+
const out = {};
|
|
367
|
+
for (const f of fields) out[f] = valuesAtPath(doc, f);
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
/** The keys for a resolved value set (see {@link collectSearchTokens}), in `fields` order. */
|
|
371
|
+
function keysFromValues(values, fields) {
|
|
372
|
+
return collectSearchTokens(fields.map((f) => values[f]));
|
|
373
|
+
}
|
|
374
|
+
//#endregion
|
|
375
|
+
export { tokenizeSearchText as _, insertValues as a, writeKeys as c, buildSearchKeys as d, prefixSearchFilter as f, normalizeSearchText as g, findSearchMatches as h, fieldValuesOf as i, DEFAULT_SEARCH_KEYS_FIELD as l, searchKeysPath as m, SearchKeysWriteError as n, keysFromValues as o, searchKeysIndex as p, analyzeUpdate as r, unresolvedForUpdate as s, SEARCH_KEYS_ERROR_CODES as t, MAX_PREFIX_SEARCH_TERMS as u };
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
//#region src/search/derive.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* How a write changes the search fields: the pure analysis behind the
|
|
4
|
+
* search-keys plugin. Given one write (a document, a replacement, or an update
|
|
5
|
+
* with its filter), it says which search-field values the write determines and
|
|
6
|
+
* which it leaves to the stored document, so the plugin can derive the keys in
|
|
7
|
+
* the SAME write (no second round-trip, no window where keys are stale).
|
|
8
|
+
*/
|
|
9
|
+
/** Thrown (as `status: 400` with a `code`) for a write the keys cannot follow. */
|
|
10
|
+
declare const SEARCH_KEYS_ERROR_CODES: {
|
|
11
|
+
/** An operator or path the plugin cannot evaluate touches a search field. */
|
|
12
|
+
readonly UNSUPPORTED_UPDATE: "SEARCH_KEYS_UNSUPPORTED_UPDATE";
|
|
13
|
+
/** A multi-document update sets SOME search fields; the rest differ per document. */
|
|
14
|
+
readonly PARTIAL_MULTI_UPDATE: "SEARCH_KEYS_PARTIAL_MULTI_UPDATE";
|
|
15
|
+
};
|
|
16
|
+
declare class SearchKeysWriteError extends Error {
|
|
17
|
+
readonly code: (typeof SEARCH_KEYS_ERROR_CODES)[keyof typeof SEARCH_KEYS_ERROR_CODES];
|
|
18
|
+
readonly status = 400;
|
|
19
|
+
constructor(message: string, code: (typeof SEARCH_KEYS_ERROR_CODES)[keyof typeof SEARCH_KEYS_ERROR_CODES]);
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/search/normalize.d.ts
|
|
23
|
+
/**
|
|
24
|
+
* Search-as-you-type text rules, shared by the WRITER (the search keys stored on
|
|
25
|
+
* every document) and the READER (the query typed into a search box).
|
|
26
|
+
*
|
|
27
|
+
* Both sides MUST run the same function, otherwise a key written as `mohammed`
|
|
28
|
+
* is never found by a query normalised to `Mohammed`. That is also why this
|
|
29
|
+
* module is pure (no mongoose, no driver): a portal or mobile client imports it
|
|
30
|
+
* from `@classytic/mongokit/search` to highlight matches with the exact rules
|
|
31
|
+
* the server indexed with.
|
|
32
|
+
*
|
|
33
|
+
* Rules, in order:
|
|
34
|
+
* 1. lower-case (before NFKD: `'İ'.toLowerCase()` yields a combining dot the
|
|
35
|
+
* next step removes);
|
|
36
|
+
* 2. Unicode NFKD, then every combining mark (`\p{M}`) dropped: `é → e`,
|
|
37
|
+
* `fi → fi`, and Arabic tashkeel (fatha, damma, kasra, shadda, sukun,
|
|
38
|
+
* tanween, superscript alef) disappear;
|
|
39
|
+
* 3. Arabic folding: tatweel removed; `أ إ آ ٱ → ا` (the hamza/madda forms
|
|
40
|
+
* decompose to bare alef in step 2, wasla is folded here); `ى → ي`;
|
|
41
|
+
* `ة → ه`; Arabic-Indic and Persian digits → ASCII digits;
|
|
42
|
+
* 4. format characters (`\p{Cf}`: ZWJ, ZWNJ, bidi marks) removed so a pasted
|
|
43
|
+
* name does not split or miss;
|
|
44
|
+
* 5. split on anything that is not a letter or a digit (whitespace and
|
|
45
|
+
* punctuation: `al-harbi` → `al`, `harbi`; `noura.saad@x.io` → `noura`,
|
|
46
|
+
* `saad`, `x`, `io`).
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* The search tokens of `text`, in order, duplicates kept. Empty for blank text
|
|
50
|
+
* or text with no letters/digits.
|
|
51
|
+
*
|
|
52
|
+
* @example tokenizeSearchText(' Mohammed AL-Harbi ') // ['mohammed', 'al', 'harbi']
|
|
53
|
+
* @example tokenizeSearchText('مُحَمَّد أحمد') // ['محمد', 'احمد']
|
|
54
|
+
*/
|
|
55
|
+
declare function tokenizeSearchText(text: string): string[];
|
|
56
|
+
/**
|
|
57
|
+
* `text` normalised to its tokens joined by single spaces: the canonical form to
|
|
58
|
+
* compare or display ("what the index sees").
|
|
59
|
+
*
|
|
60
|
+
* @example normalizeSearchText('Café Crème') // 'cafe creme'
|
|
61
|
+
*/
|
|
62
|
+
declare function normalizeSearchText(text: string): string;
|
|
63
|
+
/** A `[start, end)` range of the ORIGINAL text, as {@link findSearchMatches} returns. */
|
|
64
|
+
interface SearchMatchRange {
|
|
65
|
+
start: number;
|
|
66
|
+
end: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Where `query` matches `text` under the prefix-search rules: for every word of
|
|
70
|
+
* `text` whose normalised form starts with one of the query's tokens, the
|
|
71
|
+
* shortest leading run of the ORIGINAL characters that covers that token.
|
|
72
|
+
* Offsets index the original string, so a client wraps them in `<mark>` with no
|
|
73
|
+
* re-normalisation of its own.
|
|
74
|
+
*
|
|
75
|
+
* @example findSearchMatches('Mohammed Al-Harbi', 'moh har')
|
|
76
|
+
* // [{ start: 0, end: 3 }, { start: 12, end: 15 }]
|
|
77
|
+
*/
|
|
78
|
+
declare function findSearchMatches(text: string, query: string): SearchMatchRange[];
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/search/prefix.d.ts
|
|
81
|
+
/**
|
|
82
|
+
* Indexed search-as-you-type (`searchMode: 'prefix'`): the stored keys, the
|
|
83
|
+
* query filter, and the index/schema declarations. Pure: no mongoose import.
|
|
84
|
+
*
|
|
85
|
+
* ## Storage choice: word tokens + anchored regex (not edge n-grams)
|
|
86
|
+
*
|
|
87
|
+
* Each document stores the DISTINCT normalised word tokens of its search fields
|
|
88
|
+
* in one array field (`_searchKeys` by default), covered by a multikey index. A
|
|
89
|
+
* query token `moh` becomes `{ _searchKeys: { $regex: '^moh' } }`: a
|
|
90
|
+
* case-sensitive regex with a literal prefix, which the planner turns into the
|
|
91
|
+
* index range `["moh", "moi")` (IXSCAN, keys examined ≈ matches). Several query
|
|
92
|
+
* tokens AND together, one `$and` arm each; the planner bounds one arm and
|
|
93
|
+
* filters the rest on the fetched documents.
|
|
94
|
+
*
|
|
95
|
+
* The alternative, edge n-grams (`m, mo, moh, ..., mohammed` stored, matched by
|
|
96
|
+
* an exact `$all`), gives the SAME index-range query plan, only with point
|
|
97
|
+
* bounds, while storing one key per prefix length instead of one per word.
|
|
98
|
+
* Measured on 2,000 two-to-three-word names (mongod 8, WiredTiger; see
|
|
99
|
+
* `tests/integration/prefix-search.test.ts`): tokens 6,000 keys / 60 KiB index,
|
|
100
|
+
* edge n-grams (N=20) 34,140 keys / 256 KiB index, ~5.7x the keys and ~4x the
|
|
101
|
+
* index bytes. N-grams also cost a write amplification on every
|
|
102
|
+
* rename, and an arbitrary cut-off N past which longer prefixes stop matching.
|
|
103
|
+
* Tokens keep the index small, have no length cliff, and make the backfill a
|
|
104
|
+
* pure function of the source fields. So tokens are the standard here.
|
|
105
|
+
*/
|
|
106
|
+
/** Default name of the field that stores the search keys. */
|
|
107
|
+
declare const DEFAULT_SEARCH_KEYS_FIELD = "_searchKeys";
|
|
108
|
+
/** Query tokens beyond this many are ignored (each one is an `$and` arm). */
|
|
109
|
+
declare const MAX_PREFIX_SEARCH_TERMS = 8;
|
|
110
|
+
/**
|
|
111
|
+
* The search keys of `doc`: the distinct normalised tokens of every value at
|
|
112
|
+
* every `fields` path (strings and numbers; arrays flattened; other values
|
|
113
|
+
* ignored), in first-seen order. This is exactly what the write paths store.
|
|
114
|
+
*
|
|
115
|
+
* @example buildSearchKeys({ name: 'Ahmed bin Ali', email: 'a.ali@x.io' }, ['name'])
|
|
116
|
+
* // ['ahmed', 'bin', 'ali']
|
|
117
|
+
*/
|
|
118
|
+
declare function buildSearchKeys(doc: unknown, fields: readonly string[]): string[];
|
|
119
|
+
interface PrefixSearchFilterOptions {
|
|
120
|
+
/** Field holding the keys. Default {@link DEFAULT_SEARCH_KEYS_FIELD}. */
|
|
121
|
+
keysField?: string;
|
|
122
|
+
/** Cap on query tokens. Default {@link MAX_PREFIX_SEARCH_TERMS}. */
|
|
123
|
+
maxTerms?: number;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The MongoDB filter for a prefix search: every query token must be the start of
|
|
127
|
+
* some stored key (AND across words, any order). `null` when `query` has no
|
|
128
|
+
* tokens (blank or punctuation only): the caller decides whether that means "no
|
|
129
|
+
* search" (the repository's choice) or "match nothing".
|
|
130
|
+
*
|
|
131
|
+
* Uses `{ $regex: '^…' }` strings, never RegExp objects, so the filter survives
|
|
132
|
+
* JSON (cache keys, logs, wire) unchanged.
|
|
133
|
+
*
|
|
134
|
+
* @example prefixSearchFilter('Moh Ali')
|
|
135
|
+
* // { $and: [{ _searchKeys: { $regex: '^moh' } }, { _searchKeys: { $regex: '^ali' } }] }
|
|
136
|
+
*/
|
|
137
|
+
declare function prefixSearchFilter(query: string, options?: PrefixSearchFilterOptions): Record<string, unknown> | null;
|
|
138
|
+
/**
|
|
139
|
+
* Mongoose schema path for the keys field: an array of strings, never selected
|
|
140
|
+
* by default (`select: false`), absent until written (so a backfill can find
|
|
141
|
+
* rows without it).
|
|
142
|
+
*
|
|
143
|
+
* @example schema.add({ _searchKeys: searchKeysPath() });
|
|
144
|
+
*/
|
|
145
|
+
declare function searchKeysPath(): {
|
|
146
|
+
type: [StringConstructor];
|
|
147
|
+
select: false;
|
|
148
|
+
default: undefined;
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Index declaration for the keys field (multikey), in the
|
|
152
|
+
* `{ fields, options }` shape `schema.index(fields, options)` and the Better
|
|
153
|
+
* Auth overlay's `indexes` take. Name defaults to `<field>_1`.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* // Serves getAll({ search }) prefix search (searchMode: 'prefix').
|
|
157
|
+
* const idx = searchKeysIndex();
|
|
158
|
+
* schema.index(idx.fields, idx.options);
|
|
159
|
+
*/
|
|
160
|
+
declare function searchKeysIndex(field?: string, options?: {
|
|
161
|
+
name?: string;
|
|
162
|
+
}): {
|
|
163
|
+
fields: Record<string, 1>;
|
|
164
|
+
options: {
|
|
165
|
+
name: string;
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
//#endregion
|
|
169
|
+
export { prefixSearchFilter as a, SearchMatchRange as c, tokenizeSearchText as d, SEARCH_KEYS_ERROR_CODES as f, buildSearchKeys as i, findSearchMatches as l, MAX_PREFIX_SEARCH_TERMS as n, searchKeysIndex as o, SearchKeysWriteError as p, PrefixSearchFilterOptions as r, searchKeysPath as s, DEFAULT_SEARCH_KEYS_FIELD as t, normalizeSearchText as u };
|
package/dist/index.d.mts
CHANGED
|
@@ -2,16 +2,17 @@ import { S as LookupOptions, _ as SessionOptions, a as ConvenientTransactionOpti
|
|
|
2
2
|
import { A as ObjectId, C as configureQueryDefaults, D as AnyDocument, E as resolveQueryOptions, F as SortSpec$1, M as ReadPreferenceType, N as SelectSpec, O as AnyModel, P as SortDirection, S as configureAggregateDefaults, T as resetQueryDefaults, _ as QueryDefaults, a as CursorPayload, b as WriteConcernSpec, c as MongokitPageExtras, d as ValueType, f as CursorSecret, g as QUERY_DEFAULTS_ERROR_CODES, h as PerCallQueryOptions, i as CountStrategy, j as PopulateSpec, l as OffsetPaginationOptions, m as CommandKind, n as BasePaginationOptions, o as DecodedCursor, p as AggregateDefaults, r as CollationOptions, s as KeysetPaginationOptions, t as AggregatePaginationOptions, u as PaginationConfig, v as ReadConcernLevel, w as getQueryDefaults, x as assertQueryDefaultsConfigured, y as ResolvedQueryOptions } from "./pagination-D6psses0.mjs";
|
|
3
3
|
import { a as CreateManyFailure, l as isCreateManyPartialError, o as CreateManyPartial, s as CreateManyPartialError, t as index_d_exports } from "./index-SYF8N28D.mjs";
|
|
4
4
|
import { a as InferRawDoc, c as NonNullableFields, d as Strict, f as UpdatePatch, i as InferDocument, l as PartialBy, n as DeepPartial, o as KeysOfType, p as UpdateWithValidationResult, r as DocField, s as MongoOperatorUpdate, t as CreateInput, u as RequiredBy } from "./type-utils-CLzRFtIA.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import { A as EnsureAuditTrailReadyOptions, C as AuditQueryOptions, D as AuditTrailOptions, E as AuditTrailMode, M as ensureAuditTrailReady, O as AuditTrailQuery, S as AuditOperation, T as AuditTrailMethods, _ as CacheOptions, a as DateSequentialIdOptions, b as cachePlugin, c as SequentialIdOptions, d as ensureSequenceAtLeast, f as getNextSequence, g as CacheAdapter, h as sequentialId, i as CustomIdOptions, j as auditTrailPlugin, k as AuditTrailQueryOptions, l as customIdPlugin, m as readSequence, n as adminBypass, o as IdGenerator, p as prefixedId, r as multiTenantPlugin, s as PrefixedIdOptions, t as MultiTenantOptions, u as dateSequentialId, v as RepositoryCacheHandle, w as AuditQueryResult, x as AuditEntry, y as RepositoryCachePluginOptions } from "./multi-tenant.plugin-
|
|
5
|
+
import { C as RepositoryPluginName, S as MongoKitPluginName, _ as RepositoryOptions, a as HookMode, b as WithPlugins, c as MinimalRepoView, d as PluginType, f as PrioritizedHook, g as RepositoryOperation, h as RepositoryInstance, i as EventPhase, l as Plugin, m as RepositoryEvent, n as EventHandlers, o as Middleware, p as RepositoryContext, r as EventPayload, s as MiddlewareContext, t as AllPluginMethods, u as PluginFunction, v as RepositorySearchMode, x as MONGOKIT_PLUGIN_NAMES, y as UserContext } from "./repository-MqAP409e.mjs";
|
|
6
|
+
import { A as EnsureAuditTrailReadyOptions, C as AuditQueryOptions, D as AuditTrailOptions, E as AuditTrailMode, M as ensureAuditTrailReady, O as AuditTrailQuery, S as AuditOperation, T as AuditTrailMethods, _ as CacheOptions, a as DateSequentialIdOptions, b as cachePlugin, c as SequentialIdOptions, d as ensureSequenceAtLeast, f as getNextSequence, g as CacheAdapter, h as sequentialId, i as CustomIdOptions, j as auditTrailPlugin, k as AuditTrailQueryOptions, l as customIdPlugin, m as readSequence, n as adminBypass, o as IdGenerator, p as prefixedId, r as multiTenantPlugin, s as PrefixedIdOptions, t as MultiTenantOptions, u as dateSequentialId, v as RepositoryCacheHandle, w as AuditQueryResult, x as AuditEntry, y as RepositoryCachePluginOptions } from "./multi-tenant.plugin-B_q9NUHc.mjs";
|
|
7
7
|
import { n as PaginationEngine, t as CountMemoKey } from "./PaginationEngine-CRHyuUtw.mjs";
|
|
8
|
-
import { a as AggregationBuilder, c as ProjectionSpec, d as VectorSearchOptions, i as TransitionMachine, l as SortOrder, n as HOOK_PRIORITY, o as AggregationPlan, r as Repository, s as GroupSpec, t as GetAllParams, u as SortSpec } from "./Repository-
|
|
9
|
-
import { a as SoftDeleteFilterMode, c as ValidationChainOptions, i as Logger, l as ValidatorDefinition, n as CascadeRelation, o as SoftDeleteOptions, r as FieldPreset, s as SoftDeleteRepository, t as CascadeOptions } from "./plugin-options-
|
|
10
|
-
import { n as CreateRepositoryConfig, r as createRepository, t as AuditConfig } from "./create-repository-
|
|
11
|
-
import { A as
|
|
8
|
+
import { a as AggregationBuilder, c as ProjectionSpec, d as VectorSearchOptions, i as TransitionMachine, l as SortOrder, n as HOOK_PRIORITY, o as AggregationPlan, r as Repository, s as GroupSpec, t as GetAllParams, u as SortSpec } from "./Repository-DnL4xk5w.mjs";
|
|
9
|
+
import { a as SoftDeleteFilterMode, c as ValidationChainOptions, i as Logger, l as ValidatorDefinition, n as CascadeRelation, o as SoftDeleteOptions, r as FieldPreset, s as SoftDeleteRepository, t as CascadeOptions } from "./plugin-options-Cirmb4Kp.mjs";
|
|
10
|
+
import { n as CreateRepositoryConfig, r as createRepository, t as AuditConfig } from "./create-repository-CJtVdwig.mjs";
|
|
11
|
+
import { A as immutableStatesPlugin, B as BulkWriteOptions, D as leasePlugin, E as LeasePluginOptions, F as fieldFilterPlugin, G as appendOnlyPlugin, H as batchOperationsPlugin, I as ElasticSearchOptions, K as AggregateHelpersMethods, L as elasticSearchPlugin, M as IdempotencyStoreMethods, N as IdempotencyStorePluginOptions, O as ImmutableClaimView, P as idempotencyStorePlugin, R as cascadePlugin, S as mongoOperationsPlugin, T as LeaseMethods, U as auditLogPlugin, V as BulkWriteResult, W as AppendOnlyPluginOptions, _ as searchKeysPlugin, a as requireField, b as observabilityPlugin, c as timestampPlugin, d as createTenantContext, f as SubdocumentMethods, g as SearchKeysPluginOptions, h as softDeletePlugin, i as immutableField, j as IdempotencyClaim, k as ImmutableStatesPluginOptions, l as TenantContext, m as SoftDeleteMethods, n as autoInject, o as uniqueField, p as subdocumentPlugin, q as aggregateHelpersPlugin, r as blockIf, s as validationChainPlugin, t as OperationType, u as TenantStore, v as ObservabilityOptions, w as methodRegistryPlugin, x as MongoOperationsMethods, y as OperationMetric, z as BatchOperationsMethods } from "./validation-chain.plugin-BAeC5eVT.mjs";
|
|
12
12
|
import { n as SchemaPathsLike, t as FieldType$1 } from "./coercion-BjdPX3YF.mjs";
|
|
13
13
|
import { n as SchemaIndexes } from "./indexes-DIjMpYES.mjs";
|
|
14
|
-
import {
|
|
14
|
+
import { a as prefixSearchFilter, c as SearchMatchRange, d as tokenizeSearchText, f as SEARCH_KEYS_ERROR_CODES, i as buildSearchKeys, l as findSearchMatches, n as MAX_PREFIX_SEARCH_TERMS, o as searchKeysIndex, p as SearchKeysWriteError, r as PrefixSearchFilterOptions, s as searchKeysPath, t as DEFAULT_SEARCH_KEYS_FIELD, u as normalizeSearchText } from "./index-C0AjVqX3.mjs";
|
|
15
|
+
import { S as parseDuplicateKeyError, _ as getMongooseProjection, a as createMemoryCache, b as createError, g as getFieldsForUser, h as filterResponseData, i as buildCrudSchemasFromMongooseSchema, m as createFieldPreset, n as CrudSchemasFramework, o as LoggerConfig, r as buildCrudSchemasFromModel, s as configureLogger, t as toPlain, u as idVariants, v as CreateErrorOptions, x as isDuplicateKeyError, y as ParseDuplicateKeyOptions } from "./to-plain-o5tUY2nV.mjs";
|
|
15
16
|
import { RepoCapabilities } from "@classytic/repo-core/repository";
|
|
16
17
|
import { ClientSession, Connection, Model, PipelineStage, Schema } from "mongoose";
|
|
17
18
|
import { HttpError } from "@classytic/repo-core/errors";
|
|
@@ -753,6 +754,34 @@ declare function declareCollectionScope(model: object, rule: CollectionScopeRule
|
|
|
753
754
|
tenantField?: string;
|
|
754
755
|
}): void;
|
|
755
756
|
//#endregion
|
|
757
|
+
//#region src/search/rebuild.d.ts
|
|
758
|
+
/** A prefix-mode `Repository` satisfies this: `rebuildSearchKeys(repo)`. */
|
|
759
|
+
interface SearchKeysSource {
|
|
760
|
+
Model: Model<any, any, any, any>;
|
|
761
|
+
searchFields?: readonly string[] | undefined;
|
|
762
|
+
searchKeysField?: string;
|
|
763
|
+
}
|
|
764
|
+
interface RebuildSearchKeysOptions {
|
|
765
|
+
/** Source fields. Default: the repository's `searchFields`. */
|
|
766
|
+
fields?: readonly string[];
|
|
767
|
+
/** Keys field. Default: the repository's `searchKeysField`, else `'_searchKeys'`. */
|
|
768
|
+
keysField?: string;
|
|
769
|
+
/** Only documents matching this filter. Default: all. */
|
|
770
|
+
filter?: Record<string, unknown>;
|
|
771
|
+
/** Only documents with no keys field yet (cheapest boot-time backfill). Default `false`. */
|
|
772
|
+
onlyMissing?: boolean;
|
|
773
|
+
/** Documents read and written per round-trip. Default 500. */
|
|
774
|
+
batchSize?: number;
|
|
775
|
+
session?: unknown;
|
|
776
|
+
}
|
|
777
|
+
interface RebuildSearchKeysResult {
|
|
778
|
+
/** Documents read. */
|
|
779
|
+
scanned: number;
|
|
780
|
+
/** Documents whose keys were missing or stale and were rewritten (server-reported). */
|
|
781
|
+
updated: number;
|
|
782
|
+
}
|
|
783
|
+
declare function rebuildSearchKeys(source: SearchKeysSource | Model<any, any, any, any>, options?: RebuildSearchKeysOptions): Promise<RebuildSearchKeysResult>;
|
|
784
|
+
//#endregion
|
|
756
785
|
//#region src/transaction.d.ts
|
|
757
786
|
/** Minimal shape we need from a Mongoose connection. */
|
|
758
787
|
interface SessionStarter {
|
|
@@ -1116,4 +1145,4 @@ declare function createOptionsExtractor<TCtx extends Record<string, unknown>>(fi
|
|
|
1116
1145
|
*/
|
|
1117
1146
|
declare function systemContext(extra?: SessionOptions): SessionOptions;
|
|
1118
1147
|
//#endregion
|
|
1119
|
-
export { ALL_OPERATIONS, type AggregateCallOptions, type AggregateDefaults, type AggregateHelpersMethods, type AggregateOptions, type AggregatePaginationOptions, AggregationBuilder, type AggregationPlan, type SortOrder as AggregationSortOrder, type SortSpec as AggregationSortSpec, type AllPluginMethods, type AnyDocument, type AnyModel, type AppendOnlyPluginOptions, type AuditConfig, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailMode, type AuditTrailOptions, AuditTrailQuery, type AuditTrailQueryOptions, type BasePaginationOptions, type BatchOperationsMethods, type BulkWriteOptions, type BulkWriteResult, type CacheAdapter, type CacheOperationOptions, type CacheOptions, type CacheableOptions, type CapabilityConnectionLike, type CapabilityProbeOptions, type CapabilityResolution, type CascadeOptions, type CascadePurgeReferencesOptions, type CascadeRelation, type CollationOptions, type CollectionRWLike, type CollectionScopeRule, type CommandKind, type ConvenientTransactionOptions, type CountMemoKey, type CountStrategy, type CreateErrorOptions, type CreateInput, type CreateManyFailure, type CreateManyPartial, type CreateManyPartialError, type CreateOptions, type CreateRepositoryConfig, type CrudSchemasFramework, type CursorPayload, type CursorSecret, type CustomIdOptions, type DateSequentialIdOptions, type DecodedCursor, type DeepPartial, type DefineModelOptions, type DeleteResult, type DocField, type ElasticSearchOptions, type EnsureAuditTrailReadyOptions, type EventHandlers, type EventPayload, type EventPhase, type ExistingModelPolicy, type FieldPreset, type FieldType, type FilterQuery, type FindOneAndUpdateOptions, type GetAllParams, type GetByIdsOptions, type GroupResult, type GroupSpec, HOOK_PRIORITY, type HookMode, type IdGenerator, type IdempotencyClaim, type IdempotencyStoreMethods, type IdempotencyStorePluginOptions, type ImmutableClaimView, type ImmutableStatesPluginOptions, type InferDocument, type InferRawDoc, JOIN_ERROR_CODES, type KeysOfType, type KeysetPaginationOptions, type KeysetSourceOptions, type LeaseMethods, type LeasePluginOptions, type Logger, type LoggerConfig, LookupBuilder, type LookupOptions, type LookupPopulateOptions, type LookupPopulateResult, MONGOKIT_CAPABILITIES, MONGOKIT_PLUGIN_NAMES, MUTATING_OPERATIONS, type Middleware, type MiddlewareContext, type MinMaxResult, type MinimalRepoView, type ModelBlueprint, ModelCollisionError, type ModelSpec, type MongoKitPluginName, type MongoOperationsMethods, type MongoOperatorUpdate, type MongoRepoCapabilities, type MongokitPageExtras, type MultiTenantOptions, type NonNullableFields, OP_REGISTRY, type ObjectId, type ObservabilityOptions, type OffsetPaginationOptions, type OperationDescriptor, type OperationMetric, type OperationOptions, type OperationType, PAGINATION_ERROR_CODES, type PaginationConfig, type PaginationDefaults, PaginationEngine, type ParseDuplicateKeyOptions, type ParsedQuery, type PartialBy, type PerCallQueryOptions, type Plugin, type PluginFunction, type PluginType, type PolicyKey, type PopulateOption, type PopulateSpec, type PrefixedIdOptions, type PrioritizedHook, type ProjectionSpec, type PurgeMode, type PurgeableRepoLike, QUERY_DEFAULTS_ERROR_CODES, type QueryDefaults, QueryParser, type QueryParserOptions, type QuerySchema, READ_OPERATIONS, type ReadConcernLevel, type ReadOptions, type ReadPreferenceType, type ReferencePurgeResult, type ReferenceRelation, type RepoScope, Repository, Repository as default, type RepositoryCacheHandle, type RepositoryCachePluginOptions, type RepositoryContext, type RepositoryEvent, type RepositoryInstance, type RepositoryOperation, type RepositoryOptions, type RepositoryPluginName, type RequiredBy, type ResolvedQueryOptions, type SchemaLike, type SearchMode, type SelectSpec, type SequentialIdOptions, type SessionOptions, type SessionStarter, type SoftDeleteFilterMode, type SoftDeleteMethods, type SoftDeleteOptions, type SoftDeleteRepository, type SortDirection, type SortSpec$1 as SortSpec, type Strict, type SubdocumentMethods, TRANSACTION_ERROR_CODES, type TenantContext, type TenantStore, type TransactionSupport, type TransitionMachine, type UpdateManyResult, type UpdateOptions, type UpdatePatch, type UpdateWithValidationResult, type UserContext, type ValidationChainOptions, type ValidatorDefinition, type ValueType, type VectorSearchOptions, type WithPlugins, type WithTransactionOptions, type WriteConcernSpec, index_d_exports as actions, adminBypass, aggregateHelpersPlugin, appendOnlyPlugin, assertQueryDefaultsConfigured, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, batchTransaction, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, capabilitiesForSupport, cascadePlugin, cascadePurgeReferences, classifyHelloReply, compileFilterToMongo, configureAggregateDefaults, configureLogger, configurePaginationDefaults, configureQueryDefaults, createError, createFieldPreset, createMemoryCache, createOptionsExtractor, createRepository, createTenantContext, customIdPlugin, dateSequentialId, declareCollectionScope, declareTenantContextKey, defineModel, defineModels, elasticSearchPlugin, ensureAuditTrailReady, ensureSequenceAtLeast, fieldFilterPlugin, filterResponseData, forwardScope, getFieldsForUser, getMongooseProjection, getNextSequence, getPaginationDefaults, getQueryDefaults, idVariants, idempotencyStorePlugin, immutableField, immutableStatesPlugin, isCreateManyPartialError, isDuplicateKeyError, isModelRegistered, isTransactionUnsupported, keysetSource, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, operationsByPolicyKey, parseDuplicateKeyError, prefixedId, probeMongoCapabilities, readSequence, repoOptionsFromCtx, requireField, resetCapabilityProbeCache, resetPaginationDefaults, resetQueryDefaults, resolveMongoCapabilities, resolveQueryOptions, resolveTransactionSupport, runTransaction, sequentialId, softDeletePlugin, subdocumentPlugin, supportsTransactions, systemContext, tenantContextKeysOf, timestampPlugin, toPlain, transactionResolutionOf, uniqueField, validationChainPlugin, withTransaction };
|
|
1148
|
+
export { ALL_OPERATIONS, type AggregateCallOptions, type AggregateDefaults, type AggregateHelpersMethods, type AggregateOptions, type AggregatePaginationOptions, AggregationBuilder, type AggregationPlan, type SortOrder as AggregationSortOrder, type SortSpec as AggregationSortSpec, type AllPluginMethods, type AnyDocument, type AnyModel, type AppendOnlyPluginOptions, type AuditConfig, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailMode, type AuditTrailOptions, AuditTrailQuery, type AuditTrailQueryOptions, type BasePaginationOptions, type BatchOperationsMethods, type BulkWriteOptions, type BulkWriteResult, type CacheAdapter, type CacheOperationOptions, type CacheOptions, type CacheableOptions, type CapabilityConnectionLike, type CapabilityProbeOptions, type CapabilityResolution, type CascadeOptions, type CascadePurgeReferencesOptions, type CascadeRelation, type CollationOptions, type CollectionRWLike, type CollectionScopeRule, type CommandKind, type ConvenientTransactionOptions, type CountMemoKey, type CountStrategy, type CreateErrorOptions, type CreateInput, type CreateManyFailure, type CreateManyPartial, type CreateManyPartialError, type CreateOptions, type CreateRepositoryConfig, type CrudSchemasFramework, type CursorPayload, type CursorSecret, type CustomIdOptions, DEFAULT_SEARCH_KEYS_FIELD, type DateSequentialIdOptions, type DecodedCursor, type DeepPartial, type DefineModelOptions, type DeleteResult, type DocField, type ElasticSearchOptions, type EnsureAuditTrailReadyOptions, type EventHandlers, type EventPayload, type EventPhase, type ExistingModelPolicy, type FieldPreset, type FieldType, type FilterQuery, type FindOneAndUpdateOptions, type GetAllParams, type GetByIdsOptions, type GroupResult, type GroupSpec, HOOK_PRIORITY, type HookMode, type IdGenerator, type IdempotencyClaim, type IdempotencyStoreMethods, type IdempotencyStorePluginOptions, type ImmutableClaimView, type ImmutableStatesPluginOptions, type InferDocument, type InferRawDoc, JOIN_ERROR_CODES, type KeysOfType, type KeysetPaginationOptions, type KeysetSourceOptions, type LeaseMethods, type LeasePluginOptions, type Logger, type LoggerConfig, LookupBuilder, type LookupOptions, type LookupPopulateOptions, type LookupPopulateResult, MAX_PREFIX_SEARCH_TERMS, MONGOKIT_CAPABILITIES, MONGOKIT_PLUGIN_NAMES, MUTATING_OPERATIONS, type Middleware, type MiddlewareContext, type MinMaxResult, type MinimalRepoView, type ModelBlueprint, ModelCollisionError, type ModelSpec, type MongoKitPluginName, type MongoOperationsMethods, type MongoOperatorUpdate, type MongoRepoCapabilities, type MongokitPageExtras, type MultiTenantOptions, type NonNullableFields, OP_REGISTRY, type ObjectId, type ObservabilityOptions, type OffsetPaginationOptions, type OperationDescriptor, type OperationMetric, type OperationOptions, type OperationType, PAGINATION_ERROR_CODES, type PaginationConfig, type PaginationDefaults, PaginationEngine, type ParseDuplicateKeyOptions, type ParsedQuery, type PartialBy, type PerCallQueryOptions, type Plugin, type PluginFunction, type PluginType, type PolicyKey, type PopulateOption, type PopulateSpec, type PrefixSearchFilterOptions, type PrefixedIdOptions, type PrioritizedHook, type ProjectionSpec, type PurgeMode, type PurgeableRepoLike, QUERY_DEFAULTS_ERROR_CODES, type QueryDefaults, QueryParser, type QueryParserOptions, type QuerySchema, READ_OPERATIONS, type ReadConcernLevel, type ReadOptions, type ReadPreferenceType, type RebuildSearchKeysOptions, type RebuildSearchKeysResult, type ReferencePurgeResult, type ReferenceRelation, type RepoScope, Repository, Repository as default, type RepositoryCacheHandle, type RepositoryCachePluginOptions, type RepositoryContext, type RepositoryEvent, type RepositoryInstance, type RepositoryOperation, type RepositoryOptions, type RepositoryPluginName, type RepositorySearchMode, type RequiredBy, type ResolvedQueryOptions, SEARCH_KEYS_ERROR_CODES, type SchemaLike, type SearchKeysPluginOptions, type SearchKeysSource, SearchKeysWriteError, type SearchMatchRange, type SearchMode, type SelectSpec, type SequentialIdOptions, type SessionOptions, type SessionStarter, type SoftDeleteFilterMode, type SoftDeleteMethods, type SoftDeleteOptions, type SoftDeleteRepository, type SortDirection, type SortSpec$1 as SortSpec, type Strict, type SubdocumentMethods, TRANSACTION_ERROR_CODES, type TenantContext, type TenantStore, type TransactionSupport, type TransitionMachine, type UpdateManyResult, type UpdateOptions, type UpdatePatch, type UpdateWithValidationResult, type UserContext, type ValidationChainOptions, type ValidatorDefinition, type ValueType, type VectorSearchOptions, type WithPlugins, type WithTransactionOptions, type WriteConcernSpec, index_d_exports as actions, adminBypass, aggregateHelpersPlugin, appendOnlyPlugin, assertQueryDefaultsConfigured, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, batchTransaction, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, buildSearchKeys, cachePlugin, capabilitiesForSupport, cascadePlugin, cascadePurgeReferences, classifyHelloReply, compileFilterToMongo, configureAggregateDefaults, configureLogger, configurePaginationDefaults, configureQueryDefaults, createError, createFieldPreset, createMemoryCache, createOptionsExtractor, createRepository, createTenantContext, customIdPlugin, dateSequentialId, declareCollectionScope, declareTenantContextKey, defineModel, defineModels, elasticSearchPlugin, ensureAuditTrailReady, ensureSequenceAtLeast, fieldFilterPlugin, filterResponseData, findSearchMatches, forwardScope, getFieldsForUser, getMongooseProjection, getNextSequence, getPaginationDefaults, getQueryDefaults, idVariants, idempotencyStorePlugin, immutableField, immutableStatesPlugin, isCreateManyPartialError, isDuplicateKeyError, isModelRegistered, isTransactionUnsupported, keysetSource, leasePlugin, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, normalizeSearchText, observabilityPlugin, operationsByPolicyKey, parseDuplicateKeyError, prefixSearchFilter, prefixedId, probeMongoCapabilities, readSequence, rebuildSearchKeys, repoOptionsFromCtx, requireField, resetCapabilityProbeCache, resetPaginationDefaults, resetQueryDefaults, resolveMongoCapabilities, resolveQueryOptions, resolveTransactionSupport, runTransaction, searchKeysIndex, searchKeysPath, searchKeysPlugin, sequentialId, softDeletePlugin, subdocumentPlugin, supportsTransactions, systemContext, tenantContextKeysOf, timestampPlugin, toPlain, tokenizeSearchText, transactionResolutionOf, uniqueField, validationChainPlugin, withTransaction };
|