@bedrockio/model 0.2.17 → 0.2.19
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 +218 -22
- package/dist/cjs/delete-hooks.js +129 -115
- package/dist/cjs/include.js +12 -8
- package/dist/cjs/search.js +203 -4
- package/package.json +2 -2
- package/src/delete-hooks.js +124 -108
- package/src/include.js +13 -8
- package/src/search.js +232 -5
- package/types/delete-hooks.d.ts.map +1 -1
- package/types/load.d.ts +1 -67
- package/types/load.d.ts.map +1 -1
- package/types/schema.d.ts +3 -7
- package/types/schema.d.ts.map +1 -1
- package/types/search.d.ts.map +1 -1
package/dist/cjs/search.js
CHANGED
|
@@ -16,15 +16,19 @@ var _env = require("./env");
|
|
|
16
16
|
var _query = require("./query");
|
|
17
17
|
var _warn = _interopRequireDefault(require("./warn"));
|
|
18
18
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
19
|
+
const {
|
|
20
|
+
SchemaTypes
|
|
21
|
+
} = _mongoose.default;
|
|
19
22
|
const {
|
|
20
23
|
ObjectId
|
|
21
24
|
} = _mongoose.default.Types;
|
|
22
25
|
function applySearch(schema, definition) {
|
|
23
26
|
validateDefinition(definition);
|
|
27
|
+
applySearchCache(schema, definition);
|
|
24
28
|
schema.static('search', function search(body = {}) {
|
|
25
29
|
const options = {
|
|
26
30
|
..._const.SEARCH_DEFAULTS,
|
|
27
|
-
...definition.search,
|
|
31
|
+
...definition.search?.query,
|
|
28
32
|
...body
|
|
29
33
|
};
|
|
30
34
|
const {
|
|
@@ -33,7 +37,6 @@ function applySearch(schema, definition) {
|
|
|
33
37
|
skip = 0,
|
|
34
38
|
limit,
|
|
35
39
|
sort,
|
|
36
|
-
fields,
|
|
37
40
|
...rest
|
|
38
41
|
} = options;
|
|
39
42
|
const query = {};
|
|
@@ -43,7 +46,7 @@ function applySearch(schema, definition) {
|
|
|
43
46
|
};
|
|
44
47
|
}
|
|
45
48
|
if (keyword) {
|
|
46
|
-
Object.assign(query, buildKeywordQuery(schema, keyword, fields));
|
|
49
|
+
Object.assign(query, buildKeywordQuery(schema, keyword, definition.search?.fields));
|
|
47
50
|
}
|
|
48
51
|
Object.assign(query, normalizeQuery(rest, schema.obj));
|
|
49
52
|
if (_env.debug) {
|
|
@@ -162,7 +165,7 @@ function buildKeywordQuery(schema, keyword, fields) {
|
|
|
162
165
|
} else if (hasTextIndex(schema)) {
|
|
163
166
|
queries = [getTextQuery(keyword)];
|
|
164
167
|
} else {
|
|
165
|
-
|
|
168
|
+
throw new Error('No keyword fields defined.');
|
|
166
169
|
}
|
|
167
170
|
if (ObjectId.isValid(keyword)) {
|
|
168
171
|
queries.push({
|
|
@@ -284,4 +287,200 @@ function parseRegexQuery(str) {
|
|
|
284
287
|
$regex,
|
|
285
288
|
$options
|
|
286
289
|
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Search field caching
|
|
293
|
+
|
|
294
|
+
function applySearchCache(schema, definition) {
|
|
295
|
+
normalizeCacheFields(schema, definition);
|
|
296
|
+
if (!definition.search?.cache) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
createCacheFields(schema, definition);
|
|
300
|
+
applyCacheHook(schema, definition);
|
|
301
|
+
schema.static('syncSearchFields', async function syncSearchFields(options = {}) {
|
|
302
|
+
assertIncludeModule(this);
|
|
303
|
+
const {
|
|
304
|
+
force
|
|
305
|
+
} = options;
|
|
306
|
+
const {
|
|
307
|
+
cache = {}
|
|
308
|
+
} = definition.search || {};
|
|
309
|
+
const paths = getCachePaths(definition);
|
|
310
|
+
const cachedFields = Object.keys(cache);
|
|
311
|
+
if (!cachedFields.length) {
|
|
312
|
+
throw new Error('No search fields to sync.');
|
|
313
|
+
}
|
|
314
|
+
const query = {};
|
|
315
|
+
if (!force) {
|
|
316
|
+
const $or = Object.entries(cache).map(entry => {
|
|
317
|
+
const [cachedField, def] = entry;
|
|
318
|
+
const {
|
|
319
|
+
base
|
|
320
|
+
} = def;
|
|
321
|
+
return {
|
|
322
|
+
[base]: {
|
|
323
|
+
$exists: true
|
|
324
|
+
},
|
|
325
|
+
[cachedField]: {
|
|
326
|
+
$exists: false
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
query.$or = $or;
|
|
331
|
+
}
|
|
332
|
+
const docs = await this.find(query).include(paths);
|
|
333
|
+
const ops = docs.map(doc => {
|
|
334
|
+
return {
|
|
335
|
+
updateOne: {
|
|
336
|
+
filter: {
|
|
337
|
+
_id: doc._id
|
|
338
|
+
},
|
|
339
|
+
update: {
|
|
340
|
+
$set: getUpdates(doc, paths, definition)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
return await this.bulkWrite(ops);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
function normalizeCacheFields(schema, definition) {
|
|
349
|
+
const {
|
|
350
|
+
fields,
|
|
351
|
+
cache = {}
|
|
352
|
+
} = definition.search || {};
|
|
353
|
+
if (!fields) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const normalized = [];
|
|
357
|
+
for (let path of fields) {
|
|
358
|
+
if (isForeignField(schema, path)) {
|
|
359
|
+
const cacheName = generateCacheFieldName(path);
|
|
360
|
+
const type = resolveSchemaType(schema, path);
|
|
361
|
+
const base = getRefBase(schema, path);
|
|
362
|
+
cache[cacheName] = {
|
|
363
|
+
type,
|
|
364
|
+
base,
|
|
365
|
+
path: path
|
|
366
|
+
};
|
|
367
|
+
normalized.push(cacheName);
|
|
368
|
+
} else {
|
|
369
|
+
normalized.push(path);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
definition.search.cache = cache;
|
|
373
|
+
definition.search.fields = normalized;
|
|
374
|
+
}
|
|
375
|
+
function createCacheFields(schema, definition) {
|
|
376
|
+
for (let [cachedField, def] of Object.entries(definition.search.cache)) {
|
|
377
|
+
// Fall back to string type for virtuals or not defined.
|
|
378
|
+
const {
|
|
379
|
+
type = 'String'
|
|
380
|
+
} = def;
|
|
381
|
+
schema.add({
|
|
382
|
+
[cachedField]: type
|
|
383
|
+
});
|
|
384
|
+
schema.obj[cachedField] = {
|
|
385
|
+
type,
|
|
386
|
+
readAccess: 'none'
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function applyCacheHook(schema, definition) {
|
|
391
|
+
schema.pre('save', async function () {
|
|
392
|
+
assertIncludeModule(this.constructor);
|
|
393
|
+
assertAssignModule(this.constructor);
|
|
394
|
+
const doc = this;
|
|
395
|
+
const paths = getCachePaths(definition, (cachedField, def) => {
|
|
396
|
+
if (def.lazy) {
|
|
397
|
+
return !(0, _lodash.get)(doc, cachedField);
|
|
398
|
+
} else {
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
await this.include(paths);
|
|
403
|
+
this.assign(getUpdates(this, paths, definition));
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
function resolveSchemaType(schema, path) {
|
|
407
|
+
if (!path.includes('.')) {
|
|
408
|
+
return (0, _lodash.get)(schema.obj, path)?.type;
|
|
409
|
+
}
|
|
410
|
+
const field = getRefField(schema, path);
|
|
411
|
+
if (field) {
|
|
412
|
+
const {
|
|
413
|
+
type,
|
|
414
|
+
rest
|
|
415
|
+
} = field;
|
|
416
|
+
const Model = _mongoose.default.models[type.options.ref];
|
|
417
|
+
return resolveSchemaType(Model.schema, rest.join('.'));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function isForeignField(schema, path) {
|
|
421
|
+
if (!path.includes('.')) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
return !!getRefField(schema, path);
|
|
425
|
+
}
|
|
426
|
+
function getRefBase(schema, path) {
|
|
427
|
+
const field = getRefField(schema, path);
|
|
428
|
+
if (field) {
|
|
429
|
+
return field.base.join('.');
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function getRefField(schema, path) {
|
|
433
|
+
const split = path.split('.');
|
|
434
|
+
for (let i = 1; i < split.length; i++) {
|
|
435
|
+
const base = split.slice(0, i);
|
|
436
|
+
const rest = split.slice(i);
|
|
437
|
+
const type = schema.path(base);
|
|
438
|
+
if (type instanceof SchemaTypes.ObjectId) {
|
|
439
|
+
return {
|
|
440
|
+
type,
|
|
441
|
+
base,
|
|
442
|
+
rest
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function getUpdates(doc, paths, definition) {
|
|
448
|
+
const updates = {};
|
|
449
|
+
const entries = Object.entries(definition.search.cache).filter(entry => {
|
|
450
|
+
return paths.includes(entry[1].path);
|
|
451
|
+
});
|
|
452
|
+
for (let [cachedField, def] of entries) {
|
|
453
|
+
// doc.get will not return virtuals (even with specified options),
|
|
454
|
+
// so use lodash to ensure they are included here.
|
|
455
|
+
// https://mongoosejs.com/docs/api/document.html#Document.prototype.get()
|
|
456
|
+
updates[cachedField] = (0, _lodash.get)(doc, def.path);
|
|
457
|
+
}
|
|
458
|
+
return updates;
|
|
459
|
+
}
|
|
460
|
+
function getCachePaths(definition, filter) {
|
|
461
|
+
filter ||= () => true;
|
|
462
|
+
const {
|
|
463
|
+
cache
|
|
464
|
+
} = definition.search || {};
|
|
465
|
+
return Object.entries(cache).filter(entry => {
|
|
466
|
+
return filter(...entry);
|
|
467
|
+
}).map(entry => {
|
|
468
|
+
return entry[1].path;
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
function generateCacheFieldName(field) {
|
|
472
|
+
return `cached${(0, _lodash.upperFirst)((0, _lodash.camelCase)(field))}`;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Assertions
|
|
476
|
+
|
|
477
|
+
function assertIncludeModule(Model) {
|
|
478
|
+
if (!Model.schema.methods.include) {
|
|
479
|
+
throw new Error('Include module is required for cached search fields.');
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function assertAssignModule(Model) {
|
|
483
|
+
if (!Model.schema.methods.assign) {
|
|
484
|
+
throw new Error('Assign module is required for cached search fields.');
|
|
485
|
+
}
|
|
287
486
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bedrockio/model",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"description": "Bedrock utilities for model creation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@bedrockio/yada": "^1.0.39",
|
|
34
|
-
"mongoose": "^
|
|
34
|
+
"mongoose": "^7.6.4"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@babel/cli": "^7.20.7",
|
package/src/delete-hooks.js
CHANGED
|
@@ -13,9 +13,8 @@ export function applyDeleteHooks(schema, definition) {
|
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const cleanLocal = validateCleanLocal(deleteHooks, schema);
|
|
17
|
-
const cleanForeign = validateCleanForeign(deleteHooks);
|
|
18
16
|
const errorHook = validateError(deleteHooks);
|
|
17
|
+
const cleanHooks = validateCleanHooks(deleteHooks, schema);
|
|
19
18
|
|
|
20
19
|
let references;
|
|
21
20
|
|
|
@@ -27,24 +26,21 @@ export function applyDeleteHooks(schema, definition) {
|
|
|
27
26
|
references ||= getAllReferences(this);
|
|
28
27
|
await errorOnForeignReferences(this, {
|
|
29
28
|
errorHook,
|
|
30
|
-
|
|
29
|
+
cleanHooks,
|
|
31
30
|
references,
|
|
32
31
|
});
|
|
33
32
|
}
|
|
34
33
|
try {
|
|
35
|
-
await
|
|
36
|
-
await deleteForeignReferences(this, cleanForeign);
|
|
34
|
+
await deleteReferences(this, cleanHooks);
|
|
37
35
|
} catch (error) {
|
|
38
|
-
await
|
|
39
|
-
await restoreForeignReferences(this);
|
|
36
|
+
await restoreReferences(this, cleanHooks);
|
|
40
37
|
throw error;
|
|
41
38
|
}
|
|
42
39
|
await deleteFn.apply(this, arguments);
|
|
43
40
|
});
|
|
44
41
|
|
|
45
42
|
schema.method('restore', async function () {
|
|
46
|
-
await
|
|
47
|
-
await restoreForeignReferences(this);
|
|
43
|
+
await restoreReferences(this, cleanHooks);
|
|
48
44
|
await restoreFn.apply(this, arguments);
|
|
49
45
|
});
|
|
50
46
|
|
|
@@ -60,45 +56,53 @@ export function applyDeleteHooks(schema, definition) {
|
|
|
60
56
|
|
|
61
57
|
// Clean Hook
|
|
62
58
|
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
if (!
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (typeof local !== 'string' && !Array.isArray(local)) {
|
|
69
|
-
throw new Error('Local delete hook must be an array.');
|
|
59
|
+
function validateCleanHooks(deleteHooks, schema) {
|
|
60
|
+
const { clean } = deleteHooks;
|
|
61
|
+
if (!clean) {
|
|
62
|
+
return [];
|
|
70
63
|
}
|
|
71
|
-
if (
|
|
72
|
-
|
|
64
|
+
if (!Array.isArray(clean)) {
|
|
65
|
+
throw new Error('Delete clean hook must be an array.');
|
|
73
66
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
|
|
68
|
+
for (let hook of clean) {
|
|
69
|
+
const { ref, path, paths } = hook;
|
|
70
|
+
if (path && typeof path !== 'string') {
|
|
71
|
+
throw new Error('Clean hook path must be a string.');
|
|
72
|
+
} else if (paths && !Array.isArray(paths)) {
|
|
73
|
+
throw new Error('Clean hook paths must be an array.');
|
|
74
|
+
} else if (!path && !paths) {
|
|
75
|
+
throw new Error('Clean hook must define either "path" or "paths".');
|
|
76
|
+
} else if (path && paths) {
|
|
77
|
+
throw new Error('Clean hook may not define both "path" or "paths".');
|
|
78
|
+
} else if (ref && typeof ref !== 'string') {
|
|
79
|
+
throw new Error('Clean hook ref must be a string.');
|
|
80
|
+
} else if (!ref) {
|
|
81
|
+
validateLocalCleanHook(hook, schema);
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
|
-
|
|
84
|
+
|
|
85
|
+
return clean;
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
function
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (typeof foreign !== 'object') {
|
|
89
|
-
throw new Error('Foreign delete hook must be an object.');
|
|
90
|
-
}
|
|
91
|
-
for (let [modelName, arg] of Object.entries(foreign)) {
|
|
92
|
-
if (typeof arg === 'object') {
|
|
93
|
-
const { $and, $or } = arg;
|
|
94
|
-
if ($and && $or) {
|
|
95
|
-
throw new Error(
|
|
96
|
-
`Cannot define both $or and $and in a delete hook for model ${modelName}.`
|
|
97
|
-
);
|
|
98
|
-
}
|
|
88
|
+
function validateLocalCleanHook(hook, schema) {
|
|
89
|
+
const paths = getHookPaths(hook);
|
|
90
|
+
for (let path of paths) {
|
|
91
|
+
if (schema.pathType(path) !== 'real') {
|
|
92
|
+
throw new Error(`Invalid reference in local delete hook: "${path}".`);
|
|
99
93
|
}
|
|
100
94
|
}
|
|
101
|
-
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getHookPaths(hook) {
|
|
98
|
+
const { path, paths } = hook;
|
|
99
|
+
if (path) {
|
|
100
|
+
return [path];
|
|
101
|
+
} else if (paths) {
|
|
102
|
+
return paths;
|
|
103
|
+
} else {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
function validateError(deleteHooks) {
|
|
@@ -170,11 +174,17 @@ async function errorOnForeignReferences(doc, options) {
|
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
function referenceIsAllowed(model, options) {
|
|
173
|
-
const {
|
|
174
|
-
const {
|
|
175
|
-
|
|
177
|
+
const { modelName } = model;
|
|
178
|
+
const { cleanHooks } = options;
|
|
179
|
+
|
|
180
|
+
const hasCleanHook = cleanHooks.some((hook) => {
|
|
181
|
+
return hook.ref === modelName;
|
|
182
|
+
});
|
|
183
|
+
if (hasCleanHook) {
|
|
176
184
|
return true;
|
|
177
185
|
}
|
|
186
|
+
|
|
187
|
+
const { only, except } = options?.errorHook || {};
|
|
178
188
|
if (only) {
|
|
179
189
|
return !only.includes(model.modelName);
|
|
180
190
|
} else if (except) {
|
|
@@ -227,16 +237,54 @@ function getModelReferences(model, targetName) {
|
|
|
227
237
|
return paths;
|
|
228
238
|
}
|
|
229
239
|
|
|
230
|
-
//
|
|
240
|
+
// Delete
|
|
231
241
|
|
|
232
|
-
async function
|
|
233
|
-
|
|
234
|
-
|
|
242
|
+
async function deleteReferences(doc, hooks) {
|
|
243
|
+
for (let hook of hooks) {
|
|
244
|
+
if (hook.ref) {
|
|
245
|
+
await deleteForeignReferences(doc, hook);
|
|
246
|
+
} else {
|
|
247
|
+
await deleteLocalReferences(doc, hook);
|
|
248
|
+
}
|
|
235
249
|
}
|
|
236
|
-
|
|
237
|
-
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function deleteForeignReferences(doc, hook) {
|
|
253
|
+
const { ref, path, paths, query } = hook;
|
|
254
|
+
|
|
255
|
+
const { id } = doc;
|
|
256
|
+
if (!id) {
|
|
257
|
+
throw new Error(`Refusing to apply delete hook to document without id.`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const Model = mongoose.models[ref];
|
|
261
|
+
if (!Model) {
|
|
262
|
+
throw new Error(`Unknown model: "${ref}".`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (path) {
|
|
266
|
+
await runDeletes(Model, doc, {
|
|
267
|
+
...query,
|
|
268
|
+
[path]: id,
|
|
269
|
+
});
|
|
270
|
+
} else if (paths) {
|
|
271
|
+
await runDeletes(Model, doc, {
|
|
272
|
+
$or: paths.map((refName) => {
|
|
273
|
+
return {
|
|
274
|
+
...query,
|
|
275
|
+
[refName]: id,
|
|
276
|
+
};
|
|
277
|
+
}),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function deleteLocalReferences(doc, hook) {
|
|
283
|
+
const paths = getHookPaths(hook);
|
|
284
|
+
await doc.populate(paths);
|
|
285
|
+
for (let path of paths) {
|
|
286
|
+
const value = doc.get(path);
|
|
238
287
|
|
|
239
|
-
const value = doc.get(name);
|
|
240
288
|
if (!value) {
|
|
241
289
|
continue;
|
|
242
290
|
}
|
|
@@ -248,39 +296,6 @@ async function deleteLocalReferences(doc, arr) {
|
|
|
248
296
|
}
|
|
249
297
|
}
|
|
250
298
|
|
|
251
|
-
async function deleteForeignReferences(doc, refs) {
|
|
252
|
-
if (!refs) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
const { id } = doc;
|
|
256
|
-
if (!id) {
|
|
257
|
-
throw new Error(`Refusing to apply delete hook to document without id.`);
|
|
258
|
-
}
|
|
259
|
-
for (let [modelName, arg] of Object.entries(refs)) {
|
|
260
|
-
const Model = mongoose.models[modelName];
|
|
261
|
-
if (typeof arg === 'string') {
|
|
262
|
-
await runDeletes(Model, doc, {
|
|
263
|
-
[arg]: id,
|
|
264
|
-
});
|
|
265
|
-
} else if (Array.isArray(arg)) {
|
|
266
|
-
await runDeletes(Model, doc, {
|
|
267
|
-
$or: mapArrayQuery(arg, id),
|
|
268
|
-
});
|
|
269
|
-
} else {
|
|
270
|
-
const { $and, $or } = arg;
|
|
271
|
-
if ($and) {
|
|
272
|
-
await runDeletes(Model, doc, {
|
|
273
|
-
$and: mapArrayQuery($and, id),
|
|
274
|
-
});
|
|
275
|
-
} else if ($or) {
|
|
276
|
-
await runDeletes(Model, doc, {
|
|
277
|
-
$or: mapArrayQuery($or, id),
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
299
|
async function runDeletes(Model, refDoc, query) {
|
|
285
300
|
const docs = await Model.find(query);
|
|
286
301
|
for (let doc of docs) {
|
|
@@ -292,33 +307,14 @@ async function runDeletes(Model, refDoc, query) {
|
|
|
292
307
|
}
|
|
293
308
|
}
|
|
294
309
|
|
|
295
|
-
function mapArrayQuery(arr, id) {
|
|
296
|
-
return arr.map((refName) => {
|
|
297
|
-
return {
|
|
298
|
-
[refName]: id,
|
|
299
|
-
};
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
|
|
303
310
|
// Restore
|
|
304
311
|
|
|
305
|
-
async function
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const value = refDoc.get(name);
|
|
312
|
-
const ids = Array.isArray(value) ? value : [value];
|
|
313
|
-
const Model = mongoose.models[ref];
|
|
314
|
-
|
|
315
|
-
// @ts-ignore
|
|
316
|
-
const docs = await Model.findDeleted({
|
|
317
|
-
_id: { $in: ids },
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
for (let doc of docs) {
|
|
321
|
-
await doc.restore();
|
|
312
|
+
async function restoreReferences(doc, hooks) {
|
|
313
|
+
for (let hook of hooks) {
|
|
314
|
+
if (hook.ref) {
|
|
315
|
+
await restoreForeignReferences(doc);
|
|
316
|
+
} else {
|
|
317
|
+
await restoreLocalReferences(doc, hook);
|
|
322
318
|
}
|
|
323
319
|
}
|
|
324
320
|
}
|
|
@@ -344,3 +340,23 @@ async function restoreForeignReferences(refDoc) {
|
|
|
344
340
|
|
|
345
341
|
refDoc.deletedRefs = [];
|
|
346
342
|
}
|
|
343
|
+
|
|
344
|
+
async function restoreLocalReferences(refDoc, hook) {
|
|
345
|
+
const paths = getHookPaths(hook);
|
|
346
|
+
|
|
347
|
+
for (let path of paths) {
|
|
348
|
+
const { ref } = getInnerField(refDoc.constructor.schema.obj, path);
|
|
349
|
+
const value = refDoc.get(path);
|
|
350
|
+
const ids = Array.isArray(value) ? value : [value];
|
|
351
|
+
const Model = mongoose.models[ref];
|
|
352
|
+
|
|
353
|
+
// @ts-ignore
|
|
354
|
+
const docs = await Model.findDeleted({
|
|
355
|
+
_id: { $in: ids },
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
for (let doc of docs) {
|
|
359
|
+
await doc.restore();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
package/src/include.js
CHANGED
|
@@ -228,15 +228,20 @@ function setNodePath(node, options) {
|
|
|
228
228
|
node[key] = null;
|
|
229
229
|
}
|
|
230
230
|
} else if (type === 'virtual') {
|
|
231
|
-
node[key] ||= {};
|
|
232
231
|
const virtual = schema.virtual(key);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
232
|
+
// @ts-ignore
|
|
233
|
+
const ref = virtual.options.ref;
|
|
234
|
+
|
|
235
|
+
if (ref) {
|
|
236
|
+
node[key] ||= {};
|
|
237
|
+
setNodePath(node[key], {
|
|
238
|
+
// @ts-ignore
|
|
239
|
+
modelName: ref,
|
|
240
|
+
path: path.slice(parts.length),
|
|
241
|
+
depth: depth + 1,
|
|
242
|
+
exclude,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
240
245
|
halt = true;
|
|
241
246
|
} else if (type !== 'nested') {
|
|
242
247
|
throw new Error(`Unknown path on ${modelName}: ${key}.`);
|