@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.
- package/dist/actions/index.d.ts +2 -2
- package/dist/{index-CMCrkd2v.d.ts → index-BljuCDFC.d.ts} +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +712 -23
- package/dist/pagination/PaginationEngine.d.ts +1 -1
- package/dist/plugins/index.d.ts +34 -2
- package/dist/plugins/index.js +148 -12
- package/dist/queryParser-CxzCjzXd.d.ts +303 -0
- package/dist/{types-B3dPUKjs.d.ts → types-CHIDluaP.d.ts} +53 -20
- package/dist/utils/index.d.ts +4 -111
- package/dist/utils/index.js +86 -16
- package/package.json +1 -1
- package/dist/memory-cache-Bn_-Kk-0.d.ts +0 -142
|
@@ -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-
|
|
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
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FieldPreset, r as Plugin, L as Logger,
|
|
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
|
|
package/dist/plugins/index.js
CHANGED
|
@@ -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 (
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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 (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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,
|
|
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 };
|