@classytic/mongokit 3.5.3 → 3.5.5

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 CHANGED
@@ -192,6 +192,45 @@ const repo = new Repository(UserModel, plugins, {
192
192
  });
193
193
  ```
194
194
 
195
+ ### Search configuration
196
+
197
+ `Repository.getAll({ search })` has three layers, ordered from simplest to most
198
+ flexible. Pick the lowest one that solves your problem.
199
+
200
+ | Layer | When to use | Where it lives |
201
+ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------- |
202
+ | **`searchMode: 'text'`** | You have a MongoDB text index and want `$text`-ranked results. | Repository option (default) |
203
+ | **`searchMode: 'regex'`** | Small/medium collection, no text index, simple substring matching across known fields. Works behind any HTTP framework. | Repository option |
204
+ | **Search-resolver plugin** | External engine (Elasticsearch, Meilisearch, Typesense, pgvector, hybrid BM25+vector). Composes with cache, multi-tenant. | `before:getAll` plugin — see Plugins below |
205
+
206
+ ```javascript
207
+ // Built-in regex mode — no MongoDB text index required
208
+ const repo = new Repository(
209
+ MemoModel,
210
+ [],
211
+ {},
212
+ {
213
+ // 'text' → default, requires text index
214
+ // 'regex' → case-insensitive $or of $regex across searchFields, no index needed
215
+ // 'auto' → text if a text index exists, otherwise regex
216
+ searchMode: "regex",
217
+ searchFields: ["title", "scope", "body"],
218
+ },
219
+ );
220
+
221
+ await repo.getAll({ search: "alpha", filters: { scope: "public" } });
222
+ ```
223
+
224
+ User input is regex-escaped (literal substring match), and any pre-existing
225
+ `$or` filter is preserved by promoting both clauses to `$and`.
226
+
227
+ > **QueryParser ↔ Repository:** `QueryParser` also exposes `searchMode`/`searchFields`
228
+ > for the HTTP boundary (`?search=foo` URL → parsed query). The two layers are
229
+ > independent: configure the parser if you want URL-time consumption (Arc does
230
+ > this), configure the Repository if you want data-layer consumption
231
+ > (Express/Nest/raw handlers), or both for defense in depth — they will not
232
+ > conflict because the parser clears `parsed.search` before it reaches the repo.
233
+
195
234
  ## Plugins
196
235
 
197
236
  ### Using Plugins
@@ -754,6 +793,62 @@ const results = await repo.searchSimilar({ query: [0.1, 0.2, ...], limit: 5 });
754
793
  const vector = await repo.embed('some text');
755
794
  ```
756
795
 
796
+ ### Custom Search Backends (search-resolver plugin contract)
797
+
798
+ The built-in `searchMode: 'text' | 'regex'` is sugar over a more general
799
+ **plugin contract** for any search backend — Elasticsearch, Meilisearch,
800
+ Typesense, Algolia, pgvector, Pinecone, hybrid BM25+vector. The contract
801
+ keeps mongokit core free of backend-specific dependencies, and the same
802
+ plugin works behind any HTTP framework (Arc, Express, Nest, raw handlers)
803
+ without further wiring.
804
+
805
+ **The contract** (4 lines):
806
+
807
+ 1. Hook `before:getAll`.
808
+ 2. If `ctx.search` is set, resolve it against your backend (returns IDs).
809
+ 3. Mutate `ctx.filters` to constrain the Mongo query: `_id: { $in: ids }`.
810
+ 4. Set `ctx.search = undefined` so Repository's text-index check is bypassed.
811
+
812
+ ```javascript
813
+ function meilisearchPlugin({ client, index }) {
814
+ return (repo) => {
815
+ repo.on("before:getAll", async (ctx) => {
816
+ if (!ctx.search) return;
817
+ const hits = await client.index(index).search(ctx.search, {
818
+ limit: ctx.limit ?? 20,
819
+ });
820
+ const ids = hits.hits.map((h) => h.id);
821
+ ctx.filters = { ...(ctx.filters ?? {}), _id: { $in: ids } };
822
+ ctx.search = undefined; // bypass text-index check
823
+ ctx._meiliRanking = ids; // stash for after:getAll re-sort
824
+ });
825
+ repo.on("after:getAll", (ctx) => {
826
+ if (!ctx._meiliRanking) return;
827
+ const order = new Map(ctx._meiliRanking.map((id, i) => [String(id), i]));
828
+ ctx.result.docs.sort(
829
+ (a, b) => order.get(String(a._id)) - order.get(String(b._id)),
830
+ );
831
+ });
832
+ };
833
+ }
834
+
835
+ // Wire at Repository construction — works in Arc, Express, Nest, anywhere
836
+ const repo = new Repository(ProductModel, [
837
+ meilisearchPlugin({ client: meili, index: "products" }),
838
+ cachePlugin({ adapter: createMemoryCache(), ttl: 60 }), // composes cleanly
839
+ ]);
840
+ ```
841
+
842
+ **Why this contract works:**
843
+
844
+ - `before:getAll` runs inside `_buildContext`, _before_ Repository reads `search` or hits the text-index check. A plugin that clears `ctx.search` short-circuits the built-in search path entirely.
845
+ - `Repository.getAll` reads `search` only from the post-hook context (no fallback to original params) — this is the framework-level guarantee that makes `ctx.search = undefined` a real clear, not a silently overridden no-op.
846
+ - The caller's existing filters are preserved by spreading: `ctx.filters = { ...(ctx.filters ?? {}), _id: ... }`. Multi-tenant scoping (which also runs in `before:getAll`), soft-delete filters, and the search plugin compose without ordering concerns — they all mutate `ctx.filters` independently and all survive.
847
+ - Cache plugin composes too: it sees the post-hook context (filters with the resolved ID list), so cache keys are accurate. ES/Meili results get cached just like Mongo results.
848
+ - The same plugin shape works for every backend; only the resolver call changes.
849
+
850
+ The bundled `elasticSearchPlugin` (next section) is the canonical reference implementation, and `tests/repository-search-mode.test.ts` exercises this contract end-to-end with multi-tenant + soft-delete + cache composition.
851
+
757
852
  ### Elasticsearch / OpenSearch Plugin
