@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +932 -0
  3. package/babel.config.cjs +41 -0
  4. package/dist/cjs/access.js +66 -0
  5. package/dist/cjs/assign.js +50 -0
  6. package/dist/cjs/const.js +16 -0
  7. package/dist/cjs/errors.js +17 -0
  8. package/dist/cjs/include.js +222 -0
  9. package/dist/cjs/index.js +62 -0
  10. package/dist/cjs/load.js +40 -0
  11. package/dist/cjs/package.json +1 -0
  12. package/dist/cjs/references.js +104 -0
  13. package/dist/cjs/schema.js +277 -0
  14. package/dist/cjs/search.js +266 -0
  15. package/dist/cjs/serialization.js +55 -0
  16. package/dist/cjs/slug.js +47 -0
  17. package/dist/cjs/soft-delete.js +192 -0
  18. package/dist/cjs/testing.js +33 -0
  19. package/dist/cjs/utils.js +73 -0
  20. package/dist/cjs/validation.js +313 -0
  21. package/dist/cjs/warn.js +13 -0
  22. package/jest-mongodb-config.js +10 -0
  23. package/jest.config.js +8 -0
  24. package/package.json +53 -0
  25. package/src/access.js +60 -0
  26. package/src/assign.js +45 -0
  27. package/src/const.js +9 -0
  28. package/src/errors.js +9 -0
  29. package/src/include.js +209 -0
  30. package/src/index.js +5 -0
  31. package/src/load.js +37 -0
  32. package/src/references.js +101 -0
  33. package/src/schema.js +286 -0
  34. package/src/search.js +263 -0
  35. package/src/serialization.js +49 -0
  36. package/src/slug.js +45 -0
  37. package/src/soft-delete.js +234 -0
  38. package/src/testing.js +29 -0
  39. package/src/utils.js +63 -0
  40. package/src/validation.js +329 -0
  41. package/src/warn.js +7 -0
  42. package/test/assign.test.js +225 -0
  43. package/test/definitions/custom-model.json +9 -0
  44. package/test/definitions/special-category.json +18 -0
  45. package/test/include.test.js +896 -0
  46. package/test/load.test.js +47 -0
  47. package/test/references.test.js +71 -0
  48. package/test/schema.test.js +919 -0
  49. package/test/search.test.js +652 -0
  50. package/test/serialization.test.js +748 -0
  51. package/test/setup.js +27 -0
  52. package/test/slug.test.js +112 -0
  53. package/test/soft-delete.test.js +333 -0
  54. package/test/validation.test.js +1925 -0
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.RESERVED_FIELDS = void 0;
7
+ exports.createSchema = createSchema;
8
+ exports.normalizeAttributes = normalizeAttributes;
9
+ var _camelCase2 = _interopRequireDefault(require("lodash/camelCase"));
10
+ var _capitalize2 = _interopRequireDefault(require("lodash/capitalize"));
11
+ var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
12
+ var _pick2 = _interopRequireDefault(require("lodash/pick"));
13
+ var _mongoose = _interopRequireDefault(require("mongoose"));
14
+ var _utils = require("./utils");
15
+ var _serialization = require("./serialization");
16
+ var _slug = require("./slug");
17
+ var _search = require("./search");
18
+ var _assign = require("./assign");
19
+ var _include = require("./include");
20
+ var _references = require("./references");
21
+ var _softDelete = require("./soft-delete");
22
+ var _validation = require("./validation");
23
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
24
+ const RESERVED_FIELDS = ['createdAt', 'updatedAt', 'deletedAt', 'deleted'];
25
+ exports.RESERVED_FIELDS = RESERVED_FIELDS;
26
+ function createSchema(definition, options = {}) {
27
+ const schema = new _mongoose.default.Schema(attributesToMongoose(normalizeAttributes({
28
+ ...definition.attributes,
29
+ // Although timestamps are being set below, we still need to add
30
+ // them to the schema so that validation can be generated for them,
31
+ // namely in getSearchValidation.
32
+ createdAt: 'Date',
33
+ updatedAt: 'Date',
34
+ deletedAt: 'Date',
35
+ deleted: {
36
+ type: 'Boolean',
37
+ default: false
38
+ }
39
+ })), {
40
+ timestamps: true,
41
+ toJSON: _serialization.serializeOptions,
42
+ toObject: _serialization.serializeOptions,
43
+ ...options
44
+ });
45
+ (0, _softDelete.applySoftDelete)(schema, definition);
46
+ (0, _validation.applyValidation)(schema, definition);
47
+ (0, _references.applyReferences)(schema, definition);
48
+ (0, _include.applyInclude)(schema, definition);
49
+ (0, _search.applySearch)(schema, definition);
50
+ (0, _assign.applyAssign)(schema, definition);
51
+ (0, _slug.applySlug)(schema, definition);
52
+ return schema;
53
+ }
54
+ function normalizeAttributes(arg, path = []) {
55
+ if (arg instanceof _mongoose.default.Schema) {
56
+ return arg;
57
+ } else if (typeof arg === 'function') {
58
+ throw new Error('Native functions are not allowed as types.');
59
+ } else if (typeof arg === 'string') {
60
+ return normalizeSchemaTypedef({
61
+ type: arg
62
+ }, path);
63
+ } else if (Array.isArray(arg)) {
64
+ const type = normalizeArrayAttributes(arg, path);
65
+ return normalizeSchemaTypedef({
66
+ type
67
+ }, path);
68
+ } else if (typeof arg === 'object') {
69
+ assertRefs(arg, path);
70
+ if ((0, _utils.isSchemaTypedef)(arg)) {
71
+ return normalizeSchemaTypedef(arg, path);
72
+ }
73
+ const attributes = {};
74
+ for (let [key, val] of Object.entries(arg)) {
75
+ attributes[key] = normalizeAttributes(val, [...path, key]);
76
+ }
77
+ return attributes;
78
+ }
79
+ }
80
+ function normalizeSchemaTypedef(typedef, path) {
81
+ const {
82
+ type
83
+ } = typedef;
84
+ if (Array.isArray(type)) {
85
+ typedef.type = normalizeArrayAttributes(type, path);
86
+ } else if (typeof type === 'object') {
87
+ typedef.type = normalizeAttributes(type, path);
88
+ } else {
89
+ assertSchemaType(type, path);
90
+ }
91
+ return typedef;
92
+ }
93
+ function normalizeArrayAttributes(arr, path) {
94
+ return arr.map((el, i) => {
95
+ return normalizeAttributes(el, [...path, i]);
96
+ });
97
+ }
98
+ function attributesToMongoose(attributes) {
99
+ if (typeof attributes === 'string') {
100
+ return attributes;
101
+ } else if (Array.isArray(attributes)) {
102
+ return attributes.map(attributesToMongoose);
103
+ }
104
+ let definition = {};
105
+ const isTypedef = (0, _utils.isSchemaTypedef)(attributes);
106
+ for (let [key, val] of Object.entries(attributes)) {
107
+ const type = typeof val;
108
+ if (isTypedef) {
109
+ if (key === 'type' && type !== 'function') {
110
+ val = attributesToMongoose(val);
111
+ } else if (key === 'match' && type === 'string') {
112
+ // Convert match field to RegExp that cannot be expressed in JSON.
113
+ val = parseRegExp(val);
114
+ } else if (key === 'validate' && type === 'string') {
115
+ // Allow custom mongoose validation function that derives from the schema.
116
+ val = (0, _validation.getNamedValidator)(val);
117
+ }
118
+ } else if ((0, _isPlainObject2.default)(val)) {
119
+ if (isScopeExtension(val)) {
120
+ applyScopeExtension(val, definition);
121
+ continue;
122
+ } else {
123
+ val = attributesToMongoose(val);
124
+ }
125
+ }
126
+ definition[key] = val;
127
+ }
128
+ if (isTypedef) {
129
+ applyExtensions(definition);
130
+ }
131
+ return definition;
132
+ }
133
+ function assertSchemaType(type, path) {
134
+ if (type === 'Mixed') {
135
+ throw new Error('Type "Mixed" is not allowed. Use "Object" instead.');
136
+ }
137
+ if (typeof type === 'string') {
138
+ if (!isMongooseType(type)) {
139
+ const p = path.join('.');
140
+ const upper = camelUpper(type);
141
+ if (isMongooseType(upper)) {
142
+ throw new Error(`Type "${type}" in "${p}" should be "${upper}".`);
143
+ } else if (type !== 'Scope') {
144
+ throw new Error(`Invalid type "${type}" for "${p}".`);
145
+ }
146
+ }
147
+ }
148
+ }
149
+ function assertRefs(field, path) {
150
+ const {
151
+ type,
152
+ ref,
153
+ refPath
154
+ } = field;
155
+ const p = path.join('.');
156
+ if (isObjectIdType(type) && !ref && !refPath) {
157
+ throw new Error(`Ref must be passed for "${p}".`);
158
+ } else if (ref && !isMongooseType(ref) && !isObjectIdType(type)) {
159
+ throw new Error(`Ref field "${p}" must be type "ObjectId".`);
160
+ }
161
+ }
162
+ function camelUpper(str) {
163
+ return (0, _capitalize2.default)((0, _camelCase2.default)(str));
164
+ }
165
+ function isObjectIdType(type) {
166
+ return type === 'ObjectId' || type === _mongoose.default.Schema.Types.ObjectId;
167
+ }
168
+ function isMongooseType(type) {
169
+ return !!_mongoose.default.Schema.Types[type];
170
+ }
171
+ function applyExtensions(typedef) {
172
+ applySyntaxExtensions(typedef);
173
+ applyTupleExtension(typedef);
174
+ }
175
+ function applySyntaxExtensions(typedef) {
176
+ const {
177
+ type,
178
+ attributes
179
+ } = typedef;
180
+ if (isExtendedSyntax(typedef)) {
181
+ typedef.type = new _mongoose.default.Schema(attributes);
182
+ if (type === 'Array') {
183
+ typedef.type = [typedef.type];
184
+ }
185
+ }
186
+ if (Array.isArray(typedef.type)) {
187
+ applyArrayValidators(typedef);
188
+ applyOptionHoisting(typedef);
189
+ }
190
+ }
191
+
192
+ // Hoist read/write scopes from a nested element.
193
+ // See the readme for more.
194
+ function applyOptionHoisting(typedef) {
195
+ Object.assign(typedef, (0, _pick2.default)(typedef.type[0], 'readAccess', 'writeAccess'));
196
+ }
197
+ function isExtendedSyntax(typedef) {
198
+ const {
199
+ type,
200
+ attributes
201
+ } = typedef;
202
+ return attributes && (type === 'Object' || type === 'Array');
203
+ }
204
+ function isScopeExtension(obj) {
205
+ return (0, _utils.isSchemaTypedef)(obj) && obj.type === 'Scope';
206
+ }
207
+ function applyScopeExtension(scope, definition) {
208
+ const {
209
+ type,
210
+ attributes,
211
+ ...options
212
+ } = scope;
213
+ for (let [key, val] of Object.entries(normalizeAttributes(attributes))) {
214
+ definition[key] = {
215
+ ...val,
216
+ ...options
217
+ };
218
+ }
219
+ }
220
+
221
+ // Extended tuple syntax. Return mixed type and set validator.
222
+ function applyTupleExtension(typedef) {
223
+ const {
224
+ type
225
+ } = typedef;
226
+ if (Array.isArray(type) && type.length > 1) {
227
+ typedef.type = ['Mixed'];
228
+ typedef.validate = (0, _validation.getTupleValidator)(type);
229
+ }
230
+ }
231
+ function applyArrayValidators(typedef) {
232
+ let {
233
+ minLength,
234
+ maxLength,
235
+ validate
236
+ } = typedef;
237
+ if (minLength) {
238
+ validate = chain(validate, validateMinLength(minLength));
239
+ }
240
+ if (maxLength) {
241
+ validate = chain(validate, validateMaxLength(maxLength));
242
+ }
243
+ if (validate) {
244
+ typedef.validate = validate;
245
+ }
246
+ }
247
+ function validateMinLength(min) {
248
+ const s = min === 1 ? '' : 's';
249
+ return arr => {
250
+ if (arr.length < min) {
251
+ throw new Error(`Must have at least ${min} element${s}.`);
252
+ }
253
+ };
254
+ }
255
+ function validateMaxLength(max) {
256
+ const s = max === 1 ? '' : 's';
257
+ return arr => {
258
+ if (arr.length > max) {
259
+ throw new Error(`Cannot have more than ${max} element${s}.`);
260
+ }
261
+ };
262
+ }
263
+ function chain(fn1, fn2) {
264
+ return (...args) => {
265
+ fn1?.(...args);
266
+ fn2?.(...args);
267
+ };
268
+ }
269
+ const REG_MATCH = /^\/(.+)\/(\w+)$/;
270
+ function parseRegExp(str) {
271
+ const match = str.match(REG_MATCH);
272
+ if (!match) {
273
+ throw new Error('Could not parse regex.');
274
+ }
275
+ const [, source, flags] = match;
276
+ return RegExp(source, flags);
277
+ }
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.applySearch = applySearch;
7
+ exports.searchValidation = searchValidation;
8
+ var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
9
+ var _isEmpty2 = _interopRequireDefault(require("lodash/isEmpty"));
10
+ var _pick2 = _interopRequireDefault(require("lodash/pick"));
11
+ var _yada = _interopRequireDefault(require("@bedrockio/yada"));
12
+ var _mongoose = _interopRequireDefault(require("mongoose"));
13
+ var _utils = require("./utils");
14
+ var _const = require("./const");
15
+ var _warn = _interopRequireDefault(require("./warn"));
16
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
+ const {
18
+ ObjectId
19
+ } = _mongoose.default.Types;
20
+ const SORT_SCHEMA = _yada.default.object({
21
+ field: _yada.default.string().required(),
22
+ order: _yada.default.string().allow('desc', 'asc').required()
23
+ });
24
+ function applySearch(schema, definition) {
25
+ validateDefinition(definition);
26
+ schema.static('search', function search(body = {}) {
27
+ const options = {
28
+ ..._const.SEARCH_DEFAULTS,
29
+ ...definition.search,
30
+ ...body
31
+ };
32
+ const {
33
+ ids,
34
+ keyword,
35
+ skip = 0,
36
+ limit,
37
+ sort,
38
+ fields,
39
+ ...rest
40
+ } = options;
41
+ const query = {};
42
+ if (ids?.length) {
43
+ query._id = {
44
+ $in: ids
45
+ };
46
+ }
47
+ if (keyword) {
48
+ Object.assign(query, buildKeywordQuery(keyword, fields));
49
+ }
50
+ Object.assign(query, normalizeQuery(rest, schema.obj));
51
+ const mQuery = this.find(query).sort(resolveSort(sort)).skip(skip).limit(limit);
52
+
53
+ // The following construct is awkward but it allows the mongoose query
54
+ // object to be returned while still ultimately resolving with metadata
55
+ // so that this method can behave like other find methods and importantly
56
+ // allow custom population with the same API.
57
+
58
+ const runQuery = mQuery.then.bind(mQuery);
59
+ mQuery.then = async (resolve, reject) => {
60
+ try {
61
+ const [data, total] = await Promise.all([runQuery(), this.countDocuments(query)]);
62
+ resolve({
63
+ data,
64
+ meta: {
65
+ total,
66
+ skip,
67
+ limit
68
+ }
69
+ });
70
+ } catch (err) {
71
+ reject(err);
72
+ }
73
+ };
74
+ return mQuery;
75
+ });
76
+ }
77
+ function searchValidation(definition, options = {}) {
78
+ const {
79
+ limit,
80
+ sort,
81
+ ...rest
82
+ } = {
83
+ ..._const.SEARCH_DEFAULTS,
84
+ ...(0, _pick2.default)(definition.search, 'limit', 'sort'),
85
+ ...options
86
+ };
87
+ return {
88
+ ids: _yada.default.array(_yada.default.string().mongo()),
89
+ keyword: _yada.default.string(),
90
+ include: _yada.default.string(),
91
+ skip: _yada.default.number().default(0),
92
+ sort: _yada.default.allow(SORT_SCHEMA, _yada.default.array(SORT_SCHEMA)).default(sort),
93
+ limit: _yada.default.number().positive().default(limit),
94
+ ...rest
95
+ };
96
+ }
97
+ function validateDefinition(definition) {
98
+ if (Array.isArray(definition.search)) {
99
+ (0, _warn.default)(['"search" field on model definition must not be an array.', 'Use "search.fields" to define fields for keyword queries.'].join('\n'));
100
+ throw new Error('Invalid model definition.');
101
+ }
102
+ }
103
+ function resolveSort(sort) {
104
+ if (!Array.isArray(sort)) {
105
+ sort = [sort];
106
+ }
107
+ return sort.map(({
108
+ field,
109
+ order
110
+ }) => {
111
+ return [field, order === 'desc' ? -1 : 1];
112
+ });
113
+ }
114
+
115
+ // Keyword queries
116
+ //
117
+ // Mongo supports text indexes, however search operations do not support partial
118
+ // word matches except for stemming rules (eg: "taste", "tastes", and "tasteful").
119
+ //
120
+ // Text indexes are preferred for performance, diacritic handling and more, however
121
+ // for smaller collections partial matches can be manually enabled by specifying an
122
+ // array of "search" fields on the definition:
123
+ //
124
+ // {
125
+ // "attributes": {
126
+ // "name": {
127
+ // "type": "String",
128
+ // "required": true,
129
+ // "trim": true
130
+ // },
131
+ // },
132
+ // "search": [
133
+ // "name",
134
+ // "description"
135
+ // ]
136
+ // },
137
+ //
138
+ // Be aware that this may impact performance in which case moving to a text index
139
+ // may be preferable, however partial word matches will stop working. Support for
140
+ // ngram based text search appears to be coming but has no landing date yet.
141
+ //
142
+ // References:
143
+ // https://stackoverflow.com/questions/44833817/mongodb-full-and-partial-text-search
144
+ // https://jira.mongodb.org/browse/SERVER-15090
145
+
146
+ function buildKeywordQuery(keyword, fields) {
147
+ if (fields) {
148
+ return buildRegexQuery(keyword, fields);
149
+ } else {
150
+ return buildTextIndexQuery(keyword);
151
+ }
152
+ }
153
+ function buildRegexQuery(keyword, fields) {
154
+ const queries = fields.map(field => {
155
+ const regexKeyword = keyword.replace(/\+/g, '\\+');
156
+ return {
157
+ [field]: {
158
+ $regex: `${regexKeyword}`,
159
+ $options: 'i'
160
+ }
161
+ };
162
+ });
163
+ if (ObjectId.isValid(keyword)) {
164
+ queries.push({
165
+ _id: keyword
166
+ });
167
+ }
168
+ return {
169
+ $or: queries
170
+ };
171
+ }
172
+ function buildTextIndexQuery(keyword) {
173
+ if (ObjectId.isValid(keyword)) {
174
+ return {
175
+ $or: [{
176
+ $text: {
177
+ $search: keyword
178
+ }
179
+ }, {
180
+ _id: keyword
181
+ }]
182
+ };
183
+ } else {
184
+ return {
185
+ $text: {
186
+ $search: keyword
187
+ }
188
+ };
189
+ }
190
+ }
191
+
192
+ // Normalizes mongo queries. Flattens plain nested paths
193
+ // to dot syntax while preserving mongo operators and
194
+ // handling specialed query syntax:
195
+ // ranges:
196
+ // path: { min: n, max n }
197
+ // regex:
198
+ // path: "/reg/"
199
+ // array:
200
+ // path; [1,2,3]
201
+ function normalizeQuery(query, schema, root = {}, rootPath = []) {
202
+ for (let [key, value] of Object.entries(query)) {
203
+ const path = [...rootPath, key];
204
+ if (isRangeQuery(schema, key, value)) {
205
+ if (!(0, _isEmpty2.default)(value)) {
206
+ root[path.join('.')] = mapOperatorQuery(value);
207
+ }
208
+ } else if (isNestedQuery(key, value)) {
209
+ normalizeQuery(value, (0, _utils.resolveField)(schema, key), root, path);
210
+ } else if (isRegexQuery(key, value)) {
211
+ root[path.join('.')] = parseRegexQuery(value);
212
+ } else if (isArrayQuery(key, value)) {
213
+ root[path.join('.')] = {
214
+ $in: value
215
+ };
216
+ } else {
217
+ root[path.join('.')] = value;
218
+ }
219
+ }
220
+ return root;
221
+ }
222
+ function isNestedQuery(key, value) {
223
+ if (isMongoOperator(key) || !(0, _isPlainObject2.default)(value)) {
224
+ return false;
225
+ }
226
+ return Object.keys(value).every(key => {
227
+ return !isMongoOperator(key);
228
+ });
229
+ }
230
+ function isArrayQuery(key, value) {
231
+ return !isMongoOperator(key) && Array.isArray(value);
232
+ }
233
+ function isRangeQuery(schema, key, value) {
234
+ // Range queries only allowed on Date and Number fields.
235
+ if (!(0, _utils.isDateField)(schema, key) && !(0, _utils.isNumberField)(schema, key)) {
236
+ return false;
237
+ }
238
+ return typeof value === 'object';
239
+ }
240
+ function mapOperatorQuery(obj) {
241
+ const query = {};
242
+ for (let [key, val] of Object.entries(obj)) {
243
+ query[`$${key}`] = val;
244
+ }
245
+ return query;
246
+ }
247
+ function isMongoOperator(str) {
248
+ return str.startsWith('$');
249
+ }
250
+
251
+ // Regex queries
252
+
253
+ const REGEX_QUERY = /^\/(.+)\/(\w*)$/;
254
+ function isRegexQuery(key, value) {
255
+ return REGEX_QUERY.test(value);
256
+ }
257
+ function parseRegexQuery(str) {
258
+ // Note that using the $options syntax allows for PCRE features
259
+ // that aren't supported in Javascript as compared to RegExp(...):
260
+ // https://docs.mongodb.com/manual/reference/operator/query/regex/#pcre-vs-javascript
261
+ const [, $regex, $options] = str.match(REGEX_QUERY);
262
+ return {
263
+ $regex,
264
+ $options
265
+ };
266
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.serializeOptions = void 0;
7
+ var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
8
+ var _include = require("./include");
9
+ var _access = require("./access");
10
+ var _utils = require("./utils");
11
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
+ const serializeOptions = {
13
+ getters: true,
14
+ versionKey: false,
15
+ transform: (doc, ret, options) => {
16
+ options.document = doc;
17
+ (0, _include.checkSelects)(doc, ret);
18
+ transformField(ret, doc.schema.obj, options);
19
+ }
20
+ };
21
+ exports.serializeOptions = serializeOptions;
22
+ function transformField(obj, field, options) {
23
+ if (Array.isArray(obj)) {
24
+ for (let el of obj) {
25
+ transformField(el, field, options);
26
+ }
27
+ } else if ((0, _isPlainObject2.default)(obj)) {
28
+ for (let [key, val] of Object.entries(obj)) {
29
+ if (!isAllowedField(key, field, options)) {
30
+ delete obj[key];
31
+ } else {
32
+ transformField(val, (0, _utils.resolveField)(field, key), options);
33
+ }
34
+ }
35
+ }
36
+ }
37
+ function isAllowedField(key, field, options) {
38
+ if (key[0] === '_') {
39
+ // Strip internal keys like _id and __v
40
+ return false;
41
+ } else if (key === 'deleted') {
42
+ // Strip "deleted" field which defaults
43
+ // to false and should not be exposed.
44
+ return false;
45
+ } else {
46
+ const {
47
+ readAccess
48
+ } = (0, _utils.resolveField)(field, key);
49
+ try {
50
+ return (0, _access.hasReadAccess)(readAccess, options);
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.applySlug = applySlug;
7
+ var _mongoose = _interopRequireDefault(require("mongoose"));
8
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
+ const {
10
+ ObjectId
11
+ } = _mongoose.default.Types;
12
+ function applySlug(schema) {
13
+ schema.static('findByIdOrSlug', function findByIdOrSlug(str, ...args) {
14
+ return find(this, str, args);
15
+ });
16
+ schema.static('findByIdOrSlugDeleted', function findByIdOrSlugDeleted(str, ...args) {
17
+ return find(this, str, args, {
18
+ deleted: true
19
+ });
20
+ });
21
+ schema.static('findByIdOrSlugWithDeleted', function findByIdOrSlugWithDeleted(str, ...args) {
22
+ return find(this, str, args, {
23
+ deleted: {
24
+ $in: [true, false]
25
+ }
26
+ });
27
+ });
28
+ }
29
+ function find(Model, str, args, query) {
30
+ const isObjectId = str.length === 24 && ObjectId.isValid(str);
31
+ // There is a non-zero chance of a slug colliding with an ObjectId but
32
+ // is exceedingly rare (run of exactly 24 [a-f0-9] chars together
33
+ // without a hyphen) so this should be acceptable.
34
+ if (!query && isObjectId) {
35
+ return Model.findById(str, ...args);
36
+ } else {
37
+ query = {
38
+ ...query
39
+ };
40
+ if (isObjectId) {
41
+ query._id = str;
42
+ } else {
43
+ query.slug = str;
44
+ }
45
+ return Model.findOne(query, ...args);
46
+ }
47
+ }