@bedrockio/model 0.2.20 → 0.2.22

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.
@@ -4,22 +4,13 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.hasAccess = hasAccess;
7
- exports.hasReadAccess = hasReadAccess;
8
- exports.hasWriteAccess = hasWriteAccess;
9
7
  var _errors = require("./errors");
10
8
  var _warn = _interopRequireDefault(require("./warn"));
11
9
  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
-
19
10
  /**
20
11
  * @param {string|string[]} allowed
21
12
  */
22
- function hasAccess(type, allowed = 'all', options = {}) {
13
+ function hasAccess(allowed = 'all', options = {}) {
23
14
  if (allowed === 'all') {
24
15
  return true;
25
16
  } else if (allowed === 'none') {
@@ -6,7 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.INCLUDE_FIELD_SCHEMA = void 0;
7
7
  exports.applyInclude = applyInclude;
8
8
  exports.checkSelects = checkSelects;
9
- exports.getIncludes = getIncludes;
9
+ exports.getDocumentParams = getDocumentParams;
10
+ exports.getParams = getParams;
10
11
  var _mongoose = _interopRequireDefault(require("mongoose"));
11
12
  var _lodash = require("lodash");
12
13
  var _yada = _interopRequireDefault(require("@bedrockio/yada"));
@@ -36,7 +37,7 @@ function applyInclude(schema) {
36
37
  const {
37
38
  select,
38
39
  populate
39
- } = getQueryIncludes(this, filter.include);
40
+ } = getQueryParams(this, filter.include);
40
41
  this.select(select);
41
42
  this.populate(populate);
42
43
  delete filter.include;
@@ -78,7 +79,7 @@ function applyInclude(schema) {
78
79
  const {
79
80
  select,
80
81
  populate
81
- } = getDocumentIncludes(this, include);
82
+ } = getDocumentParams(this, include);
82
83
  this.$locals.select = select;
83
84
  this.$locals.populate = populate;
84
85
  }
@@ -103,7 +104,7 @@ function applyInclude(schema) {
103
104
  const {
104
105
  select,
105
106
  populate
106
- } = getDocumentIncludes(this, include);
107
+ } = getDocumentParams(this, include);
107
108
  this.$locals.select = select;
108
109
  await this.populate(populate);
109
110
  }
@@ -147,16 +148,32 @@ function checkSelects(doc, ret) {
147
148
  }
148
149
 
149
150
  // Exported for testing.
150
- function getIncludes(modelName, arg) {
151
+ function getParams(modelName, arg) {
151
152
  const paths = Array.isArray(arg) ? arg : [arg];
152
153
  const node = pathsToNode(paths, modelName);
153
154
  return nodeToPopulates(node);
154
155
  }
155
- function getQueryIncludes(query, arg) {
156
- return getIncludes(query.model.modelName, arg);
156
+
157
+ // Exported for testing.
158
+ function getDocumentParams(doc, arg) {
159
+ const params = getParams(doc.constructor.modelName, arg);
160
+ params.populate = params.populate.filter(p => {
161
+ return !isDocumentPopulated(doc, p);
162
+ });
163
+ return params;
164
+ }
165
+ function isDocumentPopulated(doc, params) {
166
+ if (doc.populated(params.path)) {
167
+ const sub = doc.get(params.path);
168
+ return params.populate.every(p => {
169
+ return isDocumentPopulated(sub, p);
170
+ });
171
+ } else {
172
+ return false;
173
+ }
157
174
  }
158
- function getDocumentIncludes(doc, arg) {
159
- return getIncludes(doc.constructor.modelName, arg);
175
+ function getQueryParams(query, arg) {
176
+ return getParams(query.model.modelName, arg);
160
177
  }
161
178
 
162
179
  // Note that:
@@ -181,6 +198,12 @@ function nodeToPopulates(node) {
181
198
  populate
182
199
  };
183
200
  }
201
+
202
+ // Null serves as a flag that the key terminates
203
+ // the branch and this is a leaf node. Using null
204
+ // here as it's simple and serializes to JSON for
205
+ // easy inspection.
206
+ const LEAF_NODE = null;
184
207
  function pathsToNode(paths, modelName) {
185
208
  const node = {};
186
209
  for (let str of paths) {
@@ -228,7 +251,7 @@ function setNodePath(node, options) {
228
251
  // -user.name - Implies population of "user" but exclude "user.name",
229
252
  // so continue traversing into object when part is "user".
230
253
  if (isExact && exclude) {
231
- node['-' + key] = null;
254
+ node['-' + key] = LEAF_NODE;
232
255
  } else if (field.ref) {
233
256
  node[key] ||= {};
234
257
  setNodePath(node[key], {
@@ -239,7 +262,7 @@ function setNodePath(node, options) {
239
262
  });
240
263
  halt = true;
241
264
  } else if ((0, _utils.isSchemaTypedef)(field)) {
242
- node[key] = null;
265
+ node[key] = LEAF_NODE;
243
266
  }
244
267
  } else if (type === 'virtual') {
245
268
  const virtual = schema.virtual(key);
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.serializeOptions = void 0;
7
7
  var _lodash = require("lodash");
8
8
  var _include = require("./include");
9
- var _access = require("./access");
10
9
  var _utils = require("./utils");
10
+ var _access = require("./access");
11
11
  const DISALLOWED_FIELDS = ['deleted', 'deletedRefs'];
12
12
  const serializeOptions = exports.serializeOptions = {
13
13
  getters: true,
@@ -63,7 +63,7 @@ function isAllowedField(key, field, options) {
63
63
  readAccess
64
64
  } = (0, _utils.getField)(field, key);
65
65
  try {
66
- return (0, _access.hasReadAccess)(readAccess, options);
66
+ return (0, _access.hasAccess)(readAccess, options);
67
67
  } catch {
68
68
  return false;
69
69
  }
@@ -105,13 +105,13 @@ function applyValidation(schema, definition) {
105
105
  model: this,
106
106
  appendSchema,
107
107
  allowInclude,
108
+ allowUnset: true,
108
109
  skipRequired: true,
109
110
  stripUnknown: true,
110
111
  stripDeleted: true,
111
112
  stripTimestamps: true,
112
113
  allowExpandedRefs: true,
113
114
  requireWriteAccess: true,
114
- allowNullForPrimitives: true,
115
115
  ...(hasUnique && {
116
116
  assertUniqueOptions: {
117
117
  schema,
@@ -270,8 +270,8 @@ function getSchemaForTypedef(typedef, options = {}) {
270
270
  }
271
271
  if (isRequired(typedef, options)) {
272
272
  schema = schema.required();
273
- } else if (allowsNull(typedef, options)) {
274
- schema = _yada.default.allow(null, schema);
273
+ } else if (allowUnset(typedef, options)) {
274
+ schema = _yada.default.allow(null, '', schema);
275
275
  }
276
276
  if (typedef.default && options.allowDefaultTags) {
277
277
  // Tag the default value to allow OpenAPI description
@@ -379,15 +379,8 @@ function getSearchSchema(schema, type) {
379
379
  function isRequired(typedef, options) {
380
380
  return typedef.required && !typedef.default && !options.skipRequired;
381
381
  }
382
- function allowsNull(typedef, options) {
383
- if (!options.allowNullForPrimitives) {
384
- return false;
385
- }
386
- return !typedef.required && isPrimitiveTypedef(typedef);
387
- }
388
- const PRIMITIVE_TYPES = ['String', 'Number', 'Boolean'];
389
- function isPrimitiveTypedef(typedef) {
390
- return PRIMITIVE_TYPES.includes(typedef.type);
382
+ function allowUnset(typedef, options) {
383
+ return options.allowUnset && !typedef.required;
391
384
  }
392
385
  function isExcludedField(field, options) {
393
386
  if ((0, _utils.isSchemaTypedef)(field)) {
@@ -413,7 +406,7 @@ function validateAccess(type, schema, allowed, options) {
413
406
  const document = options[(0, _lodash.lowerFirst)(modelName)] || options['document'];
414
407
  let isAllowed;
415
408
  try {
416
- isAllowed = (0, _access.hasAccess)(type, allowed, {
409
+ isAllowed = (0, _access.hasAccess)(allowed, {
417
410
  ...options,
418
411
  document
419
412
  });
@@ -0,0 +1,14 @@
1
+ // Note this file MUST be a CJS module as it will be required
2
+ // by @shelf/jest-mongodb. Note also that the binary version
3
+ // does not match the mongodb driver version.
4
+
5
+ module.exports = {
6
+ mongodbMemoryServerOptions: {
7
+ binary: {
8
+ version: '6.0.2',
9
+ skipMD5: true,
10
+ },
11
+ autoStart: false,
12
+ instance: {},
13
+ },
14
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrockio/model",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "description": "Bedrock utilities for model creation.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -30,7 +30,7 @@
30
30
  "lodash": "^4.17.21"
31
31
  },
32
32
  "peerDependencies": {
33
- "@bedrockio/yada": "^1.0.39",
33
+ "@bedrockio/yada": "^1.0.40",
34
34
  "mongoose": "^7.6.4"
35
35
  },
36
36
  "devDependencies": {
@@ -38,13 +38,13 @@
38
38
  "@babel/core": "^7.20.12",
39
39
  "@babel/preset-env": "^7.20.2",
40
40
  "@bedrockio/prettier-config": "^1.0.2",
41
- "@bedrockio/yada": "^1.0.39",
42
- "@shelf/jest-mongodb": "^4.1.7",
41
+ "@bedrockio/yada": "^1.0.40",
42
+ "@shelf/jest-mongodb": "^4.2.0",
43
43
  "eslint": "^8.33.0",
44
44
  "eslint-plugin-bedrock": "^1.0.26",
45
45
  "jest": "^29.4.1",
46
46
  "jest-environment-node": "^29.4.1",
47
- "mongodb": "^6.2.0",
47
+ "mongodb": "^6.5.0",
48
48
  "mongoose": "^7.6.4",
49
49
  "prettier-eslint": "^15.0.1",
50
50
  "typescript": "^4.9.5"
package/src/access.js CHANGED
@@ -1,18 +1,10 @@
1
1
  import { ImplementationError } from './errors';
2
2
  import warn from './warn';
3
3
 
4
- export function hasReadAccess(allowed, options) {
5
- return hasAccess('read', allowed, options);
6
- }
7
-
8
- export function hasWriteAccess(allowed, options) {
9
- return hasAccess('write', allowed, options);
10
- }
11
-
12
4
  /**
13
5
  * @param {string|string[]} allowed
14
6
  */
15
- export function hasAccess(type, allowed = 'all', options = {}) {
7
+ export function hasAccess(allowed = 'all', options = {}) {
16
8
  if (allowed === 'all') {
17
9
  return true;
18
10
  } else if (allowed === 'none') {
package/src/include.js CHANGED
@@ -31,7 +31,7 @@ export function applyInclude(schema) {
31
31
  schema.pre(/^find/, function (next) {
32
32
  const filter = this.getFilter();
33
33
  if (filter.include) {
34
- const { select, populate } = getQueryIncludes(this, filter.include);
34
+ const { select, populate } = getQueryParams(this, filter.include);
35
35
  this.select(select);
36
36
  this.populate(populate);
37
37
  delete filter.include;
@@ -70,7 +70,7 @@ export function applyInclude(schema) {
70
70
  this.assign(rest);
71
71
 
72
72
  if (include) {
73
- const { select, populate } = getDocumentIncludes(this, include);
73
+ const { select, populate } = getDocumentParams(this, include);
74
74
  this.$locals.select = select;
75
75
  this.$locals.populate = populate;
76
76
  }
@@ -91,7 +91,7 @@ export function applyInclude(schema) {
91
91
  // during serialization.
92
92
  schema.method('include', async function include(include) {
93
93
  if (include) {
94
- const { select, populate } = getDocumentIncludes(this, include);
94
+ const { select, populate } = getDocumentParams(this, include);
95
95
  this.$locals.select = select;
96
96
  await this.populate(populate);
97
97
  }
@@ -133,18 +133,34 @@ export function checkSelects(doc, ret) {
133
133
  }
134
134
 
135
135
  // Exported for testing.
136
- export function getIncludes(modelName, arg) {
136
+ export function getParams(modelName, arg) {
137
137
  const paths = Array.isArray(arg) ? arg : [arg];
138
138
  const node = pathsToNode(paths, modelName);
139
139
  return nodeToPopulates(node);
140
140
  }
141
141
 
142
- function getQueryIncludes(query, arg) {
143
- return getIncludes(query.model.modelName, arg);
142
+ // Exported for testing.
143
+ export function getDocumentParams(doc, arg) {
144
+ const params = getParams(doc.constructor.modelName, arg);
145
+ params.populate = params.populate.filter((p) => {
146
+ return !isDocumentPopulated(doc, p);
147
+ });
148
+ return params;
149
+ }
150
+
151
+ function isDocumentPopulated(doc, params) {
152
+ if (doc.populated(params.path)) {
153
+ const sub = doc.get(params.path);
154
+ return params.populate.every((p) => {
155
+ return isDocumentPopulated(sub, p);
156
+ });
157
+ } else {
158
+ return false;
159
+ }
144
160
  }
145
161
 
146
- function getDocumentIncludes(doc, arg) {
147
- return getIncludes(doc.constructor.modelName, arg);
162
+ function getQueryParams(query, arg) {
163
+ return getParams(query.model.modelName, arg);
148
164
  }
149
165
 
150
166
  // Note that:
@@ -170,6 +186,12 @@ function nodeToPopulates(node) {
170
186
  };
171
187
  }
172
188
 
189
+ // Null serves as a flag that the key terminates
190
+ // the branch and this is a leaf node. Using null
191
+ // here as it's simple and serializes to JSON for
192
+ // easy inspection.
193
+ const LEAF_NODE = null;
194
+
173
195
  function pathsToNode(paths, modelName) {
174
196
  const node = {};
175
197
  for (let str of paths) {
@@ -214,7 +236,7 @@ function setNodePath(node, options) {
214
236
  // -user.name - Implies population of "user" but exclude "user.name",
215
237
  // so continue traversing into object when part is "user".
216
238
  if (isExact && exclude) {
217
- node['-' + key] = null;
239
+ node['-' + key] = LEAF_NODE;
218
240
  } else if (field.ref) {
219
241
  node[key] ||= {};
220
242
  setNodePath(node[key], {
@@ -225,7 +247,7 @@ function setNodePath(node, options) {
225
247
  });
226
248
  halt = true;
227
249
  } else if (isSchemaTypedef(field)) {
228
- node[key] = null;
250
+ node[key] = LEAF_NODE;
229
251
  }
230
252
  } else if (type === 'virtual') {
231
253
  const virtual = schema.virtual(key);
@@ -1,8 +1,8 @@
1
1
  import { isPlainObject } from 'lodash';
2
2
 
3
3
  import { checkSelects } from './include';
4
- import { hasReadAccess } from './access';
5
4
  import { getField, getInnerField } from './utils';
5
+ import { hasAccess } from './access';
6
6
 
7
7
  const DISALLOWED_FIELDS = ['deleted', 'deletedRefs'];
8
8
 
@@ -60,7 +60,7 @@ function isAllowedField(key, field, options) {
60
60
  } else {
61
61
  const { readAccess } = getField(field, key);
62
62
  try {
63
- return hasReadAccess(readAccess, options);
63
+ return hasAccess(readAccess, options);
64
64
  } catch {
65
65
  return false;
66
66
  }
package/src/validation.js CHANGED
@@ -118,13 +118,13 @@ export function applyValidation(schema, definition) {
118
118
  model: this,
119
119
  appendSchema,
120
120
  allowInclude,
121
+ allowUnset: true,
121
122
  skipRequired: true,
122
123
  stripUnknown: true,
123
124
  stripDeleted: true,
124
125
  stripTimestamps: true,
125
126
  allowExpandedRefs: true,
126
127
  requireWriteAccess: true,
127
- allowNullForPrimitives: true,
128
128
  ...(hasUnique && {
129
129
  assertUniqueOptions: {
130
130
  schema,
@@ -283,8 +283,8 @@ function getSchemaForTypedef(typedef, options = {}) {
283
283
 
284
284
  if (isRequired(typedef, options)) {
285
285
  schema = schema.required();
286
- } else if (allowsNull(typedef, options)) {
287
- schema = yd.allow(null, schema);
286
+ } else if (allowUnset(typedef, options)) {
287
+ schema = yd.allow(null, '', schema);
288
288
  }
289
289
 
290
290
  if (typedef.default && options.allowDefaultTags) {
@@ -434,17 +434,8 @@ function isRequired(typedef, options) {
434
434
  return typedef.required && !typedef.default && !options.skipRequired;
435
435
  }
436
436
 
437
- function allowsNull(typedef, options) {
438
- if (!options.allowNullForPrimitives) {
439
- return false;
440
- }
441
- return !typedef.required && isPrimitiveTypedef(typedef);
442
- }
443
-
444
- const PRIMITIVE_TYPES = ['String', 'Number', 'Boolean'];
445
-
446
- function isPrimitiveTypedef(typedef) {
447
- return PRIMITIVE_TYPES.includes(typedef.type);
437
+ function allowUnset(typedef, options) {
438
+ return options.allowUnset && !typedef.required;
448
439
  }
449
440
 
450
441
  function isExcludedField(field, options) {
@@ -473,7 +464,7 @@ function validateAccess(type, schema, allowed, options) {
473
464
 
474
465
  let isAllowed;
475
466
  try {
476
- isAllowed = hasAccess(type, allowed, {
467
+ isAllowed = hasAccess(allowed, {
477
468
  ...options,
478
469
  document,
479
470
  });
package/types/access.d.ts CHANGED
@@ -1,7 +1,5 @@
1
- export function hasReadAccess(allowed: any, options: any): boolean;
2
- export function hasWriteAccess(allowed: any, options: any): boolean;
3
1
  /**
4
2
  * @param {string|string[]} allowed
5
3
  */
6
- export function hasAccess(type: any, allowed?: string | string[], options?: {}): boolean;
4
+ export function hasAccess(allowed?: string | string[], options?: {}): boolean;
7
5
  //# sourceMappingURL=access.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.js"],"names":[],"mappings":"AAGA,mEAEC;AAED,oEAEC;AAED;;GAEG;AACH,+CAFW,MAAM,GAAC,MAAM,EAAE,yBA2BzB"}
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.js"],"names":[],"mappings":"AAGA;;GAEG;AACH,oCAFW,MAAM,GAAC,MAAM,EAAE,yBA2BzB"}
@@ -1,6 +1,7 @@
1
1
  export function applyInclude(schema: any): void;
2
2
  export function checkSelects(doc: any, ret: any): void;
3
- export function getIncludes(modelName: any, arg: any): any;
3
+ export function getParams(modelName: any, arg: any): any;
4
+ export function getDocumentParams(doc: any, arg: any): any;
4
5
  export const INCLUDE_FIELD_SCHEMA: {
5
6
  setup: any;
6
7
  getFields: any;
@@ -1 +1 @@
1
- {"version":3,"file":"include.d.ts","sourceRoot":"","sources":["../src/include.js"],"names":[],"mappings":"AA2BA,gDAuEC;AAMD,uDA4BC;AAGD,2DAIC;AAvHD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKG"}
1
+ {"version":3,"file":"include.d.ts","sourceRoot":"","sources":["../src/include.js"],"names":[],"mappings":"AA2BA,gDAuEC;AAMD,uDA4BC;AAGD,yDAIC;AAGD,2DAMC;AAhID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKG"}
@@ -11,6 +11,7 @@ export function getTupleValidator(types: any): {
11
11
  };
12
12
  export const OBJECT_ID_SCHEMA: {
13
13
  required(): any;
14
+ allowEmpty(): any;
14
15
  length(length: number): any;
15
16
  min(length: number): any;
16
17
  max(length: number): any;
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"AAkFA,kDAEC;AAED,oEAqFC;AAsBD,wEAkBC;AAySD;;;EAEC;AAED;;;EAOC;AArfD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQK"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"AAkFA,kDAEC;AAED,oEAqFC;AAsBD,wEAkBC;AAgSD;;;EAEC;AAED;;;EAOC;AA5eD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQK"}
@@ -1,12 +0,0 @@
1
- const { devDependencies } = require('./package.json');
2
-
3
- module.exports = {
4
- mongodbMemoryServerOptions: {
5
- binary: {
6
- version: devDependencies.mongodb,
7
- skipMD5: true,
8
- },
9
- autoStart: false,
10
- instance: {},
11
- },
12
- };