@classytic/mongokit 3.0.6 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +625 -463
- package/dist/actions/index.d.ts +2 -2
- package/dist/actions/index.js +3 -484
- package/dist/chunks/chunk-2ZN65ZOP.js +93 -0
- package/dist/chunks/chunk-CF6FLC2G.js +46 -0
- package/dist/chunks/chunk-CSLJ2PL2.js +1092 -0
- package/dist/chunks/chunk-IT7DCOKR.js +299 -0
- package/dist/chunks/chunk-M2XHQGZB.js +361 -0
- package/dist/chunks/chunk-SAKSLT47.js +470 -0
- package/dist/chunks/chunk-VJXDGP3C.js +14 -0
- package/dist/{index-CkwbNdpJ.d.ts → index-C2NCVxJK.d.ts} +170 -3
- package/dist/index.d.ts +997 -8
- package/dist/index.js +1143 -2476
- package/dist/{queryParser-Do3SgsyJ.d.ts → mongooseToJsonSchema-BKMxPbPp.d.ts} +8 -111
- package/dist/pagination/PaginationEngine.d.ts +1 -1
- package/dist/pagination/PaginationEngine.js +2 -368
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.js +4 -1170
- package/dist/{types-DDDYo18H.d.ts → types-DA0rs2Jh.d.ts} +109 -35
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +3 -711
- package/package.json +8 -3
package/dist/utils/index.js
CHANGED
|
@@ -1,711 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
function getFieldsForUser(user, preset) {
|
|
5
|
-
if (!preset) {
|
|
6
|
-
throw new Error("Field preset is required");
|
|
7
|
-
}
|
|
8
|
-
const fields = [...preset.public || []];
|
|
9
|
-
if (user) {
|
|
10
|
-
fields.push(...preset.authenticated || []);
|
|
11
|
-
const roles = Array.isArray(user.roles) ? user.roles : user.roles ? [user.roles] : [];
|
|
12
|
-
if (roles.includes("admin") || roles.includes("superadmin")) {
|
|
13
|
-
fields.push(...preset.admin || []);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return [...new Set(fields)];
|
|
17
|
-
}
|
|
18
|
-
function getMongooseProjection(user, preset) {
|
|
19
|
-
const fields = getFieldsForUser(user, preset);
|
|
20
|
-
return fields.join(" ");
|
|
21
|
-
}
|
|
22
|
-
function filterObject(obj, allowedFields) {
|
|
23
|
-
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
24
|
-
return obj;
|
|
25
|
-
}
|
|
26
|
-
const filtered = {};
|
|
27
|
-
for (const field of allowedFields) {
|
|
28
|
-
if (field in obj) {
|
|
29
|
-
filtered[field] = obj[field];
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return filtered;
|
|
33
|
-
}
|
|
34
|
-
function filterResponseData(data, preset, user = null) {
|
|
35
|
-
const allowedFields = getFieldsForUser(user, preset);
|
|
36
|
-
if (Array.isArray(data)) {
|
|
37
|
-
return data.map((item) => filterObject(item, allowedFields));
|
|
38
|
-
}
|
|
39
|
-
return filterObject(data, allowedFields);
|
|
40
|
-
}
|
|
41
|
-
function createFieldPreset(config) {
|
|
42
|
-
return {
|
|
43
|
-
public: config.public || [],
|
|
44
|
-
authenticated: config.authenticated || [],
|
|
45
|
-
admin: config.admin || []
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
var QueryParser = class {
|
|
49
|
-
options;
|
|
50
|
-
operators = {
|
|
51
|
-
eq: "$eq",
|
|
52
|
-
ne: "$ne",
|
|
53
|
-
gt: "$gt",
|
|
54
|
-
gte: "$gte",
|
|
55
|
-
lt: "$lt",
|
|
56
|
-
lte: "$lte",
|
|
57
|
-
in: "$in",
|
|
58
|
-
nin: "$nin",
|
|
59
|
-
like: "$regex",
|
|
60
|
-
contains: "$regex",
|
|
61
|
-
regex: "$regex",
|
|
62
|
-
exists: "$exists",
|
|
63
|
-
size: "$size",
|
|
64
|
-
type: "$type"
|
|
65
|
-
};
|
|
66
|
-
/**
|
|
67
|
-
* Dangerous MongoDB operators that should never be accepted from user input
|
|
68
|
-
* Security: Prevent NoSQL injection attacks
|
|
69
|
-
*/
|
|
70
|
-
dangerousOperators;
|
|
71
|
-
/**
|
|
72
|
-
* Regex pattern characters that can cause catastrophic backtracking (ReDoS)
|
|
73
|
-
*/
|
|
74
|
-
dangerousRegexPatterns = /(\{[0-9,]+\}|\*\+|\+\+|\?\+|(\([^)]*\))\1|\(\?[^)]*\)|[\[\]].*[\[\]])/;
|
|
75
|
-
constructor(options = {}) {
|
|
76
|
-
this.options = {
|
|
77
|
-
maxRegexLength: options.maxRegexLength ?? 500,
|
|
78
|
-
maxSearchLength: options.maxSearchLength ?? 200,
|
|
79
|
-
maxFilterDepth: options.maxFilterDepth ?? 10,
|
|
80
|
-
additionalDangerousOperators: options.additionalDangerousOperators ?? []
|
|
81
|
-
};
|
|
82
|
-
this.dangerousOperators = [
|
|
83
|
-
"$where",
|
|
84
|
-
"$function",
|
|
85
|
-
"$accumulator",
|
|
86
|
-
"$expr",
|
|
87
|
-
...this.options.additionalDangerousOperators
|
|
88
|
-
];
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Parse query parameters into MongoDB query format
|
|
92
|
-
*/
|
|
93
|
-
parseQuery(query) {
|
|
94
|
-
const {
|
|
95
|
-
page,
|
|
96
|
-
limit = 20,
|
|
97
|
-
sort = "-createdAt",
|
|
98
|
-
populate,
|
|
99
|
-
search,
|
|
100
|
-
after,
|
|
101
|
-
cursor,
|
|
102
|
-
...filters
|
|
103
|
-
} = query || {};
|
|
104
|
-
const parsed = {
|
|
105
|
-
filters: this._parseFilters(filters),
|
|
106
|
-
limit: parseInt(String(limit), 10),
|
|
107
|
-
sort: this._parseSort(sort),
|
|
108
|
-
populate,
|
|
109
|
-
search: this._sanitizeSearch(search)
|
|
110
|
-
};
|
|
111
|
-
if (after || cursor) {
|
|
112
|
-
parsed.after = after || cursor;
|
|
113
|
-
} else if (page !== void 0) {
|
|
114
|
-
parsed.page = parseInt(String(page), 10);
|
|
115
|
-
} else {
|
|
116
|
-
parsed.page = 1;
|
|
117
|
-
}
|
|
118
|
-
const orGroup = this._parseOr(query);
|
|
119
|
-
if (orGroup) {
|
|
120
|
-
parsed.filters = { ...parsed.filters, $or: orGroup };
|
|
121
|
-
}
|
|
122
|
-
parsed.filters = this._enhanceWithBetween(parsed.filters);
|
|
123
|
-
return parsed;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Parse sort parameter
|
|
127
|
-
* Converts string like '-createdAt' to { createdAt: -1 }
|
|
128
|
-
* Handles multiple sorts: '-createdAt,name' → { createdAt: -1, name: 1 }
|
|
129
|
-
*/
|
|
130
|
-
_parseSort(sort) {
|
|
131
|
-
if (!sort) return void 0;
|
|
132
|
-
if (typeof sort === "object") return sort;
|
|
133
|
-
const sortObj = {};
|
|
134
|
-
const fields = sort.split(",").map((s) => s.trim());
|
|
135
|
-
for (const field of fields) {
|
|
136
|
-
if (field.startsWith("-")) {
|
|
137
|
-
sortObj[field.substring(1)] = -1;
|
|
138
|
-
} else {
|
|
139
|
-
sortObj[field] = 1;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return sortObj;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Parse standard filter parameter (filter[field]=value)
|
|
146
|
-
*/
|
|
147
|
-
_parseFilters(filters) {
|
|
148
|
-
const parsedFilters = {};
|
|
149
|
-
const regexFields = {};
|
|
150
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
151
|
-
if (this.dangerousOperators.includes(key) || key.startsWith("$") && !["$or", "$and"].includes(key)) {
|
|
152
|
-
console.warn(`[mongokit] Blocked dangerous operator: ${key}`);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (["page", "limit", "sort", "populate", "search", "select", "lean", "includeDeleted"].includes(key)) {
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
const operatorMatch = key.match(/^(.+)\[(.+)\]$/);
|
|
159
|
-
if (operatorMatch) {
|
|
160
|
-
const [, , operator] = operatorMatch;
|
|
161
|
-
if (this.dangerousOperators.includes("$" + operator)) {
|
|
162
|
-
console.warn(`[mongokit] Blocked dangerous operator: ${operator}`);
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
this._handleOperatorSyntax(parsedFilters, regexFields, operatorMatch, value);
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
169
|
-
this._handleBracketSyntax(key, value, parsedFilters);
|
|
170
|
-
} else {
|
|
171
|
-
parsedFilters[key] = this._convertValue(value);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return parsedFilters;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Handle operator syntax: field[operator]=value
|
|
178
|
-
*/
|
|
179
|
-
_handleOperatorSyntax(filters, regexFields, operatorMatch, value) {
|
|
180
|
-
const [, field, operator] = operatorMatch;
|
|
181
|
-
if (operator.toLowerCase() === "options" && regexFields[field]) {
|
|
182
|
-
const fieldValue = filters[field];
|
|
183
|
-
if (typeof fieldValue === "object" && fieldValue !== null && "$regex" in fieldValue) {
|
|
184
|
-
fieldValue.$options = value;
|
|
185
|
-
}
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if (operator.toLowerCase() === "contains" || operator.toLowerCase() === "like") {
|
|
189
|
-
const safeRegex = this._createSafeRegex(value);
|
|
190
|
-
if (safeRegex) {
|
|
191
|
-
filters[field] = { $regex: safeRegex };
|
|
192
|
-
regexFields[field] = true;
|
|
193
|
-
}
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const mongoOperator = this._toMongoOperator(operator);
|
|
197
|
-
if (this.dangerousOperators.includes(mongoOperator)) {
|
|
198
|
-
console.warn(`[mongokit] Blocked dangerous operator in field[${operator}]: ${mongoOperator}`);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
if (mongoOperator === "$eq") {
|
|
202
|
-
filters[field] = value;
|
|
203
|
-
} else if (mongoOperator === "$regex") {
|
|
204
|
-
filters[field] = { $regex: value };
|
|
205
|
-
regexFields[field] = true;
|
|
206
|
-
} else {
|
|
207
|
-
if (typeof filters[field] !== "object" || filters[field] === null || Array.isArray(filters[field])) {
|
|
208
|
-
filters[field] = {};
|
|
209
|
-
}
|
|
210
|
-
let processedValue;
|
|
211
|
-
const op = operator.toLowerCase();
|
|
212
|
-
if (["gt", "gte", "lt", "lte", "size"].includes(op)) {
|
|
213
|
-
processedValue = parseFloat(String(value));
|
|
214
|
-
if (isNaN(processedValue)) return;
|
|
215
|
-
} else if (op === "in" || op === "nin") {
|
|
216
|
-
processedValue = Array.isArray(value) ? value : String(value).split(",").map((v) => v.trim());
|
|
217
|
-
} else {
|
|
218
|
-
processedValue = this._convertValue(value);
|
|
219
|
-
}
|
|
220
|
-
filters[field][mongoOperator] = processedValue;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Handle bracket syntax with object value
|
|
225
|
-
*/
|
|
226
|
-
_handleBracketSyntax(field, operators, parsedFilters) {
|
|
227
|
-
if (!parsedFilters[field]) {
|
|
228
|
-
parsedFilters[field] = {};
|
|
229
|
-
}
|
|
230
|
-
for (const [operator, value] of Object.entries(operators)) {
|
|
231
|
-
if (operator === "between") {
|
|
232
|
-
parsedFilters[field].between = value;
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
if (this.operators[operator]) {
|
|
236
|
-
const mongoOperator = this.operators[operator];
|
|
237
|
-
let processedValue;
|
|
238
|
-
if (["gt", "gte", "lt", "lte", "size"].includes(operator)) {
|
|
239
|
-
processedValue = parseFloat(String(value));
|
|
240
|
-
if (isNaN(processedValue)) continue;
|
|
241
|
-
} else if (operator === "in" || operator === "nin") {
|
|
242
|
-
processedValue = Array.isArray(value) ? value : String(value).split(",").map((v) => v.trim());
|
|
243
|
-
} else if (operator === "like" || operator === "contains") {
|
|
244
|
-
const safeRegex = this._createSafeRegex(value);
|
|
245
|
-
if (!safeRegex) continue;
|
|
246
|
-
processedValue = safeRegex;
|
|
247
|
-
} else {
|
|
248
|
-
processedValue = this._convertValue(value);
|
|
249
|
-
}
|
|
250
|
-
parsedFilters[field][mongoOperator] = processedValue;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Convert operator to MongoDB format
|
|
256
|
-
*/
|
|
257
|
-
_toMongoOperator(operator) {
|
|
258
|
-
const op = operator.toLowerCase();
|
|
259
|
-
return op.startsWith("$") ? op : "$" + op;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Create a safe regex pattern with protection against ReDoS attacks
|
|
263
|
-
* @param pattern - The pattern string from user input
|
|
264
|
-
* @param flags - Regex flags (default: 'i' for case-insensitive)
|
|
265
|
-
* @returns A safe RegExp or null if pattern is invalid/dangerous
|
|
266
|
-
*/
|
|
267
|
-
_createSafeRegex(pattern, flags = "i") {
|
|
268
|
-
if (pattern === null || pattern === void 0) {
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
const patternStr = String(pattern);
|
|
272
|
-
if (patternStr.length > this.options.maxRegexLength) {
|
|
273
|
-
console.warn(`[mongokit] Regex pattern too long (${patternStr.length} > ${this.options.maxRegexLength}), truncating`);
|
|
274
|
-
return new RegExp(this._escapeRegex(patternStr.substring(0, this.options.maxRegexLength)), flags);
|
|
275
|
-
}
|
|
276
|
-
if (this.dangerousRegexPatterns.test(patternStr)) {
|
|
277
|
-
console.warn("[mongokit] Potentially dangerous regex pattern detected, escaping");
|
|
278
|
-
return new RegExp(this._escapeRegex(patternStr), flags);
|
|
279
|
-
}
|
|
280
|
-
try {
|
|
281
|
-
return new RegExp(patternStr, flags);
|
|
282
|
-
} catch {
|
|
283
|
-
return new RegExp(this._escapeRegex(patternStr), flags);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Escape special regex characters for literal matching
|
|
288
|
-
*/
|
|
289
|
-
_escapeRegex(str) {
|
|
290
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Sanitize text search query for MongoDB $text search
|
|
294
|
-
* @param search - Raw search input from user
|
|
295
|
-
* @returns Sanitized search string or undefined
|
|
296
|
-
*/
|
|
297
|
-
_sanitizeSearch(search) {
|
|
298
|
-
if (search === null || search === void 0 || search === "") {
|
|
299
|
-
return void 0;
|
|
300
|
-
}
|
|
301
|
-
let searchStr = String(search).trim();
|
|
302
|
-
if (!searchStr) {
|
|
303
|
-
return void 0;
|
|
304
|
-
}
|
|
305
|
-
if (searchStr.length > this.options.maxSearchLength) {
|
|
306
|
-
console.warn(`[mongokit] Search query too long (${searchStr.length} > ${this.options.maxSearchLength}), truncating`);
|
|
307
|
-
searchStr = searchStr.substring(0, this.options.maxSearchLength);
|
|
308
|
-
}
|
|
309
|
-
return searchStr;
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Convert values based on operator type
|
|
313
|
-
*/
|
|
314
|
-
_convertValue(value) {
|
|
315
|
-
if (value === null || value === void 0) return value;
|
|
316
|
-
if (Array.isArray(value)) return value.map((v) => this._convertValue(v));
|
|
317
|
-
if (typeof value === "object") return value;
|
|
318
|
-
const stringValue = String(value);
|
|
319
|
-
if (stringValue === "true") return true;
|
|
320
|
-
if (stringValue === "false") return false;
|
|
321
|
-
if (mongoose2.Types.ObjectId.isValid(stringValue) && stringValue.length === 24) {
|
|
322
|
-
return stringValue;
|
|
323
|
-
}
|
|
324
|
-
return stringValue;
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Parse $or conditions
|
|
328
|
-
*/
|
|
329
|
-
_parseOr(query) {
|
|
330
|
-
const orArray = [];
|
|
331
|
-
const raw = query?.or || query?.OR || query?.$or;
|
|
332
|
-
if (!raw) return void 0;
|
|
333
|
-
const items = Array.isArray(raw) ? raw : typeof raw === "object" ? Object.values(raw) : [];
|
|
334
|
-
for (const item of items) {
|
|
335
|
-
if (typeof item === "object" && item) {
|
|
336
|
-
orArray.push(this._parseFilters(item));
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
return orArray.length ? orArray : void 0;
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Enhance filters with between operator
|
|
343
|
-
*/
|
|
344
|
-
_enhanceWithBetween(filters) {
|
|
345
|
-
const output = { ...filters };
|
|
346
|
-
for (const [key, value] of Object.entries(filters || {})) {
|
|
347
|
-
if (value && typeof value === "object" && "between" in value) {
|
|
348
|
-
const between = value.between;
|
|
349
|
-
const [from, to] = String(between).split(",").map((s) => s.trim());
|
|
350
|
-
const fromDate = from ? new Date(from) : void 0;
|
|
351
|
-
const toDate = to ? new Date(to) : void 0;
|
|
352
|
-
const range = {};
|
|
353
|
-
if (fromDate && !isNaN(fromDate.getTime())) range.$gte = fromDate;
|
|
354
|
-
if (toDate && !isNaN(toDate.getTime())) range.$lte = toDate;
|
|
355
|
-
output[key] = range;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return output;
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
var defaultQueryParser = new QueryParser();
|
|
362
|
-
var queryParser_default = defaultQueryParser;
|
|
363
|
-
function isMongooseSchema(value) {
|
|
364
|
-
return value instanceof mongoose2.Schema;
|
|
365
|
-
}
|
|
366
|
-
function isPlainObject(value) {
|
|
367
|
-
return Object.prototype.toString.call(value) === "[object Object]";
|
|
368
|
-
}
|
|
369
|
-
function isObjectIdType(t) {
|
|
370
|
-
return t === mongoose2.Schema.Types.ObjectId || t === mongoose2.Types.ObjectId;
|
|
371
|
-
}
|
|
372
|
-
function buildCrudSchemasFromMongooseSchema(mongooseSchema, options = {}) {
|
|
373
|
-
const tree = mongooseSchema?.obj || {};
|
|
374
|
-
const jsonCreate = buildJsonSchemaForCreate(tree, options);
|
|
375
|
-
const jsonUpdate = buildJsonSchemaForUpdate(jsonCreate, options);
|
|
376
|
-
const jsonParams = {
|
|
377
|
-
type: "object",
|
|
378
|
-
properties: { id: { type: "string", pattern: "^[0-9a-fA-F]{24}$" } },
|
|
379
|
-
required: ["id"]
|
|
380
|
-
};
|
|
381
|
-
const jsonQuery = buildJsonSchemaForQuery(tree, options);
|
|
382
|
-
return { createBody: jsonCreate, updateBody: jsonUpdate, params: jsonParams, listQuery: jsonQuery };
|
|
383
|
-
}
|
|
384
|
-
function buildCrudSchemasFromModel(mongooseModel, options = {}) {
|
|
385
|
-
if (!mongooseModel || !mongooseModel.schema) {
|
|
386
|
-
throw new Error("Invalid mongoose model");
|
|
387
|
-
}
|
|
388
|
-
return buildCrudSchemasFromMongooseSchema(mongooseModel.schema, options);
|
|
389
|
-
}
|
|
390
|
-
function getImmutableFields(options = {}) {
|
|
391
|
-
const immutable = [];
|
|
392
|
-
const fieldRules = options?.fieldRules || {};
|
|
393
|
-
Object.entries(fieldRules).forEach(([field, rules]) => {
|
|
394
|
-
if (rules.immutable || rules.immutableAfterCreate) {
|
|
395
|
-
immutable.push(field);
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
(options?.update?.omitFields || []).forEach((f) => {
|
|
399
|
-
if (!immutable.includes(f)) immutable.push(f);
|
|
400
|
-
});
|
|
401
|
-
return immutable;
|
|
402
|
-
}
|
|
403
|
-
function getSystemManagedFields(options = {}) {
|
|
404
|
-
const systemManaged = [];
|
|
405
|
-
const fieldRules = options?.fieldRules || {};
|
|
406
|
-
Object.entries(fieldRules).forEach(([field, rules]) => {
|
|
407
|
-
if (rules.systemManaged) {
|
|
408
|
-
systemManaged.push(field);
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
return systemManaged;
|
|
412
|
-
}
|
|
413
|
-
function isFieldUpdateAllowed(fieldName, options = {}) {
|
|
414
|
-
const immutableFields = getImmutableFields(options);
|
|
415
|
-
const systemManagedFields = getSystemManagedFields(options);
|
|
416
|
-
return !immutableFields.includes(fieldName) && !systemManagedFields.includes(fieldName);
|
|
417
|
-
}
|
|
418
|
-
function validateUpdateBody(body = {}, options = {}) {
|
|
419
|
-
const violations = [];
|
|
420
|
-
const immutableFields = getImmutableFields(options);
|
|
421
|
-
const systemManagedFields = getSystemManagedFields(options);
|
|
422
|
-
Object.keys(body).forEach((field) => {
|
|
423
|
-
if (immutableFields.includes(field)) {
|
|
424
|
-
violations.push({ field, reason: "Field is immutable" });
|
|
425
|
-
} else if (systemManagedFields.includes(field)) {
|
|
426
|
-
violations.push({ field, reason: "Field is system-managed" });
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
return {
|
|
430
|
-
valid: violations.length === 0,
|
|
431
|
-
violations
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
function jsonTypeFor(def, options, seen) {
|
|
435
|
-
if (Array.isArray(def)) {
|
|
436
|
-
if (def[0] === mongoose2.Schema.Types.Mixed) {
|
|
437
|
-
return { type: "array", items: { type: "object", additionalProperties: true } };
|
|
438
|
-
}
|
|
439
|
-
return { type: "array", items: jsonTypeFor(def[0] ?? String, options, seen) };
|
|
440
|
-
}
|
|
441
|
-
if (isPlainObject(def) && "type" in def) {
|
|
442
|
-
const typedDef = def;
|
|
443
|
-
if (typedDef.enum && Array.isArray(typedDef.enum) && typedDef.enum.length) {
|
|
444
|
-
return { type: "string", enum: typedDef.enum.map(String) };
|
|
445
|
-
}
|
|
446
|
-
if (Array.isArray(typedDef.type)) {
|
|
447
|
-
const inner = typedDef.type[0] !== void 0 ? typedDef.type[0] : String;
|
|
448
|
-
if (inner === mongoose2.Schema.Types.Mixed) {
|
|
449
|
-
return { type: "array", items: { type: "object", additionalProperties: true } };
|
|
450
|
-
}
|
|
451
|
-
return { type: "array", items: jsonTypeFor(inner, options, seen) };
|
|
452
|
-
}
|
|
453
|
-
if (typedDef.type === String) return { type: "string" };
|
|
454
|
-
if (typedDef.type === Number) return { type: "number" };
|
|
455
|
-
if (typedDef.type === Boolean) return { type: "boolean" };
|
|
456
|
-
if (typedDef.type === Date) {
|
|
457
|
-
const mode = options?.dateAs || "datetime";
|
|
458
|
-
return mode === "date" ? { type: "string", format: "date" } : { type: "string", format: "date-time" };
|
|
459
|
-
}
|
|
460
|
-
if (typedDef.type === Map || typedDef.type === mongoose2.Schema.Types.Map) {
|
|
461
|
-
const ofSchema = jsonTypeFor(typedDef.of || String, options, seen);
|
|
462
|
-
return { type: "object", additionalProperties: ofSchema };
|
|
463
|
-
}
|
|
464
|
-
if (typedDef.type === mongoose2.Schema.Types.Mixed) {
|
|
465
|
-
return { type: "object", additionalProperties: true };
|
|
466
|
-
}
|
|
467
|
-
if (isObjectIdType(typedDef.type)) {
|
|
468
|
-
return { type: "string", pattern: "^[0-9a-fA-F]{24}$" };
|
|
469
|
-
}
|
|
470
|
-
if (isMongooseSchema(typedDef.type)) {
|
|
471
|
-
const obj = typedDef.type.obj;
|
|
472
|
-
if (obj && typeof obj === "object") {
|
|
473
|
-
if (seen.has(obj)) return { type: "object", additionalProperties: true };
|
|
474
|
-
seen.add(obj);
|
|
475
|
-
return convertTreeToJsonSchema(obj, options, seen);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
if (def === String) return { type: "string" };
|
|
480
|
-
if (def === Number) return { type: "number" };
|
|
481
|
-
if (def === Boolean) return { type: "boolean" };
|
|
482
|
-
if (def === Date) {
|
|
483
|
-
const mode = options?.dateAs || "datetime";
|
|
484
|
-
return mode === "date" ? { type: "string", format: "date" } : { type: "string", format: "date-time" };
|
|
485
|
-
}
|
|
486
|
-
if (isObjectIdType(def)) return { type: "string", pattern: "^[0-9a-fA-F]{24}$" };
|
|
487
|
-
if (isPlainObject(def)) {
|
|
488
|
-
if (seen.has(def)) return { type: "object", additionalProperties: true };
|
|
489
|
-
seen.add(def);
|
|
490
|
-
return convertTreeToJsonSchema(def, options, seen);
|
|
491
|
-
}
|
|
492
|
-
return {};
|
|
493
|
-
}
|
|
494
|
-
function convertTreeToJsonSchema(tree, options, seen = /* @__PURE__ */ new WeakSet()) {
|
|
495
|
-
if (!tree || typeof tree !== "object") {
|
|
496
|
-
return { type: "object", properties: {} };
|
|
497
|
-
}
|
|
498
|
-
if (seen.has(tree)) {
|
|
499
|
-
return { type: "object", additionalProperties: true };
|
|
500
|
-
}
|
|
501
|
-
seen.add(tree);
|
|
502
|
-
const properties = {};
|
|
503
|
-
const required = [];
|
|
504
|
-
for (const [key, val] of Object.entries(tree || {})) {
|
|
505
|
-
if (key === "__v" || key === "_id" || key === "id") continue;
|
|
506
|
-
const cfg = isPlainObject(val) && "type" in val ? val : { };
|
|
507
|
-
properties[key] = jsonTypeFor(val, options, seen);
|
|
508
|
-
if (cfg.required === true) required.push(key);
|
|
509
|
-
}
|
|
510
|
-
const schema = { type: "object", properties };
|
|
511
|
-
if (required.length) schema.required = required;
|
|
512
|
-
return schema;
|
|
513
|
-
}
|
|
514
|
-
function buildJsonSchemaForCreate(tree, options) {
|
|
515
|
-
const base = convertTreeToJsonSchema(tree, options, /* @__PURE__ */ new WeakSet());
|
|
516
|
-
const fieldsToOmit = /* @__PURE__ */ new Set(["createdAt", "updatedAt", "__v"]);
|
|
517
|
-
(options?.create?.omitFields || []).forEach((f) => fieldsToOmit.add(f));
|
|
518
|
-
const fieldRules = options?.fieldRules || {};
|
|
519
|
-
Object.entries(fieldRules).forEach(([field, rules]) => {
|
|
520
|
-
if (rules.systemManaged) {
|
|
521
|
-
fieldsToOmit.add(field);
|
|
522
|
-
}
|
|
523
|
-
});
|
|
524
|
-
fieldsToOmit.forEach((field) => {
|
|
525
|
-
if (base.properties?.[field]) {
|
|
526
|
-
delete base.properties[field];
|
|
527
|
-
}
|
|
528
|
-
if (base.required) {
|
|
529
|
-
base.required = base.required.filter((k) => k !== field);
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
const reqOv = options?.create?.requiredOverrides || {};
|
|
533
|
-
const optOv = options?.create?.optionalOverrides || {};
|
|
534
|
-
base.required = base.required || [];
|
|
535
|
-
for (const [k, v] of Object.entries(reqOv)) {
|
|
536
|
-
if (v && !base.required.includes(k)) base.required.push(k);
|
|
537
|
-
}
|
|
538
|
-
for (const [k, v] of Object.entries(optOv)) {
|
|
539
|
-
if (v && base.required) base.required = base.required.filter((x) => x !== k);
|
|
540
|
-
}
|
|
541
|
-
Object.entries(fieldRules).forEach(([field, rules]) => {
|
|
542
|
-
if (rules.optional && base.required) {
|
|
543
|
-
base.required = base.required.filter((x) => x !== field);
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
const schemaOverrides = options?.create?.schemaOverrides || {};
|
|
547
|
-
for (const [k, override] of Object.entries(schemaOverrides)) {
|
|
548
|
-
if (base.properties?.[k]) {
|
|
549
|
-
base.properties[k] = override;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
if (options?.strictAdditionalProperties === true) {
|
|
553
|
-
base.additionalProperties = false;
|
|
554
|
-
}
|
|
555
|
-
return base;
|
|
556
|
-
}
|
|
557
|
-
function buildJsonSchemaForUpdate(createJson, options) {
|
|
558
|
-
const clone = JSON.parse(JSON.stringify(createJson));
|
|
559
|
-
delete clone.required;
|
|
560
|
-
const fieldsToOmit = /* @__PURE__ */ new Set();
|
|
561
|
-
(options?.update?.omitFields || []).forEach((f) => fieldsToOmit.add(f));
|
|
562
|
-
const fieldRules = options?.fieldRules || {};
|
|
563
|
-
Object.entries(fieldRules).forEach(([field, rules]) => {
|
|
564
|
-
if (rules.immutable || rules.immutableAfterCreate) {
|
|
565
|
-
fieldsToOmit.add(field);
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
fieldsToOmit.forEach((field) => {
|
|
569
|
-
if (clone.properties?.[field]) {
|
|
570
|
-
delete clone.properties[field];
|
|
571
|
-
}
|
|
572
|
-
});
|
|
573
|
-
if (options?.strictAdditionalProperties === true) {
|
|
574
|
-
clone.additionalProperties = false;
|
|
575
|
-
}
|
|
576
|
-
return clone;
|
|
577
|
-
}
|
|
578
|
-
function buildJsonSchemaForQuery(_tree, options) {
|
|
579
|
-
const basePagination = {
|
|
580
|
-
type: "object",
|
|
581
|
-
properties: {
|
|
582
|
-
page: { type: "string" },
|
|
583
|
-
limit: { type: "string" },
|
|
584
|
-
sort: { type: "string" },
|
|
585
|
-
populate: { type: "string" },
|
|
586
|
-
search: { type: "string" },
|
|
587
|
-
select: { type: "string" },
|
|
588
|
-
lean: { type: "string" },
|
|
589
|
-
includeDeleted: { type: "string" }
|
|
590
|
-
},
|
|
591
|
-
additionalProperties: true
|
|
592
|
-
};
|
|
593
|
-
const filterable = options?.query?.filterableFields || {};
|
|
594
|
-
for (const [k, v] of Object.entries(filterable)) {
|
|
595
|
-
if (basePagination.properties) {
|
|
596
|
-
basePagination.properties[k] = v && typeof v === "object" && "type" in v ? v : { type: "string" };
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
return basePagination;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// src/utils/error.ts
|
|
603
|
-
function createError(status, message) {
|
|
604
|
-
const error = new Error(message);
|
|
605
|
-
error.status = status;
|
|
606
|
-
return error;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// src/utils/memory-cache.ts
|
|
610
|
-
function createMemoryCache(maxEntries = 1e3) {
|
|
611
|
-
const cache = /* @__PURE__ */ new Map();
|
|
612
|
-
function cleanup() {
|
|
613
|
-
const now = Date.now();
|
|
614
|
-
for (const [key, entry] of cache) {
|
|
615
|
-
if (entry.expiresAt < now) {
|
|
616
|
-
cache.delete(key);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
function evictOldest() {
|
|
621
|
-
if (cache.size >= maxEntries) {
|
|
622
|
-
const firstKey = cache.keys().next().value;
|
|
623
|
-
if (firstKey) cache.delete(firstKey);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
return {
|
|
627
|
-
async get(key) {
|
|
628
|
-
cleanup();
|
|
629
|
-
const entry = cache.get(key);
|
|
630
|
-
if (!entry) return null;
|
|
631
|
-
if (entry.expiresAt < Date.now()) {
|
|
632
|
-
cache.delete(key);
|
|
633
|
-
return null;
|
|
634
|
-
}
|
|
635
|
-
return entry.value;
|
|
636
|
-
},
|
|
637
|
-
async set(key, value, ttl) {
|
|
638
|
-
cleanup();
|
|
639
|
-
evictOldest();
|
|
640
|
-
cache.set(key, {
|
|
641
|
-
value,
|
|
642
|
-
expiresAt: Date.now() + ttl * 1e3
|
|
643
|
-
});
|
|
644
|
-
},
|
|
645
|
-
async del(key) {
|
|
646
|
-
cache.delete(key);
|
|
647
|
-
},
|
|
648
|
-
async clear(pattern) {
|
|
649
|
-
if (!pattern) {
|
|
650
|
-
cache.clear();
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
const regex = new RegExp(
|
|
654
|
-
"^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
655
|
-
);
|
|
656
|
-
for (const key of cache.keys()) {
|
|
657
|
-
if (regex.test(key)) {
|
|
658
|
-
cache.delete(key);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// src/utils/cache-keys.ts
|
|
666
|
-
function hashString(str) {
|
|
667
|
-
let hash = 5381;
|
|
668
|
-
for (let i = 0; i < str.length; i++) {
|
|
669
|
-
hash = (hash << 5) + hash ^ str.charCodeAt(i);
|
|
670
|
-
}
|
|
671
|
-
return (hash >>> 0).toString(16);
|
|
672
|
-
}
|
|
673
|
-
function stableStringify(obj) {
|
|
674
|
-
if (obj === null || obj === void 0) return "";
|
|
675
|
-
if (typeof obj !== "object") return String(obj);
|
|
676
|
-
if (Array.isArray(obj)) {
|
|
677
|
-
return "[" + obj.map(stableStringify).join(",") + "]";
|
|
678
|
-
}
|
|
679
|
-
const sorted = Object.keys(obj).sort().map((key) => `${key}:${stableStringify(obj[key])}`);
|
|
680
|
-
return "{" + sorted.join(",") + "}";
|
|
681
|
-
}
|
|
682
|
-
function byIdKey(prefix, model, id) {
|
|
683
|
-
return `${prefix}:id:${model}:${id}`;
|
|
684
|
-
}
|
|
685
|
-
function byQueryKey(prefix, model, query, options) {
|
|
686
|
-
const hashInput = stableStringify({ q: query, s: options?.select, p: options?.populate });
|
|
687
|
-
return `${prefix}:one:${model}:${hashString(hashInput)}`;
|
|
688
|
-
}
|
|
689
|
-
function listQueryKey(prefix, model, version, params) {
|
|
690
|
-
const hashInput = stableStringify({
|
|
691
|
-
f: params.filters,
|
|
692
|
-
s: params.sort,
|
|
693
|
-
pg: params.page,
|
|
694
|
-
lm: params.limit,
|
|
695
|
-
af: params.after,
|
|
696
|
-
sl: params.select,
|
|
697
|
-
pp: params.populate
|
|
698
|
-
});
|
|
699
|
-
return `${prefix}:list:${model}:${version}:${hashString(hashInput)}`;
|
|
700
|
-
}
|
|
701
|
-
function versionKey(prefix, model) {
|
|
702
|
-
return `${prefix}:ver:${model}`;
|
|
703
|
-
}
|
|
704
|
-
function modelPattern(prefix, model) {
|
|
705
|
-
return `${prefix}:*:${model}:*`;
|
|
706
|
-
}
|
|
707
|
-
function listPattern(prefix, model) {
|
|
708
|
-
return `${prefix}:list:${model}:*`;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
export { QueryParser, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, byIdKey, byQueryKey, createError, createFieldPreset, createMemoryCache, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getSystemManagedFields, isFieldUpdateAllowed, listPattern, listQueryKey, modelPattern, queryParser_default as queryParser, validateUpdateBody, versionKey };
|
|
1
|
+
export { buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, createMemoryCache, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, validateUpdateBody } from '../chunks/chunk-IT7DCOKR.js';
|
|
2
|
+
export { byIdKey, byQueryKey, createFieldPreset, filterResponseData, getFieldsForUser, getMongooseProjection, listPattern, listQueryKey, modelPattern, versionKey } from '../chunks/chunk-2ZN65ZOP.js';
|
|
3
|
+
export { createError } from '../chunks/chunk-VJXDGP3C.js';
|