@classytic/mongokit 3.3.2 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +137 -7
  2. package/dist/PaginationEngine-nY04eGUM.mjs +290 -0
  3. package/dist/actions/index.d.mts +2 -9
  4. package/dist/actions/index.mjs +3 -5
  5. package/dist/ai/index.d.mts +1 -1
  6. package/dist/ai/index.mjs +3 -3
  7. package/dist/chunk-CfYAbeIz.mjs +13 -0
  8. package/dist/{limits-s1-d8rWb.mjs → cursor-CHToazHy.mjs} +122 -171
  9. package/dist/{logger-D8ily-PP.mjs → error-Bpbi_NKo.mjs} +34 -22
  10. package/dist/{cache-keys-CzFwVnLy.mjs → field-selection-reyDRzXf.mjs} +110 -112
  11. package/dist/{aggregate-BkOG9qwr.d.mts → index-BuoZIZ15.d.mts} +132 -129
  12. package/dist/index.d.mts +549 -543
  13. package/dist/index.mjs +33 -101
  14. package/dist/{mongooseToJsonSchema-D_i2Am_O.mjs → mongooseToJsonSchema-B6Qyl8BK.mjs} +13 -12
  15. package/dist/{mongooseToJsonSchema-B6O2ED3n.d.mts → mongooseToJsonSchema-RX9YfJLu.d.mts} +24 -17
  16. package/dist/pagination/PaginationEngine.d.mts +1 -1
  17. package/dist/pagination/PaginationEngine.mjs +2 -209
  18. package/dist/plugins/index.d.mts +1 -2
  19. package/dist/plugins/index.mjs +2 -3
  20. package/dist/sort-C-BJEWUZ.mjs +57 -0
  21. package/dist/{types-pVY0w1Pp.d.mts → types-COINbsdL.d.mts} +57 -27
  22. package/dist/{aggregate-BClp040M.mjs → update-DGKMmBgG.mjs} +575 -565
  23. package/dist/utils/index.d.mts +2 -2
  24. package/dist/utils/index.mjs +4 -5
  25. package/dist/{custom-id.plugin-BJ3FSnzt.d.mts → validation-chain.plugin-BNoaKDOm.d.mts} +832 -832
  26. package/dist/{custom-id.plugin-FInXDsUX.mjs → validation-chain.plugin-da3fOo8A.mjs} +2410 -2246
  27. package/package.json +11 -6
  28. package/dist/chunk-DQk6qfdC.mjs +0 -18
package/dist/index.mjs CHANGED
@@ -1,87 +1,13 @@
1
- import { i as createError, r as warn, t as configureLogger } from "./logger-D8ily-PP.mjs";
2
- import { r as LookupBuilder } from "./aggregate-BClp040M.mjs";
3
- import { PaginationEngine } from "./pagination/PaginationEngine.mjs";
4
- import { A as AggregationBuilder, C as methodRegistryPlugin, D as fieldFilterPlugin, E as timestampPlugin, O as HOOK_PRIORITY, S as validationChainPlugin, T as auditLogPlugin, _ as autoInject, a as sequentialId, b as requireField, c as auditTrailPlugin, d as cascadePlugin, f as cachePlugin, g as mongoOperationsPlugin, h as batchOperationsPlugin, i as prefixedId, k as Repository, l as observabilityPlugin, m as aggregateHelpersPlugin, n as dateSequentialId, o as elasticSearchPlugin, p as subdocumentPlugin, r as getNextSequence, s as AuditTrailQuery, t as customIdPlugin, u as multiTenantPlugin, v as blockIf, w as softDeletePlugin, x as uniqueField, y as immutableField } from "./custom-id.plugin-FInXDsUX.mjs";
5
- import { c as filterResponseData, l as getFieldsForUser, s as createFieldPreset, u as getMongooseProjection } from "./cache-keys-CzFwVnLy.mjs";
6
- import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-D_i2Am_O.mjs";
1
+ import { a as warn, n as parseDuplicateKeyError, r as configureLogger, t as createError } from "./error-Bpbi_NKo.mjs";
2
+ import { _ as LookupBuilder } from "./update-DGKMmBgG.mjs";
7
3
  import { t as actions_exports } from "./actions/index.mjs";
4
+ import { t as PaginationEngine } from "./PaginationEngine-nY04eGUM.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-da3fOo8A.mjs";
6
+ import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "./field-selection-reyDRzXf.mjs";
7
+ import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-B6Qyl8BK.mjs";
8
8
  import mongoose from "mongoose";
9
-
10
9
  //#region src/query/QueryParser.ts
