@bedrockio/model 0.1.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 +60 -0
- package/README.md +932 -0
- package/babel.config.cjs +41 -0
- package/dist/cjs/access.js +66 -0
- package/dist/cjs/assign.js +50 -0
- package/dist/cjs/const.js +16 -0
- package/dist/cjs/errors.js +17 -0
- package/dist/cjs/include.js +222 -0
- package/dist/cjs/index.js +62 -0
- package/dist/cjs/load.js +40 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/references.js +104 -0
- package/dist/cjs/schema.js +277 -0
- package/dist/cjs/search.js +266 -0
- package/dist/cjs/serialization.js +55 -0
- package/dist/cjs/slug.js +47 -0
- package/dist/cjs/soft-delete.js +192 -0
- package/dist/cjs/testing.js +33 -0
- package/dist/cjs/utils.js +73 -0
- package/dist/cjs/validation.js +313 -0
- package/dist/cjs/warn.js +13 -0
- package/jest-mongodb-config.js +10 -0
- package/jest.config.js +8 -0
- package/package.json +53 -0
- package/src/access.js +60 -0
- package/src/assign.js +45 -0
- package/src/const.js +9 -0
- package/src/errors.js +9 -0
- package/src/include.js +209 -0
- package/src/index.js +5 -0
- package/src/load.js +37 -0
- package/src/references.js +101 -0
- package/src/schema.js +286 -0
- package/src/search.js +263 -0
- package/src/serialization.js +49 -0
- package/src/slug.js +45 -0
- package/src/soft-delete.js +234 -0
- package/src/testing.js +29 -0
- package/src/utils.js +63 -0
- package/src/validation.js +329 -0
- package/src/warn.js +7 -0
- package/test/assign.test.js +225 -0
- package/test/definitions/custom-model.json +9 -0
- package/test/definitions/special-category.json +18 -0
- package/test/include.test.js +896 -0
- package/test/load.test.js +47 -0
- package/test/references.test.js +71 -0
- package/test/schema.test.js +919 -0
- package/test/search.test.js +652 -0
- package/test/serialization.test.js +748 -0
- package/test/setup.js +27 -0
- package/test/slug.test.js +112 -0
- package/test/soft-delete.test.js +333 -0
- package/test/validation.test.js +1925 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.applySoftDelete = applySoftDelete;
|
|
7
|
+
var _warn = _interopRequireDefault(require("./warn"));
|
|
8
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
9
|
+
function applySoftDelete(schema) {
|
|
10
|
+
// Implementation
|
|
11
|
+
|
|
12
|
+
schema.pre(/^find|count|exists/, function (next) {
|
|
13
|
+
const filter = this.getFilter();
|
|
14
|
+
if (filter.deleted === undefined) {
|
|
15
|
+
// Search non-deleted docs by default
|
|
16
|
+
filter.deleted = false;
|
|
17
|
+
}
|
|
18
|
+
return next();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Instance Methods
|
|
22
|
+
|
|
23
|
+
schema.method('delete', function () {
|
|
24
|
+
this.deleted = true;
|
|
25
|
+
this.deletedAt = new Date();
|
|
26
|
+
return this.save();
|
|
27
|
+
});
|
|
28
|
+
schema.method('restore', function restore() {
|
|
29
|
+
this.deleted = false;
|
|
30
|
+
this.deletedAt = undefined;
|
|
31
|
+
return this.save();
|
|
32
|
+
});
|
|
33
|
+
schema.method('destroy', function destroy(...args) {
|
|
34
|
+
const filter = {
|
|
35
|
+
_id: this._id
|
|
36
|
+
};
|
|
37
|
+
return this.constructor.destroyOne(filter, ...args);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Static Methods
|
|
41
|
+
|
|
42
|
+
schema.static('deleteOne', async function deleteOne(filter, ...rest) {
|
|
43
|
+
const update = getDelete();
|
|
44
|
+
const res = await this.updateOne(filter, update, ...rest);
|
|
45
|
+
return {
|
|
46
|
+
acknowledged: res.acknowledged,
|
|
47
|
+
deletedCount: res.modifiedCount
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
schema.static('deleteMany', async function deleteMany(filter, ...rest) {
|
|
51
|
+
const update = getDelete();
|
|
52
|
+
const res = await this.updateMany(filter, update, ...rest);
|
|
53
|
+
return {
|
|
54
|
+
acknowledged: res.acknowledged,
|
|
55
|
+
deletedCount: res.modifiedCount
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
schema.static('findOneAndDelete', async function findOneAndDelete(filter, ...args) {
|
|
59
|
+
return await this.findOneAndUpdate(filter, getDelete(), ...args);
|
|
60
|
+
});
|
|
61
|
+
schema.static('restoreOne', async function restoreOne(filter, ...rest) {
|
|
62
|
+
const update = getRestore();
|
|
63
|
+
const res = await this.updateOne(filter, update, ...rest);
|
|
64
|
+
return {
|
|
65
|
+
acknowledged: res.acknowledged,
|
|
66
|
+
restoredCount: res.modifiedCount
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
schema.static('restoreMany', async function restoreMany(filter, ...rest) {
|
|
70
|
+
const update = getRestore();
|
|
71
|
+
const res = await this.updateMany(filter, update, ...rest);
|
|
72
|
+
return {
|
|
73
|
+
acknowledged: res.acknowledged,
|
|
74
|
+
restoredCount: res.modifiedCount
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
schema.static('destroyOne', async function destroyOne(...args) {
|
|
78
|
+
const res = await this.collection.deleteOne(...args);
|
|
79
|
+
return {
|
|
80
|
+
acknowledged: res.acknowledged,
|
|
81
|
+
destroyedCount: res.deletedCount
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
schema.static('destroyMany', async function destroyMany(...args) {
|
|
85
|
+
const res = await this.collection.deleteMany(...args);
|
|
86
|
+
return {
|
|
87
|
+
acknowledged: res.acknowledged,
|
|
88
|
+
destroyedCount: res.deletedCount
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
schema.static('findDeleted', function findDeleted(filter) {
|
|
92
|
+
return this.find({
|
|
93
|
+
...filter,
|
|
94
|
+
deleted: true
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
schema.static('findOneDeleted', function findOneDeleted(filter) {
|
|
98
|
+
return this.findOne({
|
|
99
|
+
...filter,
|
|
100
|
+
deleted: true
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
schema.static('findByIdDeleted', function findByIdDeleted(id) {
|
|
104
|
+
return this.findOne({
|
|
105
|
+
_id: id,
|
|
106
|
+
deleted: true
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
schema.static('existsDeleted', function existsDeleted() {
|
|
110
|
+
return this.exists({
|
|
111
|
+
deleted: true
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
schema.static('countDocumentsDeleted', function countDocumentsDeleted(filter) {
|
|
115
|
+
return this.countDocuments({
|
|
116
|
+
...filter,
|
|
117
|
+
deleted: true
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
schema.static('findWithDeleted', function findWithDeleted(filter) {
|
|
121
|
+
return this.find({
|
|
122
|
+
...filter,
|
|
123
|
+
...getWithDeletedQuery()
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
schema.static('findOneWithDeleted', function findOneWithDeleted(filter) {
|
|
127
|
+
return this.findOne({
|
|
128
|
+
...filter,
|
|
129
|
+
...getWithDeletedQuery()
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
schema.static('findByIdWithDeleted', function findByIdWithDeleted(id) {
|
|
133
|
+
return this.findOne({
|
|
134
|
+
_id: id,
|
|
135
|
+
...getWithDeletedQuery()
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
schema.static('existsWithDeleted', function existsWithDeleted() {
|
|
139
|
+
return this.exists({
|
|
140
|
+
...getWithDeletedQuery()
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
schema.static('countDocumentsWithDeleted', function countDocumentsWithDeleted(filter) {
|
|
144
|
+
return this.countDocuments({
|
|
145
|
+
...filter,
|
|
146
|
+
...getWithDeletedQuery()
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
schema.method('remove', function () {
|
|
150
|
+
(0, _warn.default)('The "remove" method on documents is disallowed due to ambiguity.', 'To permanently delete a document use "destroy", otherwise "delete".');
|
|
151
|
+
throw new Error('Method not allowed.');
|
|
152
|
+
}, {
|
|
153
|
+
suppressWarning: true
|
|
154
|
+
});
|
|
155
|
+
schema.method('deleteOne', function () {
|
|
156
|
+
(0, _warn.default)('The "deleteOne" method on documents is disallowed due to ambiguity', 'Use either "delete" or "deleteOne" on the model.');
|
|
157
|
+
throw new Error('Method not allowed.');
|
|
158
|
+
});
|
|
159
|
+
schema.static('remove', function () {
|
|
160
|
+
(0, _warn.default)('The "remove" method on models is disallowed due to ambiguity.', 'To permanently delete a document use "destroyMany", otherwise "deleteMany".');
|
|
161
|
+
throw new Error('Method not allowed.');
|
|
162
|
+
});
|
|
163
|
+
schema.static('findOneAndRemove', function () {
|
|
164
|
+
(0, _warn.default)('The "findOneAndRemove" method on models is disallowed due to ambiguity.', 'To permanently delete a document use "findOneAndDestroy", otherwise "findOneAndDelete".');
|
|
165
|
+
throw new Error('Method not allowed.');
|
|
166
|
+
});
|
|
167
|
+
schema.static('findByIdAndRemove', function () {
|
|
168
|
+
(0, _warn.default)('The "findByIdAndRemove" method on models is disallowed due to ambiguity.', 'To permanently delete a document use "findByIdAndDestroy", otherwise "findByIdAndDelete".');
|
|
169
|
+
throw new Error('Method not allowed.');
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function getDelete() {
|
|
173
|
+
return {
|
|
174
|
+
deleted: true,
|
|
175
|
+
deletedAt: new Date()
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function getRestore() {
|
|
179
|
+
return {
|
|
180
|
+
deleted: false,
|
|
181
|
+
$unset: {
|
|
182
|
+
deletedAt: true
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function getWithDeletedQuery() {
|
|
187
|
+
return {
|
|
188
|
+
deleted: {
|
|
189
|
+
$in: [true, false]
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createTestModel = createTestModel;
|
|
7
|
+
exports.getTestModelName = getTestModelName;
|
|
8
|
+
var _mongoose = _interopRequireDefault(require("mongoose"));
|
|
9
|
+
var _schema = require("./schema");
|
|
10
|
+
var _utils = require("./utils");
|
|
11
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
12
|
+
let counter = 0;
|
|
13
|
+
function createTestModel(...args) {
|
|
14
|
+
let modelName, attributes, schema;
|
|
15
|
+
if (typeof args[0] === 'string') {
|
|
16
|
+
modelName = args[0];
|
|
17
|
+
attributes = args[1];
|
|
18
|
+
} else {
|
|
19
|
+
attributes = args[0];
|
|
20
|
+
}
|
|
21
|
+
if ((0, _utils.isMongooseSchema)(attributes)) {
|
|
22
|
+
schema = attributes;
|
|
23
|
+
} else {
|
|
24
|
+
schema = (0, _schema.createSchema)({
|
|
25
|
+
attributes
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
modelName ||= getTestModelName();
|
|
29
|
+
return _mongoose.default.model(modelName, schema);
|
|
30
|
+
}
|
|
31
|
+
function getTestModelName() {
|
|
32
|
+
return `TestModel${counter++}`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isDateField = isDateField;
|
|
7
|
+
exports.isMongooseSchema = isMongooseSchema;
|
|
8
|
+
exports.isNumberField = isNumberField;
|
|
9
|
+
exports.isReferenceField = isReferenceField;
|
|
10
|
+
exports.isSchemaTypedef = isSchemaTypedef;
|
|
11
|
+
exports.resolveField = resolveField;
|
|
12
|
+
exports.resolveInnerField = resolveInnerField;
|
|
13
|
+
var _mongoose = _interopRequireDefault(require("mongoose"));
|
|
14
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
15
|
+
function isMongooseSchema(obj) {
|
|
16
|
+
return obj instanceof _mongoose.default.Schema;
|
|
17
|
+
}
|
|
18
|
+
function isReferenceField(obj, path) {
|
|
19
|
+
return isType(obj, path, 'ObjectId');
|
|
20
|
+
}
|
|
21
|
+
function isDateField(obj, path) {
|
|
22
|
+
return isType(obj, path, 'Date');
|
|
23
|
+
}
|
|
24
|
+
function isNumberField(obj, path) {
|
|
25
|
+
return isType(obj, path, 'Number');
|
|
26
|
+
}
|
|
27
|
+
function isType(obj, path, test) {
|
|
28
|
+
const {
|
|
29
|
+
type
|
|
30
|
+
} = resolveInnerField(obj, path);
|
|
31
|
+
return type === test || type === _mongoose.default.Schema.Types[test];
|
|
32
|
+
}
|
|
33
|
+
function isSchemaTypedef(arg) {
|
|
34
|
+
// Has a type defined and is not a literal type field.
|
|
35
|
+
return arg?.type && !arg.type?.type;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Note: Resolved field may be an object or a function
|
|
39
|
+
// from mongoose.Schema.Types that is resolved from the
|
|
40
|
+
// shorthand: field: 'String'.
|
|
41
|
+
function resolveField(obj, path) {
|
|
42
|
+
let typedef = obj;
|
|
43
|
+
for (let key of path.split('.')) {
|
|
44
|
+
typedef = resolveFieldForKey(typedef, key);
|
|
45
|
+
}
|
|
46
|
+
return typedef;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The same as resolveField but gets the element
|
|
50
|
+
// typedef in the case of arrays.
|
|
51
|
+
function resolveInnerField(obj, path) {
|
|
52
|
+
let typedef = resolveField(obj, path);
|
|
53
|
+
if (Array.isArray(typedef.type)) {
|
|
54
|
+
typedef = typedef.type[0];
|
|
55
|
+
}
|
|
56
|
+
return typedef;
|
|
57
|
+
}
|
|
58
|
+
function resolveFieldForKey(obj, key) {
|
|
59
|
+
let typedef;
|
|
60
|
+
if (isSchemaTypedef(obj)) {
|
|
61
|
+
const {
|
|
62
|
+
type
|
|
63
|
+
} = obj;
|
|
64
|
+
if (Array.isArray(type)) {
|
|
65
|
+
typedef = type[0][key];
|
|
66
|
+
} else {
|
|
67
|
+
typedef = type[key];
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
typedef = obj[key];
|
|
71
|
+
}
|
|
72
|
+
return typedef || {};
|
|
73
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.addValidators = addValidators;
|
|
7
|
+
exports.applyValidation = applyValidation;
|
|
8
|
+
exports.getNamedValidator = getNamedValidator;
|
|
9
|
+
exports.getTupleValidator = getTupleValidator;
|
|
10
|
+
exports.getValidationSchema = getValidationSchema;
|
|
11
|
+
var _lowerFirst2 = _interopRequireDefault(require("lodash/lowerFirst"));
|
|
12
|
+
var _omit2 = _interopRequireDefault(require("lodash/omit"));
|
|
13
|
+
var _get2 = _interopRequireDefault(require("lodash/get"));
|
|
14
|
+
var _mongoose = _interopRequireDefault(require("mongoose"));
|
|
15
|
+
var _yada = _interopRequireDefault(require("@bedrockio/yada"));
|
|
16
|
+
var _access = require("./access");
|
|
17
|
+
var _search = require("./search");
|
|
18
|
+
var _errors = require("./errors");
|
|
19
|
+
var _utils = require("./utils");
|
|
20
|
+
var _schema = require("./schema");
|
|
21
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
22
|
+
const namedSchemas = {
|
|
23
|
+
// Email is special as we are assuming that in
|
|
24
|
+
// all cases lowercase should be allowed but coerced.
|
|
25
|
+
email: _yada.default.string().lowercase().email(),
|
|
26
|
+
// Force "ObjectId" to have parity with refs.
|
|
27
|
+
// "mongo" is notably excluded here for this reason.
|
|
28
|
+
ObjectId: _yada.default.string().mongo(),
|
|
29
|
+
ascii: _yada.default.string().ascii(),
|
|
30
|
+
base64: _yada.default.string().base64(),
|
|
31
|
+
btc: _yada.default.string().btc(),
|
|
32
|
+
country: _yada.default.string().country(),
|
|
33
|
+
creditCard: _yada.default.string().creditCard(),
|
|
34
|
+
domain: _yada.default.string().domain(),
|
|
35
|
+
eth: _yada.default.string().eth(),
|
|
36
|
+
hex: _yada.default.string().hex(),
|
|
37
|
+
ip: _yada.default.string().ip(),
|
|
38
|
+
jwt: _yada.default.string().jwt(),
|
|
39
|
+
latlng: _yada.default.string().latlng(),
|
|
40
|
+
locale: _yada.default.string().locale(),
|
|
41
|
+
md5: _yada.default.string().md5(),
|
|
42
|
+
phone: _yada.default.string().phone(),
|
|
43
|
+
postalCode: _yada.default.string().postalCode(),
|
|
44
|
+
sha1: _yada.default.string().sha1(),
|
|
45
|
+
slug: _yada.default.string().slug(),
|
|
46
|
+
swift: _yada.default.string().swift(),
|
|
47
|
+
url: _yada.default.string().url(),
|
|
48
|
+
uuid: _yada.default.string().uuid()
|
|
49
|
+
};
|
|
50
|
+
function addValidators(schemas) {
|
|
51
|
+
Object.assign(namedSchemas, schemas);
|
|
52
|
+
}
|
|
53
|
+
function applyValidation(schema, definition) {
|
|
54
|
+
schema.static('getCreateValidation', function getCreateValidation(appendSchema) {
|
|
55
|
+
return getSchemaFromMongoose(schema, {
|
|
56
|
+
appendSchema,
|
|
57
|
+
stripReserved: true,
|
|
58
|
+
requireWriteAccess: true,
|
|
59
|
+
modelName: this.modelName
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
schema.static('getUpdateValidation', function getUpdateValidation(appendSchema) {
|
|
63
|
+
return getSchemaFromMongoose(schema, {
|
|
64
|
+
appendSchema,
|
|
65
|
+
skipRequired: true,
|
|
66
|
+
stripReserved: true,
|
|
67
|
+
stripUnknown: true,
|
|
68
|
+
requireWriteAccess: true,
|
|
69
|
+
modelName: this.modelName
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
schema.static('getSearchValidation', function getSearchValidation(searchOptions) {
|
|
73
|
+
return getSchemaFromMongoose(schema, {
|
|
74
|
+
allowRanges: true,
|
|
75
|
+
skipRequired: true,
|
|
76
|
+
allowMultiple: true,
|
|
77
|
+
unwindArrayFields: true,
|
|
78
|
+
requireReadAccess: true,
|
|
79
|
+
appendSchema: (0, _search.searchValidation)(definition, searchOptions),
|
|
80
|
+
modelName: this.modelName
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Yada schemas
|
|
86
|
+
|
|
87
|
+
function getSchemaFromMongoose(schema, options) {
|
|
88
|
+
let {
|
|
89
|
+
obj
|
|
90
|
+
} = schema;
|
|
91
|
+
if (options.stripReserved) {
|
|
92
|
+
obj = (0, _omit2.default)(obj, _schema.RESERVED_FIELDS);
|
|
93
|
+
}
|
|
94
|
+
return getValidationSchema(obj, options);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Exported for testing
|
|
98
|
+
function getValidationSchema(attributes, options = {}) {
|
|
99
|
+
const {
|
|
100
|
+
appendSchema
|
|
101
|
+
} = options;
|
|
102
|
+
let schema = getObjectSchema(attributes, options);
|
|
103
|
+
if (appendSchema) {
|
|
104
|
+
schema = schema.append(appendSchema);
|
|
105
|
+
}
|
|
106
|
+
return schema;
|
|
107
|
+
}
|
|
108
|
+
function getObjectSchema(arg, options) {
|
|
109
|
+
const {
|
|
110
|
+
stripUnknown
|
|
111
|
+
} = options;
|
|
112
|
+
if ((0, _utils.isSchemaTypedef)(arg)) {
|
|
113
|
+
return getSchemaForTypedef(arg, options);
|
|
114
|
+
} else if (arg instanceof _mongoose.default.Schema) {
|
|
115
|
+
return getObjectSchema(arg.obj, options);
|
|
116
|
+
} else if (Array.isArray(arg)) {
|
|
117
|
+
return getArraySchema(arg, options);
|
|
118
|
+
} else if (typeof arg === 'object') {
|
|
119
|
+
const map = {};
|
|
120
|
+
for (let [key, field] of Object.entries(arg)) {
|
|
121
|
+
if (!isExcludedField(field, options)) {
|
|
122
|
+
map[key] = getObjectSchema(field, options);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
let schema = _yada.default.object(map);
|
|
126
|
+
if (stripUnknown) {
|
|
127
|
+
schema = schema.options({
|
|
128
|
+
stripUnknown: true
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return schema;
|
|
132
|
+
} else {
|
|
133
|
+
return getSchemaForType(arg);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function getArraySchema(obj, options) {
|
|
137
|
+
// Nested array fields may not skip required
|
|
138
|
+
// validations as they are a new context.
|
|
139
|
+
let schema = getObjectSchema(obj[0], {
|
|
140
|
+
...options,
|
|
141
|
+
skipRequired: false
|
|
142
|
+
});
|
|
143
|
+
if (!options.unwindArrayFields) {
|
|
144
|
+
schema = _yada.default.array(schema);
|
|
145
|
+
}
|
|
146
|
+
return schema;
|
|
147
|
+
}
|
|
148
|
+
function getSchemaForTypedef(typedef, options = {}) {
|
|
149
|
+
let {
|
|
150
|
+
type
|
|
151
|
+
} = typedef;
|
|
152
|
+
let schema;
|
|
153
|
+
if ((0, _utils.isMongooseSchema)(type)) {
|
|
154
|
+
schema = getSchemaFromMongoose(type, options);
|
|
155
|
+
} else if (Array.isArray(type)) {
|
|
156
|
+
schema = getArraySchema(type, options);
|
|
157
|
+
} else if (typeof type === 'object') {
|
|
158
|
+
schema = getObjectSchema(type, options);
|
|
159
|
+
} else {
|
|
160
|
+
schema = getSchemaForType(type);
|
|
161
|
+
}
|
|
162
|
+
if (isRequired(typedef, options)) {
|
|
163
|
+
schema = schema.required();
|
|
164
|
+
}
|
|
165
|
+
if (typedef.validate?.schema) {
|
|
166
|
+
schema = schema.append(typedef.validate.schema);
|
|
167
|
+
}
|
|
168
|
+
if (typedef.enum) {
|
|
169
|
+
schema = schema.allow(...typedef.enum);
|
|
170
|
+
}
|
|
171
|
+
if (typedef.match) {
|
|
172
|
+
schema = schema.match(RegExp(typedef.match));
|
|
173
|
+
}
|
|
174
|
+
if (typedef.min != null || typedef.minLength != null) {
|
|
175
|
+
schema = schema.min(typedef.min ?? typedef.minLength);
|
|
176
|
+
}
|
|
177
|
+
if (typedef.max != null || typedef.maxLength != null) {
|
|
178
|
+
schema = schema.max(typedef.max ?? typedef.maxLength);
|
|
179
|
+
}
|
|
180
|
+
if (options.allowRanges) {
|
|
181
|
+
schema = getRangeSchema(schema, type);
|
|
182
|
+
}
|
|
183
|
+
if (options.allowMultiple) {
|
|
184
|
+
schema = _yada.default.allow(schema, _yada.default.array(schema));
|
|
185
|
+
}
|
|
186
|
+
if (typedef.readAccess && options.requireReadAccess) {
|
|
187
|
+
schema = validateReadAccess(schema, typedef.readAccess, options);
|
|
188
|
+
}
|
|
189
|
+
if (typedef.writeAccess && options.requireWriteAccess) {
|
|
190
|
+
schema = validateWriteAccess(schema, typedef.writeAccess, options);
|
|
191
|
+
}
|
|
192
|
+
return schema;
|
|
193
|
+
}
|
|
194
|
+
function getSchemaForType(type) {
|
|
195
|
+
switch (type) {
|
|
196
|
+
case 'String':
|
|
197
|
+
return _yada.default.string();
|
|
198
|
+
case 'Number':
|
|
199
|
+
return _yada.default.number();
|
|
200
|
+
case 'Boolean':
|
|
201
|
+
return _yada.default.boolean();
|
|
202
|
+
case 'Date':
|
|
203
|
+
return _yada.default.date().iso();
|
|
204
|
+
case 'Mixed':
|
|
205
|
+
case 'Object':
|
|
206
|
+
return _yada.default.object();
|
|
207
|
+
case 'Array':
|
|
208
|
+
return _yada.default.array();
|
|
209
|
+
case 'ObjectId':
|
|
210
|
+
return _yada.default.custom(async val => {
|
|
211
|
+
const id = String(val.id || val);
|
|
212
|
+
await namedSchemas['ObjectId'].validate(id);
|
|
213
|
+
return id;
|
|
214
|
+
});
|
|
215
|
+
default:
|
|
216
|
+
throw new TypeError(`Unknown schema type ${type}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function getRangeSchema(schema, type) {
|
|
220
|
+
if (type === 'Number') {
|
|
221
|
+
schema = _yada.default.allow(schema, _yada.default.object({
|
|
222
|
+
lt: _yada.default.number(),
|
|
223
|
+
gt: _yada.default.number(),
|
|
224
|
+
lte: _yada.default.number(),
|
|
225
|
+
gte: _yada.default.number()
|
|
226
|
+
}));
|
|
227
|
+
} else if (type === 'Date') {
|
|
228
|
+
return _yada.default.allow(schema, _yada.default.object({
|
|
229
|
+
lt: _yada.default.date().iso(),
|
|
230
|
+
gt: _yada.default.date().iso(),
|
|
231
|
+
lte: _yada.default.date().iso(),
|
|
232
|
+
gte: _yada.default.date().iso()
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
return schema;
|
|
236
|
+
}
|
|
237
|
+
function isRequired(typedef, options) {
|
|
238
|
+
return typedef.required && !typedef.default && !options.skipRequired;
|
|
239
|
+
}
|
|
240
|
+
function isExcludedField(field, options) {
|
|
241
|
+
if ((0, _utils.isSchemaTypedef)(field)) {
|
|
242
|
+
const {
|
|
243
|
+
requireWriteAccess
|
|
244
|
+
} = options;
|
|
245
|
+
return requireWriteAccess && field.writeAccess === 'none';
|
|
246
|
+
} else {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function validateReadAccess(schema, allowed, options) {
|
|
251
|
+
return validateAccess('read', schema, allowed, options);
|
|
252
|
+
}
|
|
253
|
+
function validateWriteAccess(schema, allowed, options) {
|
|
254
|
+
return validateAccess('write', schema, allowed, options);
|
|
255
|
+
}
|
|
256
|
+
function validateAccess(type, schema, allowed, options) {
|
|
257
|
+
const {
|
|
258
|
+
modelName
|
|
259
|
+
} = options;
|
|
260
|
+
return schema.custom((val, options) => {
|
|
261
|
+
const document = options[(0, _lowerFirst2.default)(modelName)] || options['document'];
|
|
262
|
+
const isAllowed = (0, _access.hasAccess)(type, allowed, {
|
|
263
|
+
...options,
|
|
264
|
+
document
|
|
265
|
+
});
|
|
266
|
+
if (!isAllowed) {
|
|
267
|
+
const currentValue = (0, _get2.default)(document, options.path);
|
|
268
|
+
if (val !== currentValue) {
|
|
269
|
+
throw new _errors.PermissionsError('requires write permissions.');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Mongoose Validators
|
|
276
|
+
|
|
277
|
+
function getNamedValidator(name) {
|
|
278
|
+
return wrapMongooseValidator(getNamedSchema(name));
|
|
279
|
+
}
|
|
280
|
+
function getTupleValidator(types) {
|
|
281
|
+
types = types.map(type => {
|
|
282
|
+
return getSchemaForTypedef(type);
|
|
283
|
+
});
|
|
284
|
+
return wrapMongooseValidator(_yada.default.array(types).length(types.length));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Returns an async function that will error on failure.
|
|
288
|
+
//
|
|
289
|
+
// Note that mongoose validator functions will not be called
|
|
290
|
+
// if the field is optional and not set or unset with undefined.
|
|
291
|
+
// If the field is not optional the "required" field will also
|
|
292
|
+
// perform valdation so no additional checks are necessary.
|
|
293
|
+
//
|
|
294
|
+
// Also note that throwing an error inside a validator and passing
|
|
295
|
+
// the "message" field result in an identical error message. In this
|
|
296
|
+
// case we want the schema error messages to trickle down so using
|
|
297
|
+
// the first style here.
|
|
298
|
+
//
|
|
299
|
+
// https://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-validate
|
|
300
|
+
function wrapMongooseValidator(schema) {
|
|
301
|
+
const validator = async val => {
|
|
302
|
+
await schema.validate(val);
|
|
303
|
+
};
|
|
304
|
+
validator.schema = schema;
|
|
305
|
+
return validator;
|
|
306
|
+
}
|
|
307
|
+
function getNamedSchema(name) {
|
|
308
|
+
const schema = namedSchemas[name];
|
|
309
|
+
if (!schema) {
|
|
310
|
+
throw new Error(`Cannot find schema for "${name}".`);
|
|
311
|
+
}
|
|
312
|
+
return schema;
|
|
313
|
+
}
|
package/dist/cjs/warn.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = warn;
|
|
7
|
+
var _logger = _interopRequireDefault(require("@bedrockio/logger"));
|
|
8
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
9
|
+
function warn(...lines) {
|
|
10
|
+
if (process.env.ENV_NAME !== 'test') {
|
|
11
|
+
_logger.default.warn(lines.join('\n'));
|
|
12
|
+
}
|
|
13
|
+
}
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bedrockio/model",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bedrock utilities for model creation.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest",
|
|
8
|
+
"lint": "eslint",
|
|
9
|
+
"build": "scripts/build",
|
|
10
|
+
"prepublishOnly": "npm run build"
|
|
11
|
+
},
|
|
12
|
+
"main": "dist/cjs/index.js",
|
|
13
|
+
"module": "src/index.js",
|
|
14
|
+
"contributors": [
|
|
15
|
+
{
|
|
16
|
+
"name": "Andrew Plummer",
|
|
17
|
+
"email": "andrew@rekall.ai"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/bedrockio/model"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@bedrockio/logger": "^1.0.3",
|
|
27
|
+
"@bedrockio/yada": "^1.0.9",
|
|
28
|
+
"lodash": "^4.17.21"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"mongoose": "^6.9.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@babel/cli": "^7.20.7",
|
|
35
|
+
"@babel/core": "^7.20.12",
|
|
36
|
+
"@babel/preset-env": "^7.20.2",
|
|
37
|
+
"@bedrockio/prettier-config": "^1.0.2",
|
|
38
|
+
"@shelf/jest-mongodb": "^4.1.6",
|
|
39
|
+
"babel-plugin-import-replacement": "^1.0.1",
|
|
40
|
+
"babel-plugin-lodash": "^3.3.4",
|
|
41
|
+
"eslint": "^8.33.0",
|
|
42
|
+
"eslint-plugin-bedrock": "^1.0.21",
|
|
43
|
+
"jest": "^29.4.1",
|
|
44
|
+
"jest-environment-node": "^29.4.1",
|
|
45
|
+
"mongodb": "4.13.0",
|
|
46
|
+
"mongoose": "^6.9.0",
|
|
47
|
+
"prettier-eslint": "^15.0.1"
|
|
48
|
+
},
|
|
49
|
+
"volta": {
|
|
50
|
+
"node": "18.14.0",
|
|
51
|
+
"yarn": "1.22.19"
|
|
52
|
+
}
|
|
53
|
+
}
|