@classytic/mongokit 3.2.5 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@
17
17
  - **Search governance** - Text index guard (throws `400` if no index), allowlisted sort/filter fields, ReDoS protection
18
18
  - **Vector search** - MongoDB Atlas `$vectorSearch` with auto-embedding and multimodal support
19
19
  - **TypeScript first** - Full type safety with discriminated unions
20
- - **592 passing tests** - Battle-tested and production-ready
20
+ - **700+ passing tests** - Battle-tested and production-ready
21
21
 
22
22
  ## Installation
23
23
 
@@ -1247,7 +1247,7 @@ Extending Repository works exactly the same with Mongoose 8 and 9. The package:
1247
1247
  - Uses its own event system (not Mongoose middleware)
1248
1248
  - Defines its own `FilterQuery` type (unaffected by Mongoose 9 rename)
1249
1249
  - Properly gates update pipelines (safe for Mongoose 9's stricter defaults)
1250
- - All 597 tests pass on Mongoose 9
1250
+ - All 700+ tests pass on Mongoose 9
1251
1251
 
1252
1252
  ## License
1253
1253
 
@@ -1,5 +1,5 @@
1
- import "../types-Bnwv9NV6.mjs";
2
- import { a as create_d_exports, i as read_d_exports, n as delete_d_exports, r as update_d_exports, t as aggregate_d_exports } from "../aggregate-D9x87vej.mjs";
1
+ import "../types-pVY0w1Pp.mjs";
2
+ import { a as create_d_exports, i as read_d_exports, n as delete_d_exports, r as update_d_exports, t as aggregate_d_exports } from "../aggregate-BkOG9qwr.mjs";
3
3
 
4
4
  //#region src/actions/index.d.ts
5
5
  declare namespace index_d_exports {
@@ -1,6 +1,5 @@
1
1
  import { t as __exportAll } from "../chunk-DQk6qfdC.mjs";
2
- import { r as create_exports } from "../create-BuO6xt0v.mjs";
3
- import { c as update_exports, m as read_exports, n as aggregate_exports, o as delete_exports } from "../aggregate-BAi4Do-X.mjs";
2
+ import { a as delete_exports, g as create_exports, p as read_exports, s as update_exports, t as aggregate_exports } from "../aggregate-BClp040M.mjs";
4
3
 
5
4
  //#region src/actions/index.ts
6
5
  var actions_exports = /* @__PURE__ */ __exportAll({
@@ -1,6 +1,58 @@
1
1
  import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
2
2
  import { i as createError, r as warn } from "./logger-D8ily-PP.mjs";
3
3
 
4
+ //#region src/actions/create.ts
5
+ var create_exports = /* @__PURE__ */ __exportAll({
6
+ create: () => create,
7
+ createDefault: () => createDefault,
8
+ createMany: () => createMany,
9
+ upsert: () => upsert
10
+ });
11
+ /**
12
+ * Create single document
13
+ */
14
+ async function create(Model, data, options = {}) {
15
+ const document = new Model(data);
16
+ await document.save({ session: options.session });
17
+ return document;
18
+ }
19
+ /**
20
+ * Create multiple documents
21
+ */
22
+ async function createMany(Model, dataArray, options = {}) {
23
+ return Model.insertMany(dataArray, {
24
+ session: options.session,
25
+ ordered: options.ordered !== false
26
+ });
27
+ }
28
+ /**
29
+ * Create with defaults (useful for initialization)
30
+ */
31
+ async function createDefault(Model, overrides = {}, options = {}) {
32
+ const defaults = {};
33
+ Model.schema.eachPath((path, schemaType) => {
34
+ const schemaOptions = schemaType.options;
35
+ if (schemaOptions.default !== void 0 && path !== "_id") defaults[path] = typeof schemaOptions.default === "function" ? schemaOptions.default() : schemaOptions.default;
36
+ });
37
+ return create(Model, {
38
+ ...defaults,
39
+ ...overrides
40
+ }, options);
41
+ }
42
+ /**
43
+ * Upsert (create or update)
44
+ */
45
+ async function upsert(Model, query, data, options = {}) {
46
+ return Model.findOneAndUpdate(query, { $setOnInsert: data }, {
47
+ upsert: true,
48
+ returnDocument: "after",
49
+ runValidators: true,
50
+ session: options.session,
51
+ ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
52
+ });
53
+ }
54
+
55
+ //#endregion
4
56
  //#region src/actions/read.ts
5
57
  var read_exports = /* @__PURE__ */ __exportAll({
6
58
  count: () => count,
@@ -154,7 +206,8 @@ async function update(Model, id, data, options = {}) {
154
206
  returnDocument: "after",
155
207
  runValidators: true,
156
208
  session: options.session,
157
- ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
209
+ ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {},
210
+ ...options.arrayFilters ? { arrayFilters: options.arrayFilters } : {}
158
211
  }).select(options.select || "").populate(parsePopulate(options.populate)).lean(options.lean ?? false);
159
212
  if (!document) throw createError(404, "Document not found");
160
213
  return document;
@@ -173,7 +226,8 @@ async function updateWithConstraints(Model, id, data, constraints = {}, options
173
226
  returnDocument: "after",
174
227
  runValidators: true,
175
228
  session: options.session,
176
- ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
229
+ ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {},
230
+ ...options.arrayFilters ? { arrayFilters: options.arrayFilters } : {}
177
231
  }).select(options.select || "").populate(parsePopulate(options.populate)).lean(options.lean ?? false);
178
232
  }
179
233
  /**
@@ -190,7 +244,11 @@ async function updateWithValidation(Model, id, data, validationOptions = {}, opt
190
244
  data: document
191
245
  };
192
246
  }
193
- const existing = await Model.findById(id).select(options.select || "").lean();
247
+ const findQuery = {
248
+ _id: id,
249
+ ...options.query
250
+ };
251
+ const existing = await Model.findOne(findQuery).select(options.select || "").session(options.session ?? null).lean();
194
252
  if (!existing) return {
195
253
  success: false,
196
254
  error: {
@@ -222,7 +280,8 @@ async function updateMany(Model, query, data, options = {}) {
222
280
  const result = await Model.updateMany(query, data, {
223
281
  runValidators: true,
224
282
  session: options.session,
225
- ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
283
+ ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {},
284
+ ...options.arrayFilters ? { arrayFilters: options.arrayFilters } : {}
226
285
  });
227
286
  return {
228
287
  matchedCount: result.matchedCount,
@@ -238,7 +297,8 @@ async function updateByQuery(Model, query, data, options = {}) {
238
297
  returnDocument: "after",
239
298
  runValidators: true,
240
299
  session: options.session,
241
- ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
300
+ ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {},
301
+ ...options.arrayFilters ? { arrayFilters: options.arrayFilters } : {}
242
302
  }).select(options.select || "").populate(parsePopulate(options.populate)).lean(options.lean ?? false);
243
303
  if (!document && options.throwOnNotFound !== false) throw createError(404, "Document not found");
244
304
  return document;
@@ -282,7 +342,8 @@ async function deleteById(Model, id, options = {}) {
282
342
  if (!await Model.findOneAndDelete(query).session(options.session ?? null)) throw createError(404, "Document not found");
283
343
  return {
284
344
  success: true,
285
- message: "Deleted successfully"
345
+ message: "Deleted successfully",
346
+ id: String(id)
286
347
  };
287
348
  }
288
349
  /**
@@ -299,10 +360,12 @@ async function deleteMany(Model, query, options = {}) {
299
360
  * Delete by query
300
361
  */
301
362
  async function deleteByQuery(Model, query, options = {}) {
302
- if (!await Model.findOneAndDelete(query).session(options.session ?? null) && options.throwOnNotFound !== false) throw createError(404, "Document not found");
363
+ const document = await Model.findOneAndDelete(query).session(options.session ?? null);
364
+ if (!document && options.throwOnNotFound !== false) throw createError(404, "Document not found");
303
365
  return {
304
366
  success: true,
305
- message: "Deleted successfully"
367
+ message: "Deleted successfully",
368
+ ...document ? { id: String(document._id) } : {}
306
369
  };
307
370
  }
308
371
  /**
@@ -319,7 +382,9 @@ async function softDelete(Model, id, options = {}) {
319
382
  })) throw createError(404, "Document not found");
320
383
  return {
321
384
  success: true,
322
- message: "Soft deleted successfully"
385
+ message: "Soft deleted successfully",
386
+ id: String(id),
387
+ soft: true
323
388
  };
324
389
  }
325
390
  /**
@@ -336,7 +401,8 @@ async function restore(Model, id, options = {}) {
336
401
  })) throw createError(404, "Document not found");
337
402
  return {
338
403
  success: true,
339
- message: "Restored successfully"
404
+ message: "Restored successfully",
405
+ id: String(id)
340
406
  };
341
407
  }
342
408
 
@@ -720,7 +786,9 @@ async function facet(Model, facets, options = {}) {
720
786
  * Get distinct values
721
787
  */
722
788
  async function distinct(Model, field, query = {}, options = {}) {
723
- return Model.distinct(field, query).session(options.session ?? null);
789
+ const q = Model.distinct(field, query).session(options.session ?? null);
790
+ if (options.readPreference) q.read(options.readPreference);
791
+ return q;
724
792
  }
725
793
  /**
726
794
  * Calculate sum
@@ -764,4 +832,4 @@ async function minMax(Model, field, query = {}, options = {}) {
764
832
  }
765
833
 
766
834
  //#endregion
767
- export { deleteById as a, update_exports as c, getById as d, getByQuery as f, LookupBuilder as i, count as l, read_exports as m, aggregate_exports as n, delete_exports as o, getOrCreate as p, distinct as r, update as s, aggregate as t, exists as u };
835
+ export { upsert as _, delete_exports as a, count as c, getByQuery as d, getOrCreate as f, create_exports as g, createMany as h, deleteById as i, exists as l, create as m, distinct as n, update as o, read_exports as p, LookupBuilder as r, update_exports as s, aggregate_exports as t, getById as u };
@@ -1,4 +1,4 @@
1
- import { C as GroupResult, F as ObjectId, G as PopulateSpec, K as ReadPreferenceType, N as MinMaxResult, R as OperationOptions, St as LookupOptions, _ as DeleteResult, at as SortSpec, ct as UpdateManyResult, et as SelectSpec, i as AnyDocument, lt as UpdateOptions, p as CreateOptions, ut as UpdateWithValidationResult } from "./types-Bnwv9NV6.mjs";
1
+ import { C as GroupResult, F as ObjectId, G as PopulateSpec, K as ReadPreferenceType, N as MinMaxResult, R as OperationOptions, St as LookupOptions, _ as DeleteResult, at as SortSpec, ct as UpdateManyResult, et as SelectSpec, i as AnyDocument, lt as UpdateOptions, p as CreateOptions, ut as UpdateWithValidationResult } from "./types-pVY0w1Pp.mjs";
2
2
  import { ClientSession, Model, PipelineStage } from "mongoose";
3
3
 
4
4
  //#region src/actions/create.d.ts
@@ -125,6 +125,7 @@ declare function updateWithValidation<TDoc = AnyDocument>(Model: Model<TDoc>, id
125
125
  declare function updateMany(Model: Model<unknown>, query: Record<string, unknown>, data: Record<string, unknown>, options?: {
126
126
  session?: ClientSession;
127
127
  updatePipeline?: boolean;
128
+ arrayFilters?: Record<string, unknown>[];
128
129
  }): Promise<UpdateManyResult>;
129
130
  /**
130
131
  * Update by query
@@ -246,6 +247,7 @@ declare function facet<TResult = Record<string, unknown[]>>(Model: Model<any>, f
246
247
  */
247
248
  declare function distinct<T = unknown>(Model: Model<any>, field: string, query?: Record<string, unknown>, options?: {
248
249
  session?: ClientSession;
250
+ readPreference?: string;
249
251
  }): Promise<T[]>;
250
252
  /**
251
253
  * Calculate sum
@@ -1,4 +1,4 @@
1
- import { H as Plugin } from "../types-Bnwv9NV6.mjs";
1
+ import { H as Plugin } from "../types-pVY0w1Pp.mjs";
2
2
  import { ClientSession, PipelineStage } from "mongoose";
3
3
 
4
4
  //#region src/ai/types.d.ts
@@ -119,15 +119,27 @@ function stableStringify(obj) {
119
119
  }
120
120
  /**
121
121
  * Generate cache key for getById operations
122
- *
123
- * Format: {prefix}:id:{model}:{documentId}
124
- *
122
+ *
123
+ * Includes select/populate/lean in the key so different query shapes
124
+ * (e.g., plain vs populated, lean vs hydrated) never collide.
125
+ *
126
+ * Format: {prefix}:id:{model}:{documentId} (no options)
127
+ * Format: {prefix}:id:{model}:{documentId}:{optionsHash} (with options)
128
+ *
125
129
  * @example
126
130
  * byIdKey('mk', 'User', '507f1f77bcf86cd799439011')
127
131
  * // => 'mk:id:User:507f1f77bcf86cd799439011'
132
+ * byIdKey('mk', 'User', '507f1f77bcf86cd799439011', { select: 'name email' })
133
+ * // => 'mk:id:User:507f1f77bcf86cd799439011:a1b2c3d4'
128
134
  */
129
- function byIdKey(prefix, model, id) {
130
- return `${prefix}:id:${model}:${id}`;
135
+ function byIdKey(prefix, model, id, options) {
136
+ const base = `${prefix}:id:${model}:${id}`;
137
+ if (!options || options.select == null && options.populate == null && options.lean == null) return base;
138
+ return `${base}:${hashString(stableStringify({
139
+ s: options.select,
140
+ p: options.populate,
141
+ l: options.lean
142
+ }))}`;
131
143
  }
132
144
  /**
133
145
  * Generate cache key for single-document queries
@@ -138,8 +150,8 @@ function byIdKey(prefix, model, id) {
138
150
  * byQueryKey('mk', 'User', { email: 'john@example.com' })
139
151
  * // => 'mk:one:User:a1b2c3d4'
140
152
  */
141
- function byQueryKey(prefix, model, query, options) {
142
- return `${prefix}:one:${model}:${hashString(stableStringify({
153
+ function byQueryKey(prefix, model, version, query, options) {
154
+ return `${prefix}:one:${model}:${version}:${hashString(stableStringify({
143
155
  q: query,
144
156
  s: options?.select,
145
157
  p: options?.populate
@@ -166,7 +178,14 @@ function listQueryKey(prefix, model, version, params) {
166
178
  lm: params.limit,
167
179
  af: params.after,
168
180
  sl: params.select,
169
- pp: params.populate
181
+ pp: params.populate,
182
+ sr: params.search,
183
+ md: params.mode,
184
+ ln: params.lean,
185
+ rp: params.readPreference,
186
+ hn: params.hint,
187
+ mt: params.maxTimeMS,
188
+ cs: params.countStrategy
170
189
  }))}`;
171
190
  }
172
191
  /**
@@ -193,7 +212,7 @@ function modelPattern(prefix, model) {
193
212
  }
194
213
  /**
195
214
  * Generate pattern for clearing all list cache keys for a model
196
- *
215
+ *
197
216
  * Format: {prefix}:list:{model}:*
198
217
  */
199
218
  function listPattern(prefix, model) {
@@ -1,4 +1,4 @@
1
- import { F as ObjectId, G as PopulateSpec, H as Plugin, L as OffsetPaginationResult, M as Logger, Y as RepositoryInstance, at as SortSpec, c as CacheOptions, et as SelectSpec, ft as ValidationChainOptions, l as CacheStats, mt as ValidatorDefinition, nt as SoftDeleteOptions, q as RepositoryContext, u as CascadeOptions, x as FieldPreset } from "./types-Bnwv9NV6.mjs";
1
+ import { F as ObjectId, G as PopulateSpec, H as Plugin, L as OffsetPaginationResult, M as Logger, Y as RepositoryInstance, at as SortSpec, c as CacheOptions, et as SelectSpec, ft as ValidationChainOptions, l as CacheStats, mt as ValidatorDefinition, nt as SoftDeleteOptions, q as RepositoryContext, u as CascadeOptions, x as FieldPreset } from "./types-pVY0w1Pp.mjs";
2
2
  import mongoose, { ClientSession } from "mongoose";
3
3
 
4
4
  //#region src/plugins/field-filter.plugin.d.ts
@@ -363,6 +363,23 @@ interface MongoOperationsMethods<TDoc> {
363
363
  * @returns Updated document
364
364
  */
365
365
  setMax(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
366
+ /**
367
+ * Atomic update with multiple MongoDB operators in a single call.
368
+ * Combines $inc, $set, $push, $pull, $addToSet, $unset, $setOnInsert, etc.
369
+ *
370
+ * @param id - Document ID
371
+ * @param operators - MongoDB update operators (e.g. { $inc: { views: 1 }, $set: { lastViewed: new Date() } })
372
+ * @param options - Operation options (session, arrayFilters, etc.)
373
+ * @returns Updated document
374
+ *
375
+ * @example
376
+ * await repo.atomicUpdate(postId, {
377
+ * $inc: { reactionCount: -1 },
378
+ * $set: { lastActiveAt: new Date() },
379
+ * $push: { history: { action: 'unreact', at: new Date() } }
380
+ * });
381
+ */
382
+ atomicUpdate(id: string | ObjectId, operators: Record<string, Record<string, unknown>>, options?: Record<string, unknown>): Promise<TDoc>;
366
383
  }
367
384
  //#endregion
368
385
  //#region src/plugins/batch-operations.plugin.d.ts
@@ -401,6 +418,17 @@ declare function batchOperationsPlugin(): Plugin;
401
418
  * await repo.deleteMany({ status: 'archived' });
402
419
  * ```
403
420
  */
421
+ /** Bulk write result */
422
+ interface BulkWriteResult {
423
+ ok: number;
424
+ insertedCount: number;
425
+ upsertedCount: number;
426
+ matchedCount: number;
427
+ modifiedCount: number;
428
+ deletedCount: number;
429
+ insertedIds: Record<number, unknown>;
430
+ upsertedIds: Record<number, unknown>;
431
+ }
404
432
  interface BatchOperationsMethods {
405
433
  /**
406
434
  * Update multiple documents matching the query
@@ -429,6 +457,26 @@ interface BatchOperationsMethods {
429
457
  acknowledged: boolean;
430
458
  deletedCount: number;
431
459
  }>;
460
+ /**
461
+ * Execute heterogeneous bulk write operations in a single database call.
462
+ * Supports insertOne, updateOne, updateMany, deleteOne, deleteMany, replaceOne.
463
+ *
464
+ * @param operations - Array of bulk write operations
465
+ * @param options - Options (session, ordered)
466
+ * @returns Bulk write result with counts per operation type
467
+ *
468
+ * @example
469
+ * const result = await repo.bulkWrite([
470
+ * { insertOne: { document: { name: 'Item', price: 10 } } },
471
+ * { updateOne: { filter: { _id: id }, update: { $inc: { views: 1 } } } },
472
+ * { deleteOne: { filter: { _id: oldId } } },
473
+ * ]);
474
+ * console.log(result.insertedCount, result.modifiedCount, result.deletedCount);
475
+ */
476
+ bulkWrite(operations: Record<string, unknown>[], options?: {
477
+ session?: ClientSession;
478
+ ordered?: boolean;
479
+ }): Promise<BulkWriteResult>;
432
480
  }
433
481
  //#endregion
434
482
  //#region src/plugins/aggregate-helpers.plugin.d.ts
@@ -920,7 +968,7 @@ interface CustomIdOptions {
920
968
  * const startSeq = await getNextSequence('invoices', 5);
921
969
  * // If current was 10, returns 15 (you use 11, 12, 13, 14, 15)
922
970
  */
923
- declare function getNextSequence(counterKey: string, increment?: number): Promise<number>;
971
+ declare function getNextSequence(counterKey: string, increment?: number, connection?: mongoose.Connection): Promise<number>;
924
972
  interface SequentialIdOptions {
925
973
  /** Prefix string (e.g., 'INV', 'ORD') */
926
974
  prefix: string;
@@ -1036,4 +1084,4 @@ declare function prefixedId(options: PrefixedIdOptions): IdGenerator;
1036
1084
  */
1037
1085
  declare function customIdPlugin(options: CustomIdOptions): Plugin;
1038
1086
  //#endregion
1039
- export { subdocumentPlugin as A, requireField as B, observabilityPlugin as C, CacheMethods as D, cascadePlugin as E, MongoOperationsMethods as F, SoftDeleteMethods as G, validationChainPlugin as H, mongoOperationsPlugin as I, timestampPlugin as J, softDeletePlugin as K, autoInject as L, aggregateHelpersPlugin as M, BatchOperationsMethods as N, cachePlugin as O, batchOperationsPlugin as P, blockIf as R, OperationMetric as S, multiTenantPlugin as T, MethodRegistryRepository as U, uniqueField as V, methodRegistryPlugin as W, fieldFilterPlugin as Y, AuditTrailMethods as _, SequentialIdOptions as a, auditTrailPlugin as b, getNextSequence as c, ElasticSearchOptions as d, elasticSearchPlugin as f, AuditQueryResult as g, AuditQueryOptions as h, PrefixedIdOptions as i, AggregateHelpersMethods as j, SubdocumentMethods as k, prefixedId as l, AuditOperation as m, DateSequentialIdOptions as n, customIdPlugin as o, AuditEntry as p, auditLogPlugin as q, IdGenerator as r, dateSequentialId as s, CustomIdOptions as t, sequentialId as u, AuditTrailOptions as v, MultiTenantOptions as w, ObservabilityOptions as x, AuditTrailQuery as y, immutableField as z };
1087
+ export { subdocumentPlugin as A, immutableField as B, observabilityPlugin as C, CacheMethods as D, cascadePlugin as E, batchOperationsPlugin as F, methodRegistryPlugin as G, uniqueField as H, MongoOperationsMethods as I, auditLogPlugin as J, SoftDeleteMethods as K, mongoOperationsPlugin as L, aggregateHelpersPlugin as M, BatchOperationsMethods as N, cachePlugin as O, BulkWriteResult as P, autoInject as R, OperationMetric as S, multiTenantPlugin as T, validationChainPlugin as U, requireField as V, MethodRegistryRepository as W, fieldFilterPlugin as X, timestampPlugin as Y, AuditTrailMethods as _, SequentialIdOptions as a, auditTrailPlugin as b, getNextSequence as c, ElasticSearchOptions as d, elasticSearchPlugin as f, AuditQueryResult as g, AuditQueryOptions as h, PrefixedIdOptions as i, AggregateHelpersMethods as j, SubdocumentMethods as k, prefixedId as l, AuditOperation as m, DateSequentialIdOptions as n, customIdPlugin as o, AuditEntry as p, softDeletePlugin as q, IdGenerator as r, dateSequentialId as s, CustomIdOptions as t, sequentialId as u, AuditTrailOptions as v, MultiTenantOptions as w, ObservabilityOptions as x, AuditTrailQuery as y, blockIf as z };