11
10
  /**
12
- * Modern Query Parser - URL to MongoDB Query Transpiler
13
- *
14
- * Next-generation query parser that converts URL parameters to MongoDB aggregation pipelines.
15
- * Smarter than Prisma/tRPC for MongoDB with support for:
16
- * - Custom field lookups ($lookup)
17
- * - Complex filtering with operators
18
- * - Full-text search
19
- * - Aggregations via URL
20
- * - Security hardening
21
- *
22
- * @example
23
- * ```typescript
24
- * // Simple usage
25
- * const parser = new QueryParser();
26
- * const query = parser.parse(req.query);
27
- *
28
- * // URL: ?status=active&lookup[department]=slug&sort=-createdAt&page=1&limit=20
29
- * // Result: Complete MongoDB query with $lookup, filters, sort, pagination
30
- * ```
31
- *
32
- * ## SECURITY CONSIDERATIONS FOR PRODUCTION
33
- *
34
- * ### Aggregation Security (enableAggregations option)
35
- *
36
- * **IMPORTANT:** The `enableAggregations` option exposes powerful MongoDB aggregation
37
- * pipeline capabilities via URL parameters. While this feature includes sanitization
38
- * (blocks $where, $function, $accumulator), it should be used with caution:
39
- *
40
- * **Recommended security practices:**
41
- * 1. **Disable by default for public endpoints:**
42
- * ```typescript
43
- * const parser = new QueryParser({
44
- * enableAggregations: false // Default: disabled
45
- * });
46
- * ```
47
- *
48
- * 2. **Use per-route allowlists for trusted clients:**
49
- * ```typescript
50
- * // Admin/internal routes only
51
- * if (req.user?.role === 'admin') {
52
- * const allowedStages = ['$match', '$project', '$sort', '$limit'];
53
- * // Validate aggregate parameter against allowlist
54
- * }
55
- * ```
56
- *
57
- * 3. **Validate stage structure:** Even with sanitization, complex pipelines can
58
- * cause performance issues. Consider limiting:
59
- * - Number of pipeline stages (e.g., max 5)
60
- * - Specific allowed operators per stage
61
- * - Allowed fields in $project/$match
62
- *
63
- * 4. **Monitor resource usage:** Aggregation pipelines can be expensive.
64
- * Use MongoDB profiling to track slow operations.
65
- *
66
- * ### Lookup Security
67
- *
68
- * Lookup pipelines are sanitized by default:
69
- * - Dangerous stages blocked ($out, $merge, $unionWith, $collStats, $currentOp, $listSessions)
70
- * - Dangerous operators blocked inside $match/$addFields/$set ($where, $function, $accumulator, $expr)
71
- * - Optional collection whitelist via `allowedLookupCollections`
72
- * For maximum security, use per-collection field allowlists in your controller layer.
73
- *
74
- * ### Filter Security
75
- *
76
- * All filters are sanitized:
77
- * - Dangerous operators blocked ($where, $function, $accumulator, $expr)
78
- * - Regex patterns validated (ReDoS protection)
79
- * - Max filter depth enforced (prevents filter bombs)
80
- * - Max limit enforced (prevents resource exhaustion)
81
- *
82
- * @see {@link https://github.com/classytic/mongokit/blob/main/docs/SECURITY.md}
83
- */
84
- /**
85
11
  * Modern Query Parser
86
12
  * Converts URL parameters to MongoDB queries with $lookup support
87
13
  */
