@classytic/mongokit 3.0.3 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { Model } from 'mongoose';
2
- import { A as AnyDocument, P as PaginationConfig, O as OffsetPaginationOptions, a as OffsetPaginationResult, K as KeysetPaginationOptions, b as KeysetPaginationResult, c as AggregatePaginationOptions, d as AggregatePaginationResult } from '../types-B3dPUKjs.js';
2
+ import { A as AnyDocument, P as PaginationConfig, O as OffsetPaginationOptions, a as OffsetPaginationResult, K as KeysetPaginationOptions, b as KeysetPaginationResult, c as AggregatePaginationOptions, d as AggregatePaginationResult } from '../types-CHIDluaP.js';
3
3
 
4
4
  /**
5
5
  * Pagination Engine
@@ -1,4 +1,4 @@
1
- import { F as FieldPreset, r as Plugin, L as Logger, I as SoftDeleteOptions, t as RepositoryInstance, B as ValidatorDefinition, G as ValidationChainOptions, i as RepositoryContext, W as CacheOptions, _ as CascadeOptions } from '../types-B3dPUKjs.js';
1
+ import { F as FieldPreset, r as Plugin, L as Logger, M as SoftDeleteOptions, t as RepositoryInstance, G as ValidatorDefinition, I as ValidationChainOptions, i as RepositoryContext, Z as CacheOptions, a1 as CascadeOptions } from '../types-CHIDluaP.js';
2
2
  import 'mongoose';
3
3
 
4
4
  /**
@@ -54,10 +54,42 @@ declare function auditLogPlugin(logger: Logger): Plugin;
54
54
  /**
55
55
  * Soft delete plugin
56
56
  *
57
- * @example
57
+ * @example Basic usage
58
+ * ```typescript
58
59
  * const repo = new Repository(Model, [
59
60
  * softDeletePlugin({ deletedField: 'deletedAt' })
60
61
  * ]);
62
+ *
63
+ * // Delete (soft)
64
+ * await repo.delete(id);
65
+ *
66
+ * // Restore
67
+ * await repo.restore(id);
68
+ *
69
+ * // Get deleted documents
70
+ * await repo.getDeleted({ page: 1, limit: 20 });
71
+ * ```
72
+ *
73
+ * @example With null filter mode (for schemas with default: null)
74
+ * ```typescript
75
+ * // Schema: { deletedAt: { type: Date, default: null } }
76
+ * const repo = new Repository(Model, [
77
+ * softDeletePlugin({
78
+ * deletedField: 'deletedAt',
79
+ * filterMode: 'null', // default - works with default: null
80
+ * })
81
+ * ]);
82
+ * ```
83
+ *
84
+ * @example With TTL for auto-cleanup
85
+ * ```typescript
86
+ * const repo = new Repository(Model, [
87
+ * softDeletePlugin({
88
+ * deletedField: 'deletedAt',
89
+ * ttlDays: 30, // Auto-delete after 30 days
90
+ * })
91
+ * ]);
92
+ * ```
61
93
  */
62
94
  declare function softDeletePlugin(options?: SoftDeleteOptions): Plugin;
63
95
 
@@ -115,12 +115,45 @@ function auditLogPlugin(logger) {
115
115
  }
116
116
 
117
117
  // src/plugins/soft-delete.plugin.ts
