@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
@@ -1,29 +1,90 @@
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";
1
+ import { I as ObjectId, J as RepositoryContext, K as PopulateSpec, N as Logger, R as OffsetPaginationResult, S as FieldPreset, U as Plugin, X as RepositoryInstance, c as CacheOptions, ht as ValidatorDefinition, l as CacheStats, ot as SortSpec, pt as ValidationChainOptions, rt as SoftDeleteOptions, tt as SelectSpec, u as CascadeOptions } from "./types-COINbsdL.mjs";
2
2
  import mongoose, { ClientSession } from "mongoose";
3
3
 
4
- //#region src/plugins/field-filter.plugin.d.ts
4
+ //#region src/plugins/aggregate-helpers.plugin.d.ts
5
5
  /**
6
- * Field filter plugin that restricts fields based on user context
6
+ * Aggregate helpers plugin
7
7
  *
8
8
  * @example
9
- * const fieldPreset = {
10
- * public: ['id', 'name'],
11
- * authenticated: ['email'],
12
- * admin: ['createdAt', 'internalNotes']
13
- * };
9
+ * const repo = new Repository(Model, [
10
+ * methodRegistryPlugin(),
11
+ * aggregateHelpersPlugin(),
12
+ * ]);
14
13
  *
15
- * const repo = new Repository(Model, [fieldFilterPlugin(fieldPreset)]);
14
+ * const groups = await repo.groupBy('category');
15
+ * const total = await repo.sum('amount', { status: 'completed' });
16
16
  */
17
- declare function fieldFilterPlugin(fieldPreset: FieldPreset): Plugin;
18
- //#endregion
19
- //#region src/plugins/timestamp.plugin.d.ts
17
+ declare function aggregateHelpersPlugin(): Plugin;
20
18
  /**
21
- * Timestamp plugin that auto-injects timestamps
19
+ * Type interface for repositories using aggregateHelpersPlugin
22
20
  *
23
21
  * @example
24
- * const repo = new Repository(Model, [timestampPlugin()]);
22
+ * ```typescript
23
+ * import { Repository, methodRegistryPlugin, aggregateHelpersPlugin } from '@classytic/mongokit';
24
+ * import type { AggregateHelpersMethods } from '@classytic/mongokit';
25
+ *
26
+ * class OrderRepo extends Repository<IOrder> {}
27
+ *
28
+ * type OrderRepoWithAggregates = OrderRepo & AggregateHelpersMethods;
29
+ *
30
+ * const repo = new OrderRepo(OrderModel, [
31
+ * methodRegistryPlugin(),
32
+ * aggregateHelpersPlugin(),
33
+ * ]) as OrderRepoWithAggregates;
34
+ *
35
+ * // TypeScript autocomplete works!
36
+ * const groups = await repo.groupBy('status');
37
+ * const total = await repo.sum('amount', { status: 'completed' });
38
+ * const avg = await repo.average('amount');
39
+ * ```
25
40
  */
26
- declare function timestampPlugin(): Plugin;
41
+ interface AggregateHelpersMethods {
42
+ /**
43
+ * Group documents by field value and count occurrences
44
+ * @param field - Field to group by
45
+ * @param options - Operation options
46
+ * @returns Array of groups with _id and count
47
+ */
48
+ groupBy(field: string, options?: {
49
+ limit?: number;
50
+ session?: unknown;
51
+ }): Promise<Array<{
52
+ _id: unknown;
53
+ count: number;
54
+ }>>;
55
+ /**
56
+ * Calculate sum of field values
57
+ * @param field - Field to sum
58
+ * @param query - Filter query
59
+ * @param options - Operation options
60
+ * @returns Sum of field values
61
+ */
62
+ sum(field: string, query?: Record<string, unknown>, options?: Record<string, unknown>): Promise<number>;
63
+ /**
64
+ * Calculate average of field values
65
+ * @param field - Field to average
66
+ * @param query - Filter query
67
+ * @param options - Operation options
68
+ * @returns Average of field values
69
+ */
70
+ average(field: string, query?: Record<string, unknown>, options?: Record<string, unknown>): Promise<number>;
71
+ /**
72
+ * Get minimum field value
73
+ * @param field - Field to get minimum from
74
+ * @param query - Filter query
75
+ * @param options - Operation options
76
+ * @returns Minimum field value
77
+ */
78
+ min(field: string, query?: Record<string, unknown>, options?: Record<string, unknown>): Promise<number>;
79
+ /**
80
+ * Get maximum field value
81
+ * @param field - Field to get maximum from
82
+ * @param query - Filter query
83
+ * @param options - Operation options
84
+ * @returns Maximum field value
85
+ */
86
+ max(field: string, query?: Record<string, unknown>, options?: Record<string, unknown>): Promise<number>;
87
+ }
27
88
  //#endregion
28
89
  //#region src/plugins/audit-log.plugin.d.ts
29
90
  /**
@@ -34,688 +95,711 @@ declare function timestampPlugin(): Plugin;
34
95
  */
35
96
  declare function auditLogPlugin(logger: Logger): Plugin;
36
97
  //#endregion
37
- //#region src/plugins/soft-delete.plugin.d.ts
98
+ //#region src/plugins/audit-trail.plugin.d.ts
99
+ interface AuditTrailOptions {
100
+ /** Operations to track (default: ['create', 'update', 'delete']) */
101
+ operations?: AuditOperation[];
102
+ /** Store field-level before/after diff on updates (default: true) */
103
+ trackChanges?: boolean;
104
+ /** Store full document snapshot on create (default: false — can be heavy) */
105
+ trackDocument?: boolean;
106
+ /** Auto-purge after N days via MongoDB TTL index (default: undefined — keep forever) */
107
+ ttlDays?: number;
108
+ /** MongoDB collection name (default: 'audit_trails') */
109
+ collectionName?: string;
110
+ /**
111
+ * Extract custom metadata from the repository context.
112
+ * Returned object is stored on the audit entry as `metadata`.
113
+ */
114
+ metadata?: (context: RepositoryContext) => Record<string, unknown>;
115
+ /**
116
+ * Fields to exclude from change tracking (e.g., passwords, tokens).
117
+ * These fields are redacted in the `changes` diff.
118
+ */
119
+ excludeFields?: string[];
120
+ }
121
+ type AuditOperation = 'create' | 'update' | 'delete';
122
+ interface AuditEntry {
123
+ model: string;
124
+ operation: AuditOperation;
125
+ documentId: unknown;
126
+ userId?: unknown;
127
+ orgId?: unknown;
128
+ changes?: Record<string, {
129
+ from: unknown;
130
+ to: unknown;
131
+ }>;
132
+ document?: Record<string, unknown>;
133
+ metadata?: Record<string, unknown>;
134
+ timestamp: Date;
135
+ }
136
+ declare function auditTrailPlugin(options?: AuditTrailOptions): Plugin;
137
+ interface AuditQueryOptions {
138
+ model?: string;
139
+ documentId?: string | ObjectId;
140
+ userId?: string | ObjectId;
141
+ orgId?: string | ObjectId;
142
+ operation?: AuditOperation;
143
+ from?: Date;
144
+ to?: Date;
145
+ page?: number;
146
+ limit?: number;
147
+ }
148
+ interface AuditQueryResult {
149
+ docs: AuditEntry[];
150
+ page: number;
151
+ limit: number;
152
+ total: number;
153
+ pages: number;
154
+ hasNext: boolean;
155
+ hasPrev: boolean;
156
+ }
38
157
  /**
39
- * Soft delete plugin
158
+ * Standalone audit trail query utility.
159
+ * Use this to query audits across all models — e.g., admin dashboards, audit APIs.
40
160
  *
41
- * @example Basic usage
161
+ * @example
42
162
  * ```typescript
43
- * const repo = new Repository(Model, [
44
- * softDeletePlugin({ deletedField: 'deletedAt' })
45
- * ]);
46
- *
47
- * // Delete (soft)
48
- * await repo.delete(id);
49
- *
50
- * // Restore
51
- * await repo.restore(id);
52
- *
53
- * // Get deleted documents
54
- * await repo.getDeleted({ page: 1, limit: 20 });
55
- * ```
163
+ * import { AuditTrailQuery } from '@classytic/mongokit';
56
164
  *
57
- * @example With null filter mode (for schemas with default: null)
58
- * ```typescript
59
- * // Schema: { deletedAt: { type: Date, default: null } }
60
- * const repo = new Repository(Model, [
61
- * softDeletePlugin({
62
- * deletedField: 'deletedAt',
63
- * filterMode: 'null', // default - works with default: null
64
- * })
65
- * ]);
66
- * ```
165
+ * const auditQuery = new AuditTrailQuery(); // defaults to 'audit_trails' collection
67
166
  *
68
- * @example With TTL for auto-cleanup
69
- * ```typescript
70
- * const repo = new Repository(Model, [
71
- * softDeletePlugin({
72
- * deletedField: 'deletedAt',
73
- * ttlDays: 30, // Auto-delete after 30 days
74
- * })
75
- * ]);
76
- * ```
77
- */
78
- declare function softDeletePlugin(options?: SoftDeleteOptions): Plugin;
79
- /**
80
- * TypeScript interface for soft delete plugin methods
167
+ * // All audits for an org
168
+ * const orgAudits = await auditQuery.query({ orgId: '...' });
81
169
  *
82
- * @example
83
- * ```typescript
84
- * import type { SoftDeleteMethods } from '@classytic/mongokit';
170
+ * // All updates by a user
171
+ * const userUpdates = await auditQuery.query({
172
+ * userId: '...',
173
+ * operation: 'update',
174
+ * });
85
175
  *
86
- * type UserRepoWithSoftDelete = UserRepo & SoftDeleteMethods<IUser>;
176
+ * // All audits for a specific document
177
+ * const docHistory = await auditQuery.query({
178
+ * model: 'Job',
179
+ * documentId: '...',
180
+ * });
87
181
  *
88
- * const userRepo = new UserRepo(UserModel, [
89
- * methodRegistryPlugin(),
90
- * softDeletePlugin({ deletedField: 'deletedAt' }),
91
- * ]) as UserRepoWithSoftDelete;
182
+ * // Date range
183
+ * const recent = await auditQuery.query({
184
+ * from: new Date('2025-01-01'),
185
+ * to: new Date(),
186
+ * page: 1,
187
+ * limit: 50,
188
+ * });
92
189
  *
93
- * // TypeScript autocomplete for soft delete methods
94
- * await userRepo.restore(userId);
95
- * const deleted = await userRepo.getDeleted({ page: 1, limit: 20 });
190
+ * // Direct model access for custom queries
191
+ * const model = auditQuery.getModel();
192
+ * const count = await model.countDocuments({ operation: 'delete' });
96
193
  * ```
97
194
  */