758
853
 
759
854
  Delegates heavy text and semantic search to an external search engine while fetching full documents from MongoDB. Keeps your OLTP (transactional) MongoDB operations fast by separating search I/O.
@@ -1044,12 +1139,13 @@ Converts HTTP query strings to MongoDB queries with built-in security:
1044
1139
  import { QueryParser } from "@classytic/mongokit";
1045
1140
 
1046
1141
  const parser = new QueryParser({
1142
+ schema: ProductModel.schema, // Schema-aware coercion (recommended)
1047
1143
  maxLimit: 100, // Prevent excessive queries
1048
1144
  maxFilterDepth: 5, // Prevent nested injection
1049
1145
  maxRegexLength: 100, // ReDoS protection
1050
- allowedFilterFields: ['status', 'name', 'email'], // Whitelist filter fields
1051
- allowedSortFields: ['createdAt', 'name'], // Whitelist sort fields
1052
- allowedOperators: ['eq', 'ne', 'in', 'gt', 'lt'], // Whitelist operators
1146
+ allowedFilterFields: ["status", "name", "email"], // Whitelist filter fields
1147
+ allowedSortFields: ["createdAt", "name"], // Whitelist sort fields
1148
+ allowedOperators: ["eq", "ne", "in", "gt", "lt"], // Whitelist operators
1053
1149
  });
1054
1150
 
1055
1151
  // Parse request query
@@ -1061,6 +1157,35 @@ parser.allowedSortFields; // ['createdAt', 'name'] or undefined
1061
1157
  parser.allowedOperators; // ['eq', 'ne', 'in', 'gt', 'lt'] or undefined
1062
1158
  ```
1063
1159
 
1160
+ **Schema-aware value coercion (recommended):** Pass `schema: Model.schema` and
1161
+ the parser coerces filter values exactly to each field's declared type. With
1162
+ schema configured, `?stock=50` against a `Number` field becomes `50` (number),
1163
+ `?name=12345` against a `String` field stays `'12345'`, `?releasedAt=2026-04-07`
1164
+ against a `Date` field becomes a `Date` instance, `?address.zip=01234` against
1165
+ a nested `String` field preserves the leading zero. Direct equality and
1166
+ operator syntax (`stock[gte]=50`) coerce identically — no more "why does
1167
+ operator work but direct equality doesn't" surprises. Unknown fields fall
1168
+ through to a safe heuristic so ad-hoc filters still work.
1169
+
1170
+ For DB-agnostic setups (raw MongoDB, Prisma, computed fields, models you
1171
+ don't own), use `fieldTypes` instead of `schema`. The two can be combined —
1172
+ `fieldTypes` overrides `schema` per path:
1173
+
1174
+ ```typescript
1175
+ const parser = new QueryParser({
1176
+ schema: ProductModel.schema,
1177
+ fieldTypes: {
1178
+ "address.zip": "string", // override a Mongoose path
1179
+ legacyCount: "number", // declare a non-schema field
1180
+ },
1181
+ });
1182
+ ```
1183
+
1184
+ Without either option, the parser falls back to a string-shape heuristic
1185
+ (numeric strings without leading zeros and ≤15 chars become numbers; ObjectId
1186
+ hex stays a string; `'true'`/`'false'` become booleans). The heuristic is
1187
+ safe but inherently lossy — schema-aware mode is strictly better.
1188
+
1064
1189
  **Supported query patterns:**
1065
1190
 
1066
1191
  ```bash
@@ -1078,7 +1203,7 @@ GET /users?after=eyJfaWQiOi...&limit=20 # Cursor-based
1078
1203
  # Sorting
1079
1204
  GET /users?sort=-createdAt,name
1080
1205
 
1081
- # Search (requires text index)
1206
+ # Search (default: text index; or configure searchMode: 'regex' / 'auto')
1082
1207
  GET /users?search=john
1083
1208
 
1084
1209
  # Simple populate
