@bedrockio/model 0.7.5 → 0.8.0
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/CHANGELOG.md +15 -0
- package/README.md +96 -111
- package/dist/cjs/access.js +1 -1
- package/dist/cjs/assign.js +1 -1
- package/dist/cjs/cache.js +240 -0
- package/dist/cjs/delete-hooks.js +1 -1
- package/dist/cjs/disallowed.js +1 -1
- package/dist/cjs/include.js +11 -2
- package/dist/cjs/load.js +1 -1
- package/dist/cjs/schema.js +3 -1
- package/dist/cjs/search.js +2 -159
- package/dist/cjs/slug.js +10 -13
- package/dist/cjs/soft-delete.js +1 -1
- package/dist/cjs/testing.js +1 -1
- package/dist/cjs/utils.js +31 -1
- package/dist/cjs/validation.js +3 -3
- package/dist/cjs/warn.js +1 -1
- package/package.json +6 -6
- package/src/cache.js +245 -0
- package/src/include.js +11 -1
- package/src/schema.js +2 -0
- package/src/search.js +10 -176
- package/src/slug.js +12 -10
- package/src/utils.js +27 -0
- package/src/validation.js +2 -2
- package/types/cache.d.ts +2 -0
- package/types/cache.d.ts.map +1 -0
- package/types/const.d.ts +3 -3
- package/types/const.d.ts.map +1 -1
- package/types/include.d.ts +22 -30
- package/types/include.d.ts.map +1 -1
- package/types/load.d.ts +1 -1
- package/types/load.d.ts.map +1 -1
- package/types/schema.d.ts +5 -5
- package/types/schema.d.ts.map +1 -1
- package/types/search.d.ts +22 -30
- package/types/search.d.ts.map +1 -1
- package/types/serialization.d.ts +2 -2
- package/types/testing.d.ts +1 -2
- package/types/testing.d.ts.map +1 -1
- package/types/utils.d.ts +7 -1
- package/types/utils.d.ts.map +1 -1
- package/types/validation.d.ts +49 -57
- package/types/validation.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## 0.8.0
|
|
2
|
+
|
|
3
|
+
- Moved "cache" field in "search" to definition root.
|
|
4
|
+
- Changed "lazy" on cached fields to instead use "sync".
|
|
5
|
+
- Removed "sync" field in "search". Syncing now happens automatically on synced
|
|
6
|
+
cache fields.
|
|
7
|
+
- Removed "force" option from "syncCacheFields".
|
|
8
|
+
- Better handling of deep nested and array cache fields.
|
|
9
|
+
- Small fix for field messages on errors.
|
|
10
|
+
|
|
11
|
+
## 0.7.4
|
|
12
|
+
|
|
13
|
+
- NANP phone number validation support
|
|
14
|
+
- Better handling of empty string fields for unsetting fields
|
|
15
|
+
|
|
1
16
|
## 0.7.0
|
|
2
17
|
|
|
3
18
|
- Handling null fields in search queries.
|
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Bedrock utilities for model creation.
|
|
|
15
15
|
- [Soft Delete](#soft-delete)
|
|
16
16
|
- [Validation](#validation)
|
|
17
17
|
- [Search](#search)
|
|
18
|
+
- [Cache](#cache)
|
|
18
19
|
- [Includes](#includes)
|
|
19
20
|
- [Delete Hooks](#delete-hooks)
|
|
20
21
|
- [Access Control](#access-control)
|
|
@@ -124,7 +125,7 @@ Links:
|
|
|
124
125
|
|
|
125
126
|
### Schema Extensions
|
|
126
127
|
|
|
127
|
-
This
|
|
128
|
+
This module provides a number of extensions to assist schema creation outside
|
|
128
129
|
the scope of Mongoose.
|
|
129
130
|
|
|
130
131
|
#### Attributes
|
|
@@ -302,7 +303,7 @@ with `minLength` and `maxLength` on strings.
|
|
|
302
303
|
|
|
303
304
|
### Gotchas
|
|
304
305
|
|
|
305
|
-
#### The `type` field is
|
|
306
|
+
#### The `type` field is special:
|
|
306
307
|
|
|
307
308
|
```js
|
|
308
309
|
{
|
|
@@ -421,7 +422,7 @@ Note that although monogoose allows a `unique` option on fields, this will add a
|
|
|
421
422
|
unique index to the mongo collection itself which is incompatible with soft
|
|
422
423
|
deletion.
|
|
423
424
|
|
|
424
|
-
This
|
|
425
|
+
This module will intercept `unique: true` to create a soft delete compatible
|
|
425
426
|
validation which will:
|
|
426
427
|
|
|
427
428
|
- Throw an error if other non-deleted documents with the same fields exist when
|
|
@@ -479,7 +480,7 @@ There are 4 main methods to generate schemas:
|
|
|
479
480
|
- `getCreateValidation`: Validates all fields while disallowing reserved fields
|
|
480
481
|
like `id`, `createdAt`, and `updatedAt`.
|
|
481
482
|
- `getUpdateValidation`: Validates all fields as optional (ie. they will not be
|
|
482
|
-
validated if they don't exist on the
|
|
483
|
+
validated if they don't exist on the input). Additionally will strip out
|
|
483
484
|
reserved fields to allow created objects to be passed in. Unknown fields will
|
|
484
485
|
also be stripped out rather than error to allow virtuals to be passed in.
|
|
485
486
|
- `getSearchValidation`: Validates fields for use with [search](#search). The
|
|
@@ -659,11 +660,16 @@ a text index applied, then a Mongo text query will be attempted:
|
|
|
659
660
|
}
|
|
660
661
|
```
|
|
661
662
|
|
|
662
|
-
####
|
|
663
|
+
#### Search Validation
|
|
664
|
+
|
|
665
|
+
The [validation](#validation) generated for search using `getSearchValidation`
|
|
666
|
+
is inherently looser and allows more fields to be passed to allow complex
|
|
667
|
+
searches compatible with the above.
|
|
668
|
+
|
|
669
|
+
### Cache
|
|
663
670
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
cache foreign fields on the model to allow filtering on them.
|
|
671
|
+
The cache module allows a simple way to cache foreign fields on a document and
|
|
672
|
+
optionally keep them in sync.
|
|
667
673
|
|
|
668
674
|
```json
|
|
669
675
|
{
|
|
@@ -673,20 +679,17 @@ cache foreign fields on the model to allow filtering on them.
|
|
|
673
679
|
"ref": "User"
|
|
674
680
|
}
|
|
675
681
|
},
|
|
676
|
-
"
|
|
677
|
-
"
|
|
678
|
-
"
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
}
|
|
682
|
-
},
|
|
683
|
-
"fields": ["userName"]
|
|
682
|
+
"cache": {
|
|
683
|
+
"userName": {
|
|
684
|
+
"type": "String",
|
|
685
|
+
"path": "user.name"
|
|
686
|
+
}
|
|
684
687
|
}
|
|
685
688
|
}
|
|
686
689
|
```
|
|
687
690
|
|
|
688
691
|
The above example is equivalent to creating a field called `userName` and
|
|
689
|
-
|
|
692
|
+
setting it when a document is saved:
|
|
690
693
|
|
|
691
694
|
```js
|
|
692
695
|
schema.add({
|
|
@@ -700,54 +703,44 @@ schema.pre('save', function () {
|
|
|
700
703
|
|
|
701
704
|
#### Syncing Cached Fields
|
|
702
705
|
|
|
703
|
-
|
|
704
|
-
|
|
706
|
+
By default cached fields are only updated when the reference changes. This is
|
|
707
|
+
fine when the field on the foreign document will not change or to keep a
|
|
708
|
+
snapshot of the value. However in some cases the local cached field should be
|
|
709
|
+
kept in sync when the foreign field changes:
|
|
705
710
|
|
|
706
|
-
```
|
|
707
|
-
await shop.save();
|
|
708
|
-
console.log(shop.userName);
|
|
709
|
-
// The current user name
|
|
710
|
-
|
|
711
|
-
user.name = 'New Name';
|
|
712
|
-
await user.save();
|
|
713
|
-
|
|
714
|
-
shop = await Shop.findById(shop.id);
|
|
715
|
-
console.log(shop.userName);
|
|
716
|
-
// Cached userName is out of sync as the user has been updated
|
|
717
|
-
```
|
|
718
|
-
|
|
719
|
-
A simple mechanism is provided via the `sync` key to keep the documents in sync:
|
|
720
|
-
|
|
721
|
-
```jsonc
|
|
722
|
-
// In user.json
|
|
711
|
+
```json
|
|
723
712
|
{
|
|
724
713
|
"attributes": {
|
|
725
|
-
"
|
|
714
|
+
"user": {
|
|
715
|
+
"type": "ObjectId",
|
|
716
|
+
"ref": "User"
|
|
717
|
+
}
|
|
726
718
|
},
|
|
727
|
-
"
|
|
728
|
-
"
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
]
|
|
719
|
+
"cache": {
|
|
720
|
+
"userName": {
|
|
721
|
+
"type": "String",
|
|
722
|
+
"path": "user.name",
|
|
723
|
+
"sync": true
|
|
724
|
+
}
|
|
734
725
|
}
|
|
735
726
|
}
|
|
736
727
|
```
|
|
737
728
|
|
|
738
|
-
|
|
729
|
+
The "sync" field is the equivelent of running a post save hook on the foreign
|
|
730
|
+
model to keep the cached field in sync:
|
|
739
731
|
|
|
740
732
|
```js
|
|
741
|
-
|
|
742
|
-
|
|
733
|
+
userSchema.post('save', function () {
|
|
734
|
+
await Shop.updateMany({
|
|
735
|
+
user: this.id,
|
|
736
|
+
}, {
|
|
737
|
+
$set: {
|
|
738
|
+
userName: this.name
|
|
739
|
+
}
|
|
740
|
+
})
|
|
743
741
|
});
|
|
744
|
-
for (let shop of shops) {
|
|
745
|
-
await shop.save();
|
|
746
|
-
}
|
|
747
742
|
```
|
|
748
743
|
|
|
749
|
-
This will run the hooks on each shop, synchronizing the cached fields.
|
|
750
|
-
|
|
751
744
|
##### Initial Sync
|
|
752
745
|
|
|
753
746
|
When first applying or making changes to defined cached search fields, existing
|
|
@@ -756,61 +749,11 @@ to synchronize them:
|
|
|
756
749
|
|
|
757
750
|
```js
|
|
758
751
|
// Find and update any documents that do not have
|
|
759
|
-
// existing cached fields. Generally called
|
|
760
|
-
// adding a cached field.
|
|
752
|
+
// existing cached fields. Generally called after
|
|
753
|
+
// adding or modifying a cached field.
|
|
761
754
|
await Model.syncCacheFields();
|
|
762
|
-
|
|
763
|
-
// Force an update on ALL documents to resync their
|
|
764
|
-
// cached fields. Generally called to force a cache
|
|
765
|
-
// refresh.
|
|
766
|
-
await Model.syncCacheFields({
|
|
767
|
-
force: true,
|
|
768
|
-
});
|
|
769
755
|
```
|
|
770
756
|
|
|
771
|
-
##### Lazy Cached Fields
|
|
772
|
-
|
|
773
|
-
Cached fields can be made lazy:
|
|
774
|
-
|
|
775
|
-
```json
|
|
776
|
-
{
|
|
777
|
-
"attributes": {
|
|
778
|
-
"user": {
|
|
779
|
-
"type": "ObjectId",
|
|
780
|
-
"ref": "User"
|
|
781
|
-
}
|
|
782
|
-
},
|
|
783
|
-
"search": {
|
|
784
|
-
"cache": {
|
|
785
|
-
"userName": {
|
|
786
|
-
"type": "String",
|
|
787
|
-
"path": "user.name",
|
|
788
|
-
"lazy": true
|
|
789
|
-
}
|
|
790
|
-
},
|
|
791
|
-
"fields": ["userName"]
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
```
|
|
795
|
-
|
|
796
|
-
Lazy cached fields will not update themselves once set. They can only be updated
|
|
797
|
-
by forcing a sync:
|
|
798
|
-
|
|
799
|
-
```js
|
|
800
|
-
await Model.syncCacheFields({
|
|
801
|
-
force: true,
|
|
802
|
-
});
|
|
803
|
-
```
|
|
804
|
-
|
|
805
|
-
Making fields lazy alleviates performance impact on writes and allows caches to
|
|
806
|
-
be updated at another time (such as a background job).
|
|
807
|
-
|
|
808
|
-
#### Search Validation
|
|
809
|
-
|
|
810
|
-
The [validation](#validation) generated for search using `getSearchValidation`
|
|
811
|
-
is inherently looser and allows more fields to be passed to allow complex
|
|
812
|
-
searches compatible with the above.
|
|
813
|
-
|
|
814
757
|
### Includes
|
|
815
758
|
|
|
816
759
|
Populating foreign documents with
|
|
@@ -1135,7 +1078,7 @@ await shop.include('owner', {
|
|
|
1135
1078
|
|
|
1136
1079
|
### Access Control
|
|
1137
1080
|
|
|
1138
|
-
This
|
|
1081
|
+
This module applies two forms of access control:
|
|
1139
1082
|
|
|
1140
1083
|
- [Field Access](#field-access)
|
|
1141
1084
|
- [Document Access](#document-access)
|
|
@@ -1589,16 +1532,58 @@ string, both of which would be stored in the database if naively assigned with
|
|
|
1589
1532
|
|
|
1590
1533
|
This module adds a single `findOrCreate` convenience method that is easy to
|
|
1591
1534
|
understand and avoids some of the gotchas that come with upserting documents in
|
|
1592
|
-
Mongoose
|
|
1535
|
+
Mongoose:
|
|
1536
|
+
|
|
1537
|
+
```js
|
|
1538
|
+
const shop = await Shop.findOrCreate({
|
|
1539
|
+
name: 'My Shop',
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1542
|
+
// This is equivalent to running:
|
|
1543
|
+
let shop = await Shop.findOne({
|
|
1544
|
+
name: 'My Shop',
|
|
1545
|
+
});
|
|
1546
|
+
|
|
1547
|
+
if (!shop) {
|
|
1548
|
+
shop = await Shop.create({
|
|
1549
|
+
name: 'My Shop',
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
```
|
|
1553
|
+
|
|
1554
|
+
In most cases not all of the fields should be queried on to determine if an
|
|
1555
|
+
existing document exists. In this case two arguments should be passed the first
|
|
1556
|
+
of which is the query:
|
|
1557
|
+
|
|
1558
|
+
```js
|
|
1559
|
+
const shop = await Shop.findOrCreate(
|
|
1560
|
+
{
|
|
1561
|
+
slug: 'my-shop',
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
name: 'My Shop',
|
|
1565
|
+
slug: 'my-shop',
|
|
1566
|
+
}
|
|
1567
|
+
);
|
|
1568
|
+
|
|
1569
|
+
// This is equivalent to running:
|
|
1570
|
+
let shop = await Shop.findOne({
|
|
1571
|
+
slug: 'my-shop',
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
if (!shop) {
|
|
1575
|
+
shop = await Shop.create({
|
|
1576
|
+
name: 'My Shop',
|
|
1577
|
+
slug: 'my-shop',
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
```
|
|
1593
1581
|
|
|
1594
1582
|
### Slugs
|
|
1595
1583
|
|
|
1596
1584
|
A common requirement is to allow slugs on documents to serve as ids for human
|
|
1597
|
-
readable URLs.
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
This module simplifies this by assuming a `slug` field on a model and adding a
|
|
1601
|
-
`findByIdOrSlug` method that allows searching on both:
|
|
1585
|
+
readable URLs. This module simplifies this by assuming a `slug` field on a model
|
|
1586
|
+
and adding a `findByIdOrSlug` method that allows searching on either:
|
|
1602
1587
|
|
|
1603
1588
|
```js
|
|
1604
1589
|
const post = await Post.findByIdOrSlug(str);
|
package/dist/cjs/access.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.hasAccess = hasAccess;
|
|
|
7
7
|
var _errors = require("./errors");
|
|
8
8
|
var _utils = require("./utils");
|
|
9
9
|
var _warn = _interopRequireDefault(require("./warn"));
|
|
10
|
-
function _interopRequireDefault(
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
11
|
/**
|
|
12
12
|
* @param {string|string[]} allowed
|
|
13
13
|
*/
|
package/dist/cjs/assign.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.applyAssign = applyAssign;
|
|
|
7
7
|
var _lodash = require("lodash");
|
|
8
8
|
var _mongoose = _interopRequireDefault(require("mongoose"));
|
|
9
9
|
var _utils = require("./utils");
|
|
10
|
-
function _interopRequireDefault(
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
11
|
function applyAssign(schema) {
|
|
12
12
|
schema.method('assign', function assign(fields) {
|
|
13
13
|
unsetReferenceFields(fields, schema.obj);
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.applyCache = applyCache;
|
|
7
|
+
var _mongoose = _interopRequireDefault(require("mongoose"));
|
|
8
|
+
var _lodash = require("lodash");
|
|
9
|
+
var _utils = require("./utils");
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
const definitionMap = new Map();
|
|
12
|
+
_mongoose.default.plugin(cacheSyncPlugin);
|
|
13
|
+
function applyCache(schema, definition) {
|
|
14
|
+
definitionMap.set(schema, definition);
|
|
15
|
+
if (!definition.cache) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
createCacheFields(schema, definition);
|
|
19
|
+
applyStaticMethods(schema, definition);
|
|
20
|
+
applyCacheHook(schema, definition);
|
|
21
|
+
}
|
|
22
|
+
function createCacheFields(schema, definition) {
|
|
23
|
+
for (let [cachedField, def] of Object.entries(definition.cache)) {
|
|
24
|
+
const {
|
|
25
|
+
type,
|
|
26
|
+
path,
|
|
27
|
+
...rest
|
|
28
|
+
} = def;
|
|
29
|
+
schema.add({
|
|
30
|
+
[cachedField]: type
|
|
31
|
+
});
|
|
32
|
+
schema.obj[cachedField] = {
|
|
33
|
+
...rest,
|
|
34
|
+
type,
|
|
35
|
+
writeAccess: 'none'
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function applyStaticMethods(schema, definition) {
|
|
40
|
+
schema.static('syncCacheFields', async function syncCacheFields() {
|
|
41
|
+
assertIncludeModule(this);
|
|
42
|
+
const fields = resolveCachedFields(schema, definition);
|
|
43
|
+
const hasSynced = fields.some(entry => {
|
|
44
|
+
return entry.sync;
|
|
45
|
+
});
|
|
46
|
+
const query = {};
|
|
47
|
+
if (!hasSynced) {
|
|
48
|
+
const $or = fields.map(field => {
|
|
49
|
+
return {
|
|
50
|
+
[field.name]: null
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
query.$or = $or;
|
|
54
|
+
}
|
|
55
|
+
const includes = getIncludes(fields);
|
|
56
|
+
const docs = await this.find(query).include(includes);
|
|
57
|
+
const ops = docs.flatMap(doc => {
|
|
58
|
+
return fields.map(field => {
|
|
59
|
+
const {
|
|
60
|
+
name,
|
|
61
|
+
sync
|
|
62
|
+
} = field;
|
|
63
|
+
const updates = getUpdates(doc, [field]);
|
|
64
|
+
const filter = {
|
|
65
|
+
_id: doc._id
|
|
66
|
+
};
|
|
67
|
+
if (!sync) {
|
|
68
|
+
filter[name] = null;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
updateOne: {
|
|
72
|
+
filter,
|
|
73
|
+
update: {
|
|
74
|
+
$set: updates
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
return await this.bulkWrite(ops);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function applyCacheHook(schema, definition) {
|
|
84
|
+
const fields = resolveCachedFields(schema, definition);
|
|
85
|
+
schema.pre('save', async function () {
|
|
86
|
+
assertIncludeModule(this.constructor);
|
|
87
|
+
assertAssignModule(this.constructor);
|
|
88
|
+
const doc = this;
|
|
89
|
+
const changes = fields.filter(field => {
|
|
90
|
+
const {
|
|
91
|
+
sync,
|
|
92
|
+
local,
|
|
93
|
+
name
|
|
94
|
+
} = field;
|
|
95
|
+
if (sync || doc.isModified(local)) {
|
|
96
|
+
// Always update if we are actively syncing
|
|
97
|
+
// or if the field has been changed.
|
|
98
|
+
return true;
|
|
99
|
+
} else {
|
|
100
|
+
// Otherwise only update if the value does
|
|
101
|
+
// not exist yet.
|
|
102
|
+
const value = (0, _lodash.get)(doc, name);
|
|
103
|
+
return Array.isArray(value) ? !value.length : !value;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
await this.include(getIncludes(changes));
|
|
107
|
+
this.assign(getUpdates(doc, changes));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Syncing
|
|
112
|
+
|
|
113
|
+
const syncOperations = {};
|
|
114
|
+
const compiledModels = new Set();
|
|
115
|
+
function cacheSyncPlugin(schema) {
|
|
116
|
+
// Compile sync fields each time a new schema
|
|
117
|
+
// is registered but only do it one time for.
|
|
118
|
+
const initialize = (0, _lodash.once)(compileSyncOperations);
|
|
119
|
+
schema.pre('save', async function () {
|
|
120
|
+
this.$locals.modifiedPaths = this.modifiedPaths();
|
|
121
|
+
});
|
|
122
|
+
schema.post('save', async function () {
|
|
123
|
+
initialize();
|
|
124
|
+
|
|
125
|
+
// @ts-ignore
|
|
126
|
+
const {
|
|
127
|
+
modelName
|
|
128
|
+
} = this.constructor;
|
|
129
|
+
const ops = syncOperations[modelName] || [];
|
|
130
|
+
for (let op of ops) {
|
|
131
|
+
await op(this);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function compileSyncOperations() {
|
|
136
|
+
for (let Model of Object.values(_mongoose.default.models)) {
|
|
137
|
+
const {
|
|
138
|
+
schema
|
|
139
|
+
} = Model;
|
|
140
|
+
if (compiledModels.has(Model)) {
|
|
141
|
+
// Model has already been compiled so skip.
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const definition = definitionMap.get(schema);
|
|
145
|
+
const fields = resolveCachedFields(schema, definition);
|
|
146
|
+
for (let [ref, group] of Object.entries((0, _lodash.groupBy)(fields, 'ref'))) {
|
|
147
|
+
const hasSynced = group.some(entry => {
|
|
148
|
+
return entry.sync;
|
|
149
|
+
});
|
|
150
|
+
if (!hasSynced) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const fn = async doc => {
|
|
154
|
+
const {
|
|
155
|
+
modifiedPaths
|
|
156
|
+
} = doc.$locals;
|
|
157
|
+
const changes = group.filter(entry => {
|
|
158
|
+
return entry.sync && modifiedPaths.includes(entry.foreign);
|
|
159
|
+
});
|
|
160
|
+
if (changes.length) {
|
|
161
|
+
const $or = changes.map(change => {
|
|
162
|
+
const {
|
|
163
|
+
local
|
|
164
|
+
} = change;
|
|
165
|
+
return {
|
|
166
|
+
[local]: doc.id
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
const docs = await Model.find({
|
|
170
|
+
$or
|
|
171
|
+
});
|
|
172
|
+
await Promise.all(docs.map(doc => doc.save()));
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
syncOperations[ref] ||= [];
|
|
176
|
+
syncOperations[ref].push(fn);
|
|
177
|
+
}
|
|
178
|
+
compiledModels.add(Model);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Utils
|
|
183
|
+
|
|
184
|
+
function resolveCachedFields(schema, definition) {
|
|
185
|
+
const {
|
|
186
|
+
cache = {}
|
|
187
|
+
} = definition;
|
|
188
|
+
return Object.entries(cache).map(([name, def]) => {
|
|
189
|
+
const {
|
|
190
|
+
path,
|
|
191
|
+
sync = false
|
|
192
|
+
} = def;
|
|
193
|
+
const resolved = (0, _utils.resolveRefPath)(schema, path);
|
|
194
|
+
if (!resolved) {
|
|
195
|
+
throw new Error(`Could not resolve path ${path}.`);
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
...resolved,
|
|
199
|
+
name,
|
|
200
|
+
path,
|
|
201
|
+
sync
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function getUpdates(doc, fields) {
|
|
206
|
+
const updates = {};
|
|
207
|
+
for (let field of fields) {
|
|
208
|
+
const {
|
|
209
|
+
name,
|
|
210
|
+
path
|
|
211
|
+
} = field;
|
|
212
|
+
|
|
213
|
+
// doc.get will not return virtuals (even with specified options),
|
|
214
|
+
// so fall back to lodash to ensure they are included here.
|
|
215
|
+
// https://mongoosejs.com/docs/api/document.html#Document.prototype.get()
|
|
216
|
+
const value = doc.get(path) ?? (0, _lodash.get)(doc, path);
|
|
217
|
+
updates[name] = value;
|
|
218
|
+
}
|
|
219
|
+
return updates;
|
|
220
|
+
}
|
|
221
|
+
function getIncludes(fields) {
|
|
222
|
+
const includes = new Set();
|
|
223
|
+
for (let field of fields) {
|
|
224
|
+
includes.add(field.local);
|
|
225
|
+
}
|
|
226
|
+
return includes;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Assertions
|
|
230
|
+
|
|
231
|
+
function assertIncludeModule(Model) {
|
|
232
|
+
if (!Model.schema.methods.include) {
|
|
233
|
+
throw new Error('Include module is required for cached fields.');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function assertAssignModule(Model) {
|
|
237
|
+
if (!Model.schema.methods.assign) {
|
|
238
|
+
throw new Error('Assign module is required for cached fields.');
|
|
239
|
+
}
|
|
240
|
+
}
|
package/dist/cjs/delete-hooks.js
CHANGED
|
@@ -8,7 +8,7 @@ var _mongoose = _interopRequireDefault(require("mongoose"));
|
|
|
8
8
|
var _lodash = require("lodash");
|
|
9
9
|
var _errors = require("./errors");
|
|
10
10
|
var _utils = require("./utils");
|
|
11
|
-
function _interopRequireDefault(
|
|
11
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
12
|
const {
|
|
13
13
|
ObjectId: SchemaObjectId
|
|
14
14
|
} = _mongoose.default.Schema.Types;
|
package/dist/cjs/disallowed.js
CHANGED
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.applyDisallowed = applyDisallowed;
|
|
7
7
|
var _warn = _interopRequireDefault(require("./warn"));
|
|
8
|
-
function _interopRequireDefault(
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
9
|
function applyDisallowed(schema) {
|
|
10
10
|
schema.method('deleteOne', function () {
|
|
11
11
|
(0, _warn.default)('The "deleteOne" method on documents is disallowed due to ambiguity', 'Use either "delete" or "deleteOne" on the model.');
|
package/dist/cjs/include.js
CHANGED
|
@@ -13,7 +13,7 @@ var _lodash = require("lodash");
|
|
|
13
13
|
var _yada = _interopRequireDefault(require("@bedrockio/yada"));
|
|
14
14
|
var _utils = require("./utils");
|
|
15
15
|
var _const = require("./const");
|
|
16
|
-
function _interopRequireDefault(
|
|
16
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
17
17
|
// @ts-ignore
|
|
18
18
|
// Overloading mongoose Query prototype to
|
|
19
19
|
// allow an "include" method for queries.
|
|
@@ -149,10 +149,19 @@ function checkSelects(doc, ret) {
|
|
|
149
149
|
|
|
150
150
|
// Exported for testing.
|
|
151
151
|
function getParams(modelName, arg) {
|
|
152
|
-
const paths =
|
|
152
|
+
const paths = resolvePathsArg(arg);
|
|
153
153
|
const node = pathsToNode(paths, modelName);
|
|
154
154
|
return nodeToPopulates(node);
|
|
155
155
|
}
|
|
156
|
+
function resolvePathsArg(arg) {
|
|
157
|
+
if (Array.isArray(arg)) {
|
|
158
|
+
return arg;
|
|
159
|
+
} else if (arg instanceof Set) {
|
|
160
|
+
return Array.from(arg);
|
|
161
|
+
} else {
|
|
162
|
+
return [arg];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
156
165
|
|
|
157
166
|
// Exported for testing.
|
|
158
167
|
function getDocumentParams(doc, arg, options = {}) {
|
package/dist/cjs/load.js
CHANGED
|
@@ -10,7 +10,7 @@ var _path = _interopRequireDefault(require("path"));
|
|
|
10
10
|
var _mongoose = _interopRequireDefault(require("mongoose"));
|
|
11
11
|
var _lodash = require("lodash");
|
|
12
12
|
var _schema = require("./schema");
|
|
13
|
-
function _interopRequireDefault(
|
|
13
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
14
|
/**
|
|
15
15
|
* Loads a single model by definition and name.
|
|
16
16
|
* @param {object} definition
|
package/dist/cjs/schema.js
CHANGED
|
@@ -10,6 +10,7 @@ var _lodash = require("lodash");
|
|
|
10
10
|
var _utils = require("./utils");
|
|
11
11
|
var _serialization = require("./serialization");
|
|
12
12
|
var _slug = require("./slug");
|
|
13
|
+
var _cache = require("./cache");
|
|
13
14
|
var _search = require("./search");
|
|
14
15
|
var _assign = require("./assign");
|
|
15
16
|
var _upsert = require("./upsert");
|
|
@@ -19,7 +20,7 @@ var _softDelete = require("./soft-delete");
|
|
|
19
20
|
var _deleteHooks = require("./delete-hooks");
|
|
20
21
|
var _disallowed = require("./disallowed");
|
|
21
22
|
var _validation = require("./validation");
|
|
22
|
-
function _interopRequireDefault(
|
|
23
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
23
24
|
/**
|
|
24
25
|
* Creates a new Mongoose schema with Bedrock extensions
|
|
25
26
|
* applied. For more about syntax and functionality see
|
|
@@ -54,6 +55,7 @@ function createSchema(definition, options = {}) {
|
|
|
54
55
|
(0, _validation.applyValidation)(schema, definition);
|
|
55
56
|
(0, _deleteHooks.applyDeleteHooks)(schema, definition);
|
|
56
57
|
(0, _search.applySearch)(schema, definition);
|
|
58
|
+
(0, _cache.applyCache)(schema, definition);
|
|
57
59
|
(0, _disallowed.applyDisallowed)(schema);
|
|
58
60
|
(0, _include.applyInclude)(schema);
|
|
59
61
|
(0, _hydrate.applyHydrate)(schema);
|