@bedrockio/model 0.18.1 → 0.18.2

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 CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.18.2
2
+
3
+ - Further fix for `reload` not working with delete hooks.
4
+ - Better detection of literal `type` fields.
5
+ - Schema refactor.
6
+
1
7
  ## 0.18.1
2
8
 
3
9
  - Added `export` for dumping documents and to support reload.
package/dist/cjs/cache.js CHANGED
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.addCacheFields = addCacheFields;
6
7
  exports.applyCache = applyCache;
7
8
  var _lodash = require("lodash");
8
9
  var _mongoose = _interopRequireDefault(require("mongoose"));
@@ -10,32 +11,34 @@ var _utils = require("./utils");
10
11
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
12
  const definitionMap = new Map();
12
13
  _mongoose.default.plugin(cacheSyncPlugin);
13
- function applyCache(schema, definition) {
14
- definitionMap.set(schema, definition);
15
- if (!definition.cache) {
14
+ function addCacheFields(definition) {
15
+ const {
16
+ cache
17
+ } = definition;
18
+ if (!cache) {
16
19
  return;
17
20
  }
18
- createCacheFields(schema, definition);
19
- applyStaticMethods(schema, definition);
20
- applyCacheHook(schema, definition);
21
- }
22
- function createCacheFields(schema, definition) {
23
- for (let [cachedField, def] of Object.entries(definition.cache)) {
21
+ for (let [cachedField, def] of Object.entries(cache)) {
24
22
  const {
25
23
  type,
26
24
  path,
27
25
  ...rest
28
26
  } = def;
29
- schema.add({
30
- [cachedField]: type
31
- });
32
- schema.obj[cachedField] = {
33
- ...rest,
27
+ definition.attributes[cachedField] = {
34
28
  type,
29
+ ...rest,
35
30
  writeAccess: 'none'
36
31
  };
37
32
  }
38
33
  }
34
+ function applyCache(schema, definition) {
35
+ definitionMap.set(schema, definition);
36
+ if (!definition.cache) {
37
+ return;
38
+ }
39
+ applyStaticMethods(schema, definition);
40
+ applyCacheHook(schema, definition);
41
+ }
39
42
  function applyStaticMethods(schema, definition) {
40
43
  schema.static('syncCacheFields', async function syncCacheFields() {
41
44
  assertIncludeModule(this);
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.addDeletedFields = addDeletedFields;
6
7
  exports.applyDeleteHooks = applyDeleteHooks;
7
8
  var _lodash = require("lodash");
8
9
  var _mongoose = _interopRequireDefault(require("mongoose"));
@@ -12,6 +13,18 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
12
13
  const {
13
14
  ObjectId: SchemaObjectId
14
15
  } = _mongoose.default.Schema.Types;
16
+ function addDeletedFields(definition) {
17
+ let {
18
+ onDelete: deleteHooks
19
+ } = definition;
20
+ if (!deleteHooks) {
21
+ return;
22
+ }
23
+ definition.attributes['deletedRefs'] = [{
24
+ _id: 'ObjectId',
25
+ ref: 'String'
26
+ }];
27
+ }
15
28
  function applyDeleteHooks(schema, definition) {
16
29
  let {
17
30
  onDelete: deleteHooks
@@ -45,12 +58,6 @@ function applyDeleteHooks(schema, definition) {
45
58
  await restoreReferences(this, cleanHooks);
46
59
  await restoreFn.apply(this, arguments);
47
60
  });
48
- schema.add({
49
- deletedRefs: [{
50
- _id: 'ObjectId',
51
- ref: 'String'
52
- }]
53
- });
54
61
  }
55
62
 
56
63
  // Clean Hook
@@ -33,7 +33,9 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
33
33
  * @returns mongoose.Schema
34
34
  */
35
35
  function createSchema(definition, options = {}) {
36
- const schema = new _mongoose.default.Schema(attributesToMongoose({
36
+ (0, _cache.addCacheFields)(definition);
37
+ (0, _deleteHooks.addDeletedFields)(definition);
38
+ const attributes = normalizeAttributes({
37
39
  ...definition.attributes,
38
40
  // Although timestamps are being set below, we still need to add
39
41
  // them to the schema so that validation can be generated for them,
@@ -45,7 +47,9 @@ function createSchema(definition, options = {}) {
45
47
  type: 'Boolean',
46
48
  default: false
47
49
  }
48
- }), {
50
+ });
51
+ applyExtensions(attributes);
52
+ const schema = new _mongoose.default.Schema(attributes, {
49
53
  timestamps: true,
50
54
  toJSON: _serialization.serializeOptions,
51
55
  toObject: _serialization.serializeOptions,
@@ -75,19 +79,9 @@ function normalizeAttributes(arg, path = []) {
75
79
  return arg;
76
80
  } else if (typeof arg === 'function') {
77
81
  throw new Error('Native functions are not allowed as types.');
78
- } else if (typeof arg === 'string') {
79
- return normalizeSchemaTypedef({
80
- type: arg
81
- }, path);
82
- } else if (Array.isArray(arg)) {
83
- return normalizeSchemaTypedef({
84
- type: arg
85
- }, path);
82
+ } else if (isTypedefInput(arg)) {
83
+ return normalizeTypedef(arg, path);
86
84
  } else if (typeof arg === 'object') {
87
- assertRefs(arg, path);
88
- if ((0, _utils.isSchemaTypedef)(arg)) {
89
- return normalizeSchemaTypedef(arg, path);
90
- }
91
85
  const attributes = {};
92
86
  for (let [key, val] of Object.entries(arg)) {
93
87
  attributes[key] = normalizeAttributes(val, [...path, key]);
@@ -95,68 +89,74 @@ function normalizeAttributes(arg, path = []) {
95
89
  return attributes;
96
90
  }
97
91
  }
98
- function normalizeSchemaTypedef(typedef, path) {
99
- const {
100
- type
101
- } = typedef;
102
- if (Array.isArray(type)) {
103
- typedef.type = normalizeArrayAttributes(type, path);
104
- } else if (typeof type === 'object') {
105
- typedef.type = normalizeAttributes(type, path);
106
- } else {
107
- assertSchemaType(type, path);
108
- }
109
- if (typedef.type === 'String') {
110
- typedef.trim ??= true;
111
- }
112
- return typedef;
113
- }
114
92
  function normalizeArrayAttributes(arr, path) {
115
93
  return arr.map((el, i) => {
116
94
  return normalizeAttributes(el, [...path, i]);
117
95
  });
118
96
  }
119
- function attributesToMongoose(attributes) {
120
- if (typeof attributes === 'string') {
121
- return attributes;
122
- } else if (Array.isArray(attributes)) {
123
- return attributes.map(attributesToMongoose);
97
+ function normalizeTypedef(arg, path) {
98
+ const typedef = arg.type ? arg : {
99
+ type: arg
100
+ };
101
+ if (Array.isArray(typedef.type)) {
102
+ // Normalize all inner fields.
103
+ typedef.type = normalizeArrayAttributes(typedef.type, path);
104
+ } else if (typeof typedef.type === 'object') {
105
+ // Normalize literal "type" field.
106
+ typedef.type = normalizeAttributes(typedef.type, path);
107
+ } else if (isExtendedSyntax(typedef)) {
108
+ // Normalize extended syntax: type "Object" or "Array".
109
+ typedef.attributes = normalizeAttributes(typedef.attributes, path);
124
110
  }
125
- attributes = normalizeAttributes(attributes);
126
- let definition = {};
127
- const isTypedef = (0, _utils.isSchemaTypedef)(attributes);
128
- for (let [key, val] of Object.entries(attributes)) {
129
- const type = typeof val;
130
- if (isTypedef) {
131
- if (key === 'type' && type !== 'function') {
132
- val = attributesToMongoose(val);
133
- } else if (key === 'match' && type === 'string') {
134
- // Convert match field to RegExp that cannot be expressed in JSON.
135
- val = parseRegExp(val);
136
- } else if (key === 'validate' && type === 'string') {
137
- // Allow custom mongoose validation function that derives from the schema.
138
- val = (0, _validation.getNamedValidator)(val);
139
- } else if (key === 'attributes' && type === 'object') {
140
- val = attributesToMongoose(val);
141
- }
142
- } else if (Array.isArray(val)) {
143
- val = val.map(attributesToMongoose);
144
- } else if ((0, _lodash.isPlainObject)(val)) {
145
- if (isScopeExtension(val)) {
146
- applyScopeExtension(val, definition);
147
- continue;
148
- } else {
149
- val = attributesToMongoose(val);
150
- }
111
+ if (typedef.type === 'String') {
112
+ // Auto-apply trim to string fields.
113
+ typedef.trim ??= true;
114
+ if (typeof typedef.match === 'string') {
115
+ // Convert string RegExp so that
116
+ // it can be expressed in JSON.
117
+ typedef.match = parseRegExp(typedef.match);
151
118
  }
152
- definition[key] = val;
153
119
  }
154
- if (isTypedef) {
155
- applyExtensions(definition);
120
+ assertSchemaType(typedef, path);
121
+ assertObjectRefs(typedef, path);
122
+ return typedef;
123
+ }
124
+ function isTypedefInput(arg) {
125
+ if (typeof arg === 'string') {
126
+ // "Number" as shorthand for a typedef.
127
+ return true;
128
+ } else if (Array.isArray(arg)) {
129
+ // Array signals an array field with inner schema.
130
+ return true;
131
+ } else if (hasLiteralTypeField(arg)) {
132
+ // An object with a literal "type" field.
133
+ return false;
156
134
  }
157
- return definition;
135
+ return (0, _utils.isSchemaTypedef)(arg);
158
136
  }
159
- function assertSchemaType(type, path) {
137
+
138
+ // Detects input like:
139
+ // {
140
+ // "type": "String",
141
+ // "name": "String",
142
+ // }
143
+ // Which is not intended to be a typedef.
144
+ function hasLiteralTypeField(arg) {
145
+ const {
146
+ type,
147
+ ...rest
148
+ } = arg || {};
149
+ if (!isMongooseType(type)) {
150
+ return false;
151
+ }
152
+ return Object.values(rest).some(key => {
153
+ return isMongooseType(key);
154
+ });
155
+ }
156
+ function assertSchemaType(typedef, path) {
157
+ const {
158
+ type
159
+ } = typedef;
160
160
  if (typeof type === 'string') {
161
161
  if (!isMongooseType(type)) {
162
162
  const p = path.join('.');
@@ -169,33 +169,59 @@ function assertSchemaType(type, path) {
169
169
  }
170
170
  }
171
171
  }
172
- function assertRefs(field, path) {
172
+ function assertObjectRefs(typedef, path) {
173
173
  const {
174
174
  type,
175
- ref,
176
- refPath
177
- } = field;
175
+ ref
176
+ } = typedef;
178
177
  const p = path.join('.');
179
- if (isObjectIdType(type) && !ref && !refPath) {
178
+ if (requiresRef(typedef, path)) {
180
179
  throw new Error(`Ref must be passed for "${p}".`);
180
+ // TODO: what is the middle part doing here??
181
181
  } else if (ref && !isMongooseType(ref) && !isObjectIdType(type)) {
182
182
  throw new Error(`Ref field "${p}" must be type "ObjectId".`);
183
183
  }
184
184
  }
185
- function camelUpper(str) {
186
- return (0, _lodash.capitalize)((0, _lodash.camelCase)(str));
187
- }
188
- function isObjectIdType(type) {
189
- return type === 'ObjectId' || type === _mongoose.default.Schema.Types.ObjectId;
190
- }
191
- function isMongooseType(type) {
192
- return !!_mongoose.default.Schema.Types[type];
185
+ function requiresRef(typedef, path) {
186
+ const {
187
+ type,
188
+ ref,
189
+ refPath
190
+ } = typedef;
191
+
192
+ // Allow "_id" to not have a ref for the
193
+ // delete hooks module to function.
194
+ if ((0, _lodash.last)(path) === '_id') {
195
+ return false;
196
+ }
197
+ return isObjectIdType(type) && !ref && !refPath;
193
198
  }
194
- function applyExtensions(typedef) {
195
- applySyntaxExtensions(typedef);
196
- applyUniqueExtension(typedef);
197
- applyTupleExtension(typedef);
198
- applyDateExtension(typedef);
199
+
200
+ // Extensions
201
+
202
+ function applyExtensions(arg) {
203
+ if ((0, _utils.isSchemaTypedef)(arg)) {
204
+ applySyntaxExtensions(arg);
205
+ applyValidateExtension(arg);
206
+ applyUniqueExtension(arg);
207
+ applyTupleExtension(arg);
208
+ applyDateExtension(arg);
209
+ if (Array.isArray(arg.type)) {
210
+ for (let field of arg.type) {
211
+ applyExtensions(field);
212
+ }
213
+ applyArrayValidators(arg);
214
+ applyOptionHoisting(arg);
215
+ }
216
+ } else if ((0, _lodash.isPlainObject)(arg)) {
217
+ for (let [key, value] of Object.entries(arg)) {
218
+ if (isScopeExtension(value)) {
219
+ applyScopeExtension(value, arg, key);
220
+ } else {
221
+ applyExtensions(value);
222
+ }
223
+ }
224
+ }
199
225
  }
200
226
  function applySyntaxExtensions(typedef) {
201
227
  const {
@@ -203,14 +229,13 @@ function applySyntaxExtensions(typedef) {
203
229
  attributes
204
230
  } = typedef;
205
231
  if (isExtendedSyntax(typedef)) {
206
- typedef.type = new _mongoose.default.Schema(attributes);
232
+ applyExtensions(attributes);
207
233
  if (type === 'Array') {
208
- typedef.type = [typedef.type];
234
+ typedef.type = [attributes];
235
+ } else if (type === 'Object') {
236
+ typedef.type = new _mongoose.default.Schema(attributes);
209
237
  }
210
- }
211
- if (Array.isArray(typedef.type)) {
212
- applyArrayValidators(typedef);
213
- applyOptionHoisting(typedef);
238
+ delete typedef['attributes'];
214
239
  }
215
240
  }
216
241
 
@@ -224,32 +249,42 @@ function isExtendedSyntax(typedef) {
224
249
  type,
225
250
  attributes
226
251
  } = typedef;
227
- return attributes && (type === 'Object' || type === 'Array');
252
+ if (!attributes) {
253
+ return false;
254
+ }
255
+ return type === 'Object' || type === 'Array' || type === 'Scope';
228
256
  }
229
257
  function isScopeExtension(arg) {
230
258
  return (0, _utils.isSchemaTypedef)(arg) && arg.type === 'Scope';
231
259
  }
232
- function applyScopeExtension(typedef, definition) {
260
+ function applyScopeExtension(typedef, parent, name) {
233
261
  const {
234
262
  type,
235
263
  attributes,
236
- ...options
264
+ ...rest
237
265
  } = typedef;
238
- for (let [key, val] of Object.entries(normalizeAttributes(attributes))) {
239
- if ((0, _utils.isSchemaTypedef)(val)) {
240
- val = {
241
- ...val,
242
- ...options
266
+ for (let [key, value] of Object.entries(attributes)) {
267
+ if ((0, _utils.isSchemaTypedef)(value)) {
268
+ // If the child is a typedef then apply
269
+ // options directly to the field.
270
+ applyExtensions(value);
271
+ parent[key] = {
272
+ ...value,
273
+ ...rest
243
274
  };
244
275
  } else {
245
- val = {
276
+ // If the child is a nested object then
277
+ // need to use extended object syntax.
278
+ const typedef = {
246
279
  type: 'Object',
247
- attributes: val,
248
- ...options
280
+ attributes: value,
281
+ ...rest
249
282
  };
283
+ applyExtensions(typedef);
284
+ parent[key] = typedef;
250
285
  }
251
- definition[key] = attributesToMongoose(val);
252
286
  }
287
+ delete parent[name];
253
288
  }
254
289
 
255
290
  // Extended tuple syntax. Return mixed type and set validator.
@@ -281,6 +316,16 @@ function applyDateExtension(typedef) {
281
316
  }
282
317
  }
283
318
 
319
+ // Apply custom mongoose validation by name.
320
+ function applyValidateExtension(typedef) {
321
+ const {
322
+ validate
323
+ } = typedef;
324
+ if (typeof validate === 'string') {
325
+ typedef.validate = (0, _validation.getNamedValidator)(typedef.validate);
326
+ }
327
+ }
328
+
284
329
  // Intercepts "unique" options and changes to "softUnique".
285
330
  function applyUniqueExtension(typedef) {
286
331
  if (typedef.unique === true) {
@@ -320,12 +365,9 @@ function validateMaxLength(max) {
320
365
  }
321
366
  };
322
367
  }
323
- function chain(fn1, fn2) {
324
- return (...args) => {
325
- fn1?.(...args);
326
- fn2?.(...args);
327
- };
328
- }
368
+
369
+ // Regex Parsing
370
+
329
371
  const REG_MATCH = /^\/(.+)\/(\w+)$/;
330
372
  function parseRegExp(str) {
331
373
  const match = str.match(REG_MATCH);
@@ -334,4 +376,22 @@ function parseRegExp(str) {
334
376
  }
335
377
  const [, source, flags] = match;
336
378
  return RegExp(source, flags);
379
+ }
380
+
381
+ // Utils
382
+
383
+ function camelUpper(str) {
384
+ return (0, _lodash.capitalize)((0, _lodash.camelCase)(str));
385
+ }
386
+ function isObjectIdType(type) {
387
+ return type === 'ObjectId' || type === _mongoose.default.Schema.Types.ObjectId;
388
+ }
389
+ function isMongooseType(type) {
390
+ return !!_mongoose.default.Schema.Types[type];
391
+ }
392
+ function chain(fn1, fn2) {
393
+ return (...args) => {
394
+ fn1?.(...args);
395
+ fn2?.(...args);
396
+ };
337
397
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrockio/model",
3
- "version": "0.18.1",
3
+ "version": "0.18.2",
4
4
  "description": "Bedrock utilities for model creation.",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/cache.js CHANGED
@@ -7,33 +7,34 @@ const definitionMap = new Map();
7
7
 
8
8
  mongoose.plugin(cacheSyncPlugin);
9
9
 
10
- export function applyCache(schema, definition) {
11
- definitionMap.set(schema, definition);
10
+ export function addCacheFields(definition) {
11
+ const { cache } = definition;
12
12
 
13
- if (!definition.cache) {
13
+ if (!cache) {
14
14
  return;
15
15
  }
16
16
 
17
- createCacheFields(schema, definition);
18
- applyStaticMethods(schema, definition);
19
- applyCacheHook(schema, definition);
20
- }
21
-
22
- function createCacheFields(schema, definition) {
23
- for (let [cachedField, def] of Object.entries(definition.cache)) {
17
+ for (let [cachedField, def] of Object.entries(cache)) {
24
18
  const { type, path, ...rest } = def;
25
-
26
- schema.add({
27
- [cachedField]: type,
28
- });
29
- schema.obj[cachedField] = {
30
- ...rest,
19
+ definition.attributes[cachedField] = {
31
20
  type,
21
+ ...rest,
32
22
  writeAccess: 'none',
33
23
  };
34
24
  }
35
25
  }
36
26
 
27
+ export function applyCache(schema, definition) {
28
+ definitionMap.set(schema, definition);
29
+
30
+ if (!definition.cache) {
31
+ return;
32
+ }
33
+
34
+ applyStaticMethods(schema, definition);
35
+ applyCacheHook(schema, definition);
36
+ }
37
+
37
38
  function applyStaticMethods(schema, definition) {
38
39
  schema.static('syncCacheFields', async function syncCacheFields() {
39
40
  assertIncludeModule(this);
@@ -6,6 +6,21 @@ import { getInnerField } from './utils';
6
6
 
7
7
  const { ObjectId: SchemaObjectId } = mongoose.Schema.Types;
8
8
 
9
+ export function addDeletedFields(definition) {
10
+ let { onDelete: deleteHooks } = definition;
11
+
12
+ if (!deleteHooks) {
13
+ return;
14
+ }
15
+
16
+ definition.attributes['deletedRefs'] = [
17
+ {
18
+ _id: 'ObjectId',
19
+ ref: 'String',
20
+ },
21
+ ];
22
+ }
23
+
9
24
  export function applyDeleteHooks(schema, definition) {
10
25
  let { onDelete: deleteHooks } = definition;
11
26
 
@@ -43,15 +58,6 @@ export function applyDeleteHooks(schema, definition) {
43
58
  await restoreReferences(this, cleanHooks);
44
59
  await restoreFn.apply(this, arguments);
45
60
  });
46
-
47
- schema.add({
48
- deletedRefs: [
49
- {
50
- _id: 'ObjectId',
51
- ref: 'String',
52
- },
53
- ],
54
- });
55
61
  }
56
62
 
57
63
  // Clean Hook
package/src/schema.js CHANGED
@@ -1,10 +1,10 @@
1
- import { camelCase, capitalize, isPlainObject, pick } from 'lodash';
1
+ import { camelCase, capitalize, isPlainObject, last, pick } from 'lodash';
2
2
  import mongoose from 'mongoose';
3
3
 
4
4
  import { applyAssign } from './assign';
5
- import { applyCache } from './cache';
5
+ import { addCacheFields, applyCache } from './cache';
6
6
  import { applyClone } from './clone';
7
- import { applyDeleteHooks } from './delete-hooks';
7
+ import { addDeletedFields, applyDeleteHooks } from './delete-hooks';
8
8
  import { applyDisallowed } from './disallowed';
9
9
  import { applyExport } from './export';
10
10
  import { applyHydrate } from './hydrate';
@@ -32,28 +32,32 @@ import {
32
32
  * @returns mongoose.Schema
33
33
  */
34
34
  export function createSchema(definition, options = {}) {
35
- const schema = new mongoose.Schema(
36
- attributesToMongoose({
37
- ...definition.attributes,
38
-
39
- // Although timestamps are being set below, we still need to add
40
- // them to the schema so that validation can be generated for them,
41
- // namely in getSearchValidation.
42
- createdAt: 'Date',
43
- updatedAt: 'Date',
44
- deletedAt: 'Date',
45
- deleted: {
46
- type: 'Boolean',
47
- default: false,
48
- },
49
- }),
50
- {
51
- timestamps: true,
52
- toJSON: serializeOptions,
53
- toObject: serializeOptions,
54
- ...options,
35
+ addCacheFields(definition);
36
+ addDeletedFields(definition);
37
+
38
+ const attributes = normalizeAttributes({
39
+ ...definition.attributes,
40
+
41
+ // Although timestamps are being set below, we still need to add
42
+ // them to the schema so that validation can be generated for them,
43
+ // namely in getSearchValidation.
44
+ createdAt: 'Date',
45
+ updatedAt: 'Date',
46
+ deletedAt: 'Date',
47
+ deleted: {
48
+ type: 'Boolean',
49
+ default: false,
55
50
  },
56
- );
51
+ });
52
+
53
+ applyExtensions(attributes);
54
+
55
+ const schema = new mongoose.Schema(attributes, {
56
+ timestamps: true,
57
+ toJSON: serializeOptions,
58
+ toObject: serializeOptions,
59
+ ...options,
60
+ });
57
61
 
58
62
  // Soft Delete needs to be applied
59
63
  // first for hooks to work correctly.
@@ -80,17 +84,9 @@ export function normalizeAttributes(arg, path = []) {
80
84
  return arg;
81
85
  } else if (typeof arg === 'function') {
82
86
  throw new Error('Native functions are not allowed as types.');
83
- } else if (typeof arg === 'string') {
84
- return normalizeSchemaTypedef({ type: arg }, path);
85
- } else if (Array.isArray(arg)) {
86
- return normalizeSchemaTypedef({ type: arg }, path);
87
+ } else if (isTypedefInput(arg)) {
88
+ return normalizeTypedef(arg, path);
87
89
  } else if (typeof arg === 'object') {
88
- assertRefs(arg, path);
89
-
90
- if (isSchemaTypedef(arg)) {
91
- return normalizeSchemaTypedef(arg, path);
92
- }
93
-
94
90
  const attributes = {};
95
91
  for (let [key, val] of Object.entries(arg)) {
96
92
  attributes[key] = normalizeAttributes(val, [...path, key]);
@@ -99,78 +95,77 @@ export function normalizeAttributes(arg, path = []) {
99
95
  }
100
96
  }
101
97
 
102
- function normalizeSchemaTypedef(typedef, path) {
103
- const { type } = typedef;
98
+ function normalizeArrayAttributes(arr, path) {
99
+ return arr.map((el, i) => {
100
+ return normalizeAttributes(el, [...path, i]);
101
+ });
102
+ }
104
103
 
105
- if (Array.isArray(type)) {
106
- typedef.type = normalizeArrayAttributes(type, path);
107
- } else if (typeof type === 'object') {
108
- typedef.type = normalizeAttributes(type, path);
109
- } else {
110
- assertSchemaType(type, path);
104
+ function normalizeTypedef(arg, path) {
105
+ const typedef = arg.type ? arg : { type: arg };
106
+
107
+ if (Array.isArray(typedef.type)) {
108
+ // Normalize all inner fields.
109
+ typedef.type = normalizeArrayAttributes(typedef.type, path);
110
+ } else if (typeof typedef.type === 'object') {
111
+ // Normalize literal "type" field.
112
+ typedef.type = normalizeAttributes(typedef.type, path);
113
+ } else if (isExtendedSyntax(typedef)) {
114
+ // Normalize extended syntax: type "Object" or "Array".
115
+ typedef.attributes = normalizeAttributes(typedef.attributes, path);
111
116
  }
112
117
 
113
118
  if (typedef.type === 'String') {
119
+ // Auto-apply trim to string fields.
114
120
  typedef.trim ??= true;
121
+
122
+ if (typeof typedef.match === 'string') {
123
+ // Convert string RegExp so that
124
+ // it can be expressed in JSON.
125
+ typedef.match = parseRegExp(typedef.match);
126
+ }
115
127
  }
116
128
 
117
- return typedef;
118
- }
129
+ assertSchemaType(typedef, path);
130
+ assertObjectRefs(typedef, path);
119
131
 
120
- function normalizeArrayAttributes(arr, path) {
121
- return arr.map((el, i) => {
122
- return normalizeAttributes(el, [...path, i]);
123
- });
132
+ return typedef;
124
133
  }
125
134
 
126
- function attributesToMongoose(attributes) {
127
- if (typeof attributes === 'string') {
128
- return attributes;
129
- } else if (Array.isArray(attributes)) {
130
- return attributes.map(attributesToMongoose);
131
- }
132
-
133
- attributes = normalizeAttributes(attributes);
134
-
135
- let definition = {};
136
-
137
- const isTypedef = isSchemaTypedef(attributes);
138
-
139
- for (let [key, val] of Object.entries(attributes)) {
140
- const type = typeof val;
141
- if (isTypedef) {
142
- if (key === 'type' && type !== 'function') {
143
- val = attributesToMongoose(val);
144
- } else if (key === 'match' && type === 'string') {
145
- // Convert match field to RegExp that cannot be expressed in JSON.
146
- val = parseRegExp(val);
147
- } else if (key === 'validate' && type === 'string') {
148
- // Allow custom mongoose validation function that derives from the schema.
149
- val = getNamedValidator(val);
150
- } else if (key === 'attributes' && type === 'object') {
151
- val = attributesToMongoose(val);
152
- }
153
- } else if (Array.isArray(val)) {
154
- val = val.map(attributesToMongoose);
155
- } else if (isPlainObject(val)) {
156
- if (isScopeExtension(val)) {
157
- applyScopeExtension(val, definition);
158
- continue;
159
- } else {
160
- val = attributesToMongoose(val);
161
- }
162
- }
163
- definition[key] = val;
135
+ function isTypedefInput(arg) {
136
+ if (typeof arg === 'string') {
137
+ // "Number" as shorthand for a typedef.
138
+ return true;
139
+ } else if (Array.isArray(arg)) {
140
+ // Array signals an array field with inner schema.
141
+ return true;
142
+ } else if (hasLiteralTypeField(arg)) {
143
+ // An object with a literal "type" field.
144
+ return false;
164
145
  }
146
+ return isSchemaTypedef(arg);
147
+ }
165
148
 
166
- if (isTypedef) {
167
- applyExtensions(definition);
149
+ // Detects input like:
150
+ // {
151
+ // "type": "String",
152
+ // "name": "String",
153
+ // }
154
+ // Which is not intended to be a typedef.
155
+ function hasLiteralTypeField(arg) {
156
+ const { type, ...rest } = arg || {};
157
+
158
+ if (!isMongooseType(type)) {
159
+ return false;
168
160
  }
169
161
 
170
- return definition;
162
+ return Object.values(rest).some((key) => {
163
+ return isMongooseType(key);
164
+ });
171
165
  }
172
166
 
173
- function assertSchemaType(type, path) {
167
+ function assertSchemaType(typedef, path) {
168
+ const { type } = typedef;
174
169
  if (typeof type === 'string') {
175
170
  if (!isMongooseType(type)) {
176
171
  const p = path.join('.');
@@ -184,46 +179,68 @@ function assertSchemaType(type, path) {
184
179
  }
185
180
  }
186
181
 
187
- function assertRefs(field, path) {
188
- const { type, ref, refPath } = field;
182
+ function assertObjectRefs(typedef, path) {
183
+ const { type, ref } = typedef;
189
184
  const p = path.join('.');
190
- if (isObjectIdType(type) && !ref && !refPath) {
185
+
186
+ if (requiresRef(typedef, path)) {
191
187
  throw new Error(`Ref must be passed for "${p}".`);
188
+ // TODO: what is the middle part doing here??
192
189
  } else if (ref && !isMongooseType(ref) && !isObjectIdType(type)) {
193
190
  throw new Error(`Ref field "${p}" must be type "ObjectId".`);
194
191
  }
195
192
  }
196
193
 
197
- function camelUpper(str) {
198
- return capitalize(camelCase(str));
199
- }
194
+ function requiresRef(typedef, path) {
195
+ const { type, ref, refPath } = typedef;
200
196
 
201
- function isObjectIdType(type) {
202
- return type === 'ObjectId' || type === mongoose.Schema.Types.ObjectId;
203
- }
197
+ // Allow "_id" to not have a ref for the
198
+ // delete hooks module to function.
199
+ if (last(path) === '_id') {
200
+ return false;
201
+ }
204
202
 
205
- function isMongooseType(type) {
206
- return !!mongoose.Schema.Types[type];
203
+ return isObjectIdType(type) && !ref && !refPath;
207
204
  }
208
205
 
209
- function applyExtensions(typedef) {
210
- applySyntaxExtensions(typedef);
211
- applyUniqueExtension(typedef);
212
- applyTupleExtension(typedef);
213
- applyDateExtension(typedef);
206
+ // Extensions
207
+
208
+ function applyExtensions(arg) {
209
+ if (isSchemaTypedef(arg)) {
210
+ applySyntaxExtensions(arg);
211
+ applyValidateExtension(arg);
212
+ applyUniqueExtension(arg);
213
+ applyTupleExtension(arg);
214
+ applyDateExtension(arg);
215
+
216
+ if (Array.isArray(arg.type)) {
217
+ for (let field of arg.type) {
218
+ applyExtensions(field);
219
+ }
220
+ applyArrayValidators(arg);
221
+ applyOptionHoisting(arg);
222
+ }
223
+ } else if (isPlainObject(arg)) {
224
+ for (let [key, value] of Object.entries(arg)) {
225
+ if (isScopeExtension(value)) {
226
+ applyScopeExtension(value, arg, key);
227
+ } else {
228
+ applyExtensions(value);
229
+ }
230
+ }
231
+ }
214
232
  }
215
233
 
216
234
  function applySyntaxExtensions(typedef) {
217
235
  const { type, attributes } = typedef;
218
236
  if (isExtendedSyntax(typedef)) {
219
- typedef.type = new mongoose.Schema(attributes);
237
+ applyExtensions(attributes);
220
238
  if (type === 'Array') {
221
- typedef.type = [typedef.type];
239
+ typedef.type = [attributes];
240
+ } else if (type === 'Object') {
241
+ typedef.type = new mongoose.Schema(attributes);
222
242
  }
223
- }
224
- if (Array.isArray(typedef.type)) {
225
- applyArrayValidators(typedef);
226
- applyOptionHoisting(typedef);
243
+ delete typedef['attributes'];
227
244
  }
228
245
  }
229
246
 
@@ -235,30 +252,42 @@ function applyOptionHoisting(typedef) {
235
252
 
236
253
  function isExtendedSyntax(typedef) {
237
254
  const { type, attributes } = typedef;
238
- return attributes && (type === 'Object' || type === 'Array');
255
+ if (!attributes) {
256
+ return false;
257
+ }
258
+ return type === 'Object' || type === 'Array' || type === 'Scope';
239
259
  }
240
260
 
241
261
  function isScopeExtension(arg) {
242
262
  return isSchemaTypedef(arg) && arg.type === 'Scope';
243
263
  }
244
264
 
245
- function applyScopeExtension(typedef, definition) {
246
- const { type, attributes, ...options } = typedef;
247
- for (let [key, val] of Object.entries(normalizeAttributes(attributes))) {
248
- if (isSchemaTypedef(val)) {
249
- val = {
250
- ...val,
251
- ...options,
265
+ function applyScopeExtension(typedef, parent, name) {
266
+ const { type, attributes, ...rest } = typedef;
267
+
268
+ for (let [key, value] of Object.entries(attributes)) {
269
+ if (isSchemaTypedef(value)) {
270
+ // If the child is a typedef then apply
271
+ // options directly to the field.
272
+ applyExtensions(value);
273
+ parent[key] = {
274
+ ...value,
275
+ ...rest,
252
276
  };
253
277
  } else {
254
- val = {
278
+ // If the child is a nested object then
279
+ // need to use extended object syntax.
280
+ const typedef = {
255
281
  type: 'Object',
256
- attributes: val,
257
- ...options,
282
+ attributes: value,
283
+ ...rest,
258
284
  };
285
+ applyExtensions(typedef);
286
+ parent[key] = typedef;
259
287
  }
260
- definition[key] = attributesToMongoose(val);
261
288
  }
289
+
290
+ delete parent[name];
262
291
  }
263
292
 
264
293
  // Extended tuple syntax. Return mixed type and set validator.
@@ -286,6 +315,15 @@ function applyDateExtension(typedef) {
286
315
  }
287
316
  }
288
317
 
318
+ // Apply custom mongoose validation by name.
319
+ function applyValidateExtension(typedef) {
320
+ const { validate } = typedef;
321
+
322
+ if (typeof validate === 'string') {
323
+ typedef.validate = getNamedValidator(typedef.validate);
324
+ }
325
+ }
326
+
289
327
  // Intercepts "unique" options and changes to "softUnique".
290
328
  function applyUniqueExtension(typedef) {
291
329
  if (typedef.unique === true) {
@@ -325,12 +363,7 @@ function validateMaxLength(max) {
325
363
  };
326
364
  }
327
365
 
328
- function chain(fn1, fn2) {
329
- return (...args) => {
330
- fn1?.(...args);
331
- fn2?.(...args);
332
- };
333
- }
366
+ // Regex Parsing
334
367
 
335
368
  const REG_MATCH = /^\/(.+)\/(\w+)$/;
336
369
 
@@ -342,3 +375,24 @@ function parseRegExp(str) {
342
375
  const [, source, flags] = match;
343
376
  return RegExp(source, flags);
344
377
  }
378
+
379
+ // Utils
380
+
381
+ function camelUpper(str) {
382
+ return capitalize(camelCase(str));
383
+ }
384
+
385
+ function isObjectIdType(type) {
386
+ return type === 'ObjectId' || type === mongoose.Schema.Types.ObjectId;
387
+ }
388
+
389
+ function isMongooseType(type) {
390
+ return !!mongoose.Schema.Types[type];
391
+ }
392
+
393
+ function chain(fn1, fn2) {
394
+ return (...args) => {
395
+ fn1?.(...args);
396
+ fn2?.(...args);
397
+ };
398
+ }
package/types/cache.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ export function addCacheFields(definition: any): void;
1
2
  export function applyCache(schema: any, definition: any): void;
2
3
  //# sourceMappingURL=cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.js"],"names":[],"mappings":"AASA,+DAUC"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.js"],"names":[],"mappings":"AASA,sDAeC;AAED,+DASC"}
@@ -1,2 +1,3 @@
1
+ export function addDeletedFields(definition: any): void;
1
2
  export function applyDeleteHooks(schema: any, definition: any): void;
2
3
  //# sourceMappingURL=delete-hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"delete-hooks.d.ts","sourceRoot":"","sources":["../src/delete-hooks.js"],"names":[],"mappings":"AAQA,qEA8CC"}
1
+ {"version":3,"file":"delete-hooks.d.ts","sourceRoot":"","sources":["../src/delete-hooks.js"],"names":[],"mappings":"AAQA,wDAaC;AAED,qEAqCC"}
package/types/schema.d.ts CHANGED
@@ -19,7 +19,6 @@ export function createSchema(definition: object, options?: mongoose.SchemaOption
19
19
  };
20
20
  collation?: mongoose.mongo.CollationOptions;
21
21
  collectionOptions?: mongoose.mongo.CreateCollectionOptions;
22
- lean?: boolean | mongoose.LeanOptions;
23
22
  timeseries?: mongoose.mongo.TimeSeriesCollectionOptions;
24
23
  expireAfterSeconds?: number;
25
24
  expires?: number | string;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.js"],"names":[],"mappings":"AAyBA;;;;;;;GAOG;AACH,yCAJW,MAAM,YACN,QAAQ,CAAC,aAAa;;;;;;;YA+CpB,CAAC;WAAa,CAAC;mBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;SA4Hf,CAAC;gBAA2B,CAAC;SAGhE,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAjIC;AAED,iEAsBC;qBAlGoB,UAAU"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.js"],"names":[],"mappings":"AAyBA;;;;;;;GAOG;AACH,yCAJW,MAAM,YACN,QAAQ,CAAC,aAAa;;;;;;;YA2C7B,CAAC;WAAa,CAAC;mBACH,CAAC;;;;;;;;;;;;;;;;;;;;;;;SAgIb,CAAC;gBAA4B,CAAA;SAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA3H5C;AAED,iEAcC;qBA9FoB,UAAU"}