@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/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Bedrock utilities for model creation.
|
|
|
11
11
|
- [Scopes](#scopes)
|
|
12
12
|
- [Tuples](#tuples)
|
|
13
13
|
- [Array Extensions](#array-extensions)
|
|
14
|
-
- [
|
|
14
|
+
- [Modules](#modules)
|
|
15
15
|
- [Soft Delete](#soft-delete)
|
|
16
16
|
- [Validation](#validation)
|
|
17
17
|
- [Search](#search)
|
|
@@ -352,7 +352,7 @@ unfortunately cannot be disambiguated in this case.
|
|
|
352
352
|
|
|
353
353
|
This will manually create a new nested subschema.
|
|
354
354
|
|
|
355
|
-
##
|
|
355
|
+
## Modules
|
|
356
356
|
|
|
357
357
|
### Soft Delete
|
|
358
358
|
|
|
@@ -531,8 +531,6 @@ The method takes the following options:
|
|
|
531
531
|
- `include` - Allows [include](#includes) based population.
|
|
532
532
|
- `keyword` - A keyword to perform a [keyword search](#keyword-search).
|
|
533
533
|
- `ids` - An array of document ids to search on.
|
|
534
|
-
- `fields` - Used by [keyword search](#keyword-search). Generally for internal
|
|
535
|
-
use.
|
|
536
534
|
|
|
537
535
|
Any other fields passed in will be forwarded to `find`. The return value
|
|
538
536
|
contains the found documents in `data` and `meta` which contains metadata about
|
|
@@ -603,8 +601,8 @@ this feature a `fields` key must be present on the model definition:
|
|
|
603
601
|
}
|
|
604
602
|
```
|
|
605
603
|
|
|
606
|
-
This will use the `$or` operator to search on multiple fields. If
|
|
607
|
-
|
|
604
|
+
This will use the `$or` operator to search on multiple fields. If the model has
|
|
605
|
+
a text index applied, then a Mongo text query will be attempted:
|
|
608
606
|
|
|
609
607
|
```
|
|
610
608
|
{
|
|
@@ -614,7 +612,118 @@ not defined then a Mongo text query will be attempted:
|
|
|
614
612
|
}
|
|
615
613
|
```
|
|
616
614
|
|
|
617
|
-
|
|
615
|
+
#### Keyword Field Caching
|
|
616
|
+
|
|
617
|
+
A common problem with search is filtering on fields belonging to foreign models.
|
|
618
|
+
The search module helps to alleviate this issue by allowing a simple way to
|
|
619
|
+
cache foreign fields on the model to allow filtering on them.
|
|
620
|
+
|
|
621
|
+
```json
|
|
622
|
+
{
|
|
623
|
+
"attributes": {
|
|
624
|
+
"user": {
|
|
625
|
+
"type": "ObjectId",
|
|
626
|
+
"ref": "User"
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
"search": {
|
|
630
|
+
"cache": {
|
|
631
|
+
"cachedUserName": {
|
|
632
|
+
"type": "String",
|
|
633
|
+
"path": "user.name"
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
"fields": ["cachedUserName"]
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
The above example is equivalent to creating a field called `cachedUserName` and
|
|
642
|
+
updating it when a document is saved:
|
|
643
|
+
|
|
644
|
+
```js
|
|
645
|
+
schema.add({
|
|
646
|
+
cachedUserName: 'String',
|
|
647
|
+
});
|
|
648
|
+
schema.pre('save', function () {
|
|
649
|
+
await this.populate('user');
|
|
650
|
+
this.cachedUserName = this.user.name;
|
|
651
|
+
});
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Specifying a foreign path in `fields` serves as a shortcut to manually defining
|
|
655
|
+
the cached fields:
|
|
656
|
+
|
|
657
|
+
```json
|
|
658
|
+
// Equivalent to the above example.
|
|
659
|
+
{
|
|
660
|
+
"attributes": {
|
|
661
|
+
"user": {
|
|
662
|
+
"type": "ObjectId",
|
|
663
|
+
"ref": "User"
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
"search": {
|
|
667
|
+
"fields": ["user.name"]
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
##### Syncing Search Fields
|
|
673
|
+
|
|
674
|
+
When first applying or making changes to defined cached search fields, existing
|
|
675
|
+
documents will be out of sync. The static method `syncSearchFields` is provided
|
|
676
|
+
to synchronize them:
|
|
677
|
+
|
|
678
|
+
```js
|
|
679
|
+
// Find and update any documents that do not have
|
|
680
|
+
// existing cached fields. Generally called when
|
|
681
|
+
// adding a cached field.
|
|
682
|
+
await Model.syncSearchFields();
|
|
683
|
+
|
|
684
|
+
// Force an update on ALL documents to resync their
|
|
685
|
+
// cached fields. Generally called to force a cache
|
|
686
|
+
// refresh.
|
|
687
|
+
await Model.syncSearchFields({
|
|
688
|
+
force: true,
|
|
689
|
+
});
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
##### Lazy Cached Fields
|
|
693
|
+
|
|
694
|
+
Cached fields can be made lazy:
|
|
695
|
+
|
|
696
|
+
```json
|
|
697
|
+
{
|
|
698
|
+
"attributes": {
|
|
699
|
+
"user": {
|
|
700
|
+
"type": "ObjectId",
|
|
701
|
+
"ref": "User"
|
|
702
|
+
}
|
|
703
|
+
},
|
|
704
|
+
"search": {
|
|
705
|
+
"cache": {
|
|
706
|
+
"cachedUserName": {
|
|
707
|
+
"lazy": true,
|
|
708
|
+
"path": "user.name"
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
"fields": ["user.name"]
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
Lazy cached fields will not update themselves once set. They can only be updated
|
|
717
|
+
by forcing a sync:
|
|
718
|
+
|
|
719
|
+
```js
|
|
720
|
+
await Model.syncSearchFields({
|
|
721
|
+
force: true,
|
|
722
|
+
});
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
Making fields lazy alleviates performance impact on writes and allows caches to
|
|
726
|
+
be updated at another time (such as a background job).
|
|
618
727
|
|
|
619
728
|
#### Search Validation
|
|
620
729
|
|
|
@@ -1019,12 +1128,15 @@ deletion. They are defined in the `onDelete` field of the model definition file:
|
|
|
1019
1128
|
}
|
|
1020
1129
|
},
|
|
1021
1130
|
"onDelete": {
|
|
1022
|
-
"clean":
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1131
|
+
"clean": [
|
|
1132
|
+
{
|
|
1133
|
+
"path": "profile"
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
"ref": "Shop",
|
|
1137
|
+
"path": "owner"
|
|
1026
1138
|
}
|
|
1027
|
-
|
|
1139
|
+
],
|
|
1028
1140
|
"errorOnReferenced": {
|
|
1029
1141
|
"except": ["AuditEntry"]
|
|
1030
1142
|
}
|
|
@@ -1035,12 +1147,13 @@ deletion. They are defined in the `onDelete` field of the model definition file:
|
|
|
1035
1147
|
#### Clean
|
|
1036
1148
|
|
|
1037
1149
|
`clean` determines other associated documents that will be deleted when the main
|
|
1038
|
-
document is deleted.
|
|
1150
|
+
document is deleted. It is defined as an array of operations that will be
|
|
1151
|
+
performed in order. Operations must contain either `path` or `paths`.
|
|
1039
1152
|
|
|
1040
1153
|
#### Local References
|
|
1041
1154
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1155
|
+
Operations that do not specify a `ref` are treated as local paths. In the above
|
|
1156
|
+
example:
|
|
1044
1157
|
|
|
1045
1158
|
```js
|
|
1046
1159
|
user.delete();
|
|
@@ -1050,26 +1163,110 @@ await user.populate('profile');
|
|
|
1050
1163
|
await user.profile.delete();
|
|
1051
1164
|
```
|
|
1052
1165
|
|
|
1053
|
-
#### Foreign
|
|
1166
|
+
#### Foreign References
|
|
1054
1167
|
|
|
1055
|
-
`
|
|
1056
|
-
|
|
1168
|
+
Operations that specify a `ref` are treated as foreign references. In the above
|
|
1169
|
+
example:
|
|
1057
1170
|
|
|
1058
1171
|
```js
|
|
1059
1172
|
user.delete();
|
|
1060
1173
|
|
|
1061
1174
|
// Will implicitly run:
|
|
1062
|
-
const
|
|
1175
|
+
const shops = await Shop.find({
|
|
1063
1176
|
owner: user,
|
|
1064
1177
|
});
|
|
1065
|
-
|
|
1178
|
+
for (let shop of shops) {
|
|
1179
|
+
await shop.delete();
|
|
1180
|
+
}
|
|
1181
|
+
```
|
|
1182
|
+
|
|
1183
|
+
#### Additional Filters
|
|
1184
|
+
|
|
1185
|
+
Operations may filter on additional fields with `query`:
|
|
1186
|
+
|
|
1187
|
+
```json
|
|
1188
|
+
// user.json
|
|
1189
|
+
{
|
|
1190
|
+
"onDelete": {
|
|
1191
|
+
"clean": [
|
|
1192
|
+
{
|
|
1193
|
+
"ref": "Shop",
|
|
1194
|
+
"path": "owner",
|
|
1195
|
+
"query": {
|
|
1196
|
+
"status": "active"
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
]
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
```
|
|
1203
|
+
|
|
1204
|
+
In this example:
|
|
1205
|
+
|
|
1206
|
+
```js
|
|
1207
|
+
user.delete();
|
|
1208
|
+
|
|
1209
|
+
// Will implicitly run:
|
|
1210
|
+
const shops = await Shop.find({
|
|
1211
|
+
status: 'active',
|
|
1212
|
+
owner: user,
|
|
1213
|
+
});
|
|
1214
|
+
for (let shop of shops) {
|
|
1215
|
+
await shop.delete();
|
|
1216
|
+
}
|
|
1066
1217
|
```
|
|
1067
1218
|
|
|
1219
|
+
Any query that can be serliazed as JSON is valid, however top-level `$or`
|
|
1220
|
+
operators have special behavior with multiple paths (see note below).
|
|
1221
|
+
|
|
1222
|
+
#### Multiple Paths
|
|
1223
|
+
|
|
1224
|
+
An operation that specified an array of `paths` will implicitly run an `$or`
|
|
1225
|
+
query:
|
|
1226
|
+
|
|
1227
|
+
```json
|
|
1228
|
+
// user.json
|
|
1229
|
+
{
|
|
1230
|
+
"onDelete": {
|
|
1231
|
+
"clean": [
|
|
1232
|
+
{
|
|
1233
|
+
"ref": "Shop",
|
|
1234
|
+
"path": ["owner", "administrator"]
|
|
1235
|
+
}
|
|
1236
|
+
]
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
In this example:
|
|
1242
|
+
|
|
1243
|
+
```js
|
|
1244
|
+
user.delete();
|
|
1245
|
+
|
|
1246
|
+
// Will implicitly run:
|
|
1247
|
+
const shops = await Shop.find({
|
|
1248
|
+
$or: [
|
|
1249
|
+
{
|
|
1250
|
+
owner: user,
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
administrator: user,
|
|
1254
|
+
},
|
|
1255
|
+
],
|
|
1256
|
+
});
|
|
1257
|
+
for (let shop of shops) {
|
|
1258
|
+
await shop.delete();
|
|
1259
|
+
}
|
|
1260
|
+
```
|
|
1261
|
+
|
|
1262
|
+
> [!WARNING] The ability to run an `$and` query with multiple paths is currently
|
|
1263
|
+
> not implemented.
|
|
1264
|
+
|
|
1068
1265
|
#### Erroring on Delete
|
|
1069
1266
|
|
|
1070
1267
|
The `errorOnReferenced` field helps to prevent orphaned references by defining
|
|
1071
1268
|
if and how the `delete` method will error if it is being referenced by another
|
|
1072
|
-
foreign document. In the
|
|
1269
|
+
foreign document. In the top example:
|
|
1073
1270
|
|
|
1074
1271
|
```js
|
|
1075
1272
|
user.delete();
|
|
@@ -1149,7 +1346,6 @@ const { createTestModel } = require('@bedrockio/model');
|
|
|
1149
1346
|
const User = createTestModel({
|
|
1150
1347
|
name: 'String',
|
|
1151
1348
|
});
|
|
1152
|
-
mk;
|
|
1153
1349
|
```
|
|
1154
1350
|
|
|
1155
1351
|
Note that a unique model name will be generated to prevent clashing with other
|
package/dist/cjs/delete-hooks.js
CHANGED
|
@@ -19,9 +19,8 @@ function applyDeleteHooks(schema, definition) {
|
|
|
19
19
|
if (!deleteHooks) {
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
const cleanLocal = validateCleanLocal(deleteHooks, schema);
|
|
23
|
-
const cleanForeign = validateCleanForeign(deleteHooks);
|
|
24
22
|
const errorHook = validateError(deleteHooks);
|
|
23
|
+
const cleanHooks = validateCleanHooks(deleteHooks, schema);
|
|
25
24
|
let references;
|
|
26
25
|
const deleteFn = schema.methods.delete;
|
|
27
26
|
const restoreFn = schema.methods.restore;
|
|
@@ -30,23 +29,20 @@ function applyDeleteHooks(schema, definition) {
|
|
|
30
29
|
references ||= getAllReferences(this);
|
|
31
30
|
await errorOnForeignReferences(this, {
|
|
32
31
|
errorHook,
|
|
33
|
-
|
|
32
|
+
cleanHooks,
|
|
34
33
|
references
|
|
35
34
|
});
|
|
36
35
|
}
|
|
37
36
|
try {
|
|
38
|
-
await
|
|
39
|
-
await deleteForeignReferences(this, cleanForeign);
|
|
37
|
+
await deleteReferences(this, cleanHooks);
|
|
40
38
|
} catch (error) {
|
|
41
|
-
await
|
|
42
|
-
await restoreForeignReferences(this);
|
|
39
|
+
await restoreReferences(this, cleanHooks);
|
|
43
40
|
throw error;
|
|
44
41
|
}
|
|
45
42
|
await deleteFn.apply(this, arguments);
|
|
46
43
|
});
|
|
47
44
|
schema.method('restore', async function () {
|
|
48
|
-
await
|
|
49
|
-
await restoreForeignReferences(this);
|
|
45
|
+
await restoreReferences(this, cleanHooks);
|
|
50
46
|
await restoreFn.apply(this, arguments);
|
|
51
47
|
});
|
|
52
48
|
schema.add({
|
|
@@ -59,49 +55,58 @@ function applyDeleteHooks(schema, definition) {
|
|
|
59
55
|
|
|
60
56
|
// Clean Hook
|
|
61
57
|
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
} = deleteHooks
|
|
66
|
-
if (!
|
|
67
|
-
return;
|
|
58
|
+
function validateCleanHooks(deleteHooks, schema) {
|
|
59
|
+
const {
|
|
60
|
+
clean
|
|
61
|
+
} = deleteHooks;
|
|
62
|
+
if (!clean) {
|
|
63
|
+
return [];
|
|
68
64
|
}
|
|
69
|
-
if (
|
|
70
|
-
throw new Error('
|
|
65
|
+
if (!Array.isArray(clean)) {
|
|
66
|
+
throw new Error('Delete clean hook must be an array.');
|
|
71
67
|
}
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
for (let hook of clean) {
|
|
69
|
+
const {
|
|
70
|
+
ref,
|
|
71
|
+
path,
|
|
72
|
+
paths
|
|
73
|
+
} = hook;
|
|
74
|
+
if (path && typeof path !== 'string') {
|
|
75
|
+
throw new Error('Clean hook path must be a string.');
|
|
76
|
+
} else if (paths && !Array.isArray(paths)) {
|
|
77
|
+
throw new Error('Clean hook paths must be an array.');
|
|
78
|
+
} else if (!path && !paths) {
|
|
79
|
+
throw new Error('Clean hook must define either "path" or "paths".');
|
|
80
|
+
} else if (path && paths) {
|
|
81
|
+
throw new Error('Clean hook may not define both "path" or "paths".');
|
|
82
|
+
} else if (ref && typeof ref !== 'string') {
|
|
83
|
+
throw new Error('Clean hook ref must be a string.');
|
|
84
|
+
} else if (!ref) {
|
|
85
|
+
validateLocalCleanHook(hook, schema);
|
|
86
|
+
}
|
|
74
87
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
88
|
+
return clean;
|
|
89
|
+
}
|
|
90
|
+
function validateLocalCleanHook(hook, schema) {
|
|
91
|
+
const paths = getHookPaths(hook);
|
|
92
|
+
for (let path of paths) {
|
|
93
|
+
if (schema.pathType(path) !== 'real') {
|
|
94
|
+
throw new Error(`Invalid reference in local delete hook: "${path}".`);
|
|
79
95
|
}
|
|
80
96
|
}
|
|
81
|
-
return local;
|
|
82
97
|
}
|
|
83
|
-
function
|
|
98
|
+
function getHookPaths(hook) {
|
|
84
99
|
const {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (typeof arg === 'object') {
|
|
95
|
-
const {
|
|
96
|
-
$and,
|
|
97
|
-
$or
|
|
98
|
-
} = arg;
|
|
99
|
-
if ($and && $or) {
|
|
100
|
-
throw new Error(`Cannot define both $or and $and in a delete hook for model ${modelName}.`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
100
|
+
path,
|
|
101
|
+
paths
|
|
102
|
+
} = hook;
|
|
103
|
+
if (path) {
|
|
104
|
+
return [path];
|
|
105
|
+
} else if (paths) {
|
|
106
|
+
return paths;
|
|
107
|
+
} else {
|
|
108
|
+
return [];
|
|
103
109
|
}
|
|
104
|
-
return foreign;
|
|
105
110
|
}
|
|
106
111
|
function validateError(deleteHooks) {
|
|
107
112
|
let {
|
|
@@ -173,15 +178,21 @@ async function errorOnForeignReferences(doc, options) {
|
|
|
173
178
|
}
|
|
174
179
|
function referenceIsAllowed(model, options) {
|
|
175
180
|
const {
|
|
176
|
-
|
|
181
|
+
modelName
|
|
182
|
+
} = model;
|
|
183
|
+
const {
|
|
184
|
+
cleanHooks
|
|
177
185
|
} = options;
|
|
186
|
+
const hasCleanHook = cleanHooks.some(hook => {
|
|
187
|
+
return hook.ref === modelName;
|
|
188
|
+
});
|
|
189
|
+
if (hasCleanHook) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
178
192
|
const {
|
|
179
193
|
only,
|
|
180
194
|
except
|
|
181
195
|
} = options?.errorHook || {};
|
|
182
|
-
if (model.modelName in cleanForeign) {
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
185
196
|
if (only) {
|
|
186
197
|
return !only.includes(model.modelName);
|
|
187
198
|
} else if (except) {
|
|
@@ -235,58 +246,61 @@ function getModelReferences(model, targetName) {
|
|
|
235
246
|
return paths;
|
|
236
247
|
}
|
|
237
248
|
|
|
238
|
-
//
|
|
249
|
+
// Delete
|
|
239
250
|
|
|
240
|
-
async function
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const value = doc.get(name);
|
|
247
|
-
if (!value) {
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
const arr = Array.isArray(value) ? value : [value];
|
|
251
|
-
for (let sub of arr) {
|
|
252
|
-
await sub.delete();
|
|
251
|
+
async function deleteReferences(doc, hooks) {
|
|
252
|
+
for (let hook of hooks) {
|
|
253
|
+
if (hook.ref) {
|
|
254
|
+
await deleteForeignReferences(doc, hook);
|
|
255
|
+
} else {
|
|
256
|
+
await deleteLocalReferences(doc, hook);
|
|
253
257
|
}
|
|
254
258
|
}
|
|
255
259
|
}
|
|
256
|
-
async function deleteForeignReferences(doc,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
+
async function deleteForeignReferences(doc, hook) {
|
|
261
|
+
const {
|
|
262
|
+
ref,
|
|
263
|
+
path,
|
|
264
|
+
paths,
|
|
265
|
+
query
|
|
266
|
+
} = hook;
|
|
260
267
|
const {
|
|
261
268
|
id
|
|
262
269
|
} = doc;
|
|
263
270
|
if (!id) {
|
|
264
271
|
throw new Error(`Refusing to apply delete hook to document without id.`);
|
|
265
272
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
273
|
+
const Model = _mongoose.default.models[ref];
|
|
274
|
+
if (!Model) {
|
|
275
|
+
throw new Error(`Unknown model: "${ref}".`);
|
|
276
|
+
}
|
|
277
|
+
if (path) {
|
|
278
|
+
await runDeletes(Model, doc, {
|
|
279
|
+
...query,
|
|
280
|
+
[path]: id
|
|
281
|
+
});
|
|
282
|
+
} else if (paths) {
|
|
283
|
+
await runDeletes(Model, doc, {
|
|
284
|
+
$or: paths.map(refName => {
|
|
285
|
+
return {
|
|
286
|
+
...query,
|
|
287
|
+
[refName]: id
|
|
288
|
+
};
|
|
289
|
+
})
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async function deleteLocalReferences(doc, hook) {
|
|
294
|
+
const paths = getHookPaths(hook);
|
|
295
|
+
await doc.populate(paths);
|
|
296
|
+
for (let path of paths) {
|
|
297
|
+
const value = doc.get(path);
|
|
298
|
+
if (!value) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
302
|
+
for (let sub of arr) {
|
|
303
|
+
await sub.delete();
|
|
290
304
|
}
|
|
291
305
|
}
|
|
292
306
|
}
|
|
@@ -300,27 +314,25 @@ async function runDeletes(Model, refDoc, query) {
|
|
|
300
314
|
});
|
|
301
315
|
}
|
|
302
316
|
}
|
|
303
|
-
function mapArrayQuery(arr, id) {
|
|
304
|
-
return arr.map(refName => {
|
|
305
|
-
return {
|
|
306
|
-
[refName]: id
|
|
307
|
-
};
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
317
|
|
|
311
318
|
// Restore
|
|
312
319
|
|
|
313
|
-
async function
|
|
314
|
-
|
|
315
|
-
|
|
320
|
+
async function restoreReferences(doc, hooks) {
|
|
321
|
+
for (let hook of hooks) {
|
|
322
|
+
if (hook.ref) {
|
|
323
|
+
await restoreForeignReferences(doc);
|
|
324
|
+
} else {
|
|
325
|
+
await restoreLocalReferences(doc, hook);
|
|
326
|
+
}
|
|
316
327
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
328
|
+
}
|
|
329
|
+
async function restoreForeignReferences(refDoc) {
|
|
330
|
+
const grouped = (0, _lodash.groupBy)(refDoc.deletedRefs, 'ref');
|
|
331
|
+
for (let [modelName, refs] of Object.entries(grouped)) {
|
|
332
|
+
const ids = refs.map(ref => {
|
|
333
|
+
return ref._id;
|
|
334
|
+
});
|
|
335
|
+
const Model = _mongoose.default.models[modelName];
|
|
324
336
|
|
|
325
337
|
// @ts-ignore
|
|
326
338
|
const docs = await Model.findDeleted({
|
|
@@ -332,14 +344,17 @@ async function restoreLocalReferences(refDoc, arr) {
|
|
|
332
344
|
await doc.restore();
|
|
333
345
|
}
|
|
334
346
|
}
|
|
347
|
+
refDoc.deletedRefs = [];
|
|
335
348
|
}
|
|
336
|
-
async function
|
|
337
|
-
const
|
|
338
|
-
for (let
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
});
|
|
342
|
-
const
|
|
349
|
+
async function restoreLocalReferences(refDoc, hook) {
|
|
350
|
+
const paths = getHookPaths(hook);
|
|
351
|
+
for (let path of paths) {
|
|
352
|
+
const {
|
|
353
|
+
ref
|
|
354
|
+
} = (0, _utils.getInnerField)(refDoc.constructor.schema.obj, path);
|
|
355
|
+
const value = refDoc.get(path);
|
|
356
|
+
const ids = Array.isArray(value) ? value : [value];
|
|
357
|
+
const Model = _mongoose.default.models[ref];
|
|
343
358
|
|
|
344
359
|
// @ts-ignore
|
|
345
360
|
const docs = await Model.findDeleted({
|
|
@@ -351,5 +366,4 @@ async function restoreForeignReferences(refDoc) {
|
|
|
351
366
|
await doc.restore();
|
|
352
367
|
}
|
|
353
368
|
}
|
|
354
|
-
refDoc.deletedRefs = [];
|
|
355
369
|
}
|
package/dist/cjs/include.js
CHANGED
|
@@ -242,15 +242,19 @@ function setNodePath(node, options) {
|
|
|
242
242
|
node[key] = null;
|
|
243
243
|
}
|
|
244
244
|
} else if (type === 'virtual') {
|
|
245
|
-
node[key] ||= {};
|
|
246
245
|
const virtual = schema.virtual(key);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
246
|
+
// @ts-ignore
|
|
247
|
+
const ref = virtual.options.ref;
|
|
248
|
+
if (ref) {
|
|
249
|
+
node[key] ||= {};
|
|
250
|
+
setNodePath(node[key], {
|
|
251
|
+
// @ts-ignore
|
|
252
|
+
modelName: ref,
|
|
253
|
+
path: path.slice(parts.length),
|
|
254
|
+
depth: depth + 1,
|
|
255
|
+
exclude
|
|
256
|
+
});
|
|
257
|
+
}
|
|
254
258
|
halt = true;
|
|
255
259
|
} else if (type !== 'nested') {
|
|
256
260
|
throw new Error(`Unknown path on ${modelName}: ${key}.`);
|