118
+ function buildDeletedFilter(deletedField, filterMode, includeDeleted) {
119
+ if (includeDeleted) {
120
+ return {};
121
+ }
122
+ if (filterMode === "exists") {
123
+ return { [deletedField]: { $exists: false } };
124
+ }
125
+ return { [deletedField]: null };
126
+ }
127
+ function buildGetDeletedFilter(deletedField, filterMode) {
128
+ if (filterMode === "exists") {
129
+ return { [deletedField]: { $exists: true, $ne: null } };
130
+ }
131
+ return { [deletedField]: { $ne: null } };
132
+ }
118
133
  function softDeletePlugin(options = {}) {
119
134
  const deletedField = options.deletedField || "deletedAt";
120
135
  const deletedByField = options.deletedByField || "deletedBy";
136
+ const filterMode = options.filterMode || "null";
137
+ const addRestoreMethod = options.addRestoreMethod !== false;
138
+ const addGetDeletedMethod = options.addGetDeletedMethod !== false;
139
+ const ttlDays = options.ttlDays;
121
140
  return {
122
141
  name: "softDelete",
123
142
  apply(repo) {
143
+ if (ttlDays !== void 0 && ttlDays > 0) {
144
+ const ttlSeconds = ttlDays * 24 * 60 * 60;
145
+ repo.Model.collection.createIndex(
146
+ { [deletedField]: 1 },
147
+ {
148
+ expireAfterSeconds: ttlSeconds,
149
+ partialFilterExpression: { [deletedField]: { $type: "date" } }
150
+ }
151
+ ).catch((err) => {
152
+ if (!err.message.includes("already exists")) {
153
+ console.warn(`[softDeletePlugin] Failed to create TTL index: ${err.message}`);
154
+ }
155
+ });
156
+ }
124
157
  repo.on("before:delete", async (context) => {
125
158
  if (options.soft !== false) {
126
159
  const updateData = {
@@ -134,23 +167,126 @@ function softDeletePlugin(options = {}) {
134
167
  }
135
168
  });
136
169
  repo.on("before:getAll", (context) => {
137
- if (!context.includeDeleted && options.soft !== false) {
138
- const queryParams = context.queryParams || {};
139
- queryParams.filters = {
140
- ...queryParams.filters || {},
141
- [deletedField]: { $exists: false }
142
- };
143
- context.queryParams = queryParams;
170
+ if (options.soft !== false) {
171
+ const deleteFilter = buildDeletedFilter(deletedField, filterMode, !!context.includeDeleted);
172
+ if (Object.keys(deleteFilter).length > 0) {
173
+ const existingFilters = context.filters || {};
174
+ context.filters = {
175
+ ...existingFilters,
176
+ ...deleteFilter
177
+ };
178
+ }
144
179
  }
145
180
  });
146
181
  repo.on("before:getById", (context) => {
147
- if (!context.includeDeleted && options.soft !== false) {
148
- context.query = {
149
- ...context.query || {},
150
- [deletedField]: { $exists: false }
151
- };
182
+ if (options.soft !== false) {
183
+ const deleteFilter = buildDeletedFilter(deletedField, filterMode, !!context.includeDeleted);
184
+ if (Object.keys(deleteFilter).length > 0) {
185
+ context.query = {
186
+ ...context.query || {},
187
+ ...deleteFilter
188
+ };
189
+ }
152
190
  }
153
191
  });
192
+ repo.on("before:getByQuery", (context) => {
193
+ if (options.soft !== false) {
194
+ const deleteFilter = buildDeletedFilter(deletedField, filterMode, !!context.includeDeleted);
195
+ if (Object.keys(deleteFilter).length > 0) {
196
+ context.query = {
197
+ ...context.query || {},
198
+ ...deleteFilter
199
+ };
200
+ }
201
+ }
202
+ });
203
+ if (addRestoreMethod) {
204
+ const restoreMethod = async function(id, restoreOptions = {}) {
205
+ const updateData = {
206
+ [deletedField]: null,
207
+ [deletedByField]: null
208
+ };
209
+ const result = await this.Model.findByIdAndUpdate(id, { $set: updateData }, {
210
+ new: true,
211
+ session: restoreOptions.session
212
+ });
213
+ if (!result) {
214
+ const error = new Error(`Document with id '${id}' not found`);
215
+ error.status = 404;
216
+ throw error;
217
+ }
218
+ await this.emitAsync("after:restore", { id, result });
219
+ return result;
220
+ };
221
+ if (typeof repo.registerMethod === "function") {
222
+ repo.registerMethod("restore", restoreMethod);
223
+ } else {
224
+ repo.restore = restoreMethod.bind(repo);
225
+ }
226
+ }
227
+ if (addGetDeletedMethod) {
228
+ const getDeletedMethod = async function(params = {}, getDeletedOptions = {}) {
229
+ const deletedFilter = buildGetDeletedFilter(deletedField, filterMode);
230
+ const combinedFilters = {
231
+ ...params.filters || {},
232
+ ...deletedFilter
233
+ };
234
+ const page = params.page || 1;
235
+ const limit = params.limit || 20;
236
+ const skip = (page - 1) * limit;
237
+ let sortSpec = { [deletedField]: -1 };
238
+ if (params.sort) {
239
+ if (typeof params.sort === "string") {
240
+ const sortOrder = params.sort.startsWith("-") ? -1 : 1;
241
+ const sortField = params.sort.startsWith("-") ? params.sort.substring(1) : params.sort;
242
+ sortSpec = { [sortField]: sortOrder };
243
+ } else {
244
+ sortSpec = params.sort;
245
+ }
246
+ }
247
+ let query = this.Model.find(combinedFilters).sort(sortSpec).skip(skip).limit(limit);
248
+ if (getDeletedOptions.session) {
249
+ query = query.session(getDeletedOptions.session);
250
+ }
251
+ if (getDeletedOptions.select) {
252
+ const selectValue = Array.isArray(getDeletedOptions.select) ? getDeletedOptions.select.join(" ") : getDeletedOptions.select;
253
+ query = query.select(selectValue);
254
+ }
255
+ if (getDeletedOptions.populate) {
256
+ const populateSpec = getDeletedOptions.populate;
257
+ if (typeof populateSpec === "string") {
258
+ query = query.populate(populateSpec.split(",").map((p) => p.trim()));
259
+ } else if (Array.isArray(populateSpec)) {
260
+ query = query.populate(populateSpec);
261
+ } else {
262
+ query = query.populate(populateSpec);
263
+ }
264
+ }
265
+ if (getDeletedOptions.lean !== false) {
266
+ query = query.lean();
267
+ }
268
+ const [docs, total] = await Promise.all([
269
+ query.exec(),
270
+ this.Model.countDocuments(combinedFilters)
271
+ ]);
272
+ const pages = Math.ceil(total / limit);
273
+ return {
274
+ method: "offset",
275
+ docs,
276
+ page,
277
+ limit,
278
+ total,
279
+ pages,
280
+ hasNext: page < pages,
281
+ hasPrev: page > 1
282
+ };
283
+ };
284
+ if (typeof repo.registerMethod === "function") {
285
+ repo.registerMethod("getDeleted", getDeletedMethod);
286
+ } else {
287
+ repo.getDeleted = getDeletedMethod.bind(repo);
288
+ }
289
+ }
154
290
  }
155
291
  };
156
292
  }
@@ -0,0 +1,303 @@
1
+ import { q as UserContext, F as FieldPreset, H as HttpError, Y as CacheAdapter, y as SchemaBuilderOptions, z as CrudSchemas, V as ValidationResult, v as ParsedQuery } from './types-CHIDluaP.js';
2
+ import mongoose__default, { Schema } from 'mongoose';
3
+
4
+ /**
5
+ * Field Selection Utilities
6
+ *
7
+ * Provides explicit, performant field filtering using Mongoose projections.
8
+ *
9
+ * Philosophy:
10
+ * - Explicit is better than implicit
11
+ * - Filter at DB level (10x faster than in-memory)
12
+ * - Progressive disclosure (show more fields as trust increases)
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // For Mongoose queries (PREFERRED - 90% of cases)
17
+ * const projection = getMongooseProjection(request.user, fieldPresets.gymPlans);
18
+ * const plans = await GymPlan.find().select(projection).lean();
19
+ *
20
+ * // For complex data (10% of cases - aggregations, multiple sources)
21
+ * const filtered = filterResponseData(complexData, fieldPresets.gymPlans, request.user);
22
+ * ```
23
+ */
24
+
25
+ /**
26
+ * Get allowed fields for a user based on their context
27
+ *
28
+ * @param user - User object from request.user (or null for public)
29
+ * @param preset - Field preset configuration
30
+ * @returns Array of allowed field names
31
+ *
32
+ * @example
33
+ * const fields = getFieldsForUser(request.user, {
34
+ * public: ['id', 'name', 'price'],
35
+ * authenticated: ['description', 'features'],
36
+ * admin: ['createdAt', 'internalNotes']
37
+ * });
38
+ */
39
+ declare function getFieldsForUser(user: UserContext | null | undefined, preset: FieldPreset): string[];
40
+ /**
41
+ * Get Mongoose projection string for query .select()
42
+ *
43
+ * @param user - User object from request.user
44
+ * @param preset - Field preset configuration
45
+ * @returns Space-separated field names for Mongoose .select()
46
+ *
47
+ * @example
48
+ * const projection = getMongooseProjection(request.user, fieldPresets.gymPlans);
49
+ * const plans = await GymPlan.find({ organizationId }).select(projection).lean();
50
+ */
51
+ declare function getMongooseProjection(user: UserContext | null | undefined, preset: FieldPreset): string;
52
+ /**
53
+ * Filter response data to include only allowed fields
54
+ *
55
+ * Use this for complex responses where Mongoose projections aren't applicable:
56
+ * - Aggregation pipeline results
57
+ * - Data from multiple sources
58
+ * - Custom computed fields
59
+ *
60
+ * For simple DB queries, prefer getMongooseProjection() (10x faster)
61
+ *
62
+ * @param data - Data to filter
63
+ * @param preset - Field preset configuration
64
+ * @param user - User object from request.user
65
+ * @returns Filtered data
66
+ *
67
+ * @example
68
+ * const stats = await calculateComplexStats();
69
+ * const filtered = filterResponseData(stats, fieldPresets.dashboard, request.user);
70
+ * return reply.send(filtered);
71
+ */
72
+ declare function filterResponseData<T extends Record<string, unknown>>(data: T | T[], preset: FieldPreset, user?: UserContext | null): Partial<T> | Partial<T>[];
73
+ /**
74
+ * Helper to create field presets (module-level)
75
+ *
76
+ * Each module should define its own field preset in its own directory.
77
+ * This keeps modules independent and self-contained.
78
+ *
79
+ * @param config - Field configuration
80
+ * @returns Field preset
81
+ *
82
+ * @example
83
+ * // In modules/gym-plan/gym-plan.fields.ts
84
+ * export const gymPlanFieldPreset = createFieldPreset({
85
+ * public: ['id', 'name', 'price'],
86
+ * authenticated: ['features', 'description'],
87
+ * admin: ['createdAt', 'updatedAt', 'internalNotes']
88
+ * });
89
+ */
90
+ declare function createFieldPreset(config: Partial<FieldPreset>): FieldPreset;
91
+
92
+ /**
93
+ * Error Utilities
94
+ *
95
+ * HTTP-compatible error creation for repository operations
96
+ */
97
+
98
+ /**
99
+ * Creates an error with HTTP status code
100
+ *
101
+ * @param status - HTTP status code
102
+ * @param message - Error message
103
+ * @returns Error with status property
104
+ *
105
+ * @example
106
+ * throw createError(404, 'Document not found');
107
+ * throw createError(400, 'Invalid input');
108
+ * throw createError(403, 'Access denied');
109
+ */
110
+ declare function createError(status: number, message: string): HttpError;
111
+
112
+ /**
113
+ * In-Memory Cache Adapter
114
+ *
115
+ * Simple cache adapter for development and testing.
116
+ * NOT recommended for production - use Redis or similar.
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * import { cachePlugin, createMemoryCache } from '@classytic/mongokit';
121
+ *
122
+ * const repo = new Repository(UserModel, [
123
+ * cachePlugin({
124
+ * adapter: createMemoryCache(),
125
+ * ttl: 60,
126
+ * })
127
+ * ]);
128
+ * ```
129
+ */
130
+
131
+ /**
132
+ * Creates an in-memory cache adapter
133
+ *
134
+ * Features:
135
+ * - Automatic TTL expiration
136
+ * - Pattern-based clearing (simple glob with *)
137
+ * - Max entries limit to prevent memory leaks
138
+ *
139
+ * @param maxEntries - Maximum cache entries before oldest are evicted (default: 1000)
140
+ */
141
+ declare function createMemoryCache(maxEntries?: number): CacheAdapter;
142
+
143
+ /**
144
+ * Mongoose to JSON Schema Converter with Field Rules
145
+ *
146
+ * Generates Fastify JSON schemas from Mongoose models with declarative field rules.
147
+ *
148
+ * Field Rules (options.fieldRules):
149
+ * - immutable: Field cannot be updated (omitted from update schema)
150
+ * - immutableAfterCreate: Alias for immutable
151
+ * - systemManaged: System-only field (omitted from create/update)
152
+ * - optional: Remove from required array
153
+ *
154
+ * Additional Options:
155
+ * - strictAdditionalProperties: Set to true to add "additionalProperties: false" to schemas
156
+ * This makes Fastify reject unknown fields at validation level (default: false for backward compatibility)
157
+ *
158
+ * @example
159
+ * buildCrudSchemasFromModel(Model, {
160
+ * strictAdditionalProperties: true, // Reject unknown fields
161
+ * fieldRules: {
162
+ * organizationId: { immutable: true },
163
+ * status: { systemManaged: true },
164
+ * },
165
+ * create: { omitFields: ['verifiedAt'] },
166
+ * update: { omitFields: ['customerId'] }
167
+ * })
168
+ */
169
+
170
+ /**
171
+ * Build CRUD schemas from Mongoose schema
172
+ */
173
+ declare function buildCrudSchemasFromMongooseSchema(mongooseSchema: Schema, options?: SchemaBuilderOptions): CrudSchemas;
174
+ /**
175
+ * Build CRUD schemas from Mongoose model
176
+ */
177
+ declare function buildCrudSchemasFromModel(mongooseModel: mongoose__default.Model<unknown>, options?: SchemaBuilderOptions): CrudSchemas;
178
+ /**
179
+ * Get fields that are immutable (cannot be updated)
180
+ */
181
+ declare function getImmutableFields(options?: SchemaBuilderOptions): string[];
182
+ /**
183
+ * Get fields that are system-managed (cannot be set by users)
184
+ */
185
+ declare function getSystemManagedFields(options?: SchemaBuilderOptions): string[];
186
+ /**
187
+ * Check if field is allowed in update
188
+ */
189
+ declare function isFieldUpdateAllowed(fieldName: string, options?: SchemaBuilderOptions): boolean;
190
+ /**
191
+ * Validate update body against field rules
192
+ */
193
+ declare function validateUpdateBody(body?: Record<string, unknown>, options?: SchemaBuilderOptions): ValidationResult;
194
+
195
+ /**
196
+ * Query Parser
197
+ *
198
+ * Parses HTTP query parameters into MongoDB-compatible query objects.
199
+ * Supports operators, pagination, sorting, and filtering.
200
+ */
201
+
202
+ /** Operator mapping from query syntax to MongoDB operators */
203
+ type OperatorMap = Record<string, string>;
204
+ /** Possible values in filter parameters */
205
+ type FilterValue = string | number | boolean | null | undefined | Record<string, unknown> | unknown[];
206
+ /** Configuration options for QueryParser */
207
+ interface QueryParserOptions {
208
+ /** Maximum allowed regex pattern length (default: 500) */
209
+ maxRegexLength?: number;
210
+ /** Maximum allowed text search query length (default: 200) */
211
+ maxSearchLength?: number;
212
+ /** Maximum allowed filter depth (default: 10) */
213
+ maxFilterDepth?: number;
214
+ /** Additional operators to block */
215
+ additionalDangerousOperators?: string[];
216
+ }
217
+ /**
218
+ * Query Parser Class
219
+ *
220
+ * Parses HTTP query parameters into MongoDB-compatible query objects.
221
+ * Includes security measures against NoSQL injection and ReDoS attacks.
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * import { QueryParser } from '@classytic/mongokit';
226
+ *
227
+ * const parser = new QueryParser({ maxRegexLength: 100 });
228
+ * const query = parser.parseQuery(req.query);
229
+ * ```
230
+ */
231
+ declare class QueryParser {
232
+ private readonly options;
233
+ private readonly operators;
234
+ /**
235
+ * Dangerous MongoDB operators that should never be accepted from user input
236
+ * Security: Prevent NoSQL injection attacks
237
+ */
238
+ private readonly dangerousOperators;
239
+ /**
240
+ * Regex pattern characters that can cause catastrophic backtracking (ReDoS)
241
+ */
242
+ private readonly dangerousRegexPatterns;
243
+ constructor(options?: QueryParserOptions);
244
+ /**
245
+ * Parse query parameters into MongoDB query format
246
+ */
247
+ parseQuery(query: Record<string, unknown> | null | undefined): ParsedQuery;
248
+ /**
249
+ * Parse sort parameter
250
+ * Converts string like '-createdAt' to { createdAt: -1 }
251
+ * Handles multiple sorts: '-createdAt,name' → { createdAt: -1, name: 1 }
252
+ */
253
+ private _parseSort;
254
+ /**
255
+ * Parse standard filter parameter (filter[field]=value)
256
+ */
257
+ private _parseFilters;
258
+ /**
259
+ * Handle operator syntax: field[operator]=value
260
+ */
261
+ private _handleOperatorSyntax;
262
+ /**
263
+ * Handle bracket syntax with object value
264
+ */
265
+ private _handleBracketSyntax;
266
+ /**
267
+ * Convert operator to MongoDB format
268
+ */
269
+ private _toMongoOperator;
270
+ /**
271
+ * Create a safe regex pattern with protection against ReDoS attacks
272
+ * @param pattern - The pattern string from user input
273
+ * @param flags - Regex flags (default: 'i' for case-insensitive)
274
+ * @returns A safe RegExp or null if pattern is invalid/dangerous
275
+ */
276
+ private _createSafeRegex;
277
+ /**
278
+ * Escape special regex characters for literal matching
279
+ */
280
+ private _escapeRegex;
281
+ /**
282
+ * Sanitize text search query for MongoDB $text search
283
+ * @param search - Raw search input from user
284
+ * @returns Sanitized search string or undefined
285
+ */
286
+ private _sanitizeSearch;
287
+ /**
288
+ * Convert values based on operator type
289
+ */
290
+ private _convertValue;
291
+ /**
292
+ * Parse $or conditions
293
+ */
294
+ private _parseOr;
295
+ /**
296
+ * Enhance filters with between operator
297
+ */
298
+ private _enhanceWithBetween;
299
+ }
300
+ /** Default query parser instance with standard options */
301
+ declare const defaultQueryParser: QueryParser;
302
+
303
+ export { type FilterValue as F, type OperatorMap as O, QueryParser as Q, getMongooseProjection as a, type QueryParserOptions as b, createFieldPreset as c, defaultQueryParser as d, buildCrudSchemasFromMongooseSchema as e, filterResponseData as f, getFieldsForUser as g, buildCrudSchemasFromModel as h, getImmutableFields as i, getSystemManagedFields as j, isFieldUpdateAllowed as k, createError as l, createMemoryCache as m, validateUpdateBody as v };
@@ -348,30 +348,16 @@ interface JsonSchema {
348
348
  format?: string;
349
349
  pattern?: string;
350
350
  }
351
- /** CRUD schemas result */
351
+ /** CRUD schemas result - framework-agnostic JSON schemas */
352
352
  interface CrudSchemas {
353
+ /** JSON Schema for create request body */
353
354
  createBody: JsonSchema;
355
+ /** JSON Schema for update request body */
354
356
  updateBody: JsonSchema;
357
+ /** JSON Schema for route params (id validation) */
355
358
  params: JsonSchema;
359
+ /** JSON Schema for list/query parameters */
356
360
  listQuery: JsonSchema;
357
- crudSchemas: {
358
- create: {
359
- body: JsonSchema;
360
- };
361
- update: {
362
- body: JsonSchema;
363
- params: JsonSchema;
364
- };
365
- get: {
366
- params: JsonSchema;
367
- };
368
- list: {
369
- query: JsonSchema;
370
- };
371
- remove: {
372
- params: JsonSchema;
373
- };
374
- };
375
361
  }
376
362
  /** Decoded cursor */
377
363
  interface DecodedCursor {
@@ -405,6 +391,8 @@ interface Logger {
405
391
  warn?(message: string, meta?: Record<string, unknown>): void;
406
392
  debug?(message: string, meta?: Record<string, unknown>): void;
407
393
  }
394
+ /** Filter mode for soft delete queries */
395
+ type SoftDeleteFilterMode = 'null' | 'exists';
408
396
  /** Soft delete plugin options */
409
397
  interface SoftDeleteOptions {
410
398
  /** Field name for deletion timestamp (default: 'deletedAt') */
@@ -413,6 +401,51 @@ interface SoftDeleteOptions {
413
401
  deletedByField?: string;
414
402
  /** Enable soft delete (default: true) */
415
403
  soft?: boolean;
404
+ /**
405
+ * Filter mode for excluding deleted documents (default: 'null')
406
+ * - 'null': Filters where deletedField is null (works with `default: null` in schema)
407
+ * - 'exists': Filters where deletedField does not exist (legacy behavior)
408
+ */
409
+ filterMode?: SoftDeleteFilterMode;
410
+ /** Add restore method to repository (default: true) */
411
+ addRestoreMethod?: boolean;
412
+ /** Add getDeleted method to repository (default: true) */
413
+ addGetDeletedMethod?: boolean;
414
+ /**
415
+ * TTL in days for auto-cleanup of deleted documents.
416
+ * When set, creates a TTL index on the deletedField.
417
+ * Documents will be automatically removed after the specified days.
418
+ */
419
+ ttlDays?: number;
420
+ }
421
+ /** Repository with soft delete methods */
422
+ interface SoftDeleteRepository {
423
+ /**
424
+ * Restore a soft-deleted document by setting deletedAt to null
425
+ * @param id - Document ID to restore
426
+ * @param options - Optional session for transactions
427
+ * @returns The restored document
428
+ */
429
+ restore(id: string | ObjectId, options?: {
430
+ session?: ClientSession;
431
+ }): Promise<unknown>;
432
+ /**
433
+ * Get all soft-deleted documents
434
+ * @param params - Query parameters (filters, pagination, etc.)
435
+ * @param options - Query options (select, populate, etc.)
436
+ * @returns Paginated result of deleted documents
437
+ */
438
+ getDeleted(params?: {
439
+ filters?: Record<string, unknown>;
440
+ sort?: SortSpec | string;
441
+ page?: number;
442
+ limit?: number;
443
+ }, options?: {
444
+ select?: SelectSpec;
445
+ populate?: PopulateSpec;
446
+ lean?: boolean;
447
+ session?: ClientSession;
448
+ }): Promise<OffsetPaginationResult<unknown>>;
416
449
  }
417
450
  /** Lookup options for aggregate */
418
451
  interface LookupOptions {
@@ -533,4 +566,4 @@ interface HttpError extends Error {
533
566
  }>;
534
567
  }
535
568
 
536
- export type { AnyDocument as A, ValidatorDefinition as B, CreateOptions as C, DeleteResult as D, EventPayload as E, FieldPreset as F, ValidationChainOptions as G, HttpError as H, SoftDeleteOptions as I, JsonSchema as J, KeysetPaginationOptions as K, Logger as L, LookupOptions as M, GroupResult as N, OffsetPaginationOptions as O, PaginationConfig as P, MinMaxResult as Q, RepositoryOptions as R, SelectSpec as S, CacheAdapter as T, UpdateOptions as U, ValidationResult as V, CacheOptions as W, CacheOperationOptions as X, CacheStats as Y, CascadeRelation as Z, CascadeOptions as _, OffsetPaginationResult as a, KeysetPaginationResult as b, AggregatePaginationOptions as c, AggregatePaginationResult as d, PluginType as e, ObjectId as f, PopulateSpec as g, SortSpec as h, RepositoryContext as i, AnyModel as j, SortDirection as k, HookMode as l, PaginationResult as m, OperationOptions as n, UpdateManyResult as o, UpdateWithValidationResult as p, UserContext as q, Plugin as r, PluginFunction as s, RepositoryInstance as t, RepositoryEvent as u, ParsedQuery as v, FieldRules as w, SchemaBuilderOptions as x, CrudSchemas as y, DecodedCursor as z };
569
+ export type { CacheStats as $, AnyDocument as A, DecodedCursor as B, CreateOptions as C, DeleteResult as D, EventPayload as E, FieldPreset as F, ValidatorDefinition as G, HttpError as H, ValidationChainOptions as I, JsonSchema as J, KeysetPaginationOptions as K, Logger as L, SoftDeleteOptions as M, SoftDeleteFilterMode as N, OffsetPaginationOptions as O, PaginationConfig as P, SoftDeleteRepository as Q, RepositoryOptions as R, SelectSpec as S, LookupOptions as T, UpdateOptions as U, ValidationResult as V, GroupResult as W, MinMaxResult as X, CacheAdapter as Y, CacheOptions as Z, CacheOperationOptions as _, OffsetPaginationResult as a, CascadeRelation as a0, CascadeOptions as a1, KeysetPaginationResult as b, AggregatePaginationOptions as c, AggregatePaginationResult as d, PopulateSpec as e, SortSpec as f, PluginType as g, ObjectId as h, RepositoryContext as i, AnyModel as j, SortDirection as k, HookMode as l, PaginationResult as m, OperationOptions as n, UpdateManyResult as o, UpdateWithValidationResult as p, UserContext as q, Plugin as r, PluginFunction as s, RepositoryInstance as t, RepositoryEvent as u, ParsedQuery as v, FilterQuery as w, FieldRules as x, SchemaBuilderOptions as y, CrudSchemas as z };