@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
package/babel.config.cjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const { BUILD_DIR } = process.env;
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
presets: [
|
|
7
|
+
[
|
|
8
|
+
'@babel/preset-env',
|
|
9
|
+
{
|
|
10
|
+
targets: {
|
|
11
|
+
node: 'current',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
],
|
|
16
|
+
plugins: [
|
|
17
|
+
'lodash',
|
|
18
|
+
...(BUILD_DIR
|
|
19
|
+
? [
|
|
20
|
+
[
|
|
21
|
+
'import-replacement',
|
|
22
|
+
{
|
|
23
|
+
rules: [
|
|
24
|
+
{
|
|
25
|
+
match: 'mongoose',
|
|
26
|
+
replacement: path.resolve(BUILD_DIR, 'node_modules/mongoose'),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
match: '@bedrockio/yada',
|
|
30
|
+
replacement: path.resolve(
|
|
31
|
+
BUILD_DIR,
|
|
32
|
+
'node_modules/@bedrockio/yada'
|
|
33
|
+
),
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
]
|
|
39
|
+
: []),
|
|
40
|
+
],
|
|
41
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.hasAccess = hasAccess;
|
|
7
|
+
exports.hasReadAccess = hasReadAccess;
|
|
8
|
+
exports.hasWriteAccess = hasWriteAccess;
|
|
9
|
+
var _errors = require("./errors");
|
|
10
|
+
var _warn = _interopRequireDefault(require("./warn"));
|
|
11
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
12
|
+
function hasReadAccess(allowed, options) {
|
|
13
|
+
return hasAccess('read', allowed, options);
|
|
14
|
+
}
|
|
15
|
+
function hasWriteAccess(allowed, options) {
|
|
16
|
+
return hasAccess('write', allowed, options);
|
|
17
|
+
}
|
|
18
|
+
function hasAccess(type, allowed = 'all', options = {}) {
|
|
19
|
+
if (allowed === 'all') {
|
|
20
|
+
return true;
|
|
21
|
+
} else if (allowed === 'none') {
|
|
22
|
+
return false;
|
|
23
|
+
} else {
|
|
24
|
+
const {
|
|
25
|
+
document,
|
|
26
|
+
authUser
|
|
27
|
+
} = options;
|
|
28
|
+
if (!Array.isArray(allowed)) {
|
|
29
|
+
allowed = [allowed];
|
|
30
|
+
}
|
|
31
|
+
const scopes = resolveScopes(options);
|
|
32
|
+
return allowed.some(token => {
|
|
33
|
+
if (token === 'self') {
|
|
34
|
+
assertOptions(type, token, options);
|
|
35
|
+
return document.id == authUser.id;
|
|
36
|
+
} else if (token === 'user') {
|
|
37
|
+
assertOptions(type, token, options);
|
|
38
|
+
return document.user?.id == authUser.id;
|
|
39
|
+
} else if (token === 'owner') {
|
|
40
|
+
assertOptions(type, token, options);
|
|
41
|
+
return document.owner?.id == authUser.id;
|
|
42
|
+
} else {
|
|
43
|
+
return scopes?.includes(token);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function resolveScopes(options) {
|
|
49
|
+
if (!options.scope && !options.scopes) {
|
|
50
|
+
(0, _warn.default)('Scopes were requested but not provided.');
|
|
51
|
+
}
|
|
52
|
+
const {
|
|
53
|
+
scope,
|
|
54
|
+
scopes = []
|
|
55
|
+
} = options;
|
|
56
|
+
return scope ? [scope] : scopes;
|
|
57
|
+
}
|
|
58
|
+
function assertOptions(type, token, options) {
|
|
59
|
+
if (!options.authUser || !options.document) {
|
|
60
|
+
if (type === 'read') {
|
|
61
|
+
throw new _errors.ImplementationError(`Read access "${token}" requires .toObject({ authUser }).`);
|
|
62
|
+
} else {
|
|
63
|
+
throw new _errors.ImplementationError(`Write access "${token}" requires passing { document, authUser } to the validator.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.applyAssign = applyAssign;
|
|
7
|
+
var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
|
|
8
|
+
var _utils = require("./utils");
|
|
9
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
10
|
+
function applyAssign(schema) {
|
|
11
|
+
schema.method('assign', function assign(fields) {
|
|
12
|
+
unsetReferenceFields(fields, schema.obj);
|
|
13
|
+
for (let [path, value] of Object.entries(flattenObject(fields))) {
|
|
14
|
+
if (value === null) {
|
|
15
|
+
this.set(path, undefined);
|
|
16
|
+
this.markModified(path);
|
|
17
|
+
} else {
|
|
18
|
+
this.set(path, value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Sets falsy reference fields to undefined to signal
|
|
25
|
+
// removal. Passing attributes through this function
|
|
26
|
+
// normalizes falsy values so they are not saved to the db.
|
|
27
|
+
function unsetReferenceFields(fields, schema = {}) {
|
|
28
|
+
for (let [key, value] of Object.entries(fields)) {
|
|
29
|
+
if (!value && (0, _utils.isReferenceField)(schema, key)) {
|
|
30
|
+
fields[key] = undefined;
|
|
31
|
+
} else if (value && typeof value === 'object') {
|
|
32
|
+
unsetReferenceFields(value, (0, _utils.resolveField)(schema, key));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Flattens nested objects to a dot syntax.
|
|
38
|
+
// Effectively the inverse of lodash get:
|
|
39
|
+
// { foo: { bar: 3 } } -> { 'foo.bar': 3 }
|
|
40
|
+
function flattenObject(obj, root = {}, rootPath = []) {
|
|
41
|
+
for (let [key, val] of Object.entries(obj)) {
|
|
42
|
+
const path = [...rootPath, key];
|
|
43
|
+
if ((0, _isPlainObject2.default)(val)) {
|
|
44
|
+
flattenObject(val, root, path);
|
|
45
|
+
} else {
|
|
46
|
+
root[path.join('.')] = val;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return root;
|
|
50
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.SEARCH_DEFAULTS = exports.POPULATE_MAX_DEPTH = void 0;
|
|
7
|
+
const SEARCH_DEFAULTS = {
|
|
8
|
+
limit: 50,
|
|
9
|
+
sort: {
|
|
10
|
+
field: 'createdAt',
|
|
11
|
+
order: 'desc'
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
exports.SEARCH_DEFAULTS = SEARCH_DEFAULTS;
|
|
15
|
+
const POPULATE_MAX_DEPTH = 5;
|
|
16
|
+
exports.POPULATE_MAX_DEPTH = POPULATE_MAX_DEPTH;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ReferenceError = exports.PermissionsError = exports.ImplementationError = void 0;
|
|
7
|
+
class PermissionsError extends Error {}
|
|
8
|
+
exports.PermissionsError = PermissionsError;
|
|
9
|
+
class ImplementationError extends Error {}
|
|
10
|
+
exports.ImplementationError = ImplementationError;
|
|
11
|
+
class ReferenceError extends Error {
|
|
12
|
+
constructor(message, references) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.references = references;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.ReferenceError = ReferenceError;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.applyInclude = applyInclude;
|
|
7
|
+
exports.checkSelects = checkSelects;
|
|
8
|
+
exports.getIncludes = getIncludes;
|
|
9
|
+
var _escapeRegExp2 = _interopRequireDefault(require("lodash/escapeRegExp"));
|
|
10
|
+
var _mongoose = _interopRequireDefault(require("mongoose"));
|
|
11
|
+
var _utils = require("./utils");
|
|
12
|
+
var _const = require("./const");
|
|
13
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
14
|
+
// Overloading mongoose Query prototype to
|
|
15
|
+
// allow an "include" method for queries.
|
|
16
|
+
_mongoose.default.Query.prototype.include = function include(paths) {
|
|
17
|
+
const filter = this.getFilter();
|
|
18
|
+
filter.include = paths;
|
|
19
|
+
return this;
|
|
20
|
+
};
|
|
21
|
+
function applyInclude(schema) {
|
|
22
|
+
schema.virtual('include').set(function (include) {
|
|
23
|
+
this.$locals.include = include;
|
|
24
|
+
});
|
|
25
|
+
schema.pre(/^find/, function (next) {
|
|
26
|
+
const filter = this.getFilter();
|
|
27
|
+
if (filter.include) {
|
|
28
|
+
const {
|
|
29
|
+
select,
|
|
30
|
+
populate
|
|
31
|
+
} = getQueryIncludes(this, filter.include);
|
|
32
|
+
this.select(select);
|
|
33
|
+
this.populate(populate);
|
|
34
|
+
delete filter.include;
|
|
35
|
+
}
|
|
36
|
+
return next();
|
|
37
|
+
});
|
|
38
|
+
schema.pre('save', function () {
|
|
39
|
+
const {
|
|
40
|
+
include
|
|
41
|
+
} = this.$locals;
|
|
42
|
+
if (include) {
|
|
43
|
+
let {
|
|
44
|
+
select,
|
|
45
|
+
populate
|
|
46
|
+
} = getDocumentIncludes(this, include);
|
|
47
|
+
const modifiedPaths = this.modifiedPaths();
|
|
48
|
+
populate = populate.filter(p => {
|
|
49
|
+
return modifiedPaths.includes(p.path);
|
|
50
|
+
});
|
|
51
|
+
this.$locals.select = select;
|
|
52
|
+
this.$locals.populate = populate;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
schema.post('save', async function () {
|
|
56
|
+
const {
|
|
57
|
+
populate
|
|
58
|
+
} = this.$locals;
|
|
59
|
+
if (populate) {
|
|
60
|
+
await this.populate(populate);
|
|
61
|
+
delete this.$locals.populate;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// "Selected" keys virtually project documents so that
|
|
67
|
+
// they do not return the entire document down the wire.
|
|
68
|
+
// This is to maintain parity with query projection used
|
|
69
|
+
// with the includes feature.
|
|
70
|
+
function checkSelects(doc, ret) {
|
|
71
|
+
let {
|
|
72
|
+
select
|
|
73
|
+
} = doc.$locals;
|
|
74
|
+
if (select?.length) {
|
|
75
|
+
const includes = {};
|
|
76
|
+
const excludes = {};
|
|
77
|
+
select = [...select, 'id'];
|
|
78
|
+
let hasExcludes = false;
|
|
79
|
+
for (let path of select) {
|
|
80
|
+
if (path.startsWith('-')) {
|
|
81
|
+
excludes[path.slice(1)] = true;
|
|
82
|
+
hasExcludes = true;
|
|
83
|
+
} else {
|
|
84
|
+
includes[path] = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (let key of Object.keys(ret)) {
|
|
88
|
+
// Always select populated fields.
|
|
89
|
+
if (doc.populated(key)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// Fields are either explicitly excluded with "-"
|
|
93
|
+
// or implicitly excluded by having only includes.
|
|
94
|
+
const implicitExclude = !hasExcludes && !includes[key];
|
|
95
|
+
if (excludes[key] || implicitExclude) {
|
|
96
|
+
delete ret[key];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Exported for testing.
|
|
103
|
+
function getIncludes(modelName, arg) {
|
|
104
|
+
const paths = Array.isArray(arg) ? arg : [arg];
|
|
105
|
+
const node = pathsToNode(paths, modelName);
|
|
106
|
+
return nodeToPopulates(node);
|
|
107
|
+
}
|
|
108
|
+
function getQueryIncludes(query, arg) {
|
|
109
|
+
return getIncludes(query.model.modelName, arg);
|
|
110
|
+
}
|
|
111
|
+
function getDocumentIncludes(doc, arg) {
|
|
112
|
+
return getIncludes(doc.constructor.modelName, arg);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Note that:
|
|
116
|
+
// - An empty array for "select" will select all.
|
|
117
|
+
// - Entries in the "populate" array will select the
|
|
118
|
+
// field even if not included in "select".
|
|
119
|
+
function nodeToPopulates(node) {
|
|
120
|
+
const select = [];
|
|
121
|
+
const populate = [];
|
|
122
|
+
for (let [key, value] of Object.entries(node)) {
|
|
123
|
+
if (value) {
|
|
124
|
+
populate.push({
|
|
125
|
+
path: key,
|
|
126
|
+
...nodeToPopulates(value)
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
select.push(key);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
select,
|
|
134
|
+
populate
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function pathsToNode(paths, modelName) {
|
|
138
|
+
const node = {};
|
|
139
|
+
for (let str of paths) {
|
|
140
|
+
let exclude = false;
|
|
141
|
+
if (str.startsWith('-')) {
|
|
142
|
+
exclude = true;
|
|
143
|
+
str = str.slice(1);
|
|
144
|
+
}
|
|
145
|
+
setNodePath(node, {
|
|
146
|
+
path: str.split('.'),
|
|
147
|
+
modelName,
|
|
148
|
+
exclude
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return node;
|
|
152
|
+
}
|
|
153
|
+
function setNodePath(node, options) {
|
|
154
|
+
const {
|
|
155
|
+
path,
|
|
156
|
+
modelName,
|
|
157
|
+
exclude,
|
|
158
|
+
depth = 0
|
|
159
|
+
} = options;
|
|
160
|
+
if (depth > _const.POPULATE_MAX_DEPTH) {
|
|
161
|
+
throw new Error(`Cannot populate more than ${_const.POPULATE_MAX_DEPTH} levels.`);
|
|
162
|
+
}
|
|
163
|
+
const schema = _mongoose.default.models[modelName]?.schema;
|
|
164
|
+
if (!schema) {
|
|
165
|
+
throw new Error(`Could not derive schema for ${modelName}.`);
|
|
166
|
+
}
|
|
167
|
+
const parts = [];
|
|
168
|
+
for (let part of path) {
|
|
169
|
+
parts.push(part);
|
|
170
|
+
const str = parts.join('.');
|
|
171
|
+
const isExact = parts.length === path.length;
|
|
172
|
+
let halt = false;
|
|
173
|
+
for (let [key, type] of resolvePaths(schema, str)) {
|
|
174
|
+
if (type === 'real') {
|
|
175
|
+
const field = (0, _utils.resolveInnerField)(schema.obj, key);
|
|
176
|
+
// Only exclude the field if the match is exact, ie:
|
|
177
|
+
// -name - Exclude "name"
|
|
178
|
+
// -user.name - Implies population of "user" but exclude "user.name",
|
|
179
|
+
// so continue traversing into object when part is "user".
|
|
180
|
+
if (isExact && exclude) {
|
|
181
|
+
node['-' + key] = null;
|
|
182
|
+
} else if (field.ref) {
|
|
183
|
+
node[key] ||= {};
|
|
184
|
+
setNodePath(node[key], {
|
|
185
|
+
modelName: field.ref,
|
|
186
|
+
path: path.slice(parts.length),
|
|
187
|
+
depth: depth + 1,
|
|
188
|
+
exclude
|
|
189
|
+
});
|
|
190
|
+
halt = true;
|
|
191
|
+
} else {
|
|
192
|
+
node[key] = null;
|
|
193
|
+
}
|
|
194
|
+
} else if (type === 'virtual') {
|
|
195
|
+
node[key] = {};
|
|
196
|
+
} else if (type !== 'nested') {
|
|
197
|
+
throw new Error(`Unknown path on ${modelName}: ${key}.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (halt) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function resolvePaths(schema, str) {
|
|
206
|
+
let paths;
|
|
207
|
+
if (str.includes('*')) {
|
|
208
|
+
let source = (0, _escapeRegExp2.default)(str);
|
|
209
|
+
source = source.replaceAll('\\*\\*', '.+');
|
|
210
|
+
source = source.replaceAll('\\*', '[^.]+');
|
|
211
|
+
source = `^${source}$`;
|
|
212
|
+
const reg = RegExp(source);
|
|
213
|
+
paths = Object.keys(schema.paths || {}).filter(path => {
|
|
214
|
+
return !path.startsWith('_') && reg.test(path);
|
|
215
|
+
});
|
|
216
|
+
} else {
|
|
217
|
+
paths = [str];
|
|
218
|
+
}
|
|
219
|
+
return paths.map(path => {
|
|
220
|
+
return [path, schema.pathType(path)];
|
|
221
|
+
});
|
|
222
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
var _exportNames = {
|
|
7
|
+
createSchema: true,
|
|
8
|
+
loadModel: true,
|
|
9
|
+
loadModelDir: true,
|
|
10
|
+
addValidators: true
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "addValidators", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _validation.addValidators;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "createSchema", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _schema.createSchema;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(exports, "loadModel", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () {
|
|
27
|
+
return _load.loadModel;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(exports, "loadModelDir", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function () {
|
|
33
|
+
return _load.loadModelDir;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
var _schema = require("./schema");
|
|
37
|
+
var _load = require("./load");
|
|
38
|
+
var _validation = require("./validation");
|
|
39
|
+
var _testing = require("./testing");
|
|
40
|
+
Object.keys(_testing).forEach(function (key) {
|
|
41
|
+
if (key === "default" || key === "__esModule") return;
|
|
42
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
43
|
+
if (key in exports && exports[key] === _testing[key]) return;
|
|
44
|
+
Object.defineProperty(exports, key, {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
get: function () {
|
|
47
|
+
return _testing[key];
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
var _errors = require("./errors");
|
|
52
|
+
Object.keys(_errors).forEach(function (key) {
|
|
53
|
+
if (key === "default" || key === "__esModule") return;
|
|
54
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
55
|
+
if (key in exports && exports[key] === _errors[key]) return;
|
|
56
|
+
Object.defineProperty(exports, key, {
|
|
57
|
+
enumerable: true,
|
|
58
|
+
get: function () {
|
|
59
|
+
return _errors[key];
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
package/dist/cjs/load.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.loadModel = loadModel;
|
|
7
|
+
exports.loadModelDir = loadModelDir;
|
|
8
|
+
var _startCase2 = _interopRequireDefault(require("lodash/startCase"));
|
|
9
|
+
var _fs = _interopRequireDefault(require("fs"));
|
|
10
|
+
var _path = _interopRequireDefault(require("path"));
|
|
11
|
+
var _mongoose = _interopRequireDefault(require("mongoose"));
|
|
12
|
+
var _schema = require("./schema");
|
|
13
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
14
|
+
function loadModel(definition, name) {
|
|
15
|
+
if (!definition.attributes) {
|
|
16
|
+
throw new Error(`Invalid model definition for ${name}, need attributes`);
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const schema = (0, _schema.createSchema)(definition);
|
|
20
|
+
return _mongoose.default.model(name, schema);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
throw new Error(`${err.message} (loading ${name})`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function loadModelDir(dirPath) {
|
|
26
|
+
const files = _fs.default.readdirSync(dirPath);
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
const basename = _path.default.basename(file, '.json');
|
|
29
|
+
if (file.match(/\.json$/)) {
|
|
30
|
+
const filePath = _path.default.join(dirPath, file);
|
|
31
|
+
const data = _fs.default.readFileSync(filePath);
|
|
32
|
+
const definition = JSON.parse(data);
|
|
33
|
+
const modelName = definition.modelName || (0, _startCase2.default)(basename).replace(/\s/g, '');
|
|
34
|
+
if (!_mongoose.default.models[modelName]) {
|
|
35
|
+
loadModel(definition, modelName);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return _mongoose.default.models;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "type": "commonjs" }
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.applyReferences = applyReferences;
|
|
7
|
+
var _mongoose = _interopRequireDefault(require("mongoose"));
|
|
8
|
+
var _errors = require("./errors");
|
|
9
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
10
|
+
const {
|
|
11
|
+
ObjectId: SchemaObjectId
|
|
12
|
+
} = _mongoose.default.Schema.Types;
|
|
13
|
+
function applyReferences(schema) {
|
|
14
|
+
schema.method('assertNoReferences', async function assertNoReferences(options = {}) {
|
|
15
|
+
const {
|
|
16
|
+
except = []
|
|
17
|
+
} = options;
|
|
18
|
+
const {
|
|
19
|
+
modelName
|
|
20
|
+
} = this.constructor;
|
|
21
|
+
assertExceptions(except);
|
|
22
|
+
const references = getAllReferences(modelName);
|
|
23
|
+
const results = [];
|
|
24
|
+
for (let {
|
|
25
|
+
model,
|
|
26
|
+
paths
|
|
27
|
+
} of references) {
|
|
28
|
+
const isAllowed = except.some(e => {
|
|
29
|
+
return e === model || e === model.modelName;
|
|
30
|
+
});
|
|
31
|
+
if (isAllowed) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const query = {
|
|
35
|
+
$or: paths.map(path => {
|
|
36
|
+
return {
|
|
37
|
+
[path]: this.id
|
|
38
|
+
};
|
|
39
|
+
})
|
|
40
|
+
};
|
|
41
|
+
const docs = await model.find(query, {
|
|
42
|
+
_id: 1
|
|
43
|
+
}, {
|
|
44
|
+
lean: true
|
|
45
|
+
});
|
|
46
|
+
if (docs.length > 0) {
|
|
47
|
+
const ids = docs.map(doc => {
|
|
48
|
+
return String(doc._id);
|
|
49
|
+
});
|
|
50
|
+
results.push({
|
|
51
|
+
ids,
|
|
52
|
+
model,
|
|
53
|
+
count: ids.length
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (results.length) {
|
|
58
|
+
throw new _errors.ReferenceError('Refusing to delete.', results);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function assertExceptions(except) {
|
|
63
|
+
for (let val of except) {
|
|
64
|
+
if (typeof val === 'string' && !_mongoose.default.models[val]) {
|
|
65
|
+
throw new Error(`Unknown model "${val}".`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function getAllReferences(targetName) {
|
|
70
|
+
return Object.values(_mongoose.default.models).map(model => {
|
|
71
|
+
const paths = getModelReferences(model, targetName);
|
|
72
|
+
return {
|
|
73
|
+
model,
|
|
74
|
+
paths
|
|
75
|
+
};
|
|
76
|
+
}).filter(({
|
|
77
|
+
paths
|
|
78
|
+
}) => {
|
|
79
|
+
return paths.length > 0;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function getModelReferences(model, targetName) {
|
|
83
|
+
const paths = [];
|
|
84
|
+
model.schema.eachPath((schemaPath, schemaType) => {
|
|
85
|
+
if (schemaType instanceof SchemaObjectId && schemaPath[0] !== '_') {
|
|
86
|
+
const {
|
|
87
|
+
ref,
|
|
88
|
+
refPath
|
|
89
|
+
} = schemaType.options;
|
|
90
|
+
let refs;
|
|
91
|
+
if (ref) {
|
|
92
|
+
refs = [ref];
|
|
93
|
+
} else if (refPath) {
|
|
94
|
+
refs = model.schema.path(refPath).options.enum;
|
|
95
|
+
} else {
|
|
96
|
+
throw new Error(`Cannot derive refs for ${model.modelName}#${schemaPath}.`);
|
|
97
|
+
}
|
|
98
|
+
if (refs.includes(targetName)) {
|
|
99
|
+
paths.push(schemaPath);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return paths;
|
|
104
|
+
}
|