@classytic/mongokit 3.5.5 → 3.5.6

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
@@ -15,9 +15,13 @@
15
15
  - **17 built-in plugins** - Caching, soft delete, audit trail, validation, multi-tenant, custom IDs, observability, Elasticsearch, and more
16
16
  - **Distributed cache safety** - List cache versions stored in the adapter (Redis) for multi-pod correctness
17
17
  - **Search governance** - Text index guard (throws `400` if no index), allowlisted sort/filter fields, ReDoS protection
18
+ - **Geo queries** - `[near]`, `[nearSphere]`, `[withinRadius]`, `[geoWithin]` URL operators with auto sort/count handling
19
+ - **Schema-aware coercion** - Pass `schema: Model.schema` to QueryParser for deterministic type-safe filter values
20
+ - **String/UUID/Number `_id` support** - Works out of the box with any `_id` type, not just ObjectId
21
+ - **Query primitives** - Composable pure functions (`coercion`, `geo`, `indexes`) importable standalone for custom parsers
18
22
  - **Vector search** - MongoDB Atlas `$vectorSearch` with auto-embedding and multimodal support
19
23
  - **TypeScript first** - Full type safety with discriminated unions, typed events, and field autocomplete
20
- - **Extensively tested** — battle-tested and production-ready
24
+ - **Extensively tested** — 1500+ tests, Mongoose 9.4.1 aligned, battle-tested and production-ready
21
25
 
22
26
  ## Installation
23
27
 
@@ -25,7 +29,7 @@
25
29
  npm install @classytic/mongokit mongoose
