@bedrockio/model 0.7.0 → 0.7.1

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/README.md CHANGED
@@ -1579,11 +1579,11 @@ user.assign(ctx.request.body);
1579
1579
  Object.assign(user, ctx.request.body);
1580
1580
  ```
1581
1581
 
1582
- This is functionally identical to `Object.assign` with the exception that
1583
- `ObjectId` reference fields can be unset by passing falsy values. This method is
1584
- provided as `undefined` cannot be represented in JSON which requires using
1585
- either a `null` or empty string, both of which would be stored in the database
1586
- if naively assigned with `Object.assign`.
1582
+ This is functionally identical to `Object.assign` with the exception that fields
1583
+ can be unset by passing falsy values. This method is provided as `undefined`
1584
+ cannot be represented in JSON which requires using either a `null` or empty
1585
+ string, both of which would be stored in the database if naively assigned with
1586
+ `Object.assign`.
1587
1587
 
1588
1588
  ### Upsert
1589
1589
 
@@ -12,7 +12,7 @@ function applyAssign(schema) {
12
12
  schema.method('assign', function assign(fields) {
13
13
  unsetReferenceFields(fields, schema.obj);
14
14
  for (let [path, value] of Object.entries(flattenObject(fields))) {
15
- if (value === null) {
15
+ if (value === null || value === '') {
16
16
  this.set(path, undefined);
17
17
  } else {
18
18
  this.set(path, value);
@@ -83,7 +83,7 @@ function applyValidation(schema, definition) {
83
83
  model: this,
84
84
  appendSchema,
85
85
  allowInclude,
86
- allowEmpty: true,
86
+ stripEmpty: true,
87
87
  stripDeleted: true,
88
88
  stripTimestamps: true,
89
89
  allowDefaultTags: true,
@@ -106,7 +106,7 @@ function applyValidation(schema, definition) {
106
106
  model: this,
107
107
  appendSchema,
108
108
  allowInclude,
109
- allowUnset: true,
109
+ allowNull: true,
110
110
  skipRequired: true,
111
111
  stripUnknown: true,
112
112
  stripDeleted: true,
@@ -122,13 +122,6 @@ function applyValidation(schema, definition) {
122
122
  })
123
123
  });
124
124
  });
125
- schema.static('getDeleteValidation', function getDeleteValidation() {
126
- const allowed = definition.access?.delete || 'all';
127
- return validateAccess('delete', _yada.default, allowed, {
128
- model: this,
129
- message: 'You do not have permissions to delete this document.'
130
- });
131
- });
132
125
  schema.static('getSearchValidation', function getSearchValidation(options = {}) {
133
126
  const {
134
127
  defaults,
@@ -138,6 +131,7 @@ function applyValidation(schema, definition) {
138
131
  return getSchemaFromMongoose(schema, {
139
132
  model: this,
140
133
  allowNull: true,
134
+ stripEmpty: true,
141
135
  allowSearch: true,
142
136
  skipRequired: true,
143
137
  allowInclude: true,
@@ -152,6 +146,13 @@ function applyValidation(schema, definition) {
152
146
  })
153
147
  });
154
148
  });
149
+ schema.static('getDeleteValidation', function getDeleteValidation() {
150
+ const allowed = definition.access?.delete || 'all';
151
+ return validateAccess('delete', _yada.default, allowed, {
152
+ model: this,
153
+ message: 'You do not have permissions to delete this document.'
154
+ });
155
+ });
155
156
  schema.static('getIncludeValidation', function getIncludeValidation() {
156
157
  return _include.INCLUDE_FIELD_SCHEMA;
157
158
  });
@@ -228,6 +229,7 @@ function getObjectSchema(arg, options) {
228
229
  } else if (typeof arg === 'object') {
229
230
  const {
230
231
  stripUnknown,
232
+ stripEmpty,
231
233
  expandDotSyntax
232
234
  } = options;
233
235
  const map = {};
@@ -242,6 +244,11 @@ function getObjectSchema(arg, options) {
242
244
  stripUnknown: true
243
245
  });
244
246
  }
247
+ if (stripEmpty) {
248
+ schema = schema.options({
249
+ stripEmpty: true
250
+ });
251
+ }
245
252
  if (expandDotSyntax) {
246
253
  schema = schema.options({
247
254
  expandDotSyntax: true
@@ -285,15 +292,24 @@ function getSchemaForTypedef(typedef, options = {}) {
285
292
  } else {
286
293
  schema = getSchemaForType(type, options);
287
294
 
288
- // Only allowed for primitive types.
289
- if (allowUnset(typedef, options)) {
290
- schema = _yada.default.allow(schema, '').nullable();
291
- } else if (allowNull(typedef, options)) {
295
+ // Null may be allowed to unset non-required fields
296
+ // in an update operation or to search for non-existent
297
+ // fields in a search operation.
298
+ if (allowNull(typedef, options)) {
292
299
  schema = schema.nullable();
293
- } else if (allowEmpty(typedef, options)) {
300
+ }
301
+
302
+ // Empty strings are allowed to unset non-required fields
303
+ // in an update operation. Technically this should be null,
304
+ // however empty strings are allowed here as well as they
305
+ // generally play nicer with front-end components. For
306
+ // ObjectId fields the empty string must be appended here.
307
+ if (disallowEmpty(typedef, options)) {
294
308
  schema = schema.options({
295
- allowEmpty: true
309
+ allowEmpty: false
296
310
  });
311
+ } else if (appendEmpty(typedef, options)) {
312
+ schema = _yada.default.allow(schema, '');
297
313
  }
298
314
  }
299
315
  if (isRequired(typedef, options)) {
@@ -406,13 +422,37 @@ function isRequired(typedef, options) {
406
422
  return typedef.required && !typedef.default && !options.skipRequired;
407
423
  }
408
424
  function allowNull(typedef, options) {
409
- return options.allowNull && !typedef.required;
425
+ if (!options.allowNull) {
426
+ return false;
427
+ }
428
+ const {
429
+ required,
430
+ type
431
+ } = typedef;
432
+ return !required && type !== 'Boolean';
410
433
  }
411
- function allowUnset(typedef, options) {
412
- return options.allowUnset && !typedef.required;
434
+ function disallowEmpty(typedef, options) {
435
+ if (!options.allowNull) {
436
+ return false;
437
+ }
438
+ const {
439
+ type
440
+ } = typedef;
441
+ if (type === 'String' || type === 'ObjectId') {
442
+ return typedef.required;
443
+ } else {
444
+ return false;
445
+ }
413
446
  }
414
- function allowEmpty(typedef, options) {
415
- return options.allowEmpty && typedef.type === 'String';
447
+ function appendEmpty(typedef, options) {
448
+ if (!options.allowNull) {
449
+ return false;
450
+ }
451
+ const {
452
+ required,
453
+ type
454
+ } = typedef;
455
+ return !required && type === 'ObjectId';
416
456
  }
417
457
  function isExcludedField(field, options) {
418
458
  if ((0, _utils.isSchemaTypedef)(field)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrockio/model",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Bedrock utilities for model creation.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -30,22 +30,22 @@
30
30
  "lodash": "^4.17.21"
31
31
  },
32
32
  "peerDependencies": {
33
- "@bedrockio/yada": "^1.1.3",
34
- "mongoose": "^8.5.1"
33
+ "@bedrockio/yada": "^1.2.1",
34
+ "mongoose": "^8.6.2"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@babel/cli": "^7.20.7",
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.1.3",
41
+ "@bedrockio/yada": "^1.2.1",
42
42
  "@shelf/jest-mongodb": "^4.3.2",
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
47
  "mongodb": "^6.5.0",
48
- "mongoose": "^8.5.1",
48
+ "mongoose": "^8.6.2",
49
49
  "prettier-eslint": "^15.0.1",
50
50
  "typescript": "^4.9.5"
51
51
  },
package/src/assign.js CHANGED
@@ -7,7 +7,7 @@ export function applyAssign(schema) {
7
7
  schema.method('assign', function assign(fields) {
8
8
  unsetReferenceFields(fields, schema.obj);
9
9
  for (let [path, value] of Object.entries(flattenObject(fields))) {
10
- if (value === null) {
10
+ if (value === null || value === '') {
11
11
  this.set(path, undefined);
12
12
  } else {
13
13
  this.set(path, value);
package/src/validation.js CHANGED
@@ -95,7 +95,7 @@ export function applyValidation(schema, definition) {
95
95
  model: this,
96
96
  appendSchema,
97
97
  allowInclude,
98
- allowEmpty: true,
98
+ stripEmpty: true,
99
99
  stripDeleted: true,
100
100
  stripTimestamps: true,
101
101
  allowDefaultTags: true,
@@ -119,7 +119,7 @@ export function applyValidation(schema, definition) {
119
119
  model: this,
120
120
  appendSchema,
121
121
  allowInclude,
122
- allowUnset: true,
122
+ allowNull: true,
123
123
  skipRequired: true,
124
124
  stripUnknown: true,
125
125
  stripDeleted: true,
@@ -137,22 +137,14 @@ export function applyValidation(schema, definition) {
137
137
  }
138
138
  );
139
139
 
140
- schema.static('getDeleteValidation', function getDeleteValidation() {
141
- const allowed = definition.access?.delete || 'all';
142
- return validateAccess('delete', yd, allowed, {
143
- model: this,
144
- message: 'You do not have permissions to delete this document.',
145
- });
146
- });
147
-
148
140
  schema.static(
149
141
  'getSearchValidation',
150
142
  function getSearchValidation(options = {}) {
151
143
  const { defaults, includeDeleted, ...appendSchema } = options;
152
-
153
144
  return getSchemaFromMongoose(schema, {
154
145
  model: this,
155
146
  allowNull: true,
147
+ stripEmpty: true,
156
148
  allowSearch: true,
157
149
  skipRequired: true,
158
150
  allowInclude: true,
@@ -169,6 +161,14 @@ export function applyValidation(schema, definition) {
169
161
  }
170
162
  );
171
163
 
164
+ schema.static('getDeleteValidation', function getDeleteValidation() {
165
+ const allowed = definition.access?.delete || 'all';
166
+ return validateAccess('delete', yd, allowed, {
167
+ model: this,
168
+ message: 'You do not have permissions to delete this document.',
169
+ });
170
+ });
171
+
172
172
  schema.static('getIncludeValidation', function getIncludeValidation() {
173
173
  return INCLUDE_FIELD_SCHEMA;
174
174
  });
@@ -239,7 +239,7 @@ function getObjectSchema(arg, options) {
239
239
  } else if (Array.isArray(arg)) {
240
240
  return getArraySchema(arg, options);
241
241
  } else if (typeof arg === 'object') {
242
- const { stripUnknown, expandDotSyntax } = options;
242
+ const { stripUnknown, stripEmpty, expandDotSyntax } = options;
243
243
  const map = {};
244
244
  for (let [key, field] of Object.entries(arg)) {
245
245
  if (!isExcludedField(field, options)) {
@@ -254,6 +254,13 @@ function getObjectSchema(arg, options) {
254
254
  stripUnknown: true,
255
255
  });
256
256
  }
257
+
258
+ if (stripEmpty) {
259
+ schema = schema.options({
260
+ stripEmpty: true,
261
+ });
262
+ }
263
+
257
264
  if (expandDotSyntax) {
258
265
  schema = schema.options({
259
266
  expandDotSyntax: true,
@@ -300,15 +307,24 @@ function getSchemaForTypedef(typedef, options = {}) {
300
307
  } else {
301
308
  schema = getSchemaForType(type, options);
302
309
 
303
- // Only allowed for primitive types.
304
- if (allowUnset(typedef, options)) {
305
- schema = yd.allow(schema, '').nullable();
306
- } else if (allowNull(typedef, options)) {
310
+ // Null may be allowed to unset non-required fields
311
+ // in an update operation or to search for non-existent
312
+ // fields in a search operation.
313
+ if (allowNull(typedef, options)) {
307
314
  schema = schema.nullable();
308
- } else if (allowEmpty(typedef, options)) {
315
+ }
316
+
317
+ // Empty strings are allowed to unset non-required fields
318
+ // in an update operation. Technically this should be null,
319
+ // however empty strings are allowed here as well as they
320
+ // generally play nicer with front-end components. For
321
+ // ObjectId fields the empty string must be appended here.
322
+ if (disallowEmpty(typedef, options)) {
309
323
  schema = schema.options({
310
- allowEmpty: true,
324
+ allowEmpty: false,
311
325
  });
326
+ } else if (appendEmpty(typedef, options)) {
327
+ schema = yd.allow(schema, '');
312
328
  }
313
329
  }
314
330
 
@@ -465,15 +481,31 @@ function isRequired(typedef, options) {
465
481
  }
466
482
 
467
483
  function allowNull(typedef, options) {
468
- return options.allowNull && !typedef.required;
484
+ if (!options.allowNull) {
485
+ return false;
486
+ }
487
+ const { required, type } = typedef;
488
+ return !required && type !== 'Boolean';
469
489
  }
470
490
 
471
- function allowUnset(typedef, options) {
472
- return options.allowUnset && !typedef.required;
491
+ function disallowEmpty(typedef, options) {
492
+ if (!options.allowNull) {
493
+ return false;
494
+ }
495
+ const { type } = typedef;
496
+ if (type === 'String' || type === 'ObjectId') {
497
+ return typedef.required;
498
+ } else {
499
+ return false;
500
+ }
473
501
  }
474
502
 
475
- function allowEmpty(typedef, options) {
476
- return options.allowEmpty && typedef.type === 'String';
503
+ function appendEmpty(typedef, options) {
504
+ if (!options.allowNull) {
505
+ return false;
506
+ }
507
+ const { required, type } = typedef;
508
+ return !required && type === 'ObjectId';
477
509
  }
478
510
 
479
511
  function isExcludedField(field, options) {
@@ -11,7 +11,6 @@ export function getTupleValidator(types: any): {
11
11
  };
12
12
  export const OBJECT_ID_SCHEMA: {
13
13
  required(): any;
14
- allowEmpty(): any;
15
14
  length(length: number): any;
16
15
  min(length: number): any;
17
16
  max(length: number): any;
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"AAkFA,kDAEC;AAED,oEAgGC;AAsBD,wEA2BC;AAkTD;;;EAEC;AAED;;;EAOC;AAlhBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQK"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"AAkFA,kDAEC;AAED,oEAgGC;AAsBD,wEA2BC;AAkVD;;;EAEC;AAED;;;EAOC;AAljBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQK"}