@@ -1172,6 +1297,40 @@ const { crudSchemas } = buildCrudSchemasFromModel(UserModel, {
1172
1297
  // crudSchemas.listQuery - GET query validation
1173
1298
  ```
1174
1299
 
1300
+ #### Soft-required fields (draft-friendly bodies)
1301
+
1302
+ Some resources have fields that must be non-null in the DB but should be
1303
+ **optional in the HTTP body** — draft journal entries, multi-step wizards,
1304
+ or any state-machine resource that's filled out progressively. Marking a
1305
+ path `softRequired: true` keeps the Mongoose `required: true` invariant
1306
+ (null on save is still rejected) while excluding the field from the
1307
+ generated `createBody.required[]` array.
1308
+
1309
+ ```typescript
1310
+ // 1. Per-path flag (when you own the Mongoose schema)
1311
+ const JournalEntrySchema = new Schema({
1312
+ label: { type: String, required: true }, // hard required
1313
+ journalType: {
1314
+ type: String,
1315
+ required: true, // DB invariant — null rejected on save
1316
+ softRequired: true, // HTTP body — optional in create/update
1317
+ enum: ['sale', 'purchase', 'adjustment'],
1318
+ },
1319
+ date: { type: Date, required: true, softRequired: true },
1320
+ });
1321
+
1322
+ // 2. Per-build override (when the Mongoose model lives in an upstream package)
1323
+ const { createBody } = buildCrudSchemasFromModel(UpstreamModel, {
1324
+ softRequiredFields: ['journalType', 'date'],
1325
+ });
1326
+ // createBody.required → ['label'] (journalType + date excluded)
1327
+ // createBody.properties → still includes journalType + date (validated when present)
1328
+ ```
1329
+
1330
+ The DB-level `required: true` is unaffected — `repo.create()` with `null`
1331
+ for a soft-required field still throws a Mongoose ValidationError. The
1332
+ flag only changes what HTTP clients must send.
1333
+
1175
1334
  ### Complete Controller Example
1176
1335
 
1177
1336
  ```typescript
@@ -129,7 +129,7 @@ var PaginationEngine = class {
129
129
  * console.log(result.docs, result.total, result.hasNext);
130
130
  */
131
131
  async paginate(options = {}) {
132
- const { filters = {}, sort = { _id: -1 }, page = 1, limit = this.config.defaultLimit, select, populate = [], lean = true, session, hint, maxTimeMS, countStrategy = "exact", readPreference, collation } = options;
132
+ const { filters = {}, sort, countFilters, page = 1, limit = this.config.defaultLimit, select, populate = [], lean = true, session, hint, maxTimeMS, countStrategy = "exact", readPreference, collation } = options;
133
133
  const sanitizedPage = validatePage(page, this.config);
134
134
  const sanitizedLimit = validateLimit(limit, this.config);
135
135
  const skip = calculateSkip(sanitizedPage, sanitizedLimit);
@@ -137,7 +137,8 @@ var PaginationEngine = class {
137
137
  let query = this.Model.find(filters);
138
138
  if (select) query = query.select(select);
139
139
  if (populate && (Array.isArray(populate) ? populate.length : populate)) query = query.populate(populate);
140
- query = query.sort(sort).skip(skip).limit(fetchLimit).lean(lean);
140
+ if (sort) query = query.sort(sort);
141
+ query = query.skip(skip).limit(fetchLimit).lean(lean);
141
142
  if (collation) query = query.collation(collation);
142
143
  if (session) query = query.session(session);
143
144
  if (hint) query = query.hint(hint);
@@ -149,7 +150,8 @@ var PaginationEngine = class {
149
150
  if ((countStrategy === "estimated" || useEstimated) && !hasFilters) countPromise = this.Model.estimatedDocumentCount();
150
151
  else if (countStrategy === "none") countPromise = Promise.resolve(0);
151
152
  else {
152
- const countQuery = this.Model.countDocuments(filters).session(session ?? null);
153
+ const countTarget = countFilters ?? filters;
154
+ const countQuery = this.Model.countDocuments(countTarget).session(session ?? null);
153
155
  if (hint) countQuery.hint(hint);
154
156
  if (maxTimeMS) countQuery.maxTimeMS(maxTimeMS);
155
157
  if (readPreference) countQuery.read(readPreference);
@@ -1,2 +1,2 @@
1
- import { a as create_d_exports, i as delete_d_exports, n as update_d_exports, o as aggregate_d_exports, r as read_d_exports } from "../index-D3ydSXYC.mjs";
1
+ import { a as create_d_exports, i as delete_d_exports, n as update_d_exports, o as aggregate_d_exports, r as read_d_exports } from "../index-Buj7P006.mjs";
2
2
  export { aggregate_d_exports as aggregate, create_d_exports as create, delete_d_exports as deleteActions, read_d_exports as read, update_d_exports as update };
@@ -1,4 +1,4 @@
1
- import { J as Plugin } from "../types-wCkH8jE3.mjs";
1
+ import { J as Plugin } from "../types-CWUL0yiw.mjs";
2
2
  import { ClientSession, PipelineStage } from "mongoose";
3
3
 
4
4
  //#region src/ai/types.d.ts
@@ -0,0 +1,106 @@
1
+ //#region src/query/primitives/coercion.d.ts
2
+ /**
3
+ * Value coercion primitives
4
+ *
5
+ * Pure functions that convert raw URL-decoded values into typed values
6
+ * suitable for MongoDB queries. Two layers:
7
+ *
8
+ * 1. `coerceHeuristic(value)` — string-shape based, used when no schema
9
+ * is configured. Safe defaults: rejects leading zeros, scientific
10
+ * notation, and strings >15 chars to preserve zip codes, phone codes,
11
+ * and long numeric IDs that exceed JS safe-integer precision.
12
+ *
13
+ * 2. `coerceToType(value, type)` — type-directed, used when a Mongoose
14
+ * schema or `fieldTypes` map declares the field's type. Strictly
15
+ * better than the heuristic when type information is available.
16
+ *
17
+ * QueryParser composes these via `coerceFieldValue(field, value, types)`
18
+ * which prefers the type-directed path and falls back to the heuristic.
19
+ *
20
+ * Field-type extraction from a Mongoose schema is also here so the
21
+ * normalization logic lives next to the coercion that consumes it.
22
+ */
23
+ /** Normalized field type used for schema-aware coercion */
24
+ type FieldType = 'string' | 'number' | 'boolean' | 'date' | 'objectid' | 'mixed';
25
+ /**
26
+ * Minimal structural type for a Mongoose schema (read-only `.paths`).
27
+ * Mongoose stores array element types in two different shadow properties
28
+ * depending on version and declaration style:
29
+ * - `caster.instance` — older / some array forms
30
+ * - `embeddedSchemaType.instance` — Mongoose 8+, both `[Type]` and `[{ type: Type }]`
31
+ * We read both and prefer whichever is populated.
32
+ */
33
+ interface SchemaPathsLike {
34
+ paths: Record<string, {
35
+ instance?: string;
36
+ caster?: {
37
+ instance?: string;
38
+ };
39
+ embeddedSchemaType?: {
40
+ instance?: string;
41
+ };
42
+ }>;
43
+ }
44
+ /**
45
+ * Map a Mongoose `SchemaType.instance` string to our normalized FieldType.
46
+ * For arrays (`type: [Number]` or `type: [{ type: Number }]`), Mongoose
47
+ * stores the element type on `embeddedSchemaType.instance` (Mongoose 8+) or
48
+ * `caster.instance` (older). We probe both — element-level coercion is what
49
+ * `[in]`/`[nin]` operators need against typed arrays.
50
+ *
51
+ * Returns null for paths we cannot classify (embedded documents handled by
52
+ * their own nested paths).
53
+ */
54
+ declare function normalizeMongooseType(schemaType: {
55
+ instance?: string;
56
+ caster?: {
57
+ instance?: string;
58
+ };
59
+ embeddedSchemaType?: {
60
+ instance?: string;
61
+ };
62
+ }): FieldType | null;
63
+ /**
64
+ * Build a normalized field-type map from a Mongoose schema and an optional
65
+ * override map. The override wins on a per-path basis. Paths with unmappable
66
+ * types are silently skipped — `coerceFieldValue` will fall back to the
67
+ * heuristic for them.
68
+ */
69
+ declare function buildFieldTypeMap(schema?: SchemaPathsLike | null, overrides?: Record<string, FieldType>): Map<string, FieldType>;
70
+ /**
71
+ * String-shape heuristic — used when no field type is known.
72
+ *
73
+ * Rules (deliberately conservative — silent miscoercion is worse than
74
+ * leaving a string):
75
+ * - `null`/`undefined` pass through
76
+ * - Already-typed primitives (number, boolean) pass through
77
+ * - Arrays recurse element-wise
78
+ * - Objects pass through (filter operator objects, etc.)
79
+ * - `'true'`/`'false'` → boolean
80
+ * - 24-char hex → kept as string (ObjectId)
81
+ * - Strings 1–15 chars matching `^-?(0|[1-9]\d*)(\.\d+)?$` → number
82
+ * (rejects leading zeros, scientific notation, hex/octal, NaN, Infinity)
83
+ * - Everything else stays a string
84
+ */
85
+ declare function coerceHeuristic(value: unknown): unknown;
86
+ /**
87
+ * Type-directed coercion — used when a field's declared type is known.
88
+ *
89
+ * Returns the original value unchanged when coercion is impossible
90
+ * (invalid date, non-numeric string for a Number field) — better to surface
91
+ * a no-match than to silently produce `Invalid Date` or `NaN` which MongoDB
92
+ * treats as legitimate query values.
93
+ */
94
+ declare function coerceToType(value: unknown, type: FieldType): unknown;
95
+ /**
96
+ * Field-aware coercion. Looks up `field` in `types` and uses
97
+ * `coerceToType` when found, otherwise falls back to `coerceHeuristic`.
98
+ *
99
+ * This is the single choke point QueryParser uses for typed coercion of
100
+ * direct equality, `[eq]`, `[in]`, `[nin]`, `[gt]`/`[gte]`/`[lt]`/`[lte]`
101
+ * (the last only when a type is declared — see QueryParser for the
102
+ * preserves-pre-3.5.5-behavior fallback).
103
+ */
104
+ declare function coerceFieldValue(field: string, value: unknown, types: Map<string, FieldType>): unknown;
105
+ //#endregion
106
+ export { coerceHeuristic as a, coerceFieldValue as i, SchemaPathsLike as n, coerceToType as o, buildFieldTypeMap as r, normalizeMongooseType as s, FieldType as t };
@@ -1,4 +1,4 @@
1
- import { $ as ReadPreferenceType, At as LookupOptions, D as GroupResult, V as ObjectId, W as OperationOptions, Z as PopulateSpec, _t as UpdateOptions, a as AnyDocument, b as DeleteResult, g as CreateOptions, gt as UpdateManyResult, pt as SortSpec, st as SelectSpec, vt as UpdateWithValidationResult, z as MinMaxResult } from "./types-wCkH8jE3.mjs";
1
+ import { $ as ReadPreferenceType, At as LookupOptions, D as GroupResult, V as ObjectId, W as OperationOptions, Z as PopulateSpec, _t as UpdateOptions, a as AnyDocument, b as DeleteResult, g as CreateOptions, gt as UpdateManyResult, pt as SortSpec, st as SelectSpec, vt as UpdateWithValidationResult, z as MinMaxResult } from "./types-CWUL0yiw.mjs";
2
2
  import { ClientSession, Model, PipelineStage } from "mongoose";
3
3
 
4
4
  //#region src/actions/aggregate.d.ts
package/dist/index.d.mts CHANGED
@@ -1,8 +1,10 @@
1
- import { $ as ReadPreferenceType, A as InferDocument, At as LookupOptions, B as NonNullableFields, C as EventPayload, Ct as WithPlugins, D as GroupResult, Dt as IRequestContext, E as FieldRules, Et as IControllerResponse, F as KeysetPaginationResult, G as PaginationConfig, H as OffsetPaginationOptions, I as Logger, J as Plugin, K as PaginationResult, L as LookupPopulateOptions, M as JsonSchema, N as KeysOfType, O as HookMode, Ot as IResponseFormatter, P as KeysetPaginationOptions, Q as ReadOptions, R as LookupPopulateResult, S as EventHandlers, St as ValidatorDefinition, T as FieldPreset, Tt as IController, U as OffsetPaginationResult, V as ObjectId, W as OperationOptions, X as PluginType, Y as PluginFunction, Z as PopulateSpec, _ as CrudSchemas, _t as UpdateOptions, a as AnyDocument, at as RequiredBy, b as DeleteResult, bt as ValidationChainOptions, c as CacheOperationOptions, ct as SessionOptions, d as CacheableOptions, dt as SoftDeleteRepository, et as RepositoryContext, f as CascadeOptions, ft as SortDirection, g as CreateOptions, gt as UpdateManyResult, h as CreateInput, ht as UpdateInput, i as AllPluginMethods, it as RepositoryOptions, j as InferRawDoc, k as HttpError, kt as LookupBuilder, l as CacheOptions, lt as SoftDeleteFilterMode, m as CollationOptions, mt as Strict, n as AggregatePaginationOptions, nt as RepositoryInstance, o as AnyModel, ot as SchemaBuilderOptions, p as CascadeRelation, pt as SortSpec, q as PartialBy, r as AggregatePaginationResult, rt as RepositoryOperation, s as CacheAdapter, st as SelectSpec, t as AggregateOptions, tt as RepositoryEvent, u as CacheStats, ut as SoftDeleteOptions, v as DecodedCursor, vt as UpdateWithValidationResult, w as EventPhase, wt as WithTransactionOptions, x as DocField, xt as ValidationResult, y as DeepPartial, yt as UserContext, z as MinMaxResult } from "./types-wCkH8jE3.mjs";
2
- import { t as index_d_exports } from "./index-D3ydSXYC.mjs";
1
+ import { $ as ReadPreferenceType, A as InferDocument, At as LookupOptions, B as NonNullableFields, C as EventPayload, Ct as WithPlugins, D as GroupResult, Dt as IRequestContext, E as FieldRules, Et as IControllerResponse, F as KeysetPaginationResult, G as PaginationConfig, H as OffsetPaginationOptions, I as Logger, J as Plugin, K as PaginationResult, L as LookupPopulateOptions, M as JsonSchema, N as KeysOfType, O as HookMode, Ot as IResponseFormatter, P as KeysetPaginationOptions, Q as ReadOptions, R as LookupPopulateResult, S as EventHandlers, St as ValidatorDefinition, T as FieldPreset, Tt as IController, U as OffsetPaginationResult, V as ObjectId, W as OperationOptions, X as PluginType, Y as PluginFunction, Z as PopulateSpec, _ as CrudSchemas, _t as UpdateOptions, a as AnyDocument, at as RequiredBy, b as DeleteResult, bt as ValidationChainOptions, c as CacheOperationOptions, ct as SessionOptions, d as CacheableOptions, dt as SoftDeleteRepository, et as RepositoryContext, f as CascadeOptions, ft as SortDirection, g as CreateOptions, gt as UpdateManyResult, h as CreateInput, ht as UpdateInput, i as AllPluginMethods, it as RepositoryOptions, j as InferRawDoc, k as HttpError, kt as LookupBuilder, l as CacheOptions, lt as SoftDeleteFilterMode, m as CollationOptions, mt as Strict, n as AggregatePaginationOptions, nt as RepositoryInstance, o as AnyModel, ot as SchemaBuilderOptions, p as CascadeRelation, pt as SortSpec, q as PartialBy, r as AggregatePaginationResult, rt as RepositoryOperation, s as CacheAdapter, st as SelectSpec, t as AggregateOptions, tt as RepositoryEvent, u as CacheStats, ut as SoftDeleteOptions, v as DecodedCursor, vt as UpdateWithValidationResult, w as EventPhase, wt as WithTransactionOptions, x as DocField, xt as ValidationResult, y as DeepPartial, yt as UserContext, z as MinMaxResult } from "./types-CWUL0yiw.mjs";
2
+ import { t as index_d_exports } from "./index-Buj7P006.mjs";
3
3
  import { PaginationEngine } from "./pagination/PaginationEngine.mjs";
4
- import { A as dateSequentialId, B as AuditEntry, C as elasticSearchPlugin, D as PrefixedIdOptions, E as IdGenerator, F as CacheMethods, G as AuditTrailOptions, H as AuditQueryOptions, I as cachePlugin, J as auditLogPlugin, K as AuditTrailQuery, L as BatchOperationsMethods, M as prefixedId, N as sequentialId, O as SequentialIdOptions, P as cascadePlugin, R as BulkWriteResult, S as ElasticSearchOptions, T as DateSequentialIdOptions, U as AuditQueryResult, V as AuditOperation, W as AuditTrailMethods, X as aggregateHelpersPlugin, Y as AggregateHelpersMethods, _ as MongoOperationsMethods, a as uniqueField, b as methodRegistryPlugin, c as SubdocumentMethods, d as softDeletePlugin, f as ObservabilityOptions, g as multiTenantPlugin, h as MultiTenantOptions, i as requireField, j as getNextSequence, k as customIdPlugin, l as subdocumentPlugin, m as observabilityPlugin, n as blockIf, o as validationChainPlugin, p as OperationMetric, q as auditTrailPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as SoftDeleteMethods, v as mongoOperationsPlugin, w as CustomIdOptions, x as fieldFilterPlugin, z as batchOperationsPlugin } from "./validation-chain.plugin-CGE_mKQr.mjs";
5
- import { a as isFieldUpdateAllowed, c as configureLogger, d as getFieldsForUser, f as getMongooseProjection, i as getSystemManagedFields, l as createFieldPreset, m as parseDuplicateKeyError, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, p as createError, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel, u as filterResponseData } from "./mongooseToJsonSchema-BivI9LWt.mjs";
4
+ import { A as dateSequentialId, B as AuditEntry, C as elasticSearchPlugin, D as PrefixedIdOptions, E as IdGenerator, F as CacheMethods, G as AuditTrailOptions, H as AuditQueryOptions, I as cachePlugin, J as auditLogPlugin, K as AuditTrailQuery, L as BatchOperationsMethods, M as prefixedId, N as sequentialId, O as SequentialIdOptions, P as cascadePlugin, R as BulkWriteResult, S as ElasticSearchOptions, T as DateSequentialIdOptions, U as AuditQueryResult, V as AuditOperation, W as AuditTrailMethods, X as aggregateHelpersPlugin, Y as AggregateHelpersMethods, _ as MongoOperationsMethods, a as uniqueField, b as methodRegistryPlugin, c as SubdocumentMethods, d as softDeletePlugin, f as ObservabilityOptions, g as multiTenantPlugin, h as MultiTenantOptions, i as requireField, j as getNextSequence, k as customIdPlugin, l as subdocumentPlugin, m as observabilityPlugin, n as blockIf, o as validationChainPlugin, p as OperationMetric, q as auditTrailPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as SoftDeleteMethods, v as mongoOperationsPlugin, w as CustomIdOptions, x as fieldFilterPlugin, z as batchOperationsPlugin } from "./validation-chain.plugin-Dn41aAbK.mjs";
5
+ import { n as SchemaPathsLike, t as FieldType$1 } from "./coercion-01tEHsKF.mjs";
6
+ import { n as SchemaIndexes } from "./indexes-CiIT-Zli.mjs";
7
+ import { a as isFieldUpdateAllowed, c as configureLogger, d as getFieldsForUser, f as getMongooseProjection, i as getSystemManagedFields, l as createFieldPreset, m as parseDuplicateKeyError, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, p as createError, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel, u as filterResponseData } from "./mongooseToJsonSchema-BlDfbg_0.mjs";
6
8
  import * as _$mongoose from "mongoose";
7
9
  import { ClientSession, Expression, Model, PipelineStage, PopulateOptions } from "mongoose";
8
10
 
@@ -487,6 +489,22 @@ interface ParsedQuery {
487
489
  }
488
490
  /** Search mode for query parser */
489
491
  type SearchMode = 'text' | 'regex';
492
+ /**
493
+ * Normalized field type used for schema-aware value coercion. Re-exported
494
+ * from `primitives/coercion` so the public QueryParser API stays unchanged
495
+ * while the actual logic lives in a single, unit-tested primitive module.
496
+ */
497
+ type FieldType = FieldType$1;
498
+ /**
499
+ * Minimal structural type for a Mongoose schema. We only read `.paths` and
500
+ * (optionally) `.indexes()`, so the parser stays decoupled from a specific
501
+ * Mongoose major version and is easy to mock in tests. Re-exported from the
502
+ * primitives module — the canonical definition lives there.
503
+ */
504
+ interface SchemaLike extends SchemaPathsLike {
505
+ /** Optional — when present, schema indexes are introspected for geo/text fields */
506
+ indexes?: () => Array<[Record<string, unknown>, Record<string, unknown>?]>;
507
+ }
490
508
  interface QueryParserOptions {
491
509
  /** Maximum allowed regex pattern length (default: 500) */
492
510
  maxRegexLength?: number;
@@ -534,6 +552,25 @@ interface QueryParserOptions {
534
552
  * @example ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in']
535
553
  */
536
554
  allowedOperators?: string[];
555
+ /**
556
+ * Mongoose schema (or schema-like object exposing `.paths`) used for
557
+ * authoritative, schema-aware value coercion. When provided, filter values
558
+ * are coerced to the declared field type instead of guessed from the
559
+ * string shape — `?stock=50` becomes a number against a `Number` field but
560
+ * `?name=12345` stays a string against a `String` field. Nested paths like
561
+ * `address.zip` are looked up via dot-notation, matching Mongoose's
562
+ * `schema.paths` reflection. Unknown fields fall through to the heuristic.
563
+ */
564
+ schema?: SchemaLike;
565
+ /**
566
+ * Plain field-type map used for authoritative coercion when no Mongoose
567
+ * schema is available (raw MongoDB, Prisma, computed fields, upstream
568
+ * models you don't own). Takes precedence over `schema` for paths declared
569
+ * in both — useful for runtime overrides. Use dot-notation for nested
570
+ * paths (`'address.zip': 'string'`).
571
+ * @example { stock: 'number', active: 'boolean', releasedAt: 'date' }
572
+ */
573
+ fieldTypes?: Record<string, FieldType>;
537
574
  }
538
575
  /**
539
576
  * Modern Query Parser
@@ -543,6 +580,21 @@ declare class QueryParser {
543
580
  private readonly options;
544
581
  private readonly operators;
545
582
  private readonly dangerousOperators;
583
+ /**
584
+ * Normalized dot-notation field-type map. Built once at construction from
585
+ * `options.schema` (Mongoose) and `options.fieldTypes` (override map), with
586
+ * `fieldTypes` taking precedence. An empty map means schema-aware coercion
587
+ * is disabled and the heuristic in `_convertValue` runs unchanged — this
588
+ * preserves backwards compatibility for callers that don't opt in.
589
+ */
590
+ private readonly _fieldTypes;
591
+ /**
592
+ * Structured schema-index info (geo / text / other), built once from
593
+ * `options.schema?.indexes()`. Used by the operator router to validate
594
+ * geo operators against actual geo-indexed fields, and exposed publicly
595
+ * via `parser.schemaIndexes` for downstream tools (Arc MCP, query planners).
596
+ */
597
+ private readonly _schemaIndexes;
546
598
  /**
547
599
  * Regex patterns that can cause catastrophic backtracking (ReDoS attacks)
548
600
  * Detects:
@@ -554,6 +606,16 @@ declare class QueryParser {
554
606
  */
555
607
  private readonly dangerousRegexPatterns;
556
608
  constructor(options?: QueryParserOptions);
609
+ /**
610
+ * Structured view of the configured schema's indexes — geo fields, text
611
+ * fields, and other compound indexes. Empty arrays when no schema was
612
+ * provided. Stable across the parser's lifetime; mutating the returned
613
+ * object does not affect parser behavior.
614
+ *
615
+ * Useful for downstream tooling (Arc MCP, query planners, doc generators)
616
+ * that needs to know which fields support which query types.
617
+ */
618
+ get schemaIndexes(): SchemaIndexes;
557
619
  /**
558
620
  * Get the configured allowed filter fields.
559
621
  * Returns `undefined` if no whitelist is set (all fields allowed).
@@ -738,7 +800,6 @@ declare class QueryParser {
738
800
  * // ]
739
801
  */
740
802
  private _buildRegexSearch;
741
- private _convertValue;
742
803
  private _parseOr;
743
804
  private _enhanceWithBetween;
744
805
  private _pluralize;
@@ -774,6 +835,8 @@ declare class Repository<TDoc = unknown> {
774
835
  readonly _pagination: PaginationEngine<TDoc>;
775
836
  private readonly _hookMode;
776
837
  readonly idField: string;
838
+ readonly searchMode: 'text' | 'regex' | 'auto';
839
+ readonly searchFields: string[] | undefined;
777
840
  [key: string]: unknown;
778
841
  private _hasTextIndex;
779
842
  constructor(Model: Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions);
@@ -1075,4 +1138,4 @@ declare class Repository<TDoc = unknown> {
1075
1138
  */
1076
1139
  declare function createRepository<TDoc>(Model: _$mongoose.Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions): Repository<TDoc>;
1077
1140
  //#endregion
1078
- export { type AggregateHelpersMethods, type AggregateOptions, type AggregatePaginationOptions, type AggregatePaginationResult, AggregationBuilder, type AllPluginMethods, type AnyDocument, type AnyModel, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BatchOperationsMethods, type BulkWriteResult, type CacheAdapter, type CacheMethods, type CacheOperationOptions, type CacheOptions, type CacheStats, type CacheableOptions, type CascadeOptions, type CascadeRelation, type CollationOptions, type CreateInput, type CreateOptions, type CrudSchemas, type CustomIdOptions, type DateSequentialIdOptions, type DecodedCursor, type DeepPartial, type DeleteResult, type DocField, type ElasticSearchOptions, type EventHandlers, type EventPayload, type EventPhase, type FieldPreset, type FieldRules, type FilterQuery, type GroupResult, HOOK_PRIORITY, type HookMode, type HttpError, type IController, type IControllerResponse, type IRequestContext, type IResponseFormatter, type IdGenerator, type InferDocument, type InferRawDoc, type JsonSchema, type KeysOfType, type KeysetPaginationOptions, type KeysetPaginationResult, type Logger, LookupBuilder, type LookupOptions, type LookupPopulateOptions, type LookupPopulateResult, type MinMaxResult, type MongoOperationsMethods, type MultiTenantOptions, type NonNullableFields, type ObjectId, type ObservabilityOptions, type OffsetPaginationOptions, type OffsetPaginationResult, type OperationMetric, type OperationOptions, type PaginationConfig, PaginationEngine, type PaginationResult, type ParsedQuery, type PartialBy, type Plugin, type PluginFunction, type PluginType, type PopulateOption, type PopulateSpec, type PrefixedIdOptions, QueryParser, type QueryParserOptions, type ReadOptions, type ReadPreferenceType, Repository, Repository as default, type RepositoryContext, type RepositoryEvent, type RepositoryInstance, type RepositoryOperation, type RepositoryOptions, type RequiredBy, type SchemaBuilderOptions, type SearchMode, type SelectSpec, type SequentialIdOptions, type SessionOptions, type SoftDeleteFilterMode, type SoftDeleteMethods, type SoftDeleteOptions, type SoftDeleteRepository, type SortDirection, type SortSpec, type Strict, type SubdocumentMethods, type UpdateInput, type UpdateManyResult, type UpdateOptions, type UpdateWithValidationResult, type UserContext, type ValidationChainOptions, type ValidationResult, type ValidatorDefinition, type WithPlugins, type WithTransactionOptions, index_d_exports as actions, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createRepository, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getNextSequence, getSystemManagedFields, immutableField, isFieldUpdateAllowed, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, parseDuplicateKeyError, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validateUpdateBody, validationChainPlugin };
1141
+ export { type AggregateHelpersMethods, type AggregateOptions, type AggregatePaginationOptions, type AggregatePaginationResult, AggregationBuilder, type AllPluginMethods, type AnyDocument, type AnyModel, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BatchOperationsMethods, type BulkWriteResult, type CacheAdapter, type CacheMethods, type CacheOperationOptions, type CacheOptions, type CacheStats, type CacheableOptions, type CascadeOptions, type CascadeRelation, type CollationOptions, type CreateInput, type CreateOptions, type CrudSchemas, type CustomIdOptions, type DateSequentialIdOptions, type DecodedCursor, type DeepPartial, type DeleteResult, type DocField, type ElasticSearchOptions, type EventHandlers, type EventPayload, type EventPhase, type FieldPreset, type FieldRules, type FieldType, type FilterQuery, type GroupResult, HOOK_PRIORITY, type HookMode, type HttpError, type IController, type IControllerResponse, type IRequestContext, type IResponseFormatter, type IdGenerator, type InferDocument, type InferRawDoc, type JsonSchema, type KeysOfType, type KeysetPaginationOptions, type KeysetPaginationResult, type Logger, LookupBuilder, type LookupOptions, type LookupPopulateOptions, type LookupPopulateResult, type MinMaxResult, type MongoOperationsMethods, type MultiTenantOptions, type NonNullableFields, type ObjectId, type ObservabilityOptions, type OffsetPaginationOptions, type OffsetPaginationResult, type OperationMetric, type OperationOptions, type PaginationConfig, PaginationEngine, type PaginationResult, type ParsedQuery, type PartialBy, type Plugin, type PluginFunction, type PluginType, type PopulateOption, type PopulateSpec, type PrefixedIdOptions, QueryParser, type QueryParserOptions, type ReadOptions, type ReadPreferenceType, Repository, Repository as default, type RepositoryContext, type RepositoryEvent, type RepositoryInstance, type RepositoryOperation, type RepositoryOptions, type RequiredBy, type SchemaBuilderOptions, type SchemaLike, type SearchMode, type SelectSpec, type SequentialIdOptions, type SessionOptions, type SoftDeleteFilterMode, type SoftDeleteMethods, type SoftDeleteOptions, type SoftDeleteRepository, type SortDirection, type SortSpec, type Strict, type SubdocumentMethods, type UpdateInput, type UpdateManyResult, type UpdateOptions, type UpdateWithValidationResult, type UserContext, type ValidationChainOptions, type ValidationResult, type ValidatorDefinition, type WithPlugins, type WithTransactionOptions, index_d_exports as actions, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createRepository, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getNextSequence, getSystemManagedFields, immutableField, isFieldUpdateAllowed, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, parseDuplicateKeyError, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validateUpdateBody, validationChainPlugin };