26
30
  ```
27
31
 
28
- > Requires Mongoose `^9.0.0` | Node.js `>=22`
32
+ > Requires Mongoose `>=9.4.1` | Node.js `>=22`
29
33
 
30
34
  ## Quick Start
31
35
 
@@ -231,6 +235,89 @@ User input is regex-escaped (literal substring match), and any pre-existing
231
235
  > (Express/Nest/raw handlers), or both for defense in depth — they will not
232
236
  > conflict because the parser clears `parsed.search` before it reaches the repo.
233
237
 
238
+ ### Geo queries
239
+
240
+ QueryParser supports 4 geo operators that compile to canonical GeoJSON-shaped
241
+ MongoDB queries. All validate coordinate ranges and reject malformed input by
242
+ dropping the filter (not silently widening).
243
+
244
+ ```javascript
245
+ const parser = new QueryParser({ schema: PlaceModel.schema });
246
+
247
+ // Distance-sorted (implicit distance ordering, no explicit sort allowed)
248
+ const near = parser.parse({ "location[near]": "-73.98,40.76,5000" });
249
+ // → { location: { $near: { $geometry: { type: 'Point', coordinates: [-73.98, 40.76] }, $maxDistance: 5000 } } }
250
+
251
+ // Count-compatible radius — use this for paginated getAll (supports sort + count)
252
+ const radius = parser.parse({ "location[withinRadius]": "-73.98,40.76,5000" });
253
+ // → { location: { $geoWithin: { $centerSphere: [[-73.98, 40.76], 0.000783...] } } }
254
+
255
+ // Bounding box (works without a 2dsphere index)
256
+ const box = parser.parse({ "location[geoWithin]": "-74,40.7,-73.9,40.8" });
257
+ // → { location: { $geoWithin: { $box: [[-74, 40.7], [-73.9, 40.8]] } } }
258
+ ```
259
+
260
+ **Repository integration:** `Repository.getAll` auto-detects `$near` /
261
+ `$nearSphere` in filters and handles the MongoDB constraints transparently:
262
+
263
+ - Skips the default `-createdAt` sort (MongoDB forbids explicit sort with `$near`)
264
+ - Warns and drops any caller-supplied sort that conflicts
265
+ - Rewrites the count query via `$geoWithin: $centerSphere` for accurate `total`
266
+ - Falls back to `countStrategy: 'none'` for unbounded `$near` (no `$maxDistance`)
267
+ - All plugins (multi-tenant, soft-delete, cache, populate) compose unchanged
268
+
269
+ Use `[near]` when you need distance-sorted results. Use `[withinRadius]` when
270
+ you need paginated lists with `total` and custom sort.
271
+
272
+ ### String / UUID / Number `_id`
273
+
274
+ Schemas that declare `_id` as `String`, `Number`, or `UUID` instead of the
275
+ default `ObjectId` work out of the box — no configuration needed:
276
+
277
+ ```typescript
278
+ const SessionSchema = new Schema({
279
+ _id: { type: String, default: () => randomUUID() },
280
+ userId: { type: String, required: true },
281
+ });
282
+
283
+ const repo = new Repository(SessionModel);
284
+ await repo.create({ userId: "user-1" }); // _id auto-generated as UUID
285
+ await repo.getById("550e8400-e29b-..."); // works — no ObjectId validation
286
+ await repo.update("550e8400-e29b-...", { ... }); // works
287
+ ```
288
+
289
+ The `id-resolution` utility detects the `_id` type from the schema and
290
+ validates accordingly. Schema generation also includes String `_id` as an
291
+ optional field in `createBody` so Fastify/AJV validation accepts user-supplied
292
+ IDs. Available standalone:
293
+
294
+ ```typescript
295
+ import { getSchemaIdType, isValidIdForType } from "@classytic/mongokit/utils";
296
+
297
+ const idType = getSchemaIdType(SessionModel.schema); // 'string'
298
+ isValidIdForType("550e8400-...", idType); // true
299
+ isValidIdForType("not-valid", "objectid"); // false
300
+ ```
301
+
302
+ ### Query primitives
303
+
304
+ Pure, composable functions that power QueryParser internals — importable
305
+ standalone for custom parsers, Arc controllers, migration scripts, and AI
306
+ agents that build queries programmatically:
307
+
308
+ ```typescript
309
+ // Geo
310
+ import { parseGeoFilter, hasNearOperator } from "@classytic/mongokit/query/primitives/geo";
311
+ // Coercion
312
+ import { coerceFieldValue, buildFieldTypeMap } from "@classytic/mongokit/query/primitives/coercion";
313
+ // Schema indexes
314
+ import { extractSchemaIndexes } from "@classytic/mongokit/query/primitives/indexes";
315
+ ```
316
+
317
+ Each primitive is independently unit-tested, tree-shake friendly (no barrel),
318
+ and decoupled from Mongoose — they accept structural types, not concrete
319
+ Mongoose instances.
320
+
234
321
  ## Plugins
235
322
 
236
323
  ### Using Plugins
@@ -1206,6 +1293,12 @@ GET /users?sort=-createdAt,name
1206
1293
  # Search (default: text index; or configure searchMode: 'regex' / 'auto')
1207
1294
  GET /users?search=john
1208
1295
 
1296
+ # Geo queries (requires 2dsphere index for near/nearSphere)
1297
+ GET /places?location[near]=-73.98,40.76,5000 # Within 5km, distance-sorted
1298
+ GET /places?location[nearSphere]=-73.98,40.76,5000 # Spherical variant
1299
+ GET /places?location[withinRadius]=-73.98,40.76,5000 # Count-compatible radius (use for paginated lists)
1300
+ GET /places?location[geoWithin]=-74,40.7,-73.9,40.8 # Bounding box (no index required)
1301
+
1209
1302
  # Simple populate
1210
1303
  GET /posts?populate=author,category
1211
1304
 
@@ -1601,14 +1694,15 @@ await repo.getById('my-chat-uuid', { idField: 'chatId' }); // by chatId
1601
1694
 
1602
1695
  All plugins respect `idField`: soft-delete, cascade, audit-log, audit-trail, validation-chain, elastic, cache, observability.
1603
1696
 
1604
- ## No Breaking Changes
1697
+ ## Mongoose Compatibility
1605
1698
 
1606
- Extending Repository works exactly the same with Mongoose 8 and 9. The package:
1699
+ Requires Mongoose `>=9.4.1`. Tested and aligned with Mongoose 9.4.1. The package:
1607
1700
 
1608
1701
  - Uses its own event system (not Mongoose middleware)
1609
1702
  - Defines its own `FilterQuery` type (unaffected by Mongoose 9 rename)
1610
1703
  - Properly gates update pipelines (safe for Mongoose 9's stricter defaults)
1611
- - Full test suite passes on Mongoose 9
1704
+ - Reads `embeddedSchemaType.instance` for array element types (Mongoose 9.x)
1705
+ - 1500+ tests pass on Mongoose 9.4.1
1612
1706
 
1613
1707
  ## License
1614
1708
 
@@ -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-Buj7P006.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-DmVbUzDL.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-CWUL0yiw.mjs";
1
+ import { J as Plugin } from "../types-BwO4u3JN.mjs";
2
2
  import { ClientSession, PipelineStage } from "mongoose";
3
3
 
4
4
  //#region src/ai/types.d.ts
@@ -0,0 +1,81 @@
1
+ import mongoose from "mongoose";
2
+ //#region src/utils/id-resolution.ts
3
+ /**
4
+ * ID Resolution Primitive
5
+ *
6
+ * Pure functions for detecting a Mongoose schema's _id type and validating
7
+ * id values against it. Used by Repository.getById to decide whether to
8
+ * run ObjectId validation (for ObjectId schemas) or skip it (for String /
9
+ * Number / UUID schemas).
10
+ *
11
+ * Also usable standalone — Arc controllers, custom repositories, migration
12
+ * scripts, or test harnesses can import these directly to make the same
13
+ * decision without depending on Repository.
14
+ *
15
+ * Design: no `this`, no Model, no DB calls. Only reads `schema.paths._id.instance`.
16
+ */
17
+ /**
18
+ * Check if a Mongoose SchemaType `instance` string is an ObjectId variant.
19
+ * Mongoose 9.x uses `'ObjectId'`, older versions use `'ObjectID'`. This
20
+ * is the single source of truth — use it instead of inline
21
+ * `instance === 'ObjectId' || instance === 'ObjectID'` checks.
22
+ */
23
+ function isObjectIdInstance(instance) {
24
+ return instance === "ObjectId" || instance === "ObjectID";
25
+ }
26
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
27
+ /**
28
+ * Detect the _id path type from a Mongoose schema.
29
+ *
30
+ * Returns 'objectid' as the safe default when:
31
+ * - No schema is provided (mock models, test harnesses)
32
+ * - The schema has no `paths._id` (auto-generated ObjectId)
33
+ * - The instance string is unrecognized
34
+ *
35
+ * This safe-default means existing callers that don't set _id explicitly
36
+ * get the same ObjectId validation they always had — the behavioral change
37
+ * is strictly opt-in via schema declaration.
38
+ */
39
+ function getSchemaIdType(schema) {
40
+ const instance = schema?.paths?._id?.instance;
41
+ if (!instance) return "objectid";
42
+ if (isObjectIdInstance(instance)) return "objectid";
43
+ switch (instance) {
44
+ case "String": return "string";
45
+ case "Number": return "number";
46
+ case "UUID": return "uuid";
47
+ default: return "unknown";
48
+ }
49
+ }
50
+ /**
51
+ * Validate an id value against a known _id type — WITHOUT hitting the DB.
52
+ *
53
+ * Returns `true` when the value is structurally valid for the given type.
54
+ * Returns `false` when it's definitely invalid (Repository should return
55
+ * 404 / null immediately rather than wasting a round-trip).
56
+ *
57
+ * For `'string'` and `'unknown'` types, validation is intentionally
58
+ * permissive: any truthy, non-empty value is accepted. The DB is the
59
+ * authority for whether the document exists — we only reject values that
60
+ * are structurally impossible (null, undefined, empty string).
61
+ */
62
+ function isValidIdForType(id, idType) {
63
+ if (id === null || id === void 0) return false;
64
+ switch (idType) {
65
+ case "objectid": {
66
+ if (typeof id === "object") return mongoose.Types.ObjectId.isValid(id);
67
+ const s = String(id);
68
+ return s.length > 0 && mongoose.Types.ObjectId.isValid(s);
69
+ }
70
+ case "string": return String(id).length > 0;
71
+ case "number": {
72
+ if (typeof id === "number") return Number.isFinite(id);
73
+ const n = Number(id);
74
+ return Number.isFinite(n);
75
+ }
76
+ case "uuid": return typeof id === "string" && UUID_RE.test(id);
77
+ case "unknown": return String(id).length > 0;
78
+ }
79
+ }
80
+ //#endregion
81
+ export { isObjectIdInstance as n, isValidIdForType as r, getSchemaIdType 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-CWUL0yiw.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-BwO4u3JN.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,10 +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-CWUL0yiw.mjs";
2
- import { t as index_d_exports } from "./index-Buj7P006.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-BwO4u3JN.mjs";
2
+ import { t as index_d_exports } from "./index-DmVbUzDL.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-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";
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-BtmT-FFB.mjs";
5
+ import { n as SchemaPathsLike, t as FieldType$1 } from "./coercion-B19MD38s.mjs";
6
+ import { n as SchemaIndexes } from "./indexes-qIBCZ3mY.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-DQMU0Dlv.mjs";
8
8
  import * as _$mongoose from "mongoose";
9
9
  import { ClientSession, Expression, Model, PipelineStage, PopulateOptions } from "mongoose";
10
10
 
@@ -1111,6 +1111,21 @@ declare class Repository<TDoc = unknown> {
1111
1111
  * 4. DEFAULT (500) — user-registered hooks
1112
1112
  */
1113
1113
  _buildContext(operation: string, options: Record<string, unknown> | object): Promise<RepositoryContext>;
1114
+ /**
1115
+ * Resolve the effective sort for getAll. Handles $near conflict detection
1116
+ * (warns + drops explicit sort when $near is in filters).
1117
+ */
1118
+ private _resolveSort;
1119
+ /**
1120
+ * Detect whether to use keyset (cursor) or offset (page) pagination.
1121
+ */
1122
+ private _detectPaginationMode;
1123
+ /**
1124
+ * Build the MongoDB query filter with search merged in. Handles text
1125
+ * search ($text), regex search ($or of $regex), and the search-resolver
1126
+ * plugin contract (search already cleared by a before:getAll hook).
1127
+ */
1128
+ private _buildSearchQuery;
1114
1129
  /**
1115
1130
  * Parse sort string or object
1116
1131
  */
package/dist/index.mjs CHANGED
@@ -2,12 +2,12 @@ import { a as warn, n as parseDuplicateKeyError, r as configureLogger, t as crea
2
2
  import { y as LookupBuilder } from "./update-AVfKWNGt.mjs";
3
3
  import { t as actions_exports } from "./actions/index.mjs";
4
4
  import { t as PaginationEngine } from "./PaginationEngine-CpsQl471.mjs";
5
- import { A as aggregateHelpersPlugin, C as HOOK_PRIORITY, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as cachePlugin, T as AggregationBuilder, _ as dateSequentialId, a as uniqueField, b as sequentialId, c as subdocumentPlugin, d as multiTenantPlugin, f as mongoOperationsPlugin, g as customIdPlugin, h as elasticSearchPlugin, i as requireField, k as auditLogPlugin, l as softDeletePlugin, m as fieldFilterPlugin, n as blockIf, o as validationChainPlugin, p as methodRegistryPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as observabilityPlugin, v as getNextSequence, w as Repository, x as cascadePlugin, y as prefixedId } from "./validation-chain.plugin-BCPh2UZG.mjs";
5
+ import { A as aggregateHelpersPlugin, C as HOOK_PRIORITY, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as cachePlugin, T as AggregationBuilder, _ as dateSequentialId, a as uniqueField, b as sequentialId, c as subdocumentPlugin, d as multiTenantPlugin, f as mongoOperationsPlugin, g as customIdPlugin, h as elasticSearchPlugin, i as requireField, k as auditLogPlugin, l as softDeletePlugin, m as fieldFilterPlugin, n as blockIf, o as validationChainPlugin, p as methodRegistryPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as observabilityPlugin, v as getNextSequence, w as Repository, x as cascadePlugin, y as prefixedId } from "./validation-chain.plugin-DCYjnPau.mjs";
6
6
  import { isGeoOperator, parseGeoFilter } from "./query/primitives/geo.mjs";
7
- import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "./field-selection-reyDRzXf.mjs";
7
+ import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "./field-selection-C5o7jyyA.mjs";
8
8
  import { buildFieldTypeMap, coerceFieldValue, coerceHeuristic } from "./query/primitives/coercion.mjs";
9
9
  import { extractSchemaIndexes } from "./query/primitives/indexes.mjs";
10
- import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-DpjhFfaO.mjs";
10
+ import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-8Cyu3NFJ.mjs";
11
11
  //#region src/query/QueryParser.ts
12
12
  /**
13
13
  * Modern Query Parser
@@ -1,3 +1,4 @@
1
+ import { n as isObjectIdInstance } from "./id-resolution-LZ-cCwR5.mjs";
1
2
  import "mongoose";
2
3
  //#region src/utils/memory-cache.ts
3
4
  /**
@@ -93,28 +94,56 @@ function buildCrudSchemasFromModel(mongooseModel, options = {}) {
93
94
  return buildCrudSchemasFromMongooseSchema(mongooseModel.schema, options);
94
95
  }
95
96
  /**
96
- * Get fields that are immutable (cannot be updated)
97
+ * Collect fields to omit from a generated schema based on field rules and
98
+ * explicit omit lists. This is the single source of truth for "which fields
99
+ * should NOT appear in the create/update body" — called by both the
100
+ * path-based and tree-based schema builders.
101
+ */
102
+ function collectFieldsToOmit(options, purpose) {
103
+ const result = new Set([
104
+ "createdAt",
105
+ "updatedAt",
106
+ "__v"
107
+ ]);
108
+ const rules = options?.fieldRules || {};
109
+ for (const [field, rule] of Object.entries(rules)) {
110
+ if (rule.systemManaged) result.add(field);
111
+ if (purpose === "update" && (rule.immutable || rule.immutableAfterCreate)) result.add(field);
112
+ }
113
+ const explicit = purpose === "create" ? options?.create?.omitFields : options?.update?.omitFields;
114
+ if (explicit) for (const f of explicit) result.add(f);
115
+ return result;
116
+ }
117
+ /**
118
+ * Apply omissions + optional-overrides to a built JSON schema in-place.
119
+ * Removes properties and adjusts the `required` array.
120
+ */
121
+ function applyFieldRules(schema, fieldsToOmit, options) {
122
+ for (const field of fieldsToOmit) {
123
+ if (schema.properties?.[field]) delete schema.properties[field];
124
+ if (schema.required) schema.required = schema.required.filter((k) => k !== field);
125
+ }
126
+ const rules = options?.fieldRules || {};
127
+ for (const [field, rule] of Object.entries(rules)) if (rule.optional && schema.required) schema.required = schema.required.filter((k) => k !== field);
128
+ }
129
+ /**
130
+ * Get fields that are immutable (cannot be updated).
131
+ * Delegates to `collectFieldsToOmit` internally.
97
132
  */
98
133
  function getImmutableFields(options = {}) {
99
134
  const immutable = [];
100
- const fieldRules = options?.fieldRules || {};
101
- Object.entries(fieldRules).forEach(([field, rules]) => {
102
- if (rules.immutable || rules.immutableAfterCreate) immutable.push(field);
103
- });
104
- (options?.update?.omitFields || []).forEach((f) => {
105
- if (!immutable.includes(f)) immutable.push(f);
106
- });
135
+ const rules = options?.fieldRules || {};
136
+ for (const [field, rule] of Object.entries(rules)) if (rule.immutable || rule.immutableAfterCreate) immutable.push(field);
137
+ for (const f of options?.update?.omitFields || []) if (!immutable.includes(f)) immutable.push(f);
107
138
  return immutable;
108
139
  }
109
140
  /**
110
- * Get fields that are system-managed (cannot be set by users)
141
+ * Get fields that are system-managed (cannot be set by users).
111
142
  */
112
143
  function getSystemManagedFields(options = {}) {
113
144
  const systemManaged = [];
114
- const fieldRules = options?.fieldRules || {};
115
- Object.entries(fieldRules).forEach(([field, rules]) => {
116
- if (rules.systemManaged) systemManaged.push(field);
117
- });
145
+ const rules = options?.fieldRules || {};
146
+ for (const [field, rule] of Object.entries(rules)) if (rule.systemManaged) systemManaged.push(field);
118
147
  return systemManaged;
119
148
  }
120
149
  /**
@@ -161,7 +190,11 @@ function buildJsonSchemaFromPaths(mongooseSchema, options) {
161
190
  };
162
191
  const rootFields = /* @__PURE__ */ new Map();
163
192
  for (const [path, schemaType] of Object.entries(paths)) {
164
- if (path === "_id" || path === "__v") continue;
193
+ if (path === "__v") continue;
194
+ if (path === "_id") {
195
+ const instance = schemaType.instance;
196
+ if (!instance || isObjectIdInstance(instance)) continue;
197
+ }
165
198
  const rootField = path.split(".")[0];
166
199
  if (!rootFields.has(rootField)) rootFields.set(rootField, []);
167
200
  rootFields.get(rootField)?.push({
@@ -183,30 +216,12 @@ function buildJsonSchemaFromPaths(mongooseSchema, options) {
183
216
  properties
184
217
  };
185
218
  if (required.length) schema.required = required;
186
- const fieldsToOmit = new Set([
187
- "createdAt",
188
- "updatedAt",
189
- "__v"
190
- ]);
191
- (options?.create?.omitFields || []).forEach((f) => {
192
- fieldsToOmit.add(f);
193
- });
194
- const fieldRules = options?.fieldRules || {};
195
- Object.entries(fieldRules).forEach(([field, rules]) => {
196
- if (rules.systemManaged) fieldsToOmit.add(field);
197
- });
198
- fieldsToOmit.forEach((field) => {
199
- if (schema.properties?.[field]) delete schema.properties[field];
200
- if (schema.required) schema.required = schema.required.filter((k) => k !== field);
201
- });
219
+ applyFieldRules(schema, collectFieldsToOmit(options, "create"), options);
202
220
  const reqOv = options?.create?.requiredOverrides || {};
203
221
  const optOv = options?.create?.optionalOverrides || {};
204
222
  schema.required = schema.required || [];
205
223
  for (const [k, v] of Object.entries(reqOv)) if (v && !schema.required.includes(k)) schema.required.push(k);
206
224
  for (const [k, v] of Object.entries(optOv)) if (v && schema.required) schema.required = schema.required.filter((x) => x !== k);
207
- Object.entries(fieldRules).forEach(([field, rules]) => {
208
- if (rules.optional && schema.required) schema.required = schema.required.filter((x) => x !== field);
209
- });
210
225
  const schemaOverrides = options?.create?.schemaOverrides || {};
211
226
  for (const [k, override] of Object.entries(schemaOverrides)) if (schema.properties?.[k]) schema.properties[k] = override;
212
227
  if (options?.strictAdditionalProperties === true) schema.additionalProperties = false;
@@ -270,7 +285,7 @@ function schemaTypeToJsonSchema(schemaType) {
270
285
  else if (instance === "Date") {
271
286
  result.type = "string";
272
287
  result.format = "date-time";
273
- } else if (instance === "ObjectId" || instance === "ObjectID") {
288
+ } else if (isObjectIdInstance(instance)) {
274
289
  result.type = "string";
275
290
  result.pattern = "^[0-9a-fA-F]{24}$";
276
291
  } else if (instance === "Array") {
@@ -285,17 +300,7 @@ function schemaTypeToJsonSchema(schemaType) {
285
300
  function buildJsonSchemaForUpdate(createJson, options) {
286
301
  const clone = JSON.parse(JSON.stringify(createJson));
287
302
  delete clone.required;
288
- const fieldsToOmit = /* @__PURE__ */ new Set();
289
- (options?.update?.omitFields || []).forEach((f) => {
290
- fieldsToOmit.add(f);
291
- });
292
- const fieldRules = options?.fieldRules || {};
293
- Object.entries(fieldRules).forEach(([field, rules]) => {
294
- if (rules.immutable || rules.immutableAfterCreate) fieldsToOmit.add(field);
295
- });
296
- fieldsToOmit.forEach((field) => {
297
- if (clone.properties?.[field]) delete clone.properties[field];
298
- });
303
+ applyFieldRules(clone, collectFieldsToOmit(options, "update"), options);
299
304
  if (options?.strictAdditionalProperties === true) clone.additionalProperties = false;
300
305
  if (options?.update?.requireAtLeastOne === true) clone.minProperties = 1;
301
306
  return clone;
@@ -1,4 +1,4 @@
1
- import { T as FieldPreset, _ as CrudSchemas, k as HttpError, ot as SchemaBuilderOptions, s as CacheAdapter, xt as ValidationResult, yt as UserContext } from "./types-CWUL0yiw.mjs";
1
+ import { T as FieldPreset, _ as CrudSchemas, k as HttpError, ot as SchemaBuilderOptions, s as CacheAdapter, xt as ValidationResult, yt as UserContext } from "./types-BwO4u3JN.mjs";
2
2
  import mongoose, { Schema } from "mongoose";
3
3
 
4
4
  //#region src/utils/error.d.ts
@@ -146,11 +146,12 @@ declare function buildCrudSchemasFromMongooseSchema(mongooseSchema: Schema, opti
146
146
  */
147
147
  declare function buildCrudSchemasFromModel(mongooseModel: mongoose.Model<unknown>, options?: SchemaBuilderOptions): CrudSchemas;
148
148
  /**
149
- * Get fields that are immutable (cannot be updated)
149
+ * Get fields that are immutable (cannot be updated).
150
+ * Delegates to `collectFieldsToOmit` internally.
150
151
  */
151
152
  declare function getImmutableFields(options?: SchemaBuilderOptions): string[];
152
153
  /**
153
- * Get fields that are system-managed (cannot be set by users)
154
+ * Get fields that are system-managed (cannot be set by users).
154
155
  */
155
156
  declare function getSystemManagedFields(options?: SchemaBuilderOptions): string[];
156
157
  /**
@@ -1,4 +1,4 @@
1
- import { F as KeysetPaginationResult, G as PaginationConfig, H as OffsetPaginationOptions, P as KeysetPaginationOptions, U as OffsetPaginationResult, a as AnyDocument, n as AggregatePaginationOptions, r as AggregatePaginationResult } from "../types-CWUL0yiw.mjs";
1
+ import { F as KeysetPaginationResult, G as PaginationConfig, H as OffsetPaginationOptions, P as KeysetPaginationOptions, U as OffsetPaginationResult, a as AnyDocument, n as AggregatePaginationOptions, r as AggregatePaginationResult } from "../types-BwO4u3JN.mjs";
2
2
  import { Model } from "mongoose";
3
3
 
4
4
  //#region src/pagination/PaginationEngine.d.ts
@@ -1,2 +1,2 @@
1
- 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, 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, y as MethodRegistryRepository, z as batchOperationsPlugin } from "../validation-chain.plugin-Dn41aAbK.mjs";
1
+ 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, 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, y as MethodRegistryRepository, z as batchOperationsPlugin } from "../validation-chain.plugin-BtmT-FFB.mjs";
2
2
  export { type AggregateHelpersMethods, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BatchOperationsMethods, type CacheMethods, type CustomIdOptions, type DateSequentialIdOptions, type ElasticSearchOptions, type IdGenerator, type MethodRegistryRepository, type MongoOperationsMethods, type MultiTenantOptions, type ObservabilityOptions, type OperationMetric, type PrefixedIdOptions, type SequentialIdOptions, type SoftDeleteMethods, type SubdocumentMethods, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
@@ -1,2 +1,2 @@
1
- import { A as aggregateHelpersPlugin, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as cachePlugin, _ as dateSequentialId, a as uniqueField, b as sequentialId, c as subdocumentPlugin, d as multiTenantPlugin, f as mongoOperationsPlugin, g as customIdPlugin, h as elasticSearchPlugin, i as requireField, k as auditLogPlugin, l as softDeletePlugin, m as fieldFilterPlugin, n as blockIf, o as validationChainPlugin, p as methodRegistryPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as observabilityPlugin, v as getNextSequence, x as cascadePlugin, y as prefixedId } from "../validation-chain.plugin-BCPh2UZG.mjs";
1
+ import { A as aggregateHelpersPlugin, D as AuditTrailQuery, E as batchOperationsPlugin, O as auditTrailPlugin, S as cachePlugin, _ as dateSequentialId, a as uniqueField, b as sequentialId, c as subdocumentPlugin, d as multiTenantPlugin, f as mongoOperationsPlugin, g as customIdPlugin, h as elasticSearchPlugin, i as requireField, k as auditLogPlugin, l as softDeletePlugin, m as fieldFilterPlugin, n as blockIf, o as validationChainPlugin, p as methodRegistryPlugin, r as immutableField, s as timestampPlugin, t as autoInject, u as observabilityPlugin, v as getNextSequence, x as cascadePlugin, y as prefixedId } from "../validation-chain.plugin-DCYjnPau.mjs";
2
2
  export { AuditTrailQuery, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
@@ -1,2 +1,2 @@
1
- import { a as coerceHeuristic, i as coerceFieldValue, n as SchemaPathsLike, o as coerceToType, r as buildFieldTypeMap, s as normalizeMongooseType, t as FieldType } from "../../coercion-01tEHsKF.mjs";
1
+ import { a as coerceHeuristic, i as coerceFieldValue, n as SchemaPathsLike, o as coerceToType, r as buildFieldTypeMap, s as normalizeMongooseType, t as FieldType } from "../../coercion-B19MD38s.mjs";
2
2
  export { FieldType, SchemaPathsLike, buildFieldTypeMap, coerceFieldValue, coerceHeuristic, coerceToType, normalizeMongooseType };
@@ -1,3 +1,4 @@
1
+ import { n as isObjectIdInstance } from "../../id-resolution-LZ-cCwR5.mjs";
1
2
  import mongoose from "mongoose";
2
3
  //#region src/query/primitives/coercion.ts
3
4
  /**
@@ -39,10 +40,10 @@ function normalizeMongooseType(schemaType) {
39
40
  case "Number": return "number";
40
41
  case "Boolean": return "boolean";
41
42
  case "Date": return "date";
42
- case "ObjectID":
43
- case "ObjectId": return "objectid";
44
43
  case "Mixed": return "mixed";
45
- default: return null;
44
+ default:
45
+ if (isObjectIdInstance(raw)) return "objectid";
46
+ return null;
46
47
  }
47
48
  }
48
49
  /**
@@ -1,2 +1,2 @@
1
- import { n as SchemaIndexes, r as extractSchemaIndexes, t as IndexableSchema } from "../../indexes-CiIT-Zli.mjs";
1
+ import { n as SchemaIndexes, r as extractSchemaIndexes, t as IndexableSchema } from "../../indexes-qIBCZ3mY.mjs";
2
2
  export { IndexableSchema, SchemaIndexes, extractSchemaIndexes };
@@ -1,5 +1,5 @@
1
- import { Z as PopulateSpec, pt as SortSpec, st as SelectSpec } from "../types-CWUL0yiw.mjs";
2
- import { a as isFieldUpdateAllowed, c as configureLogger, d as getFieldsForUser, f as getMongooseProjection, i as getSystemManagedFields, l as createFieldPreset, 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";
1
+ import { Z as PopulateSpec, pt as SortSpec, st as SelectSpec } from "../types-BwO4u3JN.mjs";
2
+ import { a as isFieldUpdateAllowed, c as configureLogger, d as getFieldsForUser, f as getMongooseProjection, i as getSystemManagedFields, l as createFieldPreset, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, p as createError, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel, u as filterResponseData } from "../mongooseToJsonSchema-DQMU0Dlv.mjs";
3
3
 
4
4
  //#region src/utils/cache-keys.d.ts
5
5
  /**
@@ -89,4 +89,61 @@ declare function modelPattern(prefix: string, model: string): string;
89
89
  */
90
90
  declare function listPattern(prefix: string, model: string): string;
91
91
  //#endregion
92
- export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getSystemManagedFields, isFieldUpdateAllowed, listPattern, listQueryKey, modelPattern, validateUpdateBody, versionKey };
92
+ //#region src/utils/id-resolution.d.ts
93
+ /**
94
+ * ID Resolution Primitive
95
+ *
96
+ * Pure functions for detecting a Mongoose schema's _id type and validating
97
+ * id values against it. Used by Repository.getById to decide whether to
98
+ * run ObjectId validation (for ObjectId schemas) or skip it (for String /
99
+ * Number / UUID schemas).
100
+ *
101
+ * Also usable standalone — Arc controllers, custom repositories, migration
102
+ * scripts, or test harnesses can import these directly to make the same
103
+ * decision without depending on Repository.
104
+ *
105
+ * Design: no `this`, no Model, no DB calls. Only reads `schema.paths._id.instance`.
106
+ */
107
+ /** Normalized _id type — covers the common Mongoose _id declarations */
108
+ type IdType = 'objectid' | 'string' | 'number' | 'uuid' | 'unknown';
109
+ /**
110
+ * Check if a Mongoose SchemaType `instance` string is an ObjectId variant.
111
+ * Mongoose 9.x uses `'ObjectId'`, older versions use `'ObjectID'`. This
112
+ * is the single source of truth — use it instead of inline
113
+ * `instance === 'ObjectId' || instance === 'ObjectID'` checks.
114
+ */
115
+ declare function isObjectIdInstance(instance: string | undefined): boolean;
116
+ /** Minimal structural type for schema introspection (decoupled from Mongoose version) */
117
+ interface SchemaLike {
118
+ paths?: Record<string, {
119
+ instance?: string;
120
+ }>;
121
+ }
122
+ /**
123
+ * Detect the _id path type from a Mongoose schema.
124
+ *
125
+ * Returns 'objectid' as the safe default when:
126
+ * - No schema is provided (mock models, test harnesses)
127
+ * - The schema has no `paths._id` (auto-generated ObjectId)
128
+ * - The instance string is unrecognized
129
+ *
130
+ * This safe-default means existing callers that don't set _id explicitly
131
+ * get the same ObjectId validation they always had — the behavioral change
132
+ * is strictly opt-in via schema declaration.
133
+ */
134
+ declare function getSchemaIdType(schema?: SchemaLike | null): IdType;
135
+ /**
136
+ * Validate an id value against a known _id type — WITHOUT hitting the DB.
137
+ *
138
+ * Returns `true` when the value is structurally valid for the given type.
139
+ * Returns `false` when it's definitely invalid (Repository should return
140
+ * 404 / null immediately rather than wasting a round-trip).
141
+ *
142
+ * For `'string'` and `'unknown'` types, validation is intentionally
143
+ * permissive: any truthy, non-empty value is accepted. The DB is the
144
+ * authority for whether the document exists — we only reject values that
145
+ * are structurally impossible (null, undefined, empty string).
146
+ */
147
+ declare function isValidIdForType(id: unknown, idType: IdType): boolean;
148
+ //#endregion
149
+ export { type IdType, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getSchemaIdType, getSystemManagedFields, isFieldUpdateAllowed, isObjectIdInstance, isValidIdForType, listPattern, listQueryKey, modelPattern, validateUpdateBody, versionKey };
@@ -1,4 +1,5 @@
1
1
  import { r as configureLogger, t as createError } from "../error-Bpbi_NKo.mjs";
2
- import { a as byIdKey, c as listQueryKey, i as getMongooseProjection, l as modelPattern, n as filterResponseData, o as byQueryKey, r as getFieldsForUser, s as listPattern, t as createFieldPreset, u as versionKey } from "../field-selection-reyDRzXf.mjs";
3
- import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-DpjhFfaO.mjs";
4
- export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getSystemManagedFields, isFieldUpdateAllowed, listPattern, listQueryKey, modelPattern, validateUpdateBody, versionKey };
2
+ import { n as isObjectIdInstance, r as isValidIdForType, t as getSchemaIdType } from "../id-resolution-LZ-cCwR5.mjs";
3
+ import { a as byIdKey, c as listQueryKey, i as getMongooseProjection, l as modelPattern, n as filterResponseData, o as byQueryKey, r as getFieldsForUser, s as listPattern, t as createFieldPreset, u as versionKey } from "../field-selection-C5o7jyyA.mjs";
4
+ import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "../mongooseToJsonSchema-8Cyu3NFJ.mjs";
5
+ export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, configureLogger, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getSchemaIdType, getSystemManagedFields, isFieldUpdateAllowed, isObjectIdInstance, isValidIdForType, listPattern, listQueryKey, modelPattern, validateUpdateBody, versionKey };
@@ -1,4 +1,4 @@
1
- import { I as Logger, J as Plugin, St as ValidatorDefinition, T as FieldPreset, U as OffsetPaginationResult, V as ObjectId, Z as PopulateSpec, bt as ValidationChainOptions, et as RepositoryContext, f as CascadeOptions, l as CacheOptions, nt as RepositoryInstance, pt as SortSpec, st as SelectSpec, u as CacheStats, ut as SoftDeleteOptions } from "./types-CWUL0yiw.mjs";
1
+ import { I as Logger, J as Plugin, St as ValidatorDefinition, T as FieldPreset, U as OffsetPaginationResult, V as ObjectId, Z as PopulateSpec, bt as ValidationChainOptions, et as RepositoryContext, f as CascadeOptions, l as CacheOptions, nt as RepositoryInstance, pt as SortSpec, st as SelectSpec, u as CacheStats, ut as SoftDeleteOptions } from "./types-BwO4u3JN.mjs";
2
2
  import mongoose, { ClientSession } from "mongoose";
3
3
 
4
4
  //#region src/plugins/aggregate-helpers.plugin.d.ts
@@ -2,7 +2,8 @@ import { a as warn, i as debug, n as parseDuplicateKeyError, t as createError }
2
2
  import { a as exists, c as getOrCreate, d as deleteByQuery, g as upsert, i as count, m as createMany, n as updateByQuery, o as getById, p as create, s as getByQuery, t as update, u as deleteById, v as distinct, y as LookupBuilder } from "./update-AVfKWNGt.mjs";
3
3
  import { t as PaginationEngine } from "./PaginationEngine-CpsQl471.mjs";
4
4
  import { hasNearOperator, rewriteNearForCount } from "./query/primitives/geo.mjs";
5
- import { a as byIdKey, c as listQueryKey, l as modelPattern, o as byQueryKey, r as getFieldsForUser, u as versionKey } from "./field-selection-reyDRzXf.mjs";
5
+ import { r as isValidIdForType, t as getSchemaIdType } from "./id-resolution-LZ-cCwR5.mjs";
6
+ import { a as byIdKey, c as listQueryKey, l as modelPattern, o as byQueryKey, r as getFieldsForUser, u as versionKey } from "./field-selection-C5o7jyyA.mjs";
6
7
  import mongoose from "mongoose";
7
8
  //#region src/plugins/aggregate-helpers.plugin.ts
8
9
  /**
@@ -1370,7 +1371,7 @@ var Repository = class {
1370
1371
  });
1371
1372
  return result;
1372
1373
  }
1373
- if (typeof id === "string" && !mongoose.Types.ObjectId.isValid(id)) {
1374
+ if (!isValidIdForType(id, getSchemaIdType(this.Model.schema))) {
1374
1375
  if (context.throwOnNotFound === false || options.throwOnNotFound === false) return null;
1375
1376
  throw createError(404, "Document not found");
1376
1377
  }
@@ -1549,41 +1550,14 @@ var Repository = class {
1549
1550
  });
1550
1551
  const filters = context.filters ?? params.filters ?? {};
1551
1552
  const search = context.search;
1552
- const isNearQuery = hasNearOperator(context.filters ?? params.filters ?? {});
1553
- const explicitSort = context.sort ?? params.sort;
1554
- if (isNearQuery && explicitSort !== void 0) warn(`[mongokit] Repository "${this.model}" dropping explicit sort (${JSON.stringify(explicitSort)}) because the filter contains $near / $nearSphere. MongoDB forbids explicit sort with $near. Use [withinRadius] instead of [near] if you need a custom sort.`);
1555
- const sort = isNearQuery ? void 0 : explicitSort ?? "-createdAt";
1553
+ const isNearQuery = hasNearOperator(filters);
1554
+ const sort = this._resolveSort(context, params, isNearQuery);
1556
1555
  const limit = context.limit ?? params.limit ?? params.pagination?.limit ?? this._pagination.config.defaultLimit;
1557
1556
  const page = context.page ?? params.pagination?.page ?? params.page;
1558
1557
  const after = context.after ?? params.cursor ?? params.after;
1559
1558
  const mode = context.mode ?? params.mode;
1560
- let useKeyset = false;
1561
- if (mode) useKeyset = mode === "keyset";
1562
- else useKeyset = !page && !!(after || sort !== "-createdAt" && (context.sort ?? params.sort));
1563
- const query = { ...filters };
1564
- if (search) {
1565
- if (this._hasTextIndex === null) this._hasTextIndex = this.Model.schema.indexes().some((idx) => idx[0] && Object.values(idx[0]).includes("text"));
1566
- let effectiveMode = this.searchMode === "regex" ? "regex" : "text";
1567
- if (this.searchMode === "auto") effectiveMode = this._hasTextIndex ? "text" : "regex";
1568
- if (effectiveMode === "regex") {
1569
- if (!this.searchFields || this.searchFields.length === 0) throw createError(400, `Repository "${this.model}" configured with searchMode: '${this.searchMode}' but no searchFields provided.`);
1570
- const escaped = String(search).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1571
- const orConds = this.searchFields.map((field) => ({ [field]: {
1572
- $regex: escaped,
1573
- $options: "i"
1574
- } }));
1575
- if (Array.isArray(query.$or)) {
1576
- const existingOr = query.$or;
1577
- delete query.$or;
1578
- query.$and = [
1579
- ...Array.isArray(query.$and) ? query.$and : [],
1580
- { $or: existingOr },
1581
- { $or: orConds }
1582
- ];
1583
- } else query.$or = orConds;
1584
- } else if (this._hasTextIndex) query.$text = { $search: search };
1585
- else throw createError(400, `No text index found for ${this.model}. Cannot perform text search. Configure Repository with searchMode: 'regex' (and searchFields) or 'auto' to enable index-free search.`);
1586
- }
1559
+ const useKeyset = this._detectPaginationMode(mode, page, after, sort, context, params);
1560
+ const query = this._buildSearchQuery(filters, search);
1587
1561
  const populateSpec = options.populateOptions || params.populateOptions || context.populate || options.populate;
1588
1562
  const paginationOptions = {
1589
1563
  filters: query,
@@ -2238,6 +2212,56 @@ var Repository = class {
2238
2212
  return context;
2239
2213
  }
2240
2214
  /**
2215
+ * Resolve the effective sort for getAll. Handles $near conflict detection
2216
+ * (warns + drops explicit sort when $near is in filters).
2217
+ */
2218
+ _resolveSort(context, params, isNearQuery) {
2219
+ const explicitSort = context.sort ?? params.sort;
2220
+ if (isNearQuery) {
2221
+ if (explicitSort !== void 0) warn(`[mongokit] Repository "${this.model}" dropping explicit sort (${JSON.stringify(explicitSort)}) because the filter contains $near / $nearSphere. MongoDB forbids explicit sort with $near. Use [withinRadius] instead of [near] if you need a custom sort.`);
2222
+ return;
2223
+ }
2224
+ return explicitSort ?? "-createdAt";
2225
+ }
2226
+ /**
2227
+ * Detect whether to use keyset (cursor) or offset (page) pagination.
2228
+ */
2229
+ _detectPaginationMode(mode, page, after, sort, context, params) {
2230
+ if (mode) return mode === "keyset";
2231
+ return !page && !!(after || sort !== "-createdAt" && (context.sort ?? params.sort));
2232
+ }
2233
+ /**
2234
+ * Build the MongoDB query filter with search merged in. Handles text
2235
+ * search ($text), regex search ($or of $regex), and the search-resolver
2236
+ * plugin contract (search already cleared by a before:getAll hook).
2237
+ */
2238
+ _buildSearchQuery(filters, search) {
2239
+ const query = { ...filters };
2240
+ if (!search) return query;
2241
+ if (this._hasTextIndex === null) this._hasTextIndex = this.Model.schema.indexes().some((idx) => idx[0] && Object.values(idx[0]).includes("text"));
2242
+ let effectiveMode = this.searchMode === "regex" ? "regex" : "text";
2243
+ if (this.searchMode === "auto") effectiveMode = this._hasTextIndex ? "text" : "regex";
2244
+ if (effectiveMode === "regex") {
2245
+ if (!this.searchFields || this.searchFields.length === 0) throw createError(400, `Repository "${this.model}" configured with searchMode: '${this.searchMode}' but no searchFields provided.`);
2246
+ const escaped = String(search).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2247
+ const orConds = this.searchFields.map((field) => ({ [field]: {
2248
+ $regex: escaped,
2249
+ $options: "i"
2250
+ } }));
2251
+ if (Array.isArray(query.$or)) {
2252
+ const existingOr = query.$or;
2253
+ delete query.$or;
2254
+ query.$and = [
2255
+ ...Array.isArray(query.$and) ? query.$and : [],
2256
+ { $or: existingOr },
2257
+ { $or: orConds }
2258
+ ];
2259
+ } else query.$or = orConds;
2260
+ } else if (this._hasTextIndex) query.$text = { $search: search };
2261
+ else throw createError(400, `No text index found for ${this.model}. Cannot perform text search. Configure Repository with searchMode: 'regex' (and searchFields) or 'auto' to enable index-free search.`);
2262
+ return query;
2263
+ }
2264
+ /**
2241
2265
  * Parse sort string or object
2242
2266
  */
2243
2267
  _parseSort(sort) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/mongokit",
3
- "version": "3.5.5",
3
+ "version": "3.5.6",
4
4
  "description": "Production-grade MongoDB repositories with zero dependencies - smart pagination, events, and plugins",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -93,7 +93,7 @@
93
93
  },
94
94
  "homepage": "https://github.com/classytic/mongokit#readme",
95
95
  "peerDependencies": {
96
- "mongoose": "^9.4.1"
96
+ "mongoose": ">=9.4.1"
97
97
  },
98
98
  "engines": {
99
99
  "node": ">=22"