98
- interface SoftDeleteMethods<TDoc> {
195
+ declare class AuditTrailQuery {
196
+ private model;
197
+ constructor(collectionName?: string, ttlDays?: number);
99
198
  /**
100
- * Restore a soft-deleted document
101
- * @param id - Document ID to restore
102
- * @param options - Optional restore options
103
- * @returns Restored document
199
+ * Get the underlying Mongoose model for custom queries
104
200
  */
105
- restore(id: string | ObjectId, options?: {
106
- session?: ClientSession;
107
- }): Promise<TDoc>;
201
+ getModel(): mongoose.Model<AuditEntry>;
108
202
  /**
109
- * Get paginated list of soft-deleted documents
110
- * @param params - Query parameters (filters, sort, pagination)
111
- * @param options - Query options (select, populate, lean, session)
112
- * @returns Paginated result of deleted documents
203
+ * Query audit entries with filters and pagination
113
204
  */
114
- getDeleted(params?: {
115
- filters?: Record<string, unknown>;
116
- sort?: SortSpec | string;
205
+ query(options?: AuditQueryOptions): Promise<AuditQueryResult>;
206
+ /**
207
+ * Get audit trail for a specific document
208
+ */
209
+ getDocumentTrail(model: string, documentId: string | ObjectId, options?: {
117
210
  page?: number;
118
211
  limit?: number;
119
- }, options?: {
120
- select?: SelectSpec;
121
- populate?: PopulateSpec;
122
- lean?: boolean;
123
- session?: ClientSession;
124
- }): Promise<OffsetPaginationResult<TDoc>>;
212
+ operation?: AuditOperation;
213
+ }): Promise<AuditQueryResult>;
214
+ /**
215
+ * Get all audits for a user
216
+ */
217
+ getUserTrail(userId: string | ObjectId, options?: {
218
+ page?: number;
219
+ limit?: number;
220
+ operation?: AuditOperation;
221
+ orgId?: string | ObjectId;
222
+ }): Promise<AuditQueryResult>;
223
+ /**
224
+ * Get all audits for an organization
225
+ */
226
+ getOrgTrail(orgId: string | ObjectId, options?: {
227
+ page?: number;
228
+ limit?: number;
229
+ operation?: AuditOperation;
230
+ model?: string;
231
+ }): Promise<AuditQueryResult>;
125
232
  }
126
- //#endregion
127
- //#region src/plugins/method-registry.plugin.d.ts
128
- /**
129
- * Extended repository interface with method registry
130
- */
131
- interface MethodRegistryRepository extends RepositoryInstance {
132
- registerMethod(name: string, fn: Function): void;
133
- hasMethod(name: string): boolean;
134
- getRegisteredMethods(): string[];
233
+ interface AuditTrailMethods {
234
+ /**
235
+ * Get paginated audit trail for a document
236
+ */
237
+ getAuditTrail(documentId: string | ObjectId, options?: {
238
+ page?: number;
239
+ limit?: number;
240
+ operation?: AuditOperation;
241
+ }): Promise<AuditQueryResult>;
135
242
  }
136
- /**
137
- * Method registry plugin that enables dynamic method registration
138
- */
139
- declare function methodRegistryPlugin(): Plugin;
140
243
  //#endregion
141
- //#region src/plugins/validation-chain.plugin.d.ts
142
- type OperationType = 'create' | 'createMany' | 'update' | 'delete';
244
+ //#region src/plugins/batch-operations.plugin.d.ts
143
245
  /**
144
- * Validation chain plugin
246
+ * Batch operations plugin
145
247
  *
146
248
  * @example
147
249
  * const repo = new Repository(Model, [
148
- * validationChainPlugin([
149
- * requireField('email'),
150
- * uniqueField('email', 'Email already exists'),
151
- * blockIf('no-delete-admin', ['delete'], ctx => ctx.data?.role === 'admin', 'Cannot delete admin'),
152
- * ])
250
+ * methodRegistryPlugin(),
251
+ * batchOperationsPlugin(),
153
252
  * ]);
154
- */
155
- declare function validationChainPlugin(validators?: ValidatorDefinition[], options?: ValidationChainOptions): Plugin;
156
- /**
157
- * Block operation if condition is true
158
253
  *
159
- * @example
160
- * blockIf('block-library', ['delete'], ctx => ctx.data?.managed, 'Cannot delete managed records')
161
- */
162
- declare function blockIf(name: string, operations: OperationType[], condition: (context: RepositoryContext) => boolean, errorMessage: string): ValidatorDefinition;
163
- /**
164
- * Require a field to be present
165
- */
166
- declare function requireField(field: string, operations?: OperationType[]): ValidatorDefinition;
167
- /**
168
- * Auto-inject a value if not present
169
- */
170
- declare function autoInject(field: string, getter: (context: RepositoryContext) => unknown, operations?: OperationType[]): ValidatorDefinition;
171
- /**
172
- * Make a field immutable (cannot be updated)
173
- */
174
- declare function immutableField(field: string): ValidatorDefinition;
175
- /**
176
- * Ensure field value is unique
254
+ * await repo.updateMany({ status: 'pending' }, { status: 'active' });
255
+ * await repo.deleteMany({ status: 'deleted' });
177
256
  */
178
- declare function uniqueField(field: string, errorMessage?: string): ValidatorDefinition;
179
- //#endregion
180
- //#region src/plugins/mongo-operations.plugin.d.ts
257
+ declare function batchOperationsPlugin(): Plugin;
181
258
  /**
182
- * MongoDB operations plugin
183
- *
184
- * Adds MongoDB-specific atomic operations to repositories:
185
- * - upsert: Create or update document
186
- * - increment/decrement: Atomic numeric operations
187
- * - pushToArray/pullFromArray/addToSet: Array operations
188
- * - setField/unsetField/renameField: Field operations
189
- * - multiplyField: Multiply numeric field
190
- * - setMin/setMax: Conditional min/max updates
191
- *
192
- * @example Basic usage (no TypeScript autocomplete)
193
- * ```typescript
194
- * const repo = new Repository(ProductModel, [
195
- * methodRegistryPlugin(),
196
- * mongoOperationsPlugin(),
197
- * ]);
198
- *
199
- * // Works at runtime but TypeScript doesn't know about these methods
200
- * await (repo as any).increment(productId, 'views', 1);
201
- * await (repo as any).pushToArray(productId, 'tags', 'featured');
202
- * ```
259
+ * Type interface for repositories using batchOperationsPlugin
203
260
  *
204
- * @example With TypeScript type safety (recommended)
261
+ * @example
205
262
  * ```typescript
206
- * import { Repository, mongoOperationsPlugin, methodRegistryPlugin } from '@classytic/mongokit';
207
- * import type { MongoOperationsMethods } from '@classytic/mongokit';
263
+ * import { Repository, methodRegistryPlugin, batchOperationsPlugin } from '@classytic/mongokit';
264
+ * import type { BatchOperationsMethods } from '@classytic/mongokit';
208
265
  *
209
- * class ProductRepo extends Repository<IProduct> {
210
- * // Add your custom methods here
211
- * }
266
+ * class ProductRepo extends Repository<IProduct> {}
212
267
  *
213
- * // Create with type assertion to get autocomplete for plugin methods
214
- * type ProductRepoWithPlugins = ProductRepo & MongoOperationsMethods<IProduct>;
268
+ * type ProductRepoWithBatch = ProductRepo & BatchOperationsMethods;
215
269
  *
216
270
  * const repo = new ProductRepo(ProductModel, [
217
271
  * methodRegistryPlugin(),
218
- * mongoOperationsPlugin(),
219
- * ]) as ProductRepoWithPlugins;
272
+ * batchOperationsPlugin(),
273
+ * ]) as ProductRepoWithBatch;
220
274
  *
221
- * // Now TypeScript provides autocomplete and type checking!
222
- * await repo.increment(productId, 'views', 1);
223
- * await repo.upsert({ sku: 'ABC' }, { name: 'Product', price: 99 });
224
- * await repo.pushToArray(productId, 'tags', 'featured');
275
+ * // TypeScript autocomplete works!
276
+ * await repo.updateMany({ status: 'pending' }, { status: 'active' });
277
+ * await repo.deleteMany({ status: 'archived' });
225
278
  * ```
226
279
  */
227
- declare function mongoOperationsPlugin(): Plugin;
280
+ /** Bulk write result */
281
+ interface BulkWriteResult {
282
+ ok: number;
283
+ insertedCount: number;
284
+ upsertedCount: number;
285
+ matchedCount: number;
286
+ modifiedCount: number;
287
+ deletedCount: number;
288
+ insertedIds: Record<number, unknown>;
289
+ upsertedIds: Record<number, unknown>;
290
+ }
291
+ interface BatchOperationsMethods {
292
+ /**
293
+ * Update multiple documents matching the query
294
+ * @param query - Query to match documents
295
+ * @param data - Update data
296
+ * @param options - Operation options
297
+ * @returns Update result with matchedCount, modifiedCount, etc.
298
+ */
299
+ updateMany(query: Record<string, unknown>, data: Record<string, unknown>, options?: {
300
+ session?: ClientSession;
301
+ updatePipeline?: boolean;
302
+ }): Promise<{
303
+ acknowledged: boolean;
304
+ matchedCount: number;
305
+ modifiedCount: number;
306
+ upsertedCount: number;
307
+ upsertedId: unknown;
308
+ }>;
309
+ /**
310
+ * Delete multiple documents matching the query
311
+ * @param query - Query to match documents
312
+ * @param options - Operation options
313
+ * @returns Delete result with deletedCount
314
+ */
315
+ deleteMany(query: Record<string, unknown>, options?: Record<string, unknown>): Promise<{
316
+ acknowledged: boolean;
317
+ deletedCount: number;
318
+ }>;
319
+ /**
320
+ * Execute heterogeneous bulk write operations in a single database call.
321
+ * Supports insertOne, updateOne, updateMany, deleteOne, deleteMany, replaceOne.
322
+ *
323
+ * @param operations - Array of bulk write operations
324
+ * @param options - Options (session, ordered)
325
+ * @returns Bulk write result with counts per operation type
326
+ *
327
+ * @example
328
+ * const result = await repo.bulkWrite([
329
+ * { insertOne: { document: { name: 'Item', price: 10 } } },
330
+ * { updateOne: { filter: { _id: id }, update: { $inc: { views: 1 } } } },
331
+ * { deleteOne: { filter: { _id: oldId } } },
332
+ * ]);
333
+ * console.log(result.insertedCount, result.modifiedCount, result.deletedCount);
334
+ */
335
+ bulkWrite(operations: Record<string, unknown>[], options?: {
336
+ session?: ClientSession;
337
+ ordered?: boolean;
338
+ }): Promise<BulkWriteResult>;
339
+ }
340
+ //#endregion
341
+ //#region src/plugins/cache.plugin.d.ts
228
342
  /**
229
- * Type interface for repositories using mongoOperationsPlugin
343
+ * Cache plugin factory
230
344
  *
231
- * Use this interface to get TypeScript autocomplete and type safety
232
- * for the methods added by mongoOperationsPlugin.
345
+ * @param options - Cache configuration
346
+ * @returns Plugin instance
347
+ */
348
+ declare function cachePlugin(options: CacheOptions): Plugin;
349
+ /**
350
+ * TypeScript interface for cache plugin methods
233
351
  *
234
352
  * @example
235
353
  * ```typescript
236
- * import { Repository, mongoOperationsPlugin, methodRegistryPlugin } from '@classytic/mongokit';
237
- * import type { MongoOperationsMethods } from '@classytic/mongokit';
238
- *
239
- * // Without type safety (base is flexible)
240
- * class ProductRepo extends Repository<IProduct> {
241
- * // Can add anything - fully flexible
242
- * }
354
+ * import type { CacheMethods } from '@classytic/mongokit';
243
355
  *
244
- * // With type safety for plugin methods
245
- * class ProductRepo extends Repository<IProduct> implements MongoOperationsMethods<IProduct> {
246
- * // TypeScript knows about upsert, increment, decrement, etc.
247
- * }
356
+ * type ProductRepoWithCache = ProductRepo & CacheMethods;
248
357
  *
249
- * const repo = new ProductRepo(ProductModel, [
358
+ * const productRepo = new ProductRepo(ProductModel, [
250
359
  * methodRegistryPlugin(),
251
- * mongoOperationsPlugin(),
252
- * ]);
360
+ * cachePlugin({ adapter: redisAdapter, ttl: 60 }),
361
+ * ]) as ProductRepoWithCache;
253
362
  *
254
- * // Now TypeScript provides autocomplete and type checking
255
- * await repo.increment(productId, 'views', 1);
256
- * await repo.upsert({ sku: 'ABC' }, { name: 'Product' });
363
+ * // TypeScript autocomplete for cache methods
364
+ * await productRepo.invalidateCache(productId);
365
+ * await productRepo.invalidateListCache();
366
+ * await productRepo.invalidateAllCache();
367
+ * const stats = productRepo.getCacheStats();
368
+ * productRepo.resetCacheStats();
257
369
  * ```
258
370
  */
259
- interface MongoOperationsMethods<TDoc> {
371
+ interface CacheMethods {
260
372
  /**
261
- * Update existing document or insert new one
262
- * @param query - Query to find document
263
- * @param data - Data to update or insert
264
- * @param options - Operation options (session, etc.)
265
- * @returns Created or updated document
373
+ * Invalidate cache for a specific document
374
+ * Use when document was updated outside this service
375
+ * @param id - Document ID to invalidate
266
376
  */
267
- upsert(query: Record<string, unknown>, data: Record<string, unknown>, options?: Record<string, unknown>): Promise<TDoc>;
377
+ invalidateCache(id: string): Promise<void>;
268
378
  /**
269
- * Atomically increment numeric field
270
- * @param id - Document ID
271
- * @param field - Field name to increment
272
- * @param value - Value to increment by (default: 1)
273
- * @param options - Operation options (session, etc.)
274
- * @returns Updated document
379
+ * Invalidate all list caches for this model
380
+ * Use when bulk changes happened outside this service
275
381
  */
276
- increment(id: string | ObjectId, field: string, value?: number, options?: Record<string, unknown>): Promise<TDoc>;
382
+ invalidateListCache(): Promise<void>;
277
383
  /**
278
- * Atomically decrement numeric field
279
- * @param id - Document ID
280
- * @param field - Field name to decrement
281
- * @param value - Value to decrement by (default: 1)
282
- * @param options - Operation options (session, etc.)
283
- * @returns Updated document
284
- */
285
- decrement(id: string | ObjectId, field: string, value?: number, options?: Record<string, unknown>): Promise<TDoc>;
286
- /**
287
- * Push value to array field
288
- * @param id - Document ID
289
- * @param field - Array field name
290
- * @param value - Value to push
291
- * @param options - Operation options (session, etc.)
292
- * @returns Updated document
293
- */
294
- pushToArray(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
295
- /**
296
- * Remove value from array field
297
- * @param id - Document ID
298
- * @param field - Array field name
299
- * @param value - Value to remove
300
- * @param options - Operation options (session, etc.)
301
- * @returns Updated document
302
- */
303
- pullFromArray(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
304
- /**
305
- * Add value to array only if not already present (unique)
306
- * @param id - Document ID
307
- * @param field - Array field name
308
- * @param value - Value to add
309
- * @param options - Operation options (session, etc.)
310
- * @returns Updated document
311
- */
312
- addToSet(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
313
- /**
314
- * Set field value (alias for update with $set)
315
- * @param id - Document ID
316
- * @param field - Field name
317
- * @param value - Value to set
318
- * @param options - Operation options (session, etc.)
319
- * @returns Updated document
320
- */
321
- setField(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
322
- /**
323
- * Unset (remove) field from document
324
- * @param id - Document ID
325
- * @param fields - Field name or array of field names to remove
326
- * @param options - Operation options (session, etc.)
327
- * @returns Updated document
328
- */
329
- unsetField(id: string | ObjectId, fields: string | string[], options?: Record<string, unknown>): Promise<TDoc>;
330
- /**
331
- * Rename field in document
332
- * @param id - Document ID
333
- * @param oldName - Current field name
334
- * @param newName - New field name
335
- * @param options - Operation options (session, etc.)
336
- * @returns Updated document
337
- */
338
- renameField(id: string | ObjectId, oldName: string, newName: string, options?: Record<string, unknown>): Promise<TDoc>;
339
- /**
340
- * Multiply numeric field by value
341
- * @param id - Document ID
342
- * @param field - Field name
343
- * @param multiplier - Multiplier value
344
- * @param options - Operation options (session, etc.)
345
- * @returns Updated document
346
- */
347
- multiplyField(id: string | ObjectId, field: string, multiplier: number, options?: Record<string, unknown>): Promise<TDoc>;
348
- /**
349
- * Set field to minimum value (only if current value is greater)
350
- * @param id - Document ID
351
- * @param field - Field name
352
- * @param value - Minimum value
353
- * @param options - Operation options (session, etc.)
354
- * @returns Updated document
384
+ * Invalidate ALL cache entries for this model
385
+ * Nuclear option - use sparingly
355
386
  */
356
- setMin(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
387
+ invalidateAllCache(): Promise<void>;
357
388
  /**
358
- * Set field to maximum value (only if current value is less)
359
- * @param id - Document ID
360
- * @param field - Field name
361
- * @param value - Maximum value
362
- * @param options - Operation options (session, etc.)
363
- * @returns Updated document
389
+ * Get cache statistics for monitoring
390
+ * @returns Cache statistics (hits, misses, sets, invalidations)
364
391
  */
365
- setMax(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
392
+ getCacheStats(): CacheStats;
366
393
  /**
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
- * });
394
+ * Reset cache statistics
381
395
  */
382
- atomicUpdate(id: string | ObjectId, operators: Record<string, Record<string, unknown>>, options?: Record<string, unknown>): Promise<TDoc>;
396
+ resetCacheStats(): void;
383
397
  }
384
398
  //#endregion
385
- //#region src/plugins/batch-operations.plugin.d.ts
399
+ //#region src/plugins/cascade.plugin.d.ts
386
400
  /**
387
- * Batch operations plugin
401
+ * Cascade delete plugin
402
+ *
403
+ * Deletes related documents after the parent document is deleted.
404
+ * Works with both hard delete and soft delete scenarios.
405
+ *
406
+ * @param options - Cascade configuration
407
+ * @returns Plugin
408
+ */
409
+ declare function cascadePlugin(options: CascadeOptions): Plugin;
410
+ //#endregion
411
+ //#region src/plugins/custom-id.plugin.d.ts
412
+ /**
413
+ * Generator function that produces a unique ID.
414
+ * Receives the full repository context for conditional logic.
415
+ */
416
+ type IdGenerator = (context: RepositoryContext) => string | Promise<string>;
417
+ interface CustomIdOptions {
418
+ /** Field to store the custom ID (default: 'customId') */
419
+ field?: string;
420
+ /** Function to generate the ID. Can be async. */
421
+ generator: IdGenerator;
422
+ /** Only generate if the field is missing/empty (default: true) */
423
+ generateOnlyIfEmpty?: boolean;
424
+ }
425
+ /**
426
+ * Atomically increment and return the next sequence value for a given key.
427
+ * Uses `findOneAndUpdate` with `upsert` + `$inc` — fully atomic even under
428
+ * heavy concurrency.
429
+ *
430
+ * @param counterKey - Unique key identifying this counter (e.g., "Invoice" or "Invoice:2026-02")
431
+ * @param increment - Value to increment by (default: 1)
432
+ * @returns The next sequence number (after increment)
388
433
  *
389
434
  * @example
390
- * const repo = new Repository(Model, [
391
- * methodRegistryPlugin(),
392
- * batchOperationsPlugin(),
393
- * ]);
435
+ * const seq = await getNextSequence('invoices');
436
+ * // First call → 1, second → 2, ...
394
437
  *
395
- * await repo.updateMany({ status: 'pending' }, { status: 'active' });
396
- * await repo.deleteMany({ status: 'deleted' });
438
+ * @example Batch increment for createMany
439
+ * const startSeq = await getNextSequence('invoices', 5);
440
+ * // If current was 10, returns 15 (you use 11, 12, 13, 14, 15)
397
441
  */
398
- declare function batchOperationsPlugin(): Plugin;
442
+ declare function getNextSequence(counterKey: string, increment?: number, connection?: mongoose.Connection): Promise<number>;
443
+ interface SequentialIdOptions {
444
+ /** Prefix string (e.g., 'INV', 'ORD') */
445
+ prefix: string;
446
+ /** Mongoose model — used to derive the counter key from model name */
447
+ model: mongoose.Model<any>;
448
+ /** Number of digits to pad to (default: 4 → "0001") */
449
+ padding?: number;
450
+ /** Separator between prefix and number (default: '-') */
451
+ separator?: string;
452
+ /** Custom counter key override (default: model.modelName) */
453
+ counterKey?: string;
454
+ }
399
455
  /**
400
- * Type interface for repositories using batchOperationsPlugin
456
+ * Generator: Simple sequential counter.
457
+ * Produces IDs like `INV-0001`, `INV-0002`, etc.
458
+ *
459
+ * Uses atomic MongoDB counters — safe under concurrency.
401
460
  *
402
461
  * @example
403
462
  * ```typescript
404
- * import { Repository, methodRegistryPlugin, batchOperationsPlugin } from '@classytic/mongokit';
405
- * import type { BatchOperationsMethods } from '@classytic/mongokit';
463
+ * customIdPlugin({
464
+ * field: 'invoiceNumber',
465
+ * generator: sequentialId({ prefix: 'INV', model: InvoiceModel }),
466
+ * })
467
+ * ```
468
+ */
469
+ declare function sequentialId(options: SequentialIdOptions): IdGenerator;
470
+ interface DateSequentialIdOptions {
471
+ /** Prefix string (e.g., 'BILL', 'INV') */
472
+ prefix: string;
473
+ /** Mongoose model — used to derive the counter key */
474
+ model: mongoose.Model<any>;
475
+ /**
476
+ * Partition granularity — counter resets each period.
477
+ * - 'yearly' → BILL-2026-0001, resets every January
478
+ * - 'monthly' → BILL-2026-02-0001, resets every month
479
+ * - 'daily' → BILL-2026-02-20-0001, resets every day
480
+ */
481
+ partition?: 'yearly' | 'monthly' | 'daily';
482
+ /** Number of digits to pad to (default: 4) */
483
+ padding?: number;
484
+ /** Separator (default: '-') */
485
+ separator?: string;
486
+ }
487
+ /**
488
+ * Generator: Date-partitioned sequential counter.
489
+ * Counter resets per period — great for invoice/bill numbering.
406
490
  *
407
- * class ProductRepo extends Repository<IProduct> {}
491
+ * Produces IDs like:
492
+ * - yearly: `BILL-2026-0001`
493
+ * - monthly: `BILL-2026-02-0001`
494
+ * - daily: `BILL-2026-02-20-0001`
408
495
  *
409
- * type ProductRepoWithBatch = ProductRepo & BatchOperationsMethods;
496
+ * @example
497
+ * ```typescript
498
+ * customIdPlugin({
499
+ * field: 'billNumber',
500
+ * generator: dateSequentialId({
501
+ * prefix: 'BILL',
502
+ * model: BillModel,
503
+ * partition: 'monthly',
504
+ * }),
505
+ * })
506
+ * ```
507
+ */
508
+ declare function dateSequentialId(options: DateSequentialIdOptions): IdGenerator;
509
+ interface PrefixedIdOptions {
510
+ /** Prefix string (e.g., 'USR', 'TXN') */
511
+ prefix: string;
512
+ /** Separator (default: '_') */
513
+ separator?: string;
514
+ /** Length of the random suffix (default: 12) */
515
+ length?: number;
516
+ }
517
+ /**
518
+ * Generator: Prefix + random alphanumeric suffix.
519
+ * Does NOT require a database round-trip — purely in-memory.
410
520
  *
411
- * const repo = new ProductRepo(ProductModel, [
412
- * methodRegistryPlugin(),
413
- * batchOperationsPlugin(),
414
- * ]) as ProductRepoWithBatch;
521
+ * Produces IDs like: `USR_a7b3xk9m2p1q`
415
522
  *
416
- * // TypeScript autocomplete works!
417
- * await repo.updateMany({ status: 'pending' }, { status: 'active' });
418
- * await repo.deleteMany({ status: 'archived' });
523
+ * Good for: user-facing IDs where ordering doesn't matter.
524
+ * Not suitable for sequential numbering.
525
+ *
526
+ * @example
527
+ * ```typescript
528
+ * customIdPlugin({
529
+ * field: 'publicId',
530
+ * generator: prefixedId({ prefix: 'USR', length: 10 }),
531
+ * })
419
532
  * ```
420
533
  */
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
- }
432
- interface BatchOperationsMethods {
433
- /**
434
- * Update multiple documents matching the query
435
- * @param query - Query to match documents
436
- * @param data - Update data
437
- * @param options - Operation options
438
- * @returns Update result with matchedCount, modifiedCount, etc.
439
- */
440
- updateMany(query: Record<string, unknown>, data: Record<string, unknown>, options?: {
441
- session?: ClientSession;
442
- updatePipeline?: boolean;
443
- }): Promise<{
444
- acknowledged: boolean;
445
- matchedCount: number;
446
- modifiedCount: number;
447
- upsertedCount: number;
448
- upsertedId: unknown;
449
- }>;
450
- /**
451
- * Delete multiple documents matching the query
452
- * @param query - Query to match documents
453
- * @param options - Operation options
454
- * @returns Delete result with deletedCount
455
- */
456
- deleteMany(query: Record<string, unknown>, options?: Record<string, unknown>): Promise<{
457
- acknowledged: boolean;
458
- deletedCount: number;
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>;
534
+ declare function prefixedId(options: PrefixedIdOptions): IdGenerator;
535
+ /**
536
+ * Custom ID plugin — injects generated IDs into documents before creation.
537
+ *
538
+ * @param options - Configuration for ID generation
539
+ * @returns Plugin instance
540
+ *
541
+ * @example
542
+ * ```typescript
543
+ * import { Repository, customIdPlugin, sequentialId } from '@classytic/mongokit';
544
+ *
545
+ * const invoiceRepo = new Repository(InvoiceModel, [
546
+ * customIdPlugin({
547
+ * field: 'invoiceNumber',
548
+ * generator: sequentialId({ prefix: 'INV', model: InvoiceModel }),
549
+ * }),
550
+ * ]);
551
+ *
552
+ * const inv = await invoiceRepo.create({ amount: 100 });
553
+ * console.log(inv.invoiceNumber); // "INV-0001"
554
+ * ```
555
+ */
556
+ declare function customIdPlugin(options: CustomIdOptions): Plugin;
557
+ //#endregion
558
+ //#region src/plugins/elastic.plugin.d.ts
559
+ interface ElasticSearchOptions {
560
+ /** Elasticsearch or OpenSearch client instance */
561
+ client: any;
562
+ /** Index name to perform search against */
563
+ index: string;
564
+ /** Field to extract MongoDB ID from the indexed document (default: '_id') */
565
+ idField?: string;
480
566
  }
567
+ declare function elasticSearchPlugin(options: ElasticSearchOptions): Plugin;
481
568
  //#endregion
482
- //#region src/plugins/aggregate-helpers.plugin.d.ts
569
+ //#region src/plugins/field-filter.plugin.d.ts
483
570
  /**
484
- * Aggregate helpers plugin
571
+ * Field filter plugin that restricts fields based on user context
485
572
  *
486
573
  * @example
487
- * const repo = new Repository(Model, [
574
+ * const fieldPreset = {
575
+ * public: ['id', 'name'],
576
+ * authenticated: ['email'],
577
+ * admin: ['createdAt', 'internalNotes']
578
+ * };
579
+ *
580
+ * const repo = new Repository(Model, [fieldFilterPlugin(fieldPreset)]);
581
+ */
582
+ declare function fieldFilterPlugin(fieldPreset: FieldPreset): Plugin;
583
+ //#endregion
584
+ //#region src/plugins/method-registry.plugin.d.ts
585
+ /**
586
+ * Extended repository interface with method registry
587
+ */
588
+ interface MethodRegistryRepository extends RepositoryInstance {
589
+ registerMethod(name: string, fn: (...args: any[]) => any): void;
590
+ hasMethod(name: string): boolean;
591
+ getRegisteredMethods(): string[];
592
+ }
593
+ /**
594
+ * Method registry plugin that enables dynamic method registration
595
+ */
596
+ declare function methodRegistryPlugin(): Plugin;
597
+ //#endregion
598
+ //#region src/plugins/mongo-operations.plugin.d.ts
599
+ /**
600
+ * MongoDB operations plugin
601
+ *
602
+ * Adds MongoDB-specific atomic operations to repositories:
603
+ * - upsert: Create or update document
604
+ * - increment/decrement: Atomic numeric operations
605
+ * - pushToArray/pullFromArray/addToSet: Array operations
606
+ * - setField/unsetField/renameField: Field operations
607
+ * - multiplyField: Multiply numeric field
608
+ * - setMin/setMax: Conditional min/max updates
609
+ *
610
+ * @example Basic usage (no TypeScript autocomplete)
611
+ * ```typescript
612
+ * const repo = new Repository(ProductModel, [
488
613
  * methodRegistryPlugin(),
489
- * aggregateHelpersPlugin(),
614
+ * mongoOperationsPlugin(),
490
615
  * ]);
491
616
  *
492
- * const groups = await repo.groupBy('category');
493
- * const total = await repo.sum('amount', { status: 'completed' });
494
- */
495
- declare function aggregateHelpersPlugin(): Plugin;
496
- /**
497
- * Type interface for repositories using aggregateHelpersPlugin
617
+ * // Works at runtime but TypeScript doesn't know about these methods
618
+ * await (repo as any).increment(productId, 'views', 1);
619
+ * await (repo as any).pushToArray(productId, 'tags', 'featured');
620
+ * ```
498
621
  *
499
- * @example
622
+ * @example With TypeScript type safety (recommended)
500
623
  * ```typescript
501
- * import { Repository, methodRegistryPlugin, aggregateHelpersPlugin } from '@classytic/mongokit';
502
- * import type { AggregateHelpersMethods } from '@classytic/mongokit';
624
+ * import { Repository, mongoOperationsPlugin, methodRegistryPlugin } from '@classytic/mongokit';
625
+ * import type { MongoOperationsMethods } from '@classytic/mongokit';
503
626
  *
504
- * class OrderRepo extends Repository<IOrder> {}
627
+ * class ProductRepo extends Repository<IProduct> {
628
+ * // Add your custom methods here
629
+ * }
505
630
  *
506
- * type OrderRepoWithAggregates = OrderRepo & AggregateHelpersMethods;
631
+ * // Create with type assertion to get autocomplete for plugin methods
632
+ * type ProductRepoWithPlugins = ProductRepo & MongoOperationsMethods<IProduct>;
507
633
  *
508
- * const repo = new OrderRepo(OrderModel, [
634
+ * const repo = new ProductRepo(ProductModel, [
509
635
  * methodRegistryPlugin(),
510
- * aggregateHelpersPlugin(),
511
- * ]) as OrderRepoWithAggregates;
636
+ * mongoOperationsPlugin(),
637
+ * ]) as ProductRepoWithPlugins;
512
638
  *
513
- * // TypeScript autocomplete works!
514
- * const groups = await repo.groupBy('status');
515
- * const total = await repo.sum('amount', { status: 'completed' });
516
- * const avg = await repo.average('amount');
639
+ * // Now TypeScript provides autocomplete and type checking!
640
+ * await repo.increment(productId, 'views', 1);
641
+ * await repo.upsert({ sku: 'ABC' }, { name: 'Product', price: 99 });
642
+ * await repo.pushToArray(productId, 'tags', 'featured');
517
643
  * ```
518
644
  */
519
- interface AggregateHelpersMethods {
520
- /**
521
- * Group documents by field value and count occurrences
522
- * @param field - Field to group by
523
- * @param options - Operation options
524
- * @returns Array of groups with _id and count
525
- */
526
- groupBy(field: string, options?: {
527
- limit?: number;
528
- session?: unknown;
529
- }): Promise<Array<{
530
- _id: unknown;
531
- count: number;
532
- }>>;
533
- /**
534
- * Calculate sum of field values
535
- * @param field - Field to sum
536
- * @param query - Filter query
537
- * @param options - Operation options
538
- * @returns Sum of field values
539
- */
540
- sum(field: string, query?: Record<string, unknown>, options?: Record<string, unknown>): Promise<number>;
541
- /**
542
- * Calculate average of field values
543
- * @param field - Field to average
544
- * @param query - Filter query
545
- * @param options - Operation options
546
- * @returns Average of field values
547
- */
548
- average(field: string, query?: Record<string, unknown>, options?: Record<string, unknown>): Promise<number>;
549
- /**
550
- * Get minimum field value
551
- * @param field - Field to get minimum from
552
- * @param query - Filter query
553
- * @param options - Operation options
554
- * @returns Minimum field value
555
- */
556
- min(field: string, query?: Record<string, unknown>, options?: Record<string, unknown>): Promise<number>;
557
- /**
558
- * Get maximum field value
559
- * @param field - Field to get maximum from
560
- * @param query - Filter query
561
- * @param options - Operation options
562
- * @returns Maximum field value
563
- */
564
- max(field: string, query?: Record<string, unknown>, options?: Record<string, unknown>): Promise<number>;
565
- }
566
- //#endregion
567
- //#region src/plugins/subdocument.plugin.d.ts
645
+ declare function mongoOperationsPlugin(): Plugin;
568
646
  /**
569
- * Subdocument plugin for managing nested arrays
570
- *
571
- * @example
572
- * const repo = new Repository(Model, [
573
- * methodRegistryPlugin(),
574
- * subdocumentPlugin(),
575
- * ]);
647
+ * Type interface for repositories using mongoOperationsPlugin
576
648
  *
577
- * await repo.addSubdocument(parentId, 'items', { name: 'Item 1' });
578
- * await repo.updateSubdocument(parentId, 'items', itemId, { name: 'Updated Item' });
579
- */
580
- declare function subdocumentPlugin(): Plugin;
581
- /**
582
- * Type interface for repositories using subdocumentPlugin
649
+ * Use this interface to get TypeScript autocomplete and type safety
650
+ * for the methods added by mongoOperationsPlugin.
583
651
  *
584
652
  * @example
585
653
  * ```typescript
586
- * import { Repository, methodRegistryPlugin, subdocumentPlugin } from '@classytic/mongokit';
587
- * import type { SubdocumentMethods } from '@classytic/mongokit';
654
+ * import { Repository, mongoOperationsPlugin, methodRegistryPlugin } from '@classytic/mongokit';
655
+ * import type { MongoOperationsMethods } from '@classytic/mongokit';
588
656
  *
589
- * class OrderRepo extends Repository<IOrder> {}
657
+ * // Without type safety (base is flexible)
658
+ * class ProductRepo extends Repository<IProduct> {
659
+ * // Can add anything - fully flexible
660
+ * }
590
661
  *
591
- * type OrderRepoWithSubdocs = OrderRepo & SubdocumentMethods<IOrder>;
662
+ * // With type safety for plugin methods
663
+ * class ProductRepo extends Repository<IProduct> implements MongoOperationsMethods<IProduct> {
664
+ * // TypeScript knows about upsert, increment, decrement, etc.
665
+ * }
592
666
  *
593
- * const repo = new OrderRepo(OrderModel, [
667
+ * const repo = new ProductRepo(ProductModel, [
594
668
  * methodRegistryPlugin(),
595
- * subdocumentPlugin(),
596
- * ]) as OrderRepoWithSubdocs;
669
+ * mongoOperationsPlugin(),
670
+ * ]);
597
671
  *
598
- * // TypeScript autocomplete works!
599
- * await repo.addSubdocument(orderId, 'items', { productId: '123', quantity: 2 });
600
- * await repo.updateSubdocument(orderId, 'items', itemId, { quantity: 5 });
601
- * await repo.deleteSubdocument(orderId, 'items', itemId);
672
+ * // Now TypeScript provides autocomplete and type checking
673
+ * await repo.increment(productId, 'views', 1);
674
+ * await repo.upsert({ sku: 'ABC' }, { name: 'Product' });
602
675
  * ```
603
676
  */
604
- interface SubdocumentMethods<TDoc> {
677
+ interface MongoOperationsMethods<TDoc> {
605
678
  /**
606
- * Add subdocument to array field
607
- * @param parentId - Parent document ID
608
- * @param arrayPath - Path to array field (e.g., 'items', 'addresses')
609
- * @param subData - Subdocument data
610
- * @param options - Operation options
611
- * @returns Updated parent document
679
+ * Update existing document or insert new one
680
+ * @param query - Query to find document
681
+ * @param data - Data to update or insert
682
+ * @param options - Operation options (session, etc.)
683
+ * @returns Created or updated document
612
684
  */
613
- addSubdocument(parentId: string | ObjectId, arrayPath: string, subData: Record<string, unknown>, options?: Record<string, unknown>): Promise<TDoc>;
685
+ upsert(query: Record<string, unknown>, data: Record<string, unknown>, options?: Record<string, unknown>): Promise<TDoc>;
614
686
  /**
615
- * Get subdocument from array field
616
- * @param parentId - Parent document ID
617
- * @param arrayPath - Path to array field
618
- * @param subId - Subdocument ID
619
- * @param options - Operation options
620
- * @returns Subdocument
687
+ * Atomically increment numeric field
688
+ * @param id - Document ID
689
+ * @param field - Field name to increment
690
+ * @param value - Value to increment by (default: 1)
691
+ * @param options - Operation options (session, etc.)
692
+ * @returns Updated document
621
693
  */
622
- getSubdocument(parentId: string | ObjectId, arrayPath: string, subId: string | ObjectId, options?: {
623
- lean?: boolean;
624
- session?: unknown;
625
- }): Promise<Record<string, unknown>>;
694
+ increment(id: string | ObjectId, field: string, value?: number, options?: Record<string, unknown>): Promise<TDoc>;
626
695
  /**
627
- * Update subdocument in array field
628
- * @param parentId - Parent document ID
629
- * @param arrayPath - Path to array field
630
- * @param subId - Subdocument ID
631
- * @param updateData - Update data
632
- * @param options - Operation options
633
- * @returns Updated parent document
696
+ * Atomically decrement numeric field
697
+ * @param id - Document ID
698
+ * @param field - Field name to decrement
699
+ * @param value - Value to decrement by (default: 1)
700
+ * @param options - Operation options (session, etc.)
701
+ * @returns Updated document
634
702
  */
635
- updateSubdocument(parentId: string | ObjectId, arrayPath: string, subId: string | ObjectId, updateData: Record<string, unknown>, options?: {
636
- session?: unknown;
637
- }): Promise<TDoc>;
703
+ decrement(id: string | ObjectId, field: string, value?: number, options?: Record<string, unknown>): Promise<TDoc>;
638
704
  /**
639
- * Delete subdocument from array field
640
- * @param parentId - Parent document ID
641
- * @param arrayPath - Path to array field
642
- * @param subId - Subdocument ID
643
- * @param options - Operation options
644
- * @returns Updated parent document
705
+ * Push value to array field
706
+ * @param id - Document ID
707
+ * @param field - Array field name
708
+ * @param value - Value to push
709
+ * @param options - Operation options (session, etc.)
710
+ * @returns Updated document
645
711
  */
646
- deleteSubdocument(parentId: string | ObjectId, arrayPath: string, subId: string | ObjectId, options?: Record<string, unknown>): Promise<TDoc>;
647
- }
648
- //#endregion
649
- //#region src/plugins/cache.plugin.d.ts
650
- /**
651
- * Cache plugin factory
652
- *
653
- * @param options - Cache configuration
654
- * @returns Plugin instance
655
- */
656
- declare function cachePlugin(options: CacheOptions): Plugin;
657
- /**
658
- * TypeScript interface for cache plugin methods
659
- *
660
- * @example
661
- * ```typescript
662
- * import type { CacheMethods } from '@classytic/mongokit';
663
- *
664
- * type ProductRepoWithCache = ProductRepo & CacheMethods;
665
- *
666
- * const productRepo = new ProductRepo(ProductModel, [
667
- * methodRegistryPlugin(),
668
- * cachePlugin({ adapter: redisAdapter, ttl: 60 }),
669
- * ]) as ProductRepoWithCache;
670
- *
671
- * // TypeScript autocomplete for cache methods
672
- * await productRepo.invalidateCache(productId);
673
- * await productRepo.invalidateListCache();
674
- * await productRepo.invalidateAllCache();
675
- * const stats = productRepo.getCacheStats();
676
- * productRepo.resetCacheStats();
677
- * ```
678
- */
679
- interface CacheMethods {
712
+ pushToArray(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
713
+ /**
714
+ * Remove value from array field
715
+ * @param id - Document ID
716
+ * @param field - Array field name
717
+ * @param value - Value to remove
718
+ * @param options - Operation options (session, etc.)
719
+ * @returns Updated document
720
+ */
721
+ pullFromArray(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
722
+ /**
723
+ * Add value to array only if not already present (unique)
724
+ * @param id - Document ID
725
+ * @param field - Array field name
726
+ * @param value - Value to add
727
+ * @param options - Operation options (session, etc.)
728
+ * @returns Updated document
729
+ */
730
+ addToSet(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
731
+ /**
732
+ * Set field value (alias for update with $set)
733
+ * @param id - Document ID
734
+ * @param field - Field name
735
+ * @param value - Value to set
736
+ * @param options - Operation options (session, etc.)
737
+ * @returns Updated document
738
+ */
739
+ setField(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
680
740
  /**
681
- * Invalidate cache for a specific document
682
- * Use when document was updated outside this service
683
- * @param id - Document ID to invalidate
741
+ * Unset (remove) field from document
742
+ * @param id - Document ID
743
+ * @param fields - Field name or array of field names to remove
744
+ * @param options - Operation options (session, etc.)
745
+ * @returns Updated document
684
746
  */
685
- invalidateCache(id: string): Promise<void>;
747
+ unsetField(id: string | ObjectId, fields: string | string[], options?: Record<string, unknown>): Promise<TDoc>;
686
748
  /**
687
- * Invalidate all list caches for this model
688
- * Use when bulk changes happened outside this service
749
+ * Rename field in document
750
+ * @param id - Document ID
751
+ * @param oldName - Current field name
752
+ * @param newName - New field name
753
+ * @param options - Operation options (session, etc.)
754
+ * @returns Updated document
689
755
  */
690
- invalidateListCache(): Promise<void>;
756
+ renameField(id: string | ObjectId, oldName: string, newName: string, options?: Record<string, unknown>): Promise<TDoc>;
691
757
  /**
692
- * Invalidate ALL cache entries for this model
693
- * Nuclear option - use sparingly
758
+ * Multiply numeric field by value
759
+ * @param id - Document ID
760
+ * @param field - Field name
761
+ * @param multiplier - Multiplier value
762
+ * @param options - Operation options (session, etc.)
763
+ * @returns Updated document
694
764
  */
695
- invalidateAllCache(): Promise<void>;
765
+ multiplyField(id: string | ObjectId, field: string, multiplier: number, options?: Record<string, unknown>): Promise<TDoc>;
696
766
  /**
697
- * Get cache statistics for monitoring
698
- * @returns Cache statistics (hits, misses, sets, invalidations)
767
+ * Set field to minimum value (only if current value is greater)
768
+ * @param id - Document ID
769
+ * @param field - Field name
770
+ * @param value - Minimum value
771
+ * @param options - Operation options (session, etc.)
772
+ * @returns Updated document
699
773
  */
700
- getCacheStats(): CacheStats;
774
+ setMin(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
701
775
  /**
702
- * Reset cache statistics
776
+ * Set field to maximum value (only if current value is less)
777
+ * @param id - Document ID
778
+ * @param field - Field name
779
+ * @param value - Maximum value
780
+ * @param options - Operation options (session, etc.)
781
+ * @returns Updated document
703
782
  */
704
- resetCacheStats(): void;
783
+ setMax(id: string | ObjectId, field: string, value: unknown, options?: Record<string, unknown>): Promise<TDoc>;
784
+ /**
785
+ * Atomic update with multiple MongoDB operators in a single call.
786
+ * Combines $inc, $set, $push, $pull, $addToSet, $unset, $setOnInsert, etc.
787
+ *
788
+ * @param id - Document ID
789
+ * @param operators - MongoDB update operators (e.g. { $inc: { views: 1 }, $set: { lastViewed: new Date() } })
790
+ * @param options - Operation options (session, arrayFilters, etc.)
791
+ * @returns Updated document
792
+ *
793
+ * @example
794
+ * await repo.atomicUpdate(postId, {
795
+ * $inc: { reactionCount: -1 },
796
+ * $set: { lastActiveAt: new Date() },
797
+ * $push: { history: { action: 'unreact', at: new Date() } }
798
+ * });
799
+ */
800
+ atomicUpdate(id: string | ObjectId, operators: Record<string, Record<string, unknown>>, options?: Record<string, unknown>): Promise<TDoc>;
705
801
  }
706
802
  //#endregion
707
- //#region src/plugins/cascade.plugin.d.ts
708
- /**
709
- * Cascade delete plugin
710
- *
711
- * Deletes related documents after the parent document is deleted.
712
- * Works with both hard delete and soft delete scenarios.
713
- *
714
- * @param options - Cascade configuration
715
- * @returns Plugin
716
- */
717
- declare function cascadePlugin(options: CascadeOptions): Plugin;
718
- //#endregion
719
803
  //#region src/plugins/multi-tenant.plugin.d.ts
720
804
  interface MultiTenantOptions {
721
805
  /** Field name used for tenant isolation (default: 'organizationId') */
@@ -780,308 +864,224 @@ interface ObservabilityOptions {
780
864
  }
781
865
  declare function observabilityPlugin(options: ObservabilityOptions): Plugin;
782
866
  //#endregion
783
- //#region src/plugins/audit-trail.plugin.d.ts
784
- interface AuditTrailOptions {
785
- /** Operations to track (default: ['create', 'update', 'delete']) */
786
- operations?: AuditOperation[];
787
- /** Store field-level before/after diff on updates (default: true) */
788
- trackChanges?: boolean;
789
- /** Store full document snapshot on create (default: false — can be heavy) */
790
- trackDocument?: boolean;
791
- /** Auto-purge after N days via MongoDB TTL index (default: undefined — keep forever) */
792
- ttlDays?: number;
793
- /** MongoDB collection name (default: 'audit_trails') */
794
- collectionName?: string;
795
- /**
796
- * Extract custom metadata from the repository context.
797
- * Returned object is stored on the audit entry as `metadata`.
798
- */
799
- metadata?: (context: RepositoryContext) => Record<string, unknown>;
800
- /**
801
- * Fields to exclude from change tracking (e.g., passwords, tokens).
802
- * These fields are redacted in the `changes` diff.
803
- */
804
- excludeFields?: string[];
805
- }
806
- type AuditOperation = 'create' | 'update' | 'delete';
807
- interface AuditEntry {
808
- model: string;
809
- operation: AuditOperation;
810
- documentId: unknown;
811
- userId?: unknown;
812
- orgId?: unknown;
813
- changes?: Record<string, {
814
- from: unknown;
815
- to: unknown;
816
- }>;
817
- document?: Record<string, unknown>;
818
- metadata?: Record<string, unknown>;
819
- timestamp: Date;
820
- }
821
- declare function auditTrailPlugin(options?: AuditTrailOptions): Plugin;
822
- interface AuditQueryOptions {
823
- model?: string;
824
- documentId?: string | ObjectId;
825
- userId?: string | ObjectId;
826
- orgId?: string | ObjectId;
827
- operation?: AuditOperation;
828
- from?: Date;
829
- to?: Date;
830
- page?: number;
831
- limit?: number;
832
- }
833
- interface AuditQueryResult {
834
- docs: AuditEntry[];
835
- page: number;
836
- limit: number;
837
- total: number;
838
- pages: number;
839
- hasNext: boolean;
840
- hasPrev: boolean;
841
- }
867
+ //#region src/plugins/soft-delete.plugin.d.ts
842
868
  /**
843
- * Standalone audit trail query utility.
844
- * Use this to query audits across all models — e.g., admin dashboards, audit APIs.
869
+ * Soft delete plugin
845
870
  *
846
- * @example
871
+ * @example Basic usage
847
872
  * ```typescript
848
- * import { AuditTrailQuery } from '@classytic/mongokit';
873
+ * const repo = new Repository(Model, [
874
+ * softDeletePlugin({ deletedField: 'deletedAt' })
875
+ * ]);
849
876
  *
850
- * const auditQuery = new AuditTrailQuery(); // defaults to 'audit_trails' collection
877
+ * // Delete (soft)
878
+ * await repo.delete(id);
851
879
  *
852
- * // All audits for an org
853
- * const orgAudits = await auditQuery.query({ orgId: '...' });
880
+ * // Restore
881
+ * await repo.restore(id);
854
882
  *
855
- * // All updates by a user
856
- * const userUpdates = await auditQuery.query({
857
- * userId: '...',
858
- * operation: 'update',
859
- * });
883
+ * // Get deleted documents
884
+ * await repo.getDeleted({ page: 1, limit: 20 });
885
+ * ```
860
886
  *
861
- * // All audits for a specific document
862
- * const docHistory = await auditQuery.query({
863
- * model: 'Job',
864
- * documentId: '...',
865
- * });
887
+ * @example With null filter mode (for schemas with default: null)
888
+ * ```typescript
889
+ * // Schema: { deletedAt: { type: Date, default: null } }
890
+ * const repo = new Repository(Model, [
891
+ * softDeletePlugin({
892
+ * deletedField: 'deletedAt',
893
+ * filterMode: 'null', // default - works with default: null
894
+ * })
895
+ * ]);
896
+ * ```
866
897
  *
867
- * // Date range
868
- * const recent = await auditQuery.query({
869
- * from: new Date('2025-01-01'),
870
- * to: new Date(),
871
- * page: 1,
872
- * limit: 50,
873
- * });
898
+ * @example With TTL for auto-cleanup
899
+ * ```typescript
900
+ * const repo = new Repository(Model, [
901
+ * softDeletePlugin({
902
+ * deletedField: 'deletedAt',
903
+ * ttlDays: 30, // Auto-delete after 30 days
904
+ * })
905
+ * ]);
906
+ * ```
907
+ */
908
+ declare function softDeletePlugin(options?: SoftDeleteOptions): Plugin;
909
+ /**
910
+ * TypeScript interface for soft delete plugin methods
874
911
  *
875
- * // Direct model access for custom queries
876
- * const model = auditQuery.getModel();
877
- * const count = await model.countDocuments({ operation: 'delete' });
912
+ * @example
913
+ * ```typescript
914
+ * import type { SoftDeleteMethods } from '@classytic/mongokit';
915
+ *
916
+ * type UserRepoWithSoftDelete = UserRepo & SoftDeleteMethods<IUser>;
917
+ *
918
+ * const userRepo = new UserRepo(UserModel, [
919
+ * methodRegistryPlugin(),
920
+ * softDeletePlugin({ deletedField: 'deletedAt' }),
921
+ * ]) as UserRepoWithSoftDelete;
922
+ *
923
+ * // TypeScript autocomplete for soft delete methods
924
+ * await userRepo.restore(userId);
925
+ * const deleted = await userRepo.getDeleted({ page: 1, limit: 20 });
878
926
  * ```
879
927
  */
880
- declare class AuditTrailQuery {
881
- private model;
882
- constructor(collectionName?: string, ttlDays?: number);
883
- /**
884
- * Get the underlying Mongoose model for custom queries
885
- */
886
- getModel(): mongoose.Model<AuditEntry>;
887
- /**
888
- * Query audit entries with filters and pagination
889
- */
890
- query(options?: AuditQueryOptions): Promise<AuditQueryResult>;
891
- /**
892
- * Get audit trail for a specific document
893
- */
894
- getDocumentTrail(model: string, documentId: string | ObjectId, options?: {
895
- page?: number;
896
- limit?: number;
897
- operation?: AuditOperation;
898
- }): Promise<AuditQueryResult>;
899
- /**
900
- * Get all audits for a user
901
- */
902
- getUserTrail(userId: string | ObjectId, options?: {
903
- page?: number;
904
- limit?: number;
905
- operation?: AuditOperation;
906
- orgId?: string | ObjectId;
907
- }): Promise<AuditQueryResult>;
928
+ interface SoftDeleteMethods<TDoc> {
908
929
  /**
909
- * Get all audits for an organization
910
- */
911
- getOrgTrail(orgId: string | ObjectId, options?: {
912
- page?: number;
913
- limit?: number;
914
- operation?: AuditOperation;
915
- model?: string;
916
- }): Promise<AuditQueryResult>;
917
- }
918
- interface AuditTrailMethods {
930
+ * Restore a soft-deleted document
931
+ * @param id - Document ID to restore
932
+ * @param options - Optional restore options
933
+ * @returns Restored document
934
+ */
935
+ restore(id: string | ObjectId, options?: {
936
+ session?: ClientSession;
937
+ }): Promise<TDoc>;
919
938
  /**
920
- * Get paginated audit trail for a document
939
+ * Get paginated list of soft-deleted documents
940
+ * @param params - Query parameters (filters, sort, pagination)
941
+ * @param options - Query options (select, populate, lean, session)
942
+ * @returns Paginated result of deleted documents
921
943
  */
922
- getAuditTrail(documentId: string | ObjectId, options?: {
944
+ getDeleted(params?: {
945
+ filters?: Record<string, unknown>;
946
+ sort?: SortSpec | string;
923
947
  page?: number;
924
948
  limit?: number;
925
- operation?: AuditOperation;
926
- }): Promise<AuditQueryResult>;
927
- }
928
- //#endregion
929
- //#region src/plugins/elastic.plugin.d.ts
930
- interface ElasticSearchOptions {
931
- /** Elasticsearch or OpenSearch client instance */
932
- client: any;
933
- /** Index name to perform search against */
934
- index: string;
935
- /** Field to extract MongoDB ID from the indexed document (default: '_id') */
936
- idField?: string;
949
+ }, options?: {
950
+ select?: SelectSpec;
951
+ populate?: PopulateSpec;
952
+ lean?: boolean;
953
+ session?: ClientSession;
954
+ }): Promise<OffsetPaginationResult<TDoc>>;
937
955
  }
938
- declare function elasticSearchPlugin(options: ElasticSearchOptions): Plugin;
939
956
  //#endregion
940
- //#region src/plugins/custom-id.plugin.d.ts
941
- /**
942
- * Generator function that produces a unique ID.
943
- * Receives the full repository context for conditional logic.
944
- */
945
- type IdGenerator = (context: RepositoryContext) => string | Promise<string>;
946
- interface CustomIdOptions {
947
- /** Field to store the custom ID (default: 'customId') */
948
- field?: string;
949
- /** Function to generate the ID. Can be async. */
950
- generator: IdGenerator;
951
- /** Only generate if the field is missing/empty (default: true) */
952
- generateOnlyIfEmpty?: boolean;
953
- }
957
+ //#region src/plugins/subdocument.plugin.d.ts
954
958
  /**
955
- * Atomically increment and return the next sequence value for a given key.
956
- * Uses `findOneAndUpdate` with `upsert` + `$inc` — fully atomic even under
957
- * heavy concurrency.
958
- *
959
- * @param counterKey - Unique key identifying this counter (e.g., "Invoice" or "Invoice:2026-02")
960
- * @param increment - Value to increment by (default: 1)
961
- * @returns The next sequence number (after increment)
959
+ * Subdocument plugin for managing nested arrays
962
960
  *
963
961
  * @example
964
- * const seq = await getNextSequence('invoices');
965
- * // First call → 1, second → 2, ...
962
+ * const repo = new Repository(Model, [
963
+ * methodRegistryPlugin(),
964
+ * subdocumentPlugin(),
965
+ * ]);
966
966
  *
967
- * @example Batch increment for createMany
968
- * const startSeq = await getNextSequence('invoices', 5);
969
- * // If current was 10, returns 15 (you use 11, 12, 13, 14, 15)
967
+ * await repo.addSubdocument(parentId, 'items', { name: 'Item 1' });
968
+ * await repo.updateSubdocument(parentId, 'items', itemId, { name: 'Updated Item' });
970
969
  */
971
- declare function getNextSequence(counterKey: string, increment?: number, connection?: mongoose.Connection): Promise<number>;
972
- interface SequentialIdOptions {
973
- /** Prefix string (e.g., 'INV', 'ORD') */
974
- prefix: string;
975
- /** Mongoose model — used to derive the counter key from model name */
976
- model: mongoose.Model<any>;
977
- /** Number of digits to pad to (default: 4 → "0001") */
978
- padding?: number;
979
- /** Separator between prefix and number (default: '-') */
980
- separator?: string;
981
- /** Custom counter key override (default: model.modelName) */
982
- counterKey?: string;
983
- }
970
+ declare function subdocumentPlugin(): Plugin;
984
971
  /**
985
- * Generator: Simple sequential counter.
986
- * Produces IDs like `INV-0001`, `INV-0002`, etc.
987
- *
988
- * Uses atomic MongoDB counters — safe under concurrency.
972
+ * Type interface for repositories using subdocumentPlugin
989
973
  *
990
974
  * @example
991
975
  * ```typescript
992
- * customIdPlugin({
993
- * field: 'invoiceNumber',
994
- * generator: sequentialId({ prefix: 'INV', model: InvoiceModel }),
995
- * })
976
+ * import { Repository, methodRegistryPlugin, subdocumentPlugin } from '@classytic/mongokit';
977
+ * import type { SubdocumentMethods } from '@classytic/mongokit';
978
+ *
979
+ * class OrderRepo extends Repository<IOrder> {}
980
+ *
981
+ * type OrderRepoWithSubdocs = OrderRepo & SubdocumentMethods<IOrder>;
982
+ *
983
+ * const repo = new OrderRepo(OrderModel, [
984
+ * methodRegistryPlugin(),
985
+ * subdocumentPlugin(),
986
+ * ]) as OrderRepoWithSubdocs;
987
+ *
988
+ * // TypeScript autocomplete works!
989
+ * await repo.addSubdocument(orderId, 'items', { productId: '123', quantity: 2 });
990
+ * await repo.updateSubdocument(orderId, 'items', itemId, { quantity: 5 });
991
+ * await repo.deleteSubdocument(orderId, 'items', itemId);
996
992
  * ```
997
993
  */
998
- declare function sequentialId(options: SequentialIdOptions): IdGenerator;
999
- interface DateSequentialIdOptions {
1000
- /** Prefix string (e.g., 'BILL', 'INV') */
1001
- prefix: string;
1002
- /** Mongoose model — used to derive the counter key */
1003
- model: mongoose.Model<any>;
994
+ interface SubdocumentMethods<TDoc> {
1004
995
  /**
1005
- * Partition granularity counter resets each period.
1006
- * - 'yearly' → BILL-2026-0001, resets every January
1007
- * - 'monthly' BILL-2026-02-0001, resets every month
1008
- * - 'daily' → BILL-2026-02-20-0001, resets every day
996
+ * Add subdocument to array field
997
+ * @param parentId - Parent document ID
998
+ * @param arrayPath - Path to array field (e.g., 'items', 'addresses')
999
+ * @param subData - Subdocument data
1000
+ * @param options - Operation options
1001
+ * @returns Updated parent document
1009
1002
  */
1010
- partition?: 'yearly' | 'monthly' | 'daily';
1011
- /** Number of digits to pad to (default: 4) */
1012
- padding?: number;
1013
- /** Separator (default: '-') */
1014
- separator?: string;
1003
+ addSubdocument(parentId: string | ObjectId, arrayPath: string, subData: Record<string, unknown>, options?: Record<string, unknown>): Promise<TDoc>;
1004
+ /**
1005
+ * Get subdocument from array field
1006
+ * @param parentId - Parent document ID
1007
+ * @param arrayPath - Path to array field
1008
+ * @param subId - Subdocument ID
1009
+ * @param options - Operation options
1010
+ * @returns Subdocument
1011
+ */
1012
+ getSubdocument(parentId: string | ObjectId, arrayPath: string, subId: string | ObjectId, options?: {
1013
+ lean?: boolean;
1014
+ session?: unknown;
1015
+ }): Promise<Record<string, unknown>>;
1016
+ /**
1017
+ * Update subdocument in array field
1018
+ * @param parentId - Parent document ID
1019
+ * @param arrayPath - Path to array field
1020
+ * @param subId - Subdocument ID
1021
+ * @param updateData - Update data
1022
+ * @param options - Operation options
1023
+ * @returns Updated parent document
1024
+ */
1025
+ updateSubdocument(parentId: string | ObjectId, arrayPath: string, subId: string | ObjectId, updateData: Record<string, unknown>, options?: {
1026
+ session?: unknown;
1027
+ }): Promise<TDoc>;
1028
+ /**
1029
+ * Delete subdocument from array field
1030
+ * @param parentId - Parent document ID
1031
+ * @param arrayPath - Path to array field
1032
+ * @param subId - Subdocument ID
1033
+ * @param options - Operation options
1034
+ * @returns Updated parent document
1035
+ */
1036
+ deleteSubdocument(parentId: string | ObjectId, arrayPath: string, subId: string | ObjectId, options?: Record<string, unknown>): Promise<TDoc>;
1015
1037
  }
1038
+ //#endregion
1039
+ //#region src/plugins/timestamp.plugin.d.ts
1016
1040
  /**
1017
- * Generator: Date-partitioned sequential counter.
1018
- * Counter resets per period — great for invoice/bill numbering.
1019
- *
1020
- * Produces IDs like:
1021
- * - yearly: `BILL-2026-0001`
1022
- * - monthly: `BILL-2026-02-0001`
1023
- * - daily: `BILL-2026-02-20-0001`
1041
+ * Timestamp plugin that auto-injects timestamps
1024
1042
  *
1025
1043
  * @example
1026
- * ```typescript
1027
- * customIdPlugin({
1028
- * field: 'billNumber',
1029
- * generator: dateSequentialId({
1030
- * prefix: 'BILL',
1031
- * model: BillModel,
1032
- * partition: 'monthly',
1033
- * }),
1034
- * })
1035
- * ```
1044
+ * const repo = new Repository(Model, [timestampPlugin()]);
1036
1045
  */
1037
- declare function dateSequentialId(options: DateSequentialIdOptions): IdGenerator;
1038
- interface PrefixedIdOptions {
1039
- /** Prefix string (e.g., 'USR', 'TXN') */
1040
- prefix: string;
1041
- /** Separator (default: '_') */
1042
- separator?: string;
1043
- /** Length of the random suffix (default: 12) */
1044
- length?: number;
1045
- }
1046
+ declare function timestampPlugin(): Plugin;
1047
+ //#endregion
1048
+ //#region src/plugins/validation-chain.plugin.d.ts
1049
+ type OperationType = 'create' | 'createMany' | 'update' | 'delete';
1046
1050
  /**
1047
- * Generator: Prefix + random alphanumeric suffix.
1048
- * Does NOT require a database round-trip — purely in-memory.
1049
- *
1050
- * Produces IDs like: `USR_a7b3xk9m2p1q`
1051
- *
1052
- * Good for: user-facing IDs where ordering doesn't matter.
1053
- * Not suitable for sequential numbering.
1051
+ * Validation chain plugin
1054
1052
  *
1055
1053
  * @example
1056
- * ```typescript
1057
- * customIdPlugin({
1058
- * field: 'publicId',
1059
- * generator: prefixedId({ prefix: 'USR', length: 10 }),
1060
- * })
1061
- * ```
1054
+ * const repo = new Repository(Model, [
1055
+ * validationChainPlugin([
1056
+ * requireField('email'),
1057
+ * uniqueField('email', 'Email already exists'),
1058
+ * blockIf('no-delete-admin', ['delete'], ctx => ctx.data?.role === 'admin', 'Cannot delete admin'),
1059
+ * ])
1060
+ * ]);
1062
1061
  */
1063
- declare function prefixedId(options: PrefixedIdOptions): IdGenerator;
1062
+ declare function validationChainPlugin(validators?: ValidatorDefinition[], options?: ValidationChainOptions): Plugin;
1064
1063
  /**
1065
- * Custom ID plugin injects generated IDs into documents before creation.
1066
- *
1067
- * @param options - Configuration for ID generation
1068
- * @returns Plugin instance
1064
+ * Block operation if condition is true
1069
1065
  *
1070
1066
  * @example
1071
- * ```typescript
1072
- * import { Repository, customIdPlugin, sequentialId } from '@classytic/mongokit';
1073
- *
1074
- * const invoiceRepo = new Repository(InvoiceModel, [
1075
- * customIdPlugin({
1076
- * field: 'invoiceNumber',
1077
- * generator: sequentialId({ prefix: 'INV', model: InvoiceModel }),
1078
- * }),
1079
- * ]);
1080
- *
1081
- * const inv = await invoiceRepo.create({ amount: 100 });
1082
- * console.log(inv.invoiceNumber); // "INV-0001"
1083
- * ```
1067
+ * blockIf('block-library', ['delete'], ctx => ctx.data?.managed, 'Cannot delete managed records')
1084
1068
  */
1085
- declare function customIdPlugin(options: CustomIdOptions): Plugin;
1069
+ declare function blockIf(name: string, operations: OperationType[], condition: (context: RepositoryContext) => boolean, errorMessage: string): ValidatorDefinition;
1070
+ /**
1071
+ * Require a field to be present
1072
+ */
1073
+ declare function requireField(field: string, operations?: OperationType[]): ValidatorDefinition;
1074
+ /**
1075
+ * Auto-inject a value if not present
1076
+ */
1077
+ declare function autoInject(field: string, getter: (context: RepositoryContext) => unknown, operations?: OperationType[]): ValidatorDefinition;
1078
+ /**
1079
+ * Make a field immutable (cannot be updated)
1080
+ */
1081
+ declare function immutableField(field: string): ValidatorDefinition;
1082
+ /**
1083
+ * Ensure field value is unique
1084
+ */
1085
+ declare function uniqueField(field: string, errorMessage?: string): ValidatorDefinition;
1086
1086
  //#endregion
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 };
1087
+ export { dateSequentialId as A, AuditEntry as B, elasticSearchPlugin as C, PrefixedIdOptions as D, IdGenerator as E, CacheMethods as F, AuditTrailOptions as G, AuditQueryOptions as H, cachePlugin as I, auditLogPlugin as J, AuditTrailQuery as K, BatchOperationsMethods as L, prefixedId as M, sequentialId as N, SequentialIdOptions as O, cascadePlugin as P, BulkWriteResult as R, ElasticSearchOptions as S, DateSequentialIdOptions as T, AuditQueryResult as U, AuditOperation as V, AuditTrailMethods as W, aggregateHelpersPlugin as X, AggregateHelpersMethods as Y, MongoOperationsMethods as _, uniqueField as a, methodRegistryPlugin as b, SubdocumentMethods as c, softDeletePlugin as d, ObservabilityOptions as f, multiTenantPlugin as g, MultiTenantOptions as h, requireField as i, getNextSequence as j, customIdPlugin as k, subdocumentPlugin as l, observabilityPlugin as m, blockIf as n, validationChainPlugin as o, OperationMetric as p, auditTrailPlugin as q, immutableField as r, timestampPlugin as s, autoInject as t, SoftDeleteMethods as u, mongoOperationsPlugin as v, CustomIdOptions as w, fieldFilterPlugin as x, MethodRegistryRepository as y, batchOperationsPlugin as z };