@@ -113,7 +39,7 @@ var QueryParser = class {
113
39
  * - Backreferences: \1, \2, etc.
114
40
  * - Complex character classes: [...]...[...]
115
41
  */
116
- dangerousRegexPatterns = /(\{[0-9,]+\}|\*\+|\+\+|\?\+|(\(.+\))\+|\(\?\:|\\[0-9]|(\[.+\]).+(\[.+\]))/;
42
+ dangerousRegexPatterns = /(\{[0-9,]+\}|\*\+|\+\+|\?\+|(\(.+\))\+|\(\?:|\\[0-9]|(\[.+\]).+(\[.+\]))/;
117
43
  constructor(options = {}) {
118
44
  this.options = {
119
45
  maxRegexLength: options.maxRegexLength ?? 500,
@@ -155,7 +81,7 @@ var QueryParser = class {
155
81
  parse(query) {
156
82
  const { page, limit = 20, sort = "-createdAt", populate, search, after, cursor, select, lookup, aggregate, ...filters } = query || {};
157
83
  let parsedLimit = parseInt(String(limit), 10);
158
- if (isNaN(parsedLimit) || parsedLimit < 1) parsedLimit = 20;
84
+ if (Number.isNaN(parsedLimit) || parsedLimit < 1) parsedLimit = 20;
159
85
  if (parsedLimit > this.options.maxLimit) {
160
86
  warn(`[mongokit] Limit ${parsedLimit} exceeds maximum ${this.options.maxLimit}, capping to max`);
161
87
  parsedLimit = this.options.maxLimit;
@@ -248,15 +174,15 @@ var QueryParser = class {
248
174
  description: "Cursor value for keyset pagination"
249
175
  }
250
176
  };
251
- if (this.options.enableLookups) properties["lookup"] = {
177
+ if (this.options.enableLookups) properties.lookup = {
252
178
  type: "object",
253
179
  description: "Custom field lookups ($lookup). Example: lookup[department]=slug or lookup[department][localField]=deptId&lookup[department][foreignField]=_id"
254
180
  };
255
- if (this.options.enableAggregations) properties["aggregate"] = {
181
+ if (this.options.enableAggregations) properties.aggregate = {
256
182
  type: "object",
257
183
  description: "Aggregation pipeline stages. Supports: group, match, sort, project. Example: aggregate[group][_id]=$status"
258
184
  };
259
- const availableOperators = this.options.allowedOperators ? Object.entries(this.operators).filter(([key]) => this.options.allowedOperators.includes(key)) : Object.entries(this.operators);
185
+ const availableOperators = this.options.allowedOperators ? Object.entries(this.operators).filter(([key]) => this.options.allowedOperators?.includes(key)) : Object.entries(this.operators);
260
186
  if (this.options.allowedFilterFields && this.options.allowedFilterFields.length > 0) for (const field of this.options.allowedFilterFields) {
261
187
  properties[field] = {
262
188
  type: "string",
@@ -283,8 +209,8 @@ var QueryParser = class {
283
209
  */
284
210
  getOpenAPIQuerySchema() {
285
211
  const schema = this.getQuerySchema();
286
- const availableOperators = this.options.allowedOperators ? Object.entries(this.operators).filter(([key]) => this.options.allowedOperators.includes(key)) : Object.entries(this.operators);
287
- schema.properties["_filterOperators"] = {
212
+ const availableOperators = this.options.allowedOperators ? Object.entries(this.operators).filter(([key]) => this.options.allowedOperators?.includes(key)) : Object.entries(this.operators);
213
+ schema.properties._filterOperators = {
288
214
  type: "string",
289
215
  description: this._buildOperatorDescription(availableOperators),
290
216
  "x-internal": true
@@ -420,6 +346,7 @@ var QueryParser = class {
420
346
  foreignField,
421
347
  as: opts.as || collectionName,
422
348
  single: opts.single === true || opts.single === "true",
349
+ ...opts.select ? { select: String(opts.select) } : {},
423
350
  ...opts.pipeline && Array.isArray(opts.pipeline) ? { pipeline: this._sanitizePipeline(opts.pipeline) } : {}
424
351
  };
425
352
  }
@@ -486,7 +413,14 @@ var QueryParser = class {
486
413
  */
487
414
  _parsePopulate(populate) {
488
415
  if (!populate) return {};
489
- if (typeof populate === "string") return { simplePopulate: populate };
416
+ if (typeof populate === "string") {
417
+ const paths = populate.split(",").map((p) => p.trim()).filter(Boolean);
418
+ if (paths.length > 0) return {
419
+ simplePopulate: populate,
420
+ populateOptions: paths.map((path) => ({ path }))
421
+ };
422
+ return {};
423
+ }
490
424
  if (typeof populate === "object" && populate !== null) {
491
425
  const populateObj = populate;
492
426
  if (Object.keys(populateObj).length === 0) return {};
@@ -525,7 +459,7 @@ var QueryParser = class {
525
459
  if (opts.match && typeof opts.match === "object") option.match = this._convertPopulateMatch(opts.match);
526
460
  if (opts.limit !== void 0) {
527
461
  const limit = parseInt(String(opts.limit), 10);
528
- if (!isNaN(limit) && limit > 0) {
462
+ if (!Number.isNaN(limit) && limit > 0) {
529
463
  option.options = option.options || {};
530
464
  option.options.limit = limit;
531
465
  }
@@ -539,7 +473,7 @@ var QueryParser = class {
539
473
  }
540
474
  if (opts.skip !== void 0) {
541
475
  const skip = parseInt(String(opts.skip), 10);
542
- if (!isNaN(skip) && skip >= 0) {
476
+ if (!Number.isNaN(skip) && skip >= 0) {
543
477
  option.options = option.options || {};
544
478
  option.options.skip = skip;
545
479
  }
@@ -603,7 +537,7 @@ var QueryParser = class {
603
537
  }
604
538
  if (operatorMatch) {
605
539
  const [, , operator] = operatorMatch;
606
- if (this.dangerousOperators.includes("$" + operator)) {
540
+ if (this.dangerousOperators.includes(`$${operator}`)) {
607
541
  warn(`[mongokit] Blocked dangerous operator: ${operator}`);
608
542
  continue;
609
543
  }
@@ -662,7 +596,7 @@ var QueryParser = class {
662
596
  "size"
663
597
  ].includes(op)) {
664
598
  processedValue = parseFloat(String(value));
665
- if (isNaN(processedValue)) return;
599
+ if (Number.isNaN(processedValue)) return;
666
600
  } else if (op === "in" || op === "nin") processedValue = Array.isArray(value) ? value : String(value).split(",").map((v) => v.trim());
667
601
  else processedValue = this._convertValue(value);
668
602
  if (typeof filters[field] !== "object" || filters[field] === null || Array.isArray(filters[field])) filters[field] = {};
@@ -699,7 +633,7 @@ var QueryParser = class {
699
633
  "size"
700
634
  ].includes(operator)) {
701
635
  processedValue = parseFloat(String(value));
702
- if (isNaN(processedValue)) continue;
636
+ if (Number.isNaN(processedValue)) continue;
703
637
  } else if (operator === "in" || operator === "nin") processedValue = Array.isArray(value) ? value : String(value).split(",").map((v) => v.trim());
704
638
  else if (operator === "like" || operator === "contains" || operator === "regex") {
705
639
  const safeRegex = this._createSafeRegex(value);
@@ -741,7 +675,7 @@ var QueryParser = class {
741
675
  }
742
676
  _toMongoOperator(operator) {
743
677
  const op = operator.toLowerCase();
744
- return op.startsWith("$") ? op : "$" + op;
678
+ return op.startsWith("$") ? op : `$${op}`;
745
679
  }
746
680
  _createSafeRegex(pattern, flags = "i") {
747
681
  if (pattern === null || pattern === void 0) return null;
@@ -890,22 +824,21 @@ var QueryParser = class {
890
824
  const fromDate = from ? new Date(from) : void 0;
891
825
  const toDate = to ? new Date(to) : void 0;
892
826
  const range = {};
893
- if (fromDate && !isNaN(fromDate.getTime())) range.$gte = fromDate;
894
- if (toDate && !isNaN(toDate.getTime())) range.$lte = toDate;
827
+ if (fromDate && !Number.isNaN(fromDate.getTime())) range.$gte = fromDate;
828
+ if (toDate && !Number.isNaN(toDate.getTime())) range.$lte = toDate;
895
829
  output[key] = range;
896
830
  }
897
831
  return output;
898
832
  }
899
833
  _pluralize(str) {
900
- if (str.endsWith("y")) return str.slice(0, -1) + "ies";
834
+ if (str.endsWith("y")) return `${str.slice(0, -1)}ies`;
901
835
  if (str.endsWith("s")) return str;
902
- return str + "s";
836
+ return `${str}s`;
903
837
  }
904
838
  _capitalize(str) {
905
839
  return str.charAt(0).toUpperCase() + str.slice(1);
906
840
  }
907
841
  };
908
-
909
842
  //#endregion
910
843
  //#region src/index.ts
911
844
  /**
@@ -922,6 +855,5 @@ function createRepository(Model, plugins = [], paginationConfig = {}, options =
922
855
  return new Repository(Model, plugins, paginationConfig, options);
923
856
  }
924
857
  var src_default = Repository;
925
-
926
858
  //#endregion
927
- export { AggregationBuilder, AuditTrailQuery, HOOK_PRIORITY, LookupBuilder, PaginationEngine, QueryParser, Repository, actions_exports as actions, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createRepository, customIdPlugin, dateSequentialId, src_default as default, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getNextSequence, getSystemManagedFields, immutableField, isFieldUpdateAllowed, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validateUpdateBody, validationChainPlugin };
859
+ export { AggregationBuilder, AuditTrailQuery, HOOK_PRIORITY, LookupBuilder, PaginationEngine, QueryParser, Repository, actions_exports as actions, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createRepository, customIdPlugin, dateSequentialId, src_default as default, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getNextSequence, getSystemManagedFields, immutableField, isFieldUpdateAllowed, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, parseDuplicateKeyError, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validateUpdateBody, validationChainPlugin };
@@ -1,14 +1,13 @@
1
- import mongoose, { Schema } from "mongoose";
2
-
1
+ import "mongoose";
3
2
  //#region src/utils/memory-cache.ts
4
3
  /**
5
4
  * Creates an in-memory cache adapter
6
- *
5
+ *
7
6
  * Features:
8
7
  * - Automatic TTL expiration
9
8
  * - Pattern-based clearing (simple glob with *)
10
9
  * - Max entries limit to prevent memory leaks
11
- *
10
+ *
12
11
  * @param maxEntries - Maximum cache entries before oldest are evicted (default: 1000)
13
12
  */
14
13
  function createMemoryCache(maxEntries = 1e3) {
@@ -60,12 +59,11 @@ function createMemoryCache(maxEntries = 1e3) {
60
59
  return;
61
60
  }
62
61
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
63
- const regex = new RegExp("^" + escaped + "$");
62
+ const regex = new RegExp(`^${escaped}$`);
64
63
  for (const key of cache.keys()) if (regex.test(key)) cache.delete(key);
65
64
  }
66
65
  };
67
66
  }
68
-
69
67
  //#endregion
70
68
  //#region src/utils/mongooseToJsonSchema.ts
71
69
  /**
@@ -91,7 +89,7 @@ function buildCrudSchemasFromMongooseSchema(mongooseSchema, options = {}) {
91
89
  * Build CRUD schemas from Mongoose model
92
90
  */
93
91
  function buildCrudSchemasFromModel(mongooseModel, options = {}) {
94
- if (!mongooseModel || !mongooseModel.schema) throw new Error("Invalid mongoose model");
92
+ if (!mongooseModel?.schema) throw new Error("Invalid mongoose model");
95
93
  return buildCrudSchemasFromMongooseSchema(mongooseModel.schema, options);
96
94
  }
97
95
  /**
@@ -161,7 +159,7 @@ function buildJsonSchemaFromPaths(mongooseSchema, options) {
161
159
  if (path === "_id" || path === "__v") continue;
162
160
  const rootField = path.split(".")[0];
163
161
  if (!rootFields.has(rootField)) rootFields.set(rootField, []);
164
- rootFields.get(rootField).push({
162
+ rootFields.get(rootField)?.push({
165
163
  path,
166
164
  schemaType
167
165
  });
@@ -185,7 +183,9 @@ function buildJsonSchemaFromPaths(mongooseSchema, options) {
185
183
  "updatedAt",
186
184
  "__v"
187
185
  ]);
188
- (options?.create?.omitFields || []).forEach((f) => fieldsToOmit.add(f));
186
+ (options?.create?.omitFields || []).forEach((f) => {
187
+ fieldsToOmit.add(f);
188
+ });
189
189
  const fieldRules = options?.fieldRules || {};
190
190
  Object.entries(fieldRules).forEach(([field, rules]) => {
191
191
  if (rules.systemManaged) fieldsToOmit.add(field);
@@ -281,7 +281,9 @@ function buildJsonSchemaForUpdate(createJson, options) {
281
281
  const clone = JSON.parse(JSON.stringify(createJson));
282
282
  delete clone.required;
283
283
  const fieldsToOmit = /* @__PURE__ */ new Set();
284
- (options?.update?.omitFields || []).forEach((f) => fieldsToOmit.add(f));
284
+ (options?.update?.omitFields || []).forEach((f) => {
285
+ fieldsToOmit.add(f);
286
+ });
285
287
  const fieldRules = options?.fieldRules || {};
286
288
  Object.entries(fieldRules).forEach(([field, rules]) => {
287
289
  if (rules.immutable || rules.immutableAfterCreate) fieldsToOmit.add(field);
@@ -312,6 +314,5 @@ function buildJsonSchemaForQuery(_tree, options) {
312
314
  for (const [k, v] of Object.entries(filterable)) if (basePagination.properties) basePagination.properties[k] = v && typeof v === "object" && "type" in v ? v : { type: "string" };
313
315
  return basePagination;
314
316
  }
315
-
316
317
  //#endregion
317
- export { isFieldUpdateAllowed as a, getSystemManagedFields as i, buildCrudSchemasFromMongooseSchema as n, validateUpdateBody as o, getImmutableFields as r, createMemoryCache as s, buildCrudSchemasFromModel as t };
318
+ export { isFieldUpdateAllowed as a, getSystemManagedFields as i, buildCrudSchemasFromMongooseSchema as n, validateUpdateBody as o, getImmutableFields as r, createMemoryCache as s, buildCrudSchemasFromModel as t };
@@ -1,6 +1,28 @@
1
- import { $ as SchemaBuilderOptions, T as HttpError, dt as UserContext, m as CrudSchemas, o as CacheAdapter, pt as ValidationResult, x as FieldPreset } from "./types-pVY0w1Pp.mjs";
1
+ import { E as HttpError, S as FieldPreset, et as SchemaBuilderOptions, ft as UserContext, h as CrudSchemas, mt as ValidationResult, o as CacheAdapter } from "./types-COINbsdL.mjs";
2
2
  import mongoose, { Schema } from "mongoose";
3
3
 
4
+ //#region src/utils/error.d.ts
5
+ /**
6
+ * Creates an error with HTTP status code
7
+ *
8
+ * @param status - HTTP status code
9
+ * @param message - Error message
10
+ * @returns Error with status property
11
+ *
12
+ * @example
13
+ * throw createError(404, 'Document not found');
14
+ * throw createError(400, 'Invalid input');
15
+ * throw createError(403, 'Access denied');
16
+ */
17
+ declare function createError(status: number, message: string): HttpError;
18
+ /**
19
+ * Detect and convert a MongoDB E11000 duplicate-key error into
20
+ * a 409 HttpError with an actionable message.
21
+ *
22
+ * Returns `null` when the error is not a duplicate-key error.
23
+ */
24
+ declare function parseDuplicateKeyError(error: unknown): HttpError | null;
25
+ //#endregion
4
26
  //#region src/utils/field-selection.d.ts
5
27
  /**
6
28
  * Get allowed fields for a user based on their context
@@ -69,21 +91,6 @@ declare function filterResponseData<T extends Record<string, unknown>>(data: T |
69
91
  */
70
92
  declare function createFieldPreset(config: Partial<FieldPreset>): FieldPreset;
71
93
  //#endregion
72
- //#region src/utils/error.d.ts
73
- /**
74
- * Creates an error with HTTP status code
75
- *
76
- * @param status - HTTP status code
77
- * @param message - Error message
78
- * @returns Error with status property
79
- *
80
- * @example
81
- * throw createError(404, 'Document not found');
82
- * throw createError(400, 'Invalid input');
83
- * throw createError(403, 'Access denied');
84
- */
85
- declare function createError(status: number, message: string): HttpError;
86
- //#endregion
87
94
  //#region src/utils/logger.d.ts
88
95
  /**
89
96
  * Internal Logger
@@ -155,4 +162,4 @@ declare function isFieldUpdateAllowed(fieldName: string, options?: SchemaBuilder
155
162
  */
156
163
  declare function validateUpdateBody(body?: Record<string, unknown>, options?: SchemaBuilderOptions): ValidationResult;
157
164
  //#endregion
158
- export { isFieldUpdateAllowed as a, configureLogger as c, filterResponseData as d, getFieldsForUser as f, getSystemManagedFields as i, createError as l, buildCrudSchemasFromMongooseSchema as n, validateUpdateBody as o, getMongooseProjection as p, getImmutableFields as r, createMemoryCache as s, buildCrudSchemasFromModel as t, createFieldPreset as u };
165
+ export { isFieldUpdateAllowed as a, configureLogger as c, getFieldsForUser as d, getMongooseProjection as f, getSystemManagedFields as i, createFieldPreset as l, parseDuplicateKeyError as m, buildCrudSchemasFromMongooseSchema as n, validateUpdateBody as o, createError as p, getImmutableFields as r, createMemoryCache as s, buildCrudSchemasFromModel as t, filterResponseData as u };
@@ -1,4 +1,4 @@
1
- import { A as KeysetPaginationOptions, I as OffsetPaginationOptions, L as OffsetPaginationResult, i as AnyDocument, j as KeysetPaginationResult, n as AggregatePaginationResult, t as AggregatePaginationOptions, z as PaginationConfig } from "../types-pVY0w1Pp.mjs";
1
+ import { B as PaginationConfig, L as OffsetPaginationOptions, M as KeysetPaginationResult, R as OffsetPaginationResult, i as AnyDocument, j as KeysetPaginationOptions, n as AggregatePaginationResult, t as AggregatePaginationOptions } from "../types-COINbsdL.mjs";
2
2
  import { Model } from "mongoose";
3
3
 
4
4
  //#region src/pagination/PaginationEngine.d.ts
@@ -1,209 +1,2 @@
1
- import { i as createError, r as warn } from "../logger-D8ily-PP.mjs";
2
- import { a as validatePage, c as validateKeysetSort, d as validateCursorSort, f as validateCursorVersion, i as validateLimit, l as decodeCursor, n as calculateTotalPages, o as buildKeysetFilter, r as shouldWarnDeepPagination, s as getPrimaryField, t as calculateSkip, u as encodeCursor } from "../limits-s1-d8rWb.mjs";
3
-
4
- //#region src/pagination/PaginationEngine.ts
5
- /**
6
- * Production-grade pagination engine for MongoDB
7
- * Supports offset, keyset (cursor), and aggregate pagination
8
- */
9
- var PaginationEngine = class {
10
- Model;
11
- config;
12
- /**
13
- * Create a new pagination engine
14
- *
15
- * @param Model - Mongoose model to paginate
16
- * @param config - Pagination configuration
17
- */
18
- constructor(Model, config = {}) {
19
- this.Model = Model;
20
- this.config = {
21
- defaultLimit: config.defaultLimit || 10,
22
- maxLimit: config.maxLimit || 100,
23
- maxPage: config.maxPage || 1e4,
24
- deepPageThreshold: config.deepPageThreshold || 100,
25
- cursorVersion: config.cursorVersion || 1,
26
- useEstimatedCount: config.useEstimatedCount || false
27
- };
28
- }
29
- /**
30
- * Offset-based pagination using skip/limit
31
- * Best for small datasets and when users need random page access
32
- * O(n) performance - slower for deep pages
33
- *
34
- * @param options - Pagination options
35
- * @returns Pagination result with total count
36
- *
37
- * @example
38
- * const result = await engine.paginate({
39
- * filters: { status: 'active' },
40
- * sort: { createdAt: -1 },
41
- * page: 1,
42
- * limit: 20
43
- * });
44
- * console.log(result.docs, result.total, result.hasNext);
45
- */
46
- async paginate(options = {}) {
47
- const { filters = {}, sort = { _id: -1 }, page = 1, limit = this.config.defaultLimit, select, populate = [], lean = true, session, hint, maxTimeMS, countStrategy = "exact", readPreference } = options;
48
- const sanitizedPage = validatePage(page, this.config);
49
- const sanitizedLimit = validateLimit(limit, this.config);
50
- const skip = calculateSkip(sanitizedPage, sanitizedLimit);
51
- const fetchLimit = countStrategy === "none" ? sanitizedLimit + 1 : sanitizedLimit;
52
- let query = this.Model.find(filters);
53
- if (select) query = query.select(select);
54
- if (populate && (Array.isArray(populate) ? populate.length : populate)) query = query.populate(populate);
55
- query = query.sort(sort).skip(skip).limit(fetchLimit).lean(lean);
56
- if (session) query = query.session(session);
57
- if (hint) query = query.hint(hint);
58
- if (maxTimeMS) query = query.maxTimeMS(maxTimeMS);
59
- if (readPreference) query = query.read(readPreference);
60
- const hasFilters = Object.keys(filters).length > 0;
61
- const useEstimated = this.config.useEstimatedCount && !hasFilters;
62
- let total = 0;
63
- if (countStrategy === "estimated" || useEstimated && countStrategy !== "exact") total = await this.Model.estimatedDocumentCount();
64
- else if (countStrategy === "exact") {
65
- const countQuery = this.Model.countDocuments(filters).session(session ?? null);
66
- if (hint) countQuery.hint(hint);
67
- if (maxTimeMS) countQuery.maxTimeMS(maxTimeMS);
68
- if (readPreference) countQuery.read(readPreference);
69
- total = await countQuery;
70
- }
71
- const [docs] = await Promise.all([query.exec()]);
72
- const totalPages = countStrategy === "none" ? 0 : calculateTotalPages(total, sanitizedLimit);
73
- let hasNext;
74
- if (countStrategy === "none") {
75
- hasNext = docs.length > sanitizedLimit;
76
- if (hasNext) docs.pop();
77
- } else hasNext = sanitizedPage < totalPages;
78
- const warning = shouldWarnDeepPagination(sanitizedPage, this.config.deepPageThreshold) ? `Deep pagination (page ${sanitizedPage}). Consider getAll({ after, sort, limit }) for better performance.` : void 0;
79
- return {
80
- method: "offset",
81
- docs,
82
- page: sanitizedPage,
83
- limit: sanitizedLimit,
84
- total,
85
- pages: totalPages,
86
- hasNext,
87
- hasPrev: sanitizedPage > 1,
88
- ...warning && { warning }
89
- };
90
- }
91
- /**
92
- * Keyset (cursor-based) pagination for high-performance streaming
93
- * Best for large datasets, infinite scroll, real-time feeds
94
- * O(1) performance - consistent speed regardless of position
95
- *
96
- * @param options - Pagination options (sort is required)
97
- * @returns Pagination result with next cursor
98
- *
99
- * @example
100
- * // First page
101
- * const page1 = await engine.stream({
102
- * sort: { createdAt: -1 },
103
- * limit: 20
104
- * });
105
- *
106
- * // Next page using cursor
107
- * const page2 = await engine.stream({
108
- * sort: { createdAt: -1 },
109
- * after: page1.next,
110
- * limit: 20
111
- * });
112
- */
113
- async stream(options) {
114
- const { filters = {}, sort, after, limit = this.config.defaultLimit, select, populate = [], lean = true, session, hint, maxTimeMS, readPreference } = options;
115
- if (!sort) throw createError(400, "sort is required for keyset pagination");
116
- const sanitizedLimit = validateLimit(limit, this.config);
117
- const normalizedSort = validateKeysetSort(sort);
118
- const filterKeys = Object.keys(filters).filter((k) => !k.startsWith("$"));
119
- const sortFields = Object.keys(normalizedSort);
120
- if (filterKeys.length > 0 && sortFields.length > 0) {
121
- const indexFields = [...filterKeys.map((f) => `${f}: 1`), ...sortFields.map((f) => `${f}: ${normalizedSort[f]}`)];
122
- warn(`[mongokit] Keyset pagination with filters [${filterKeys.join(", ")}] and sort [${sortFields.join(", ")}] requires a compound index for O(1) performance. Ensure index exists: { ${indexFields.join(", ")} }`);
123
- }
124
- let query = { ...filters };
125
- if (after) {
126
- const cursor = decodeCursor(after);
127
- validateCursorVersion(cursor.version, this.config.cursorVersion);
128
- validateCursorSort(cursor.sort, normalizedSort);
129
- query = buildKeysetFilter(query, normalizedSort, cursor.value, cursor.id);
130
- }
131
- let mongoQuery = this.Model.find(query);
132
- if (select) mongoQuery = mongoQuery.select(select);
133
- if (populate && (Array.isArray(populate) ? populate.length : populate)) mongoQuery = mongoQuery.populate(populate);
134
- mongoQuery = mongoQuery.sort(normalizedSort).limit(sanitizedLimit + 1).lean(lean);
135
- if (session) mongoQuery = mongoQuery.session(session);
136
- if (hint) mongoQuery = mongoQuery.hint(hint);
137
- if (maxTimeMS) mongoQuery = mongoQuery.maxTimeMS(maxTimeMS);
138
- if (readPreference) mongoQuery = mongoQuery.read(readPreference);
139
- const docs = await mongoQuery.exec();
140
- const hasMore = docs.length > sanitizedLimit;
141
- if (hasMore) docs.pop();
142
- const primaryField = getPrimaryField(normalizedSort);
143
- return {
144
- method: "keyset",
145
- docs,
146
- limit: sanitizedLimit,
147
- hasMore,
148
- next: hasMore && docs.length > 0 ? encodeCursor(docs[docs.length - 1], primaryField, normalizedSort, this.config.cursorVersion) : null
149
- };
150
- }
151
- /**
152
- * Aggregate pipeline with pagination
153
- * Best for complex queries requiring aggregation stages
154
- * Uses $facet to combine results and count in single query
155
- *
156
- * @param options - Aggregation options
157
- * @returns Pagination result with total count
158
- *
159
- * @example
160
- * const result = await engine.aggregatePaginate({
161
- * pipeline: [
162
- * { $match: { status: 'active' } },
163
- * { $group: { _id: '$category', count: { $sum: 1 } } },
164
- * { $sort: { count: -1 } }
165
- * ],
166
- * page: 1,
167
- * limit: 20
168
- * });
169
- */
170
- async aggregatePaginate(options = {}) {
171
- const { pipeline = [], page = 1, limit = this.config.defaultLimit, session, hint, maxTimeMS, countStrategy = "exact", readPreference } = options;
172
- const sanitizedPage = validatePage(page, this.config);
173
- const sanitizedLimit = validateLimit(limit, this.config);
174
- const skip = calculateSkip(sanitizedPage, sanitizedLimit);
175
- const fetchLimit = countStrategy === "none" ? sanitizedLimit + 1 : sanitizedLimit;
176
- const facetStages = { docs: [{ $skip: skip }, { $limit: fetchLimit }] };
177
- if (countStrategy !== "none") facetStages.total = [{ $count: "count" }];
178
- const facetPipeline = [...pipeline, { $facet: facetStages }];
179
- const aggregation = this.Model.aggregate(facetPipeline);
180
- if (session) aggregation.session(session);
181
- if (hint) aggregation.hint(hint);
182
- if (maxTimeMS) aggregation.option({ maxTimeMS });
183
- if (readPreference) aggregation.read(readPreference);
184
- const [result] = await aggregation.exec();
185
- const docs = result.docs;
186
- const total = result.total?.[0]?.count || 0;
187
- const totalPages = countStrategy === "none" ? 0 : calculateTotalPages(total, sanitizedLimit);
188
- let hasNext;
189
- if (countStrategy === "none") {
190
- hasNext = docs.length > sanitizedLimit;
191
- if (hasNext) docs.pop();
192
- } else hasNext = sanitizedPage < totalPages;
193
- const warning = shouldWarnDeepPagination(sanitizedPage, this.config.deepPageThreshold) ? `Deep pagination in aggregate (page ${sanitizedPage}). Uses $skip internally.` : void 0;
194
- return {
195
- method: "aggregate",
196
- docs,
197
- page: sanitizedPage,
198
- limit: sanitizedLimit,
199
- total,
200
- pages: totalPages,
201
- hasNext,
202
- hasPrev: sanitizedPage > 1,
203
- ...warning && { warning }
204
- };
205
- }
206
- };
207
-
208
- //#endregion
209
- export { PaginationEngine };
1
+ import { t as PaginationEngine } from "../PaginationEngine-nY04eGUM.mjs";
2
+ export { PaginationEngine };
@@ -1,3 +1,2 @@
1
- import "../types-pVY0w1Pp.mjs";
2
- import { A as subdocumentPlugin, B as immutableField, C as observabilityPlugin, D as CacheMethods, E as cascadePlugin, F as batchOperationsPlugin, G as methodRegistryPlugin, H as uniqueField, I as MongoOperationsMethods, J as auditLogPlugin, K as SoftDeleteMethods, L as mongoOperationsPlugin, M as aggregateHelpersPlugin, N as BatchOperationsMethods, O as cachePlugin, R as autoInject, S as OperationMetric, T as multiTenantPlugin, U as validationChainPlugin, V as requireField, W as MethodRegistryRepository, X as fieldFilterPlugin, Y as timestampPlugin, _ as AuditTrailMethods, a as SequentialIdOptions, b as auditTrailPlugin, c as getNextSequence, d as ElasticSearchOptions, f as elasticSearchPlugin, g as AuditQueryResult, h as AuditQueryOptions, i as PrefixedIdOptions, j as AggregateHelpersMethods, k as SubdocumentMethods, l as prefixedId, m as AuditOperation, n as DateSequentialIdOptions, o as customIdPlugin, p as AuditEntry, q as softDeletePlugin, r as IdGenerator, s as dateSequentialId, t as CustomIdOptions, u as sequentialId, v as AuditTrailOptions, w as MultiTenantOptions, x as ObservabilityOptions, y as AuditTrailQuery, z as blockIf } from "../custom-id.plugin-BJ3FSnzt.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-BNoaKDOm.mjs";
3
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,3 +1,2 @@
1
- import { C as methodRegistryPlugin, D as fieldFilterPlugin, E as timestampPlugin, S as validationChainPlugin, T as auditLogPlugin, _ as autoInject, a as sequentialId, b as requireField, c as auditTrailPlugin, d as cascadePlugin, f as cachePlugin, g as mongoOperationsPlugin, h as batchOperationsPlugin, i as prefixedId, l as observabilityPlugin, m as aggregateHelpersPlugin, n as dateSequentialId, o as elasticSearchPlugin, p as subdocumentPlugin, r as getNextSequence, s as AuditTrailQuery, t as customIdPlugin, u as multiTenantPlugin, v as blockIf, w as softDeletePlugin, x as uniqueField, y as immutableField } from "../custom-id.plugin-FInXDsUX.mjs";
2
-
3
- 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
+ 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-da3fOo8A.mjs";
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 };