@bedrockio/model 0.5.4 → 0.7.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/README.md +103 -2
- package/dist/cjs/schema.js +2 -0
- package/dist/cjs/search.js +34 -4
- package/dist/cjs/upsert.js +21 -0
- package/dist/cjs/utils.js +5 -0
- package/dist/cjs/validation.js +8 -2
- package/package.json +3 -3
- package/src/schema.js +2 -0
- package/src/search.js +36 -5
- package/src/upsert.js +18 -0
- package/src/utils.js +5 -0
- package/src/validation.js +10 -2
- package/types/include.d.ts +5 -1
- package/types/include.d.ts.map +1 -1
- package/types/schema.d.ts.map +1 -1
- package/types/search.d.ts +5 -1
- package/types/search.d.ts.map +1 -1
- package/types/upsert.d.ts +2 -0
- package/types/upsert.d.ts.map +1 -0
- package/types/utils.d.ts +1 -0
- package/types/utils.d.ts.map +1 -1
- package/types/validation.d.ts +5 -1
- package/types/validation.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ Bedrock utilities for model creation.
|
|
|
19
19
|
- [Delete Hooks](#delete-hooks)
|
|
20
20
|
- [Access Control](#access-control)
|
|
21
21
|
- [Assign](#assign)
|
|
22
|
+
- [Upsert](#upsert)
|
|
22
23
|
- [Slugs](#slugs)
|
|
23
24
|
- [Testing](#testing)
|
|
24
25
|
- [Troubleshooting](#troubleshooting)
|
|
@@ -568,11 +569,55 @@ will be flattened to:
|
|
|
568
569
|
}
|
|
569
570
|
```
|
|
570
571
|
|
|
572
|
+
#### Searching Arrays
|
|
573
|
+
|
|
574
|
+
Passing an array to `search` will perform a query using `$in`, which matches on
|
|
575
|
+
the intersection of any elements. For example,
|
|
576
|
+
|
|
577
|
+
```json
|
|
578
|
+
tags: ["one", "two"]
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
is effectively saying "match any documents whose `tags` array contains any of
|
|
582
|
+
`one` or `two`".
|
|
583
|
+
|
|
584
|
+
For this reason, passing an empty array here is ambiguous as it could be asking:
|
|
585
|
+
|
|
586
|
+
- Match any documents whose `tags` array is empty.
|
|
587
|
+
- Match any documents whose `tags` array contains any of `[]` (ie. no elements
|
|
588
|
+
passed so no match).
|
|
589
|
+
|
|
590
|
+
The difference here is subtle, but for example in a UI field where tags can be
|
|
591
|
+
chosen but none happen to be selected, it would be unexpected to return
|
|
592
|
+
documents simply because their `tags` array happens to be empty.
|
|
593
|
+
|
|
594
|
+
The `search` method takes the simpler approach here where an empty array will
|
|
595
|
+
simply be passed along to `$in`, which will never result in a match.
|
|
596
|
+
|
|
597
|
+
However, as a special case, `null` may instead be passed to `search` for array
|
|
598
|
+
fields to explicitly search for empty arrays:
|
|
599
|
+
|
|
600
|
+
```js
|
|
601
|
+
await User.search({
|
|
602
|
+
tags: null,
|
|
603
|
+
});
|
|
604
|
+
// Equivalent to:
|
|
605
|
+
await User.find({
|
|
606
|
+
tags: [],
|
|
607
|
+
});
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
This works because Mongoose will always initialize an array field in its
|
|
611
|
+
documents (ie. the field is always guarnateed to exist and be an array), so an
|
|
612
|
+
empty array can be thought of as equivalent to `{ $exists: false }` for array
|
|
613
|
+
fields. Thinking of it this way makes it more intuitive that `null` should
|
|
614
|
+
match.
|
|
615
|
+
|
|
571
616
|
#### Range Based Search
|
|
572
617
|
|
|
573
618
|
Additionally, date and number fields allow range queries in the form:
|
|
574
619
|
|
|
575
|
-
```
|
|
620
|
+
```json
|
|
576
621
|
age: {
|
|
577
622
|
gt: 1
|
|
578
623
|
lt: 2
|
|
@@ -653,7 +698,57 @@ schema.pre('save', function () {
|
|
|
653
698
|
});
|
|
654
699
|
```
|
|
655
700
|
|
|
656
|
-
|
|
701
|
+
#### Syncing Cached Fields
|
|
702
|
+
|
|
703
|
+
When a foreign document is updated the cached fields will be out of sync, for
|
|
704
|
+
example:
|
|
705
|
+
|
|
706
|
+
```js
|
|
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
|
|
723
|
+
{
|
|
724
|
+
"attributes": {
|
|
725
|
+
"name": "String"
|
|
726
|
+
},
|
|
727
|
+
"search": {
|
|
728
|
+
"sync": [
|
|
729
|
+
{
|
|
730
|
+
"ref": "Shop",
|
|
731
|
+
"path": "user"
|
|
732
|
+
}
|
|
733
|
+
]
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
This is the equivalent of running the following in a post save hook:
|
|
739
|
+
|
|
740
|
+
```js
|
|
741
|
+
const shops = await Shop.find({
|
|
742
|
+
user: user.id,
|
|
743
|
+
});
|
|
744
|
+
for (let shop of shops) {
|
|
745
|
+
await shop.save();
|
|
746
|
+
}
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
This will run the hooks on each shop, synchronizing the cached fields.
|
|
750
|
+
|
|
751
|
+
##### Initial Sync
|
|
657
752
|
|
|
658
753
|
When first applying or making changes to defined cached search fields, existing
|
|
659
754
|
documents will be out of sync. The static method `syncCacheFields` is provided
|
|
@@ -1490,6 +1585,12 @@ provided as `undefined` cannot be represented in JSON which requires using
|
|
|
1490
1585
|
either a `null` or empty string, both of which would be stored in the database
|
|
1491
1586
|
if naively assigned with `Object.assign`.
|
|
1492
1587
|
|
|
1588
|
+
### Upsert
|
|
1589
|
+
|
|
1590
|
+
This module adds a single `findOrCreate` convenience method that is easy to
|
|
1591
|
+
understand and avoids some of the gotchas that come with upserting documents in
|
|
1592
|
+
Mongoose.
|
|
1593
|
+
|
|
1493
1594
|
### Slugs
|
|
1494
1595
|
|
|
1495
1596
|
A common requirement is to allow slugs on documents to serve as ids for human
|
package/dist/cjs/schema.js
CHANGED
|
@@ -12,6 +12,7 @@ var _serialization = require("./serialization");
|
|
|
12
12
|
var _slug = require("./slug");
|
|
13
13
|
var _search = require("./search");
|
|
14
14
|
var _assign = require("./assign");
|
|
15
|
+
var _upsert = require("./upsert");
|
|
15
16
|
var _hydrate = require("./hydrate");
|
|
16
17
|
var _include = require("./include");
|
|
17
18
|
var _softDelete = require("./soft-delete");
|
|
@@ -57,6 +58,7 @@ function createSchema(definition, options = {}) {
|
|
|
57
58
|
(0, _include.applyInclude)(schema);
|
|
58
59
|
(0, _hydrate.applyHydrate)(schema);
|
|
59
60
|
(0, _assign.applyAssign)(schema);
|
|
61
|
+
(0, _upsert.applyUpsert)(schema);
|
|
60
62
|
(0, _slug.applySlug)(schema);
|
|
61
63
|
return schema;
|
|
62
64
|
}
|
package/dist/cjs/search.js
CHANGED
|
@@ -26,6 +26,7 @@ function applySearch(schema, definition) {
|
|
|
26
26
|
validateDefinition(definition);
|
|
27
27
|
validateSearchFields(schema, definition);
|
|
28
28
|
applySearchCache(schema, definition);
|
|
29
|
+
applySearchSync(schema, definition);
|
|
29
30
|
const {
|
|
30
31
|
query: searchQuery,
|
|
31
32
|
fields: searchFields
|
|
@@ -236,6 +237,8 @@ function normalizeQuery(query, schema, root = {}, rootPath = []) {
|
|
|
236
237
|
root[path.join('.')] = {
|
|
237
238
|
$in: value
|
|
238
239
|
};
|
|
240
|
+
} else if (isEmptyArrayQuery(schema, key, value)) {
|
|
241
|
+
root[path.join('.')] = [];
|
|
239
242
|
} else {
|
|
240
243
|
root[path.join('.')] = value;
|
|
241
244
|
}
|
|
@@ -255,12 +258,15 @@ function isNestedQuery(key, value) {
|
|
|
255
258
|
function isArrayQuery(key, value) {
|
|
256
259
|
return !isMongoOperator(key) && !isInclude(key) && Array.isArray(value);
|
|
257
260
|
}
|
|
261
|
+
function isEmptyArrayQuery(schema, key, value) {
|
|
262
|
+
return !isMongoOperator(key) && (0, _utils.isArrayField)(schema, key) && value === null;
|
|
263
|
+
}
|
|
258
264
|
function isRangeQuery(schema, key, value) {
|
|
259
265
|
// Range queries only allowed on Date and Number fields.
|
|
260
266
|
if (!(0, _utils.isDateField)(schema, key) && !(0, _utils.isNumberField)(schema, key)) {
|
|
261
267
|
return false;
|
|
262
268
|
}
|
|
263
|
-
return typeof value === 'object';
|
|
269
|
+
return typeof value === 'object' && !!value;
|
|
264
270
|
}
|
|
265
271
|
function mapOperatorQuery(obj) {
|
|
266
272
|
const query = {};
|
|
@@ -322,9 +328,7 @@ function applySearchCache(schema, definition) {
|
|
|
322
328
|
if (!force) {
|
|
323
329
|
const $or = Object.keys(cache).map(cachedField => {
|
|
324
330
|
return {
|
|
325
|
-
[cachedField]:
|
|
326
|
-
$exists: false
|
|
327
|
-
}
|
|
331
|
+
[cachedField]: null
|
|
328
332
|
};
|
|
329
333
|
});
|
|
330
334
|
query.$or = $or;
|
|
@@ -391,6 +395,32 @@ function applyCacheHook(schema, definition) {
|
|
|
391
395
|
this.assign(getUpdates(this, paths, definition));
|
|
392
396
|
});
|
|
393
397
|
}
|
|
398
|
+
|
|
399
|
+
// Search field syncing
|
|
400
|
+
|
|
401
|
+
function applySearchSync(schema, definition) {
|
|
402
|
+
if (!definition.search?.sync) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
schema.post('save', async function postSave() {
|
|
406
|
+
for (let entry of definition.search.sync) {
|
|
407
|
+
const {
|
|
408
|
+
ref,
|
|
409
|
+
path
|
|
410
|
+
} = entry;
|
|
411
|
+
const Model = _mongoose.default.models[ref];
|
|
412
|
+
const docs = await Model.find({
|
|
413
|
+
[path]: this.id
|
|
414
|
+
});
|
|
415
|
+
await Promise.all(docs.map(async doc => {
|
|
416
|
+
await doc.save();
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Utils
|
|
423
|
+
|
|
394
424
|
function isForeignField(schema, path) {
|
|
395
425
|
if (!path.includes('.')) {
|
|
396
426
|
return false;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.applyUpsert = applyUpsert;
|
|
7
|
+
function applyUpsert(schema) {
|
|
8
|
+
// Note: Intentionally avoiding the findOneAndUpdate approach here
|
|
9
|
+
// as this will prevent hooks from being run on the document. This
|
|
10
|
+
// means however that we cannot always return a query here as the
|
|
11
|
+
// operations are inherently different.
|
|
12
|
+
|
|
13
|
+
schema.static('findOrCreate', async function findOrCreate(query, fields) {
|
|
14
|
+
fields ||= query;
|
|
15
|
+
let doc = await this.findOne(query);
|
|
16
|
+
if (!doc) {
|
|
17
|
+
doc = await this.create(fields);
|
|
18
|
+
}
|
|
19
|
+
return doc;
|
|
20
|
+
});
|
|
21
|
+
}
|
package/dist/cjs/utils.js
CHANGED
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.getField = getField;
|
|
7
7
|
exports.getInnerField = getInnerField;
|
|
8
|
+
exports.isArrayField = isArrayField;
|
|
8
9
|
exports.isDateField = isDateField;
|
|
9
10
|
exports.isEqual = isEqual;
|
|
10
11
|
exports.isMongooseSchema = isMongooseSchema;
|
|
@@ -44,6 +45,10 @@ function isDateField(obj, path) {
|
|
|
44
45
|
function isNumberField(obj, path) {
|
|
45
46
|
return isType(obj, path, 'Number');
|
|
46
47
|
}
|
|
48
|
+
function isArrayField(obj, path) {
|
|
49
|
+
const field = getField(obj, path);
|
|
50
|
+
return Array.isArray(field?.type);
|
|
51
|
+
}
|
|
47
52
|
function isType(obj, path, test) {
|
|
48
53
|
const {
|
|
49
54
|
type
|
package/dist/cjs/validation.js
CHANGED
|
@@ -137,6 +137,7 @@ function applyValidation(schema, definition) {
|
|
|
137
137
|
} = options;
|
|
138
138
|
return getSchemaFromMongoose(schema, {
|
|
139
139
|
model: this,
|
|
140
|
+
allowNull: true,
|
|
140
141
|
allowSearch: true,
|
|
141
142
|
skipRequired: true,
|
|
142
143
|
allowInclude: true,
|
|
@@ -284,9 +285,11 @@ function getSchemaForTypedef(typedef, options = {}) {
|
|
|
284
285
|
} else {
|
|
285
286
|
schema = getSchemaForType(type, options);
|
|
286
287
|
|
|
287
|
-
//
|
|
288
|
+
// Only allowed for primitive types.
|
|
288
289
|
if (allowUnset(typedef, options)) {
|
|
289
|
-
schema = _yada.default.allow(
|
|
290
|
+
schema = _yada.default.allow(schema, '').nullable();
|
|
291
|
+
} else if (allowNull(typedef, options)) {
|
|
292
|
+
schema = schema.nullable();
|
|
290
293
|
} else if (allowEmpty(typedef, options)) {
|
|
291
294
|
schema = schema.options({
|
|
292
295
|
allowEmpty: true
|
|
@@ -402,6 +405,9 @@ function getSearchSchema(schema, type) {
|
|
|
402
405
|
function isRequired(typedef, options) {
|
|
403
406
|
return typedef.required && !typedef.default && !options.skipRequired;
|
|
404
407
|
}
|
|
408
|
+
function allowNull(typedef, options) {
|
|
409
|
+
return options.allowNull && !typedef.required;
|
|
410
|
+
}
|
|
405
411
|
function allowUnset(typedef, options) {
|
|
406
412
|
return options.allowUnset && !typedef.required;
|
|
407
413
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bedrockio/model",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Bedrock utilities for model creation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"lodash": "^4.17.21"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@bedrockio/yada": "^1.1.
|
|
33
|
+
"@bedrockio/yada": "^1.1.3",
|
|
34
34
|
"mongoose": "^8.5.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@babel/core": "^7.20.12",
|
|
39
39
|
"@babel/preset-env": "^7.20.2",
|
|
40
40
|
"@bedrockio/prettier-config": "^1.0.2",
|
|
41
|
-
"@bedrockio/yada": "^1.1.
|
|
41
|
+
"@bedrockio/yada": "^1.1.3",
|
|
42
42
|
"@shelf/jest-mongodb": "^4.3.2",
|
|
43
43
|
"eslint": "^8.33.0",
|
|
44
44
|
"eslint-plugin-bedrock": "^1.0.26",
|
package/src/schema.js
CHANGED
|
@@ -7,6 +7,7 @@ import { serializeOptions } from './serialization';
|
|
|
7
7
|
import { applySlug } from './slug';
|
|
8
8
|
import { applySearch } from './search';
|
|
9
9
|
import { applyAssign } from './assign';
|
|
10
|
+
import { applyUpsert } from './upsert';
|
|
10
11
|
import { applyHydrate } from './hydrate';
|
|
11
12
|
import { applyInclude } from './include';
|
|
12
13
|
import { applySoftDelete } from './soft-delete';
|
|
@@ -61,6 +62,7 @@ export function createSchema(definition, options = {}) {
|
|
|
61
62
|
applyInclude(schema);
|
|
62
63
|
applyHydrate(schema);
|
|
63
64
|
applyAssign(schema);
|
|
65
|
+
applyUpsert(schema);
|
|
64
66
|
applySlug(schema);
|
|
65
67
|
|
|
66
68
|
return schema;
|
package/src/search.js
CHANGED
|
@@ -3,7 +3,7 @@ import logger from '@bedrockio/logger';
|
|
|
3
3
|
import mongoose from 'mongoose';
|
|
4
4
|
import { get, pick, isEmpty, escapeRegExp, isPlainObject } from 'lodash';
|
|
5
5
|
|
|
6
|
-
import { isDateField, isNumberField, getField } from './utils';
|
|
6
|
+
import { isArrayField, isDateField, isNumberField, getField } from './utils';
|
|
7
7
|
import { SEARCH_DEFAULTS } from './const';
|
|
8
8
|
import { OBJECT_ID_SCHEMA } from './validation';
|
|
9
9
|
import { debug } from './env';
|
|
@@ -18,6 +18,7 @@ export function applySearch(schema, definition) {
|
|
|
18
18
|
validateDefinition(definition);
|
|
19
19
|
validateSearchFields(schema, definition);
|
|
20
20
|
applySearchCache(schema, definition);
|
|
21
|
+
applySearchSync(schema, definition);
|
|
21
22
|
|
|
22
23
|
const { query: searchQuery, fields: searchFields } = definition.search || {};
|
|
23
24
|
|
|
@@ -240,6 +241,8 @@ function normalizeQuery(query, schema, root = {}, rootPath = []) {
|
|
|
240
241
|
root[path.join('.')] = parseRegexQuery(value);
|
|
241
242
|
} else if (isArrayQuery(key, value)) {
|
|
242
243
|
root[path.join('.')] = { $in: value };
|
|
244
|
+
} else if (isEmptyArrayQuery(schema, key, value)) {
|
|
245
|
+
root[path.join('.')] = [];
|
|
243
246
|
} else {
|
|
244
247
|
root[path.join('.')] = value;
|
|
245
248
|
}
|
|
@@ -261,12 +264,16 @@ function isArrayQuery(key, value) {
|
|
|
261
264
|
return !isMongoOperator(key) && !isInclude(key) && Array.isArray(value);
|
|
262
265
|
}
|
|
263
266
|
|
|
267
|
+
function isEmptyArrayQuery(schema, key, value) {
|
|
268
|
+
return !isMongoOperator(key) && isArrayField(schema, key) && value === null;
|
|
269
|
+
}
|
|
270
|
+
|
|
264
271
|
function isRangeQuery(schema, key, value) {
|
|
265
272
|
// Range queries only allowed on Date and Number fields.
|
|
266
273
|
if (!isDateField(schema, key) && !isNumberField(schema, key)) {
|
|
267
274
|
return false;
|
|
268
275
|
}
|
|
269
|
-
return typeof value === 'object';
|
|
276
|
+
return typeof value === 'object' && !!value;
|
|
270
277
|
}
|
|
271
278
|
|
|
272
279
|
function mapOperatorQuery(obj) {
|
|
@@ -339,9 +346,7 @@ function applySearchCache(schema, definition) {
|
|
|
339
346
|
if (!force) {
|
|
340
347
|
const $or = Object.keys(cache).map((cachedField) => {
|
|
341
348
|
return {
|
|
342
|
-
[cachedField]:
|
|
343
|
-
$exists: false,
|
|
344
|
-
},
|
|
349
|
+
[cachedField]: null,
|
|
345
350
|
};
|
|
346
351
|
});
|
|
347
352
|
query.$or = $or;
|
|
@@ -415,6 +420,32 @@ function applyCacheHook(schema, definition) {
|
|
|
415
420
|
});
|
|
416
421
|
}
|
|
417
422
|
|
|
423
|
+
// Search field syncing
|
|
424
|
+
|
|
425
|
+
function applySearchSync(schema, definition) {
|
|
426
|
+
if (!definition.search?.sync) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
schema.post('save', async function postSave() {
|
|
431
|
+
for (let entry of definition.search.sync) {
|
|
432
|
+
const { ref, path } = entry;
|
|
433
|
+
const Model = mongoose.models[ref];
|
|
434
|
+
const docs = await Model.find({
|
|
435
|
+
[path]: this.id,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
await Promise.all(
|
|
439
|
+
docs.map(async (doc) => {
|
|
440
|
+
await doc.save();
|
|
441
|
+
})
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Utils
|
|
448
|
+
|
|
418
449
|
function isForeignField(schema, path) {
|
|
419
450
|
if (!path.includes('.')) {
|
|
420
451
|
return false;
|
package/src/upsert.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function applyUpsert(schema) {
|
|
2
|
+
// Note: Intentionally avoiding the findOneAndUpdate approach here
|
|
3
|
+
// as this will prevent hooks from being run on the document. This
|
|
4
|
+
// means however that we cannot always return a query here as the
|
|
5
|
+
// operations are inherently different.
|
|
6
|
+
|
|
7
|
+
schema.static('findOrCreate', async function findOrCreate(query, fields) {
|
|
8
|
+
fields ||= query;
|
|
9
|
+
|
|
10
|
+
let doc = await this.findOne(query);
|
|
11
|
+
|
|
12
|
+
if (!doc) {
|
|
13
|
+
doc = await this.create(fields);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return doc;
|
|
17
|
+
});
|
|
18
|
+
}
|
package/src/utils.js
CHANGED
|
@@ -36,6 +36,11 @@ export function isNumberField(obj, path) {
|
|
|
36
36
|
return isType(obj, path, 'Number');
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export function isArrayField(obj, path) {
|
|
40
|
+
const field = getField(obj, path);
|
|
41
|
+
return Array.isArray(field?.type);
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
function isType(obj, path, test) {
|
|
40
45
|
const { type } = getInnerField(obj, path);
|
|
41
46
|
return type === test || type === mongoose.Schema.Types[test];
|
package/src/validation.js
CHANGED
|
@@ -152,6 +152,7 @@ export function applyValidation(schema, definition) {
|
|
|
152
152
|
|
|
153
153
|
return getSchemaFromMongoose(schema, {
|
|
154
154
|
model: this,
|
|
155
|
+
allowNull: true,
|
|
155
156
|
allowSearch: true,
|
|
156
157
|
skipRequired: true,
|
|
157
158
|
allowInclude: true,
|
|
@@ -299,9 +300,11 @@ function getSchemaForTypedef(typedef, options = {}) {
|
|
|
299
300
|
} else {
|
|
300
301
|
schema = getSchemaForType(type, options);
|
|
301
302
|
|
|
302
|
-
//
|
|
303
|
+
// Only allowed for primitive types.
|
|
303
304
|
if (allowUnset(typedef, options)) {
|
|
304
|
-
schema = yd.allow(
|
|
305
|
+
schema = yd.allow(schema, '').nullable();
|
|
306
|
+
} else if (allowNull(typedef, options)) {
|
|
307
|
+
schema = schema.nullable();
|
|
305
308
|
} else if (allowEmpty(typedef, options)) {
|
|
306
309
|
schema = schema.options({
|
|
307
310
|
allowEmpty: true,
|
|
@@ -352,6 +355,7 @@ function getSchemaForTypedef(typedef, options = {}) {
|
|
|
352
355
|
if (typedef.writeAccess && options.requireWriteAccess) {
|
|
353
356
|
schema = validateAccess('write', schema, typedef.writeAccess, options);
|
|
354
357
|
}
|
|
358
|
+
|
|
355
359
|
return schema;
|
|
356
360
|
}
|
|
357
361
|
|
|
@@ -460,6 +464,10 @@ function isRequired(typedef, options) {
|
|
|
460
464
|
return typedef.required && !typedef.default && !options.skipRequired;
|
|
461
465
|
}
|
|
462
466
|
|
|
467
|
+
function allowNull(typedef, options) {
|
|
468
|
+
return options.allowNull && !typedef.required;
|
|
469
|
+
}
|
|
470
|
+
|
|
463
471
|
function allowUnset(typedef, options) {
|
|
464
472
|
return options.allowUnset && !typedef.required;
|
|
465
473
|
}
|
package/types/include.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export const INCLUDE_FIELD_SCHEMA: {
|
|
|
18
18
|
strip(strip: any): any;
|
|
19
19
|
allow(...set: any[]): any;
|
|
20
20
|
reject(...set: any[]): any;
|
|
21
|
+
nullable(): any;
|
|
21
22
|
message(message: any): any;
|
|
22
23
|
tag(tags: any): any;
|
|
23
24
|
description(description: any): any;
|
|
@@ -33,8 +34,11 @@ export const INCLUDE_FIELD_SCHEMA: {
|
|
|
33
34
|
} | {
|
|
34
35
|
default: any;
|
|
35
36
|
};
|
|
36
|
-
|
|
37
|
+
getNullable(): {
|
|
38
|
+
nullable: boolean;
|
|
39
|
+
};
|
|
37
40
|
expandExtra(extra?: {}): {};
|
|
41
|
+
inspect(): string;
|
|
38
42
|
assertEnum(set: any, allow: any): any;
|
|
39
43
|
assert(type: any, fn: any): any;
|
|
40
44
|
pushAssertion(assertion: any): void;
|
package/types/include.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"include.d.ts","sourceRoot":"","sources":["../src/include.js"],"names":[],"mappings":"AA2BA,gDAuEC;AAMD,uDA4BC;AAGD,yDAIC;AAGD,yEAUC;AApID
|
|
1
|
+
{"version":3,"file":"include.d.ts","sourceRoot":"","sources":["../src/include.js"],"names":[],"mappings":"AA2BA,gDAuEC;AAMD,uDA4BC;AAGD,yDAIC;AAGD,yEAUC;AApID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKG"}
|
package/types/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.js"],"names":[],"mappings":"AAsBA;;;;;;;GAOG;AACH,yCAJW,MAAM,YACN,SAAS,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAyChC;AAED,iEAsBC"}
|
package/types/search.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export function searchValidation(options?: {}): {
|
|
|
15
15
|
strip(strip: any): any;
|
|
16
16
|
allow(...set: any[]): any;
|
|
17
17
|
reject(...set: any[]): any;
|
|
18
|
+
nullable(): any;
|
|
18
19
|
message(message: any): any;
|
|
19
20
|
tag(tags: any): any;
|
|
20
21
|
description(description: any): any;
|
|
@@ -30,8 +31,11 @@ export function searchValidation(options?: {}): {
|
|
|
30
31
|
} | {
|
|
31
32
|
default: any;
|
|
32
33
|
};
|
|
33
|
-
|
|
34
|
+
getNullable(): {
|
|
35
|
+
nullable: boolean;
|
|
36
|
+
};
|
|
34
37
|
expandExtra(extra?: {}): {};
|
|
38
|
+
inspect(): string;
|
|
35
39
|
assertEnum(set: any, allow: any): any;
|
|
36
40
|
assert(type: any, fn: any): any;
|
|
37
41
|
pushAssertion(assertion: any): void;
|
package/types/search.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAgBA,
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAgBA,gEA0DC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyBC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../src/upsert.js"],"names":[],"mappings":"AAAA,+CAiBC"}
|
package/types/utils.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export function isMongooseSchema(obj: any): boolean;
|
|
|
3
3
|
export function isReferenceField(obj: any, path: any): boolean;
|
|
4
4
|
export function isDateField(obj: any, path: any): boolean;
|
|
5
5
|
export function isNumberField(obj: any, path: any): boolean;
|
|
6
|
+
export function isArrayField(obj: any, path: any): boolean;
|
|
6
7
|
export function isSchemaTypedef(arg: any): boolean;
|
|
7
8
|
export function getField(obj: any, path: any): any;
|
|
8
9
|
export function getInnerField(obj: any, path: any): any;
|
package/types/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAMA,iDAcC;AAED,oDAEC;AAED,+DAEC;AAED,0DAEC;AAED,4DAEC;AAOD,mDAGC;AAuBD,mDAYC;AAKD,wDAEC"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAMA,iDAcC;AAED,oDAEC;AAED,+DAEC;AAED,0DAEC;AAED,4DAEC;AAED,2DAGC;AAOD,mDAGC;AAuBD,mDAYC;AAKD,wDAEC"}
|
package/types/validation.d.ts
CHANGED
|
@@ -77,6 +77,7 @@ export const OBJECT_ID_SCHEMA: {
|
|
|
77
77
|
strip(strip: any): any;
|
|
78
78
|
allow(...set: any[]): any;
|
|
79
79
|
reject(...set: any[]): any;
|
|
80
|
+
nullable(): any;
|
|
80
81
|
message(message: any): any;
|
|
81
82
|
tag(tags: any): any;
|
|
82
83
|
description(description: any): any;
|
|
@@ -93,8 +94,11 @@ export const OBJECT_ID_SCHEMA: {
|
|
|
93
94
|
} | {
|
|
94
95
|
default: any;
|
|
95
96
|
};
|
|
96
|
-
|
|
97
|
+
getNullable(): {
|
|
98
|
+
nullable: boolean;
|
|
99
|
+
};
|
|
97
100
|
expandExtra(extra?: {}): {};
|
|
101
|
+
inspect(): string;
|
|
98
102
|
assertEnum(set: any, allow: any): any;
|
|
99
103
|
assert(type: any, fn: any): any;
|
|
100
104
|
pushAssertion(assertion: any): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"AAkFA,kDAEC;AAED,
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"AAkFA,kDAEC;AAED,oEAgGC;AAsBD,wEA2BC;AAkTD;;;EAEC;AAED;;;EAOC;AAlhBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQK"}
|