@bedrockio/model 0.9.1 → 0.10.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 CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.10.0
2
+
3
+ - Unique constraints now run sequentially and will not run on nested validations
4
+ unless their parent fields are passed.
5
+
1
6
  ## 0.9.1
2
7
 
3
8
  - Allowed deriving individual paths from a create schema.
package/README.md CHANGED
@@ -9,9 +9,10 @@ Bedrock utilities for model creation.
9
9
  - [Schema Extensions](#schema-extensions)
10
10
  - [Attributes](#attributes)
11
11
  - [Scopes](#scopes)
12
- - [Tuples](#tuples)
12
+ - [Unique Constraints](#unique-constraints)
13
13
  - [Array Extensions](#array-extensions)
14
14
  - [String Trimming](#string-trimming)
15
+ - [Tuples](#tuples)
15
16
  - [Modules](#modules)
16
17
  - [Soft Delete](#soft-delete)
17
18
  - [Validation](#validation)
@@ -214,8 +215,8 @@ type `Scope` helps make this possible:
214
215
  "readAccess": "none",
215
216
  "writeAccess": "none",
216
217
  "attributes": {
217
- "firstName": "String",
218
- "lastName": "String",
218
+ "token": "String",
219
+ "hashedPassword": "String",
219
220
  }
220
221
  }
221
222
  };
@@ -225,12 +226,12 @@ This syntax expands into the following:
225
226
 
226
227
  ```js
227
228
  {
228
- "firstName": {
229
+ "token": {
229
230
  "type": "String",
230
231
  "readAccess": "none",
231
232
  "writeAccess": "none",
232
233
  },
233
- "lastName": {
234
+ "hashedPassword": {
234
235
  "type": "String",
235
236
  "readAccess": "none",
236
237
  "writeAccess": "none",
@@ -437,9 +438,8 @@ an error:
437
438
 
438
439
  #### Unique Constraints
439
440
 
440
- Note that although monogoose allows a `unique` option on fields, this will add a
441
- unique index to the mongo collection itself which is incompatible with soft
442
- deletion.
441
+ Although monogoose allows a `unique` option on fields, this will add a unique
442
+ index to the mongo collection itself which is incompatible with soft deletion.
443
443
 
444
444
  This module will intercept `unique: true` to create a soft delete compatible
445
445
  validation which will:
@@ -462,7 +462,7 @@ validation which will:
462
462
  > unique field exists on any document **including the document being updated**.
463
463
  > This is an intentional constraint that allows `updateOne` better peformance by
464
464
  > not having to fetch the ids of the documents being updated in order to exclude
465
- > them. To avoid this call `Document.save` instead.
465
+ > them. To avoid this call `Document#save` instead.
466
466
  >
467
467
  > Note also that calling `Model.updateMany` with a unique field passed will
468
468
  > always throw an error as the result would inherently be non-unique.
@@ -525,8 +525,8 @@ Named validations can be specified on the model:
525
525
  }
526
526
  ```
527
527
 
528
- Validator functions are derived from
529
- [yada](https://github.com/bedrockio/yada#methods). Note that:
528
+ Validator functions are [yada](https://github.com/bedrockio/yada#methods)
529
+ schemas. Note that:
530
530
 
531
531
  - `email` - Will additionally downcase any input.
532
532
  - `password` - Is not supported as it requires options to be passed and is not a
@@ -538,6 +538,19 @@ Validator functions are derived from
538
538
  - `max` - Defined instead directly on the field with `maxLength` for strings and
539
539
  `max` for numbers.
540
540
 
541
+ Schemas may also be
542
+ [merged together](https://github.com/bedrockio/yada#merging-fields) to produce
543
+ new ones:
544
+
545
+ ```js
546
+ import yd from '@bedrockio/yada';
547
+
548
+ const signupSchema = yd.object({
549
+ ...User.getCreateSchema().export(),
550
+ additionalField: yd.string().required(),
551
+ });
552
+ ```
553
+
541
554
  ### Search
542
555
 
543
556
  Models are extended with a `search` method that allows for complex searching:
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.ReferenceError = exports.PermissionsError = exports.ImplementationError = void 0;
6
+ exports.UniqueConstraintError = exports.ReferenceError = exports.PermissionsError = exports.ImplementationError = void 0;
7
7
  class PermissionsError extends Error {}
8
8
  exports.PermissionsError = PermissionsError;
9
9
  class ImplementationError extends Error {
@@ -19,4 +19,11 @@ class ReferenceError extends Error {
19
19
  this.details = details;
20
20
  }
21
21
  }
22
- exports.ReferenceError = ReferenceError;
22
+ exports.ReferenceError = ReferenceError;
23
+ class UniqueConstraintError extends Error {
24
+ constructor(message, details) {
25
+ super(message);
26
+ this.details = details;
27
+ }
28
+ }
29
+ exports.UniqueConstraintError = UniqueConstraintError;
@@ -5,16 +5,51 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.applySoftDelete = applySoftDelete;
7
7
  exports.assertUnique = assertUnique;
8
- exports.hasUniqueConstraints = hasUniqueConstraints;
9
- var _mongoose = _interopRequireDefault(require("mongoose"));
10
8
  var _lodash = require("lodash");
11
9
  var _query = require("./query");
12
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ var _errors = require("./errors");
13
11
  function applySoftDelete(schema) {
14
12
  applyQueries(schema);
15
13
  applyUniqueConstraints(schema);
16
14
  applyHookPatch(schema);
17
15
  }
16
+ async function assertUnique(options) {
17
+ let {
18
+ id,
19
+ model,
20
+ path,
21
+ value
22
+ } = options;
23
+ if (!value) {
24
+ return;
25
+ }
26
+ const field = Array.isArray(path) ? path.join('.') : path;
27
+ const query = {
28
+ [field]: value,
29
+ _id: {
30
+ $ne: id
31
+ }
32
+ };
33
+ const exists = await model.exists(query);
34
+ if (exists) {
35
+ const message = getUniqueErrorMessage(model, field);
36
+ throw new _errors.UniqueConstraintError(message, {
37
+ ...options,
38
+ field
39
+ });
40
+ }
41
+ }
42
+ function getUniqueErrorMessage(model, field) {
43
+ const {
44
+ modelName
45
+ } = model;
46
+ if (modelName === 'User' && !field.includes('.')) {
47
+ const name = field === 'phone' ? 'phone number' : field;
48
+ return `A user with that ${name} already exists.`;
49
+ } else {
50
+ return `"${field}" already exists.`;
51
+ }
52
+ }
18
53
 
19
54
  // Soft Delete Querying
20
55
 
@@ -235,20 +270,24 @@ function getWithDeletedQuery() {
235
270
  // Unique Constraints
236
271
 
237
272
  function applyUniqueConstraints(schema) {
238
- const hasUnique = hasUniqueConstraints(schema);
239
- if (!hasUnique) {
273
+ const uniquePaths = getUniqueConstraints(schema);
274
+ if (!uniquePaths.length) {
240
275
  return;
241
276
  }
242
277
  schema.pre('save', async function () {
243
- await assertUnique(this, {
244
- operation: this.isNew ? 'create' : 'update',
245
- model: this.constructor,
246
- schema
247
- });
278
+ for (let path of uniquePaths) {
279
+ await assertUnique({
280
+ path,
281
+ id: this.id,
282
+ value: this.get(path),
283
+ model: this.constructor
284
+ });
285
+ }
248
286
  });
249
287
  schema.pre(/^(update|replace)/, async function () {
250
288
  await assertUniqueForQuery(this, {
251
- schema
289
+ schema,
290
+ uniquePaths
252
291
  });
253
292
  });
254
293
  schema.pre('insertMany', async function (next, obj) {
@@ -258,54 +297,39 @@ function applyUniqueConstraints(schema) {
258
297
  // as the last argument, however as we are passing an async
259
298
  // function it appears to not stop the middleware if we
260
299
  // don't call it directly.
261
- await assertUnique(obj, {
262
- operation: 'create',
300
+
301
+ await runUniqueConstraints(obj, {
263
302
  model: this,
264
- schema
303
+ uniquePaths
265
304
  });
266
305
  });
267
306
  }
268
- async function assertUnique(obj, options) {
307
+ async function runUniqueConstraints(arg, options) {
269
308
  const {
270
- operation,
271
- model,
272
- schema
309
+ uniquePaths,
310
+ model
273
311
  } = options;
274
- const id = getId(obj);
275
- const objFields = resolveUnique(schema, obj);
276
- if (Object.keys(objFields).length === 0) {
277
- return;
278
- }
279
- const query = {
280
- $or: getUniqueQueries(objFields),
281
- ...(id && {
282
- _id: {
283
- $ne: id
312
+ // Updates or inserts
313
+ const operations = Array.isArray(arg) ? arg : [arg];
314
+ for (let operation of operations) {
315
+ for (let path of uniquePaths) {
316
+ const value = operation[path];
317
+ if (value) {
318
+ await assertUnique({
319
+ path,
320
+ value,
321
+ model
322
+ });
284
323
  }
285
- })
286
- };
287
- const found = await model.findOne(query, {}, {
288
- lean: true
289
- });
290
- if (found) {
291
- const {
292
- modelName
293
- } = model;
294
- const foundFields = resolveUnique(schema, found);
295
- const collisions = getCollisions(objFields, foundFields).join(', ');
296
- throw new Error(`Cannot ${operation} ${modelName}. Duplicate fields exist: ${collisions}.`);
324
+ }
297
325
  }
298
326
  }
299
- function getId(arg) {
300
- const id = arg.id || arg._id;
301
- return id ? String(id) : null;
302
- }
303
327
 
304
328
  // Asserts than an update or insert query will not
305
329
  // result in duplicate unique fields being present
306
330
  // within non-deleted documents.
307
331
  async function assertUniqueForQuery(query, options) {
308
- let update = query.getUpdate();
332
+ const update = query.getUpdate();
309
333
  const operation = getOperationForQuery(update);
310
334
  // Note: No need to check unique constraints
311
335
  // if the operation is a delete.
@@ -314,6 +338,7 @@ async function assertUniqueForQuery(query, options) {
314
338
  model
315
339
  } = query;
316
340
  const filter = query.getFilter();
341
+ let updates;
317
342
  if (operation === 'restore') {
318
343
  // A restore operation is functionally identical to a new
319
344
  // insert so we need to fetch the deleted documents with
@@ -321,18 +346,30 @@ async function assertUniqueForQuery(query, options) {
321
346
  const docs = await model.findWithDeleted(filter, {}, {
322
347
  lean: true
323
348
  });
324
- update = docs.map(doc => {
349
+ updates = docs.map(doc => {
325
350
  return {
326
351
  ...doc,
327
352
  ...update
328
353
  };
329
354
  });
355
+ } else {
356
+ updates = [update];
357
+ }
358
+ const {
359
+ uniquePaths
360
+ } = options;
361
+ for (let update of updates) {
362
+ for (let path of uniquePaths) {
363
+ const value = update[path];
364
+ if (value) {
365
+ await assertUnique({
366
+ path,
367
+ value,
368
+ model
369
+ });
370
+ }
371
+ }
330
372
  }
331
- await assertUnique(update, {
332
- ...options,
333
- operation,
334
- model
335
- });
336
373
  }
337
374
  }
338
375
  function getOperationForQuery(update) {
@@ -344,9 +381,9 @@ function getOperationForQuery(update) {
344
381
  return 'update';
345
382
  }
346
383
  }
347
- function hasUniqueConstraints(schema) {
384
+ function getUniqueConstraints(schema) {
348
385
  const paths = [...Object.keys(schema.paths), ...Object.keys(schema.subpaths)];
349
- return paths.some(key => {
386
+ return paths.filter(key => {
350
387
  return isUniquePath(schema, key);
351
388
  });
352
389
  }
@@ -354,67 +391,6 @@ function isUniquePath(schema, key) {
354
391
  return schema.path(key)?.options?.softUnique === true;
355
392
  }
356
393
 
357
- // Returns a flattened map of key -> [...values]
358
- // consisting of only paths defined as unique on the schema.
359
- function resolveUnique(schema, obj, map = {}, path = []) {
360
- if (Array.isArray(obj)) {
361
- for (let el of obj) {
362
- resolveUnique(schema, el, map, path);
363
- }
364
- } else if (obj instanceof _mongoose.default.Document) {
365
- obj.schema.eachPath(key => {
366
- const val = obj.get(key);
367
- resolveUnique(schema, val, map, [...path, key]);
368
- });
369
- } else if (obj && typeof obj === 'object') {
370
- for (let [key, val] of Object.entries(obj)) {
371
- resolveUnique(schema, val, map, [...path, key]);
372
- }
373
- } else if (obj) {
374
- const key = path.join('.');
375
- if (isUniquePath(schema, key)) {
376
- map[key] ||= [];
377
- map[key].push(obj);
378
- }
379
- }
380
- return map;
381
- }
382
-
383
- // Argument is guaranteed to be flattened.
384
- function getUniqueQueries(obj) {
385
- return Object.entries(obj).map(([key, val]) => {
386
- if (val.length > 1) {
387
- return {
388
- [key]: {
389
- $in: val
390
- }
391
- };
392
- } else {
393
- return {
394
- [key]: val[0]
395
- };
396
- }
397
- });
398
- }
399
-
400
- // Both arguments here are guaranteed to be flattened
401
- // maps of key -> [values] of unique fields only.
402
- function getCollisions(obj1, obj2) {
403
- const collisions = [];
404
- for (let [key, arr1] of Object.entries(obj1)) {
405
- const arr2 = obj2[key];
406
- if (arr2) {
407
- const hasCollision = arr1.some(val => {
408
- return arr2.includes(val);
409
- });
410
- if (hasCollision) {
411
- collisions.push(key);
412
- }
413
- }
414
- }
415
- return collisions;
416
- }
417
-
418
394
  // Hook Patch
419
395
 
420
396
  function applyHookPatch(schema) {
@@ -14,8 +14,8 @@ var _yada = _interopRequireDefault(require("@bedrockio/yada"));
14
14
  var _lodash = require("lodash");
15
15
  var _access = require("./access");
16
16
  var _search = require("./search");
17
- var _errors = require("./errors");
18
17
  var _softDelete = require("./soft-delete");
18
+ var _errors = require("./errors");
19
19
  var _utils = require("./utils");
20
20
  var _include = require("./include");
21
21
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -75,7 +75,6 @@ function addValidators(schemas) {
75
75
  Object.assign(NAMED_SCHEMAS, schemas);
76
76
  }
77
77
  function applyValidation(schema, definition) {
78
- const hasUnique = (0, _softDelete.hasUniqueConstraints)(schema);
79
78
  schema.static('getCreateValidation', function getCreateValidation(options = {}) {
80
79
  const {
81
80
  allowInclude,
@@ -90,13 +89,7 @@ function applyValidation(schema, definition) {
90
89
  stripTimestamps: true,
91
90
  allowDefaultTags: true,
92
91
  allowExpandedRefs: true,
93
- requireWriteAccess: true,
94
- ...(hasUnique && {
95
- assertUniqueOptions: {
96
- schema,
97
- operation: 'create'
98
- }
99
- })
92
+ requireWriteAccess: true
100
93
  });
101
94
  });
102
95
  schema.static('getUpdateValidation', function getUpdateValidation(options = {}) {
@@ -115,13 +108,7 @@ function applyValidation(schema, definition) {
115
108
  stripTimestamps: true,
116
109
  allowExpandedRefs: true,
117
110
  requireWriteAccess: true,
118
- updateAccess: definition.access?.update,
119
- ...(hasUnique && {
120
- assertUniqueOptions: {
121
- schema,
122
- operation: 'update'
123
- }
124
- })
111
+ updateAccess: definition.access?.update
125
112
  });
126
113
  });
127
114
  schema.static('getSearchValidation', function getSearchValidation(options = {}) {
@@ -192,21 +179,10 @@ function getMongooseFields(schema, options) {
192
179
  function getValidationSchema(attributes, options = {}) {
193
180
  const {
194
181
  appendSchema,
195
- assertUniqueOptions,
196
182
  allowInclude,
197
183
  updateAccess
198
184
  } = options;
199
185
  let schema = getObjectSchema(attributes, options);
200
- if (assertUniqueOptions) {
201
- schema = schema.custom(async (obj, {
202
- root
203
- }) => {
204
- await (0, _softDelete.assertUnique)(root, {
205
- model: options.model,
206
- ...assertUniqueOptions
207
- });
208
- });
209
- }
210
186
  if (appendSchema) {
211
187
  schema = schema.append(appendSchema);
212
188
  }
@@ -353,6 +329,22 @@ function getSchemaForTypedef(typedef, options = {}) {
353
329
  if (typedef.writeAccess && options.requireWriteAccess) {
354
330
  schema = validateAccess('write', schema, typedef.writeAccess, options);
355
331
  }
332
+ if (typedef.softUnique) {
333
+ schema = schema.custom(async (value, {
334
+ path,
335
+ originalRoot
336
+ }) => {
337
+ const {
338
+ id
339
+ } = originalRoot;
340
+ await (0, _softDelete.assertUnique)({
341
+ ...options,
342
+ value,
343
+ path,
344
+ id
345
+ });
346
+ });
347
+ }
356
348
  return schema;
357
349
  }
358
350
  function getSchemaForType(type, options) {
@@ -507,7 +499,9 @@ function validateAccess(type, schema, allowed, options) {
507
499
  // throw the error.
508
500
  return;
509
501
  }
510
- message ||= `Field "${path.join('.')}" requires ${type} permissions.`;
502
+
503
+ // Default to not exposing the existence of this field.
504
+ message ||= `Unknown field "${path.join('.')}".`;
511
505
  }
512
506
  throw new _errors.PermissionsError(message);
513
507
  }
@@ -0,0 +1,3 @@
1
+ import { jest, recommended, nodeImports } from '@bedrockio/eslint-plugin';
2
+
3
+ export default [jest, recommended, nodeImports];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrockio/model",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "Bedrock utilities for model creation.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -37,11 +37,11 @@
37
37
  "@babel/cli": "^7.26.4",
38
38
  "@babel/core": "^7.26.0",
39
39
  "@babel/preset-env": "^7.26.0",
40
+ "@bedrockio/eslint-plugin": "^1.1.7",
40
41
  "@bedrockio/prettier-config": "^1.0.2",
41
- "@bedrockio/yada": "^1.3.0",
42
+ "@bedrockio/yada": "^1.4.1",
42
43
  "@shelf/jest-mongodb": "^4.3.2",
43
- "eslint": "^8.33.0",
44
- "eslint-plugin-bedrock": "^1.0.26",
44
+ "eslint": "^9.19.0",
45
45
  "jest": "^29.7.0",
46
46
  "jest-environment-node": "^29.7.0",
47
47
  "mongodb": "^6.12.0",
@@ -49,11 +49,9 @@
49
49
  "prettier": "^3.4.2",
50
50
  "typescript": "^5.7.2"
51
51
  },
52
- "resolutions": {
53
- "whatwg-url": "14.1.0"
54
- },
52
+ "prettier": "@bedrockio/prettier-config",
55
53
  "volta": {
56
- "node": "22.13.0",
54
+ "node": "23.7.0",
57
55
  "yarn": "1.22.22"
58
56
  }
59
57
  }
@@ -147,7 +147,7 @@ async function errorOnForeignReferences(doc, options) {
147
147
  {
148
148
  [path]: doc.id,
149
149
  },
150
- { _id: 1 }
150
+ { _id: 1 },
151
151
  )
152
152
  .lean();
153
153
 
@@ -226,7 +226,7 @@ function getModelReferences(model, targetName) {
226
226
  refs = model.schema.path(refPath).options.enum;
227
227
  } else {
228
228
  throw new Error(
229
- `Cannot derive refs for ${model.modelName}#${schemaPath}.`
229
+ `Cannot derive refs for ${model.modelName}#${schemaPath}.`,
230
230
  );
231
231
  }
232
232
  if (refs.includes(targetName)) {
package/src/disallowed.js CHANGED
@@ -4,7 +4,7 @@ export function applyDisallowed(schema) {
4
4
  schema.method('deleteOne', function () {
5
5
  warn(
6
6
  'The "deleteOne" method on documents is disallowed due to ambiguity',
7
- 'Use either "delete" or "deleteOne" on the model.'
7
+ 'Use either "delete" or "deleteOne" on the model.',
8
8
  );
9
9
  throw new Error('Method not allowed.');
10
10
  });
@@ -12,7 +12,7 @@ export function applyDisallowed(schema) {
12
12
  schema.static('findOneAndRemove', function () {
13
13
  warn(
14
14
  'The "findOneAndRemove" method on models is disallowed due to ambiguity.',
15
- 'To permanently delete a document use "findOneAndDestroy", otherwise "findOneAndDelete".'
15
+ 'To permanently delete a document use "findOneAndDestroy", otherwise "findOneAndDelete".',
16
16
  );
17
17
  throw new Error('Method not allowed.');
18
18
  });
@@ -20,14 +20,14 @@ export function applyDisallowed(schema) {
20
20
  schema.static('findByIdAndRemove', function () {
21
21
  warn(
22
22
  'The "findByIdAndRemove" method on models is disallowed due to ambiguity.',
23
- 'To permanently delete a document use "findByIdAndDestroy", otherwise "findByIdAndDelete".'
23
+ 'To permanently delete a document use "findByIdAndDestroy", otherwise "findByIdAndDelete".',
24
24
  );
25
25
  throw new Error('Method not allowed.');
26
26
  });
27
27
 
28
28
  schema.static('count', function () {
29
29
  warn(
30
- 'The "count" method on models is deprecated. Use "countDocuments" instead.'
30
+ 'The "count" method on models is deprecated. Use "countDocuments" instead.',
31
31
  );
32
32
  throw new Error('Method not allowed.');
33
33
  });
package/src/errors.js CHANGED
@@ -13,3 +13,10 @@ export class ReferenceError extends Error {
13
13
  this.details = details;
14
14
  }
15
15
  }
16
+
17
+ export class UniqueConstraintError extends Error {
18
+ constructor(message, details) {
19
+ super(message);
20
+ this.details = details;
21
+ }
22
+ }
package/src/include.js CHANGED
@@ -21,7 +21,7 @@ const DESCRIPTION = 'Field to be selected or populated.';
21
21
  export const INCLUDE_FIELD_SCHEMA = yd.object({
22
22
  include: yd.allow(
23
23
  yd.string().description(DESCRIPTION),
24
- yd.array(yd.string().description(DESCRIPTION))
24
+ yd.array(yd.string().description(DESCRIPTION)),
25
25
  ),
26
26
  });
27
27
 
@@ -58,7 +58,7 @@ export function applyInclude(schema) {
58
58
  await doc.include(include);
59
59
  }
60
60
  return doc;
61
- }
61
+ },
62
62
  );
63
63
 
64
64
  // Synchronous method assigns the includes to locals.
package/src/slug.js CHANGED
@@ -13,7 +13,7 @@ export function applySlug(schema) {
13
13
  return find(this, str, args, {
14
14
  deleted: true,
15
15
  });
16
- }
16
+ },
17
17
  );
18
18
 
19
19
  schema.static(
@@ -22,7 +22,7 @@ export function applySlug(schema) {
22
22
  return find(this, str, args, {
23
23
  deleted: { $in: [true, false] },
24
24
  });
25
- }
25
+ },
26
26
  );
27
27
  }
28
28
 
@@ -42,6 +42,6 @@ function find(Model, str, args, deleted) {
42
42
  ...deleted,
43
43
  ...query,
44
44
  },
45
- ...args
45
+ ...args,
46
46
  );
47
47
  }
@@ -1,7 +1,7 @@
1
- import mongoose from 'mongoose';
2
1
  import { isEqual } from 'lodash';
3
2
 
4
3
  import { wrapQuery } from './query';
4
+ import { UniqueConstraintError } from './errors';
5
5
 
6
6
  export function applySoftDelete(schema) {
7
7
  applyQueries(schema);
@@ -9,6 +9,40 @@ export function applySoftDelete(schema) {
9
9
  applyHookPatch(schema);
10
10
  }
11
11
 
12
+ export async function assertUnique(options) {
13
+ let { id, model, path, value } = options;
14
+
15
+ if (!value) {
16
+ return;
17
+ }
18
+
19
+ const field = Array.isArray(path) ? path.join('.') : path;
20
+
21
+ const query = {
22
+ [field]: value,
23
+ _id: { $ne: id },
24
+ };
25
+
26
+ const exists = await model.exists(query);
27
+ if (exists) {
28
+ const message = getUniqueErrorMessage(model, field);
29
+ throw new UniqueConstraintError(message, {
30
+ ...options,
31
+ field,
32
+ });
33
+ }
34
+ }
35
+
36
+ function getUniqueErrorMessage(model, field) {
37
+ const { modelName } = model;
38
+ if (modelName === 'User' && !field.includes('.')) {
39
+ const name = field === 'phone' ? 'phone number' : field;
40
+ return `A user with that ${name} already exists.`;
41
+ } else {
42
+ return `"${field}" already exists.`;
43
+ }
44
+ }
45
+
12
46
  // Soft Delete Querying
13
47
 
14
48
  function applyQueries(schema) {
@@ -58,7 +92,7 @@ function applyQueries(schema) {
58
92
  deleted: false,
59
93
  },
60
94
  update,
61
- ...omitCallback(rest)
95
+ ...omitCallback(rest),
62
96
  );
63
97
  return wrapQuery(query, async (promise) => {
64
98
  const res = await promise;
@@ -77,7 +111,7 @@ function applyQueries(schema) {
77
111
  deleted: false,
78
112
  },
79
113
  update,
80
- ...omitCallback(rest)
114
+ ...omitCallback(rest),
81
115
  );
82
116
  return wrapQuery(query, async (promise) => {
83
117
  const res = await promise;
@@ -95,7 +129,7 @@ function applyQueries(schema) {
95
129
  deleted: false,
96
130
  },
97
131
  getDelete(),
98
- ...omitCallback(rest)
132
+ ...omitCallback(rest),
99
133
  );
100
134
  });
101
135
 
@@ -106,7 +140,7 @@ function applyQueries(schema) {
106
140
  deleted: true,
107
141
  },
108
142
  getRestore(),
109
- ...omitCallback(rest)
143
+ ...omitCallback(rest),
110
144
  );
111
145
  return wrapQuery(query, async (promise) => {
112
146
  const res = await promise;
@@ -124,7 +158,7 @@ function applyQueries(schema) {
124
158
  deleted: true,
125
159
  },
126
160
  getRestore(),
127
- ...omitCallback(rest)
161
+ ...omitCallback(rest),
128
162
  );
129
163
  return wrapQuery(query, async (promise) => {
130
164
  const res = await promise;
@@ -139,7 +173,7 @@ function applyQueries(schema) {
139
173
  // Following Mongoose patterns here
140
174
  const query = new this.Query({}, {}, this, this.collection).deleteOne(
141
175
  conditions,
142
- ...omitCallback(rest)
176
+ ...omitCallback(rest),
143
177
  );
144
178
  return wrapQuery(query, async (promise) => {
145
179
  const res = await promise;
@@ -154,7 +188,7 @@ function applyQueries(schema) {
154
188
  // Following Mongoose patterns here
155
189
  const query = new this.Query({}, {}, this, this.collection).deleteMany(
156
190
  conditions,
157
- ...omitCallback(rest)
191
+ ...omitCallback(rest),
158
192
  );
159
193
  return wrapQuery(query, async (promise) => {
160
194
  const res = await promise;
@@ -205,7 +239,7 @@ function applyQueries(schema) {
205
239
  deleted: true,
206
240
  };
207
241
  return this.countDocuments(filter, ...omitCallback(rest));
208
- }
242
+ },
209
243
  );
210
244
 
211
245
  schema.static('findWithDeleted', function findWithDeleted(filter, ...rest) {
@@ -224,7 +258,7 @@ function applyQueries(schema) {
224
258
  ...getWithDeletedQuery(),
225
259
  };
226
260
  return this.findOne(filter, ...omitCallback(rest));
227
- }
261
+ },
228
262
  );
229
263
 
230
264
  schema.static(
@@ -235,7 +269,7 @@ function applyQueries(schema) {
235
269
  ...getWithDeletedQuery(),
236
270
  };
237
271
  return this.findOne(filter, ...omitCallback(rest));
238
- }
272
+ },
239
273
  );
240
274
 
241
275
  schema.static(
@@ -246,7 +280,7 @@ function applyQueries(schema) {
246
280
  ...getWithDeletedQuery(),
247
281
  };
248
282
  return this.exists(filter, ...omitCallback(rest));
249
- }
283
+ },
250
284
  );
251
285
 
252
286
  schema.static(
@@ -257,7 +291,7 @@ function applyQueries(schema) {
257
291
  ...getWithDeletedQuery(),
258
292
  };
259
293
  return this.countDocuments(filter, ...omitCallback(rest));
260
- }
294
+ },
261
295
  );
262
296
  }
263
297
 
@@ -284,23 +318,27 @@ function getWithDeletedQuery() {
284
318
  // Unique Constraints
285
319
 
286
320
  function applyUniqueConstraints(schema) {
287
- const hasUnique = hasUniqueConstraints(schema);
321
+ const uniquePaths = getUniqueConstraints(schema);
288
322
 
289
- if (!hasUnique) {
323
+ if (!uniquePaths.length) {
290
324
  return;
291
325
  }
292
326
 
293
327
  schema.pre('save', async function () {
294
- await assertUnique(this, {
295
- operation: this.isNew ? 'create' : 'update',
296
- model: this.constructor,
297
- schema,
298
- });
328
+ for (let path of uniquePaths) {
329
+ await assertUnique({
330
+ path,
331
+ id: this.id,
332
+ value: this.get(path),
333
+ model: this.constructor,
334
+ });
335
+ }
299
336
  });
300
337
 
301
338
  schema.pre(/^(update|replace)/, async function () {
302
339
  await assertUniqueForQuery(this, {
303
340
  schema,
341
+ uniquePaths,
304
342
  });
305
343
  });
306
344
 
@@ -311,71 +349,73 @@ function applyUniqueConstraints(schema) {
311
349
  // as the last argument, however as we are passing an async
312
350
  // function it appears to not stop the middleware if we
313
351
  // don't call it directly.
314
- await assertUnique(obj, {
315
- operation: 'create',
352
+
353
+ await runUniqueConstraints(obj, {
316
354
  model: this,
317
- schema,
355
+ uniquePaths,
318
356
  });
319
357
  });
320
358
  }
321
359
 
322
- export async function assertUnique(obj, options) {
323
- const { operation, model, schema } = options;
324
- const id = getId(obj);
325
- const objFields = resolveUnique(schema, obj);
326
- if (Object.keys(objFields).length === 0) {
327
- return;
328
- }
329
- const query = {
330
- $or: getUniqueQueries(objFields),
331
- ...(id && {
332
- _id: { $ne: id },
333
- }),
334
- };
335
- const found = await model.findOne(query, {}, { lean: true });
336
- if (found) {
337
- const { modelName } = model;
338
- const foundFields = resolveUnique(schema, found);
339
- const collisions = getCollisions(objFields, foundFields).join(', ');
340
- throw new Error(
341
- `Cannot ${operation} ${modelName}. Duplicate fields exist: ${collisions}.`
342
- );
360
+ async function runUniqueConstraints(arg, options) {
361
+ const { uniquePaths, model } = options;
362
+ // Updates or inserts
363
+ const operations = Array.isArray(arg) ? arg : [arg];
364
+ for (let operation of operations) {
365
+ for (let path of uniquePaths) {
366
+ const value = operation[path];
367
+ if (value) {
368
+ await assertUnique({
369
+ path,
370
+ value,
371
+ model,
372
+ });
373
+ }
374
+ }
343
375
  }
344
376
  }
345
377
 
346
- function getId(arg) {
347
- const id = arg.id || arg._id;
348
- return id ? String(id) : null;
349
- }
350
-
351
378
  // Asserts than an update or insert query will not
352
379
  // result in duplicate unique fields being present
353
380
  // within non-deleted documents.
354
381
  async function assertUniqueForQuery(query, options) {
355
- let update = query.getUpdate();
382
+ const update = query.getUpdate();
356
383
  const operation = getOperationForQuery(update);
357
384
  // Note: No need to check unique constraints
358
385
  // if the operation is a delete.
359
386
  if (operation === 'restore' || operation === 'update') {
360
387
  const { model } = query;
361
388
  const filter = query.getFilter();
389
+
390
+ let updates;
362
391
  if (operation === 'restore') {
363
392
  // A restore operation is functionally identical to a new
364
393
  // insert so we need to fetch the deleted documents with
365
394
  // all fields available to check against.
366
395
  const docs = await model.findWithDeleted(filter, {}, { lean: true });
367
- update = docs.map((doc) => {
396
+ updates = docs.map((doc) => {
368
397
  return {
369
398
  ...doc,
370
399
  ...update,
371
400
  };
372
401
  });
402
+ } else {
403
+ updates = [update];
404
+ }
405
+
406
+ const { uniquePaths } = options;
407
+ for (let update of updates) {
408
+ for (let path of uniquePaths) {
409
+ const value = update[path];
410
+ if (value) {
411
+ await assertUnique({
412
+ path,
413
+ value,
414
+ model,
415
+ });
416
+ }
417
+ }
373
418
  }
374
- await assertUnique(update, {
375
- ...options,
376
- operation,
377
- model,
378
- });
379
419
  }
380
420
  }
381
421
 
@@ -389,9 +429,9 @@ function getOperationForQuery(update) {
389
429
  }
390
430
  }
391
431
 
392
- export function hasUniqueConstraints(schema) {
432
+ function getUniqueConstraints(schema) {
393
433
  const paths = [...Object.keys(schema.paths), ...Object.keys(schema.subpaths)];
394
- return paths.some((key) => {
434
+ return paths.filter((key) => {
395
435
  return isUniquePath(schema, key);
396
436
  });
397
437
  }
@@ -400,61 +440,6 @@ function isUniquePath(schema, key) {
400
440
  return schema.path(key)?.options?.softUnique === true;
401
441
  }
402
442
 
403
- // Returns a flattened map of key -> [...values]
404
- // consisting of only paths defined as unique on the schema.
405
- function resolveUnique(schema, obj, map = {}, path = []) {
406
- if (Array.isArray(obj)) {
407
- for (let el of obj) {
408
- resolveUnique(schema, el, map, path);
409
- }
410
- } else if (obj instanceof mongoose.Document) {
411
- obj.schema.eachPath((key) => {
412
- const val = obj.get(key);
413
- resolveUnique(schema, val, map, [...path, key]);
414
- });
415
- } else if (obj && typeof obj === 'object') {
416
- for (let [key, val] of Object.entries(obj)) {
417
- resolveUnique(schema, val, map, [...path, key]);
418
- }
419
- } else if (obj) {
420
- const key = path.join('.');
421
- if (isUniquePath(schema, key)) {
422
- map[key] ||= [];
423
- map[key].push(obj);
424
- }
425
- }
426
- return map;
427
- }
428
-
429
- // Argument is guaranteed to be flattened.
430
- function getUniqueQueries(obj) {
431
- return Object.entries(obj).map(([key, val]) => {
432
- if (val.length > 1) {
433
- return { [key]: { $in: val } };
434
- } else {
435
- return { [key]: val[0] };
436
- }
437
- });
438
- }
439
-
440
- // Both arguments here are guaranteed to be flattened
441
- // maps of key -> [values] of unique fields only.
442
- function getCollisions(obj1, obj2) {
443
- const collisions = [];
444
- for (let [key, arr1] of Object.entries(obj1)) {
445
- const arr2 = obj2[key];
446
- if (arr2) {
447
- const hasCollision = arr1.some((val) => {
448
- return arr2.includes(val);
449
- });
450
- if (hasCollision) {
451
- collisions.push(key);
452
- }
453
- }
454
- }
455
- return collisions;
456
- }
457
-
458
443
  // Hook Patch
459
444
 
460
445
  function applyHookPatch(schema) {
package/src/validation.js CHANGED
@@ -5,8 +5,8 @@ import { get, omit, lowerFirst } from 'lodash';
5
5
 
6
6
  import { hasAccess } from './access';
7
7
  import { searchValidation } from './search';
8
+ import { assertUnique } from './soft-delete';
8
9
  import { PermissionsError, ImplementationError } from './errors';
9
- import { hasUniqueConstraints, assertUnique } from './soft-delete';
10
10
  import { isMongooseSchema, isSchemaTypedef } from './utils';
11
11
  import { INCLUDE_FIELD_SCHEMA } from './include';
12
12
 
@@ -38,7 +38,7 @@ const REFERENCE_SCHEMA = yd
38
38
  })
39
39
  .custom((obj) => {
40
40
  return obj.id;
41
- })
41
+ }),
42
42
  )
43
43
  .message('Must be an id or object containing an "id" field.')
44
44
  .tag({
@@ -87,8 +87,6 @@ export function addValidators(schemas) {
87
87
  }
88
88
 
89
89
  export function applyValidation(schema, definition) {
90
- const hasUnique = hasUniqueConstraints(schema);
91
-
92
90
  schema.static(
93
91
  'getCreateValidation',
94
92
  function getCreateValidation(options = {}) {
@@ -103,14 +101,8 @@ export function applyValidation(schema, definition) {
103
101
  allowDefaultTags: true,
104
102
  allowExpandedRefs: true,
105
103
  requireWriteAccess: true,
106
- ...(hasUnique && {
107
- assertUniqueOptions: {
108
- schema,
109
- operation: 'create',
110
- },
111
- }),
112
104
  });
113
- }
105
+ },
114
106
  );
115
107
 
116
108
  schema.static(
@@ -129,14 +121,8 @@ export function applyValidation(schema, definition) {
129
121
  allowExpandedRefs: true,
130
122
  requireWriteAccess: true,
131
123
  updateAccess: definition.access?.update,
132
- ...(hasUnique && {
133
- assertUniqueOptions: {
134
- schema,
135
- operation: 'update',
136
- },
137
- }),
138
124
  });
139
- }
125
+ },
140
126
  );
141
127
 
142
128
  schema.static(
@@ -160,7 +146,7 @@ export function applyValidation(schema, definition) {
160
146
  appendSchema,
161
147
  }),
162
148
  });
163
- }
149
+ },
164
150
  );
165
151
 
166
152
  schema.static('getDeleteValidation', function getDeleteValidation() {
@@ -205,17 +191,10 @@ function getMongooseFields(schema, options) {
205
191
 
206
192
  // Exported for testing
207
193
  export function getValidationSchema(attributes, options = {}) {
208
- const { appendSchema, assertUniqueOptions, allowInclude, updateAccess } =
209
- options;
194
+ const { appendSchema, allowInclude, updateAccess } = options;
195
+
210
196
  let schema = getObjectSchema(attributes, options);
211
- if (assertUniqueOptions) {
212
- schema = schema.custom(async (obj, { root }) => {
213
- await assertUnique(root, {
214
- model: options.model,
215
- ...assertUniqueOptions,
216
- });
217
- });
218
- }
197
+
219
198
  if (appendSchema) {
220
199
  schema = schema.append(appendSchema);
221
200
  }
@@ -374,6 +353,18 @@ function getSchemaForTypedef(typedef, options = {}) {
374
353
  schema = validateAccess('write', schema, typedef.writeAccess, options);
375
354
  }
376
355
 
356
+ if (typedef.softUnique) {
357
+ schema = schema.custom(async (value, { path, originalRoot }) => {
358
+ const { id } = originalRoot;
359
+ await assertUnique({
360
+ ...options,
361
+ value,
362
+ path,
363
+ id,
364
+ });
365
+ });
366
+ }
367
+
377
368
  return schema;
378
369
  }
379
370
 
@@ -423,10 +414,10 @@ function getSearchSchema(schema, type) {
423
414
  'x-schema': 'NumberRange',
424
415
  'x-description':
425
416
  'An object representing numbers falling within a range.',
426
- })
417
+ }),
427
418
  )
428
419
  .description(
429
- 'Allows searching by a value, array of values, or a numeric range.'
420
+ 'Allows searching by a value, array of values, or a numeric range.',
430
421
  );
431
422
  } else if (type === 'Date') {
432
423
  return yd
@@ -468,7 +459,7 @@ function getSearchSchema(schema, type) {
468
459
  'x-schema': 'DateRange',
469
460
  'x-description':
470
461
  'An object representing dates falling within a range.',
471
- })
462
+ }),
472
463
  )
473
464
  .description('Allows searching by a date, array of dates, or a range.');
474
465
  } else if (type === 'String' || type === 'ObjectId') {
@@ -543,7 +534,7 @@ function validateAccess(type, schema, allowed, options) {
543
534
  isAllowed = false;
544
535
  } else {
545
536
  throw new Error(
546
- `Access validation "${error.name}" requires passing { document, authUser } to the validator.`
537
+ `Access validation "${error.name}" requires passing { document, authUser } to the validator.`,
547
538
  );
548
539
  }
549
540
  } else {
@@ -560,7 +551,9 @@ function validateAccess(type, schema, allowed, options) {
560
551
  // throw the error.
561
552
  return;
562
553
  }
563
- message ||= `Field "${path.join('.')}" requires ${type} permissions.`;
554
+
555
+ // Default to not exposing the existence of this field.
556
+ message ||= `Unknown field "${path.join('.')}".`;
564
557
  }
565
558
  throw new PermissionsError(message);
566
559
  }
package/types/errors.d.ts CHANGED
@@ -8,4 +8,8 @@ export class ReferenceError extends Error {
8
8
  constructor(message: any, details: any);
9
9
  details: any;
10
10
  }
11
+ export class UniqueConstraintError extends Error {
12
+ constructor(message: any, details: any);
13
+ details: any;
14
+ }
11
15
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.js"],"names":[],"mappings":"AAAA;CAA8C;AAE9C;IACE,uBAGC;IADC,UAAgB;CAEnB;AAED;IACE,wCAGC;IADC,aAAsB;CAEzB"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.js"],"names":[],"mappings":"AAAA;CAA8C;AAE9C;IACE,uBAGC;IADC,UAAgB;CAEnB;AAED;IACE,wCAGC;IADC,aAAsB;CAEzB;AAED;IACE,wCAGC;IADC,aAAsB;CAEzB"}
@@ -4,11 +4,13 @@ export function getParams(modelName: any, arg: any): any;
4
4
  export function getDocumentParams(doc: any, arg: any, options?: {}): any;
5
5
  export const INCLUDE_FIELD_SCHEMA: {
6
6
  setup(): void;
7
- getFields(): any;
8
- append(arg: import("@bedrockio/yada/types/object").SchemaMap | import("@bedrockio/yada/types/Schema").default): /*elided*/ any;
9
7
  get(path?: string | Array<string>): any;
8
+ unwind(path?: string | Array<string>): any;
10
9
  pick(...names?: string[]): /*elided*/ any;
11
10
  omit(...names?: string[]): /*elided*/ any;
11
+ require(...fields: string[]): /*elided*/ any;
12
+ export(): any;
13
+ append(arg: import("@bedrockio/yada/types/object").SchemaMap | import("@bedrockio/yada/types/Schema").default): /*elided*/ any;
12
14
  format(name: any, fn: any): /*elided*/ any;
13
15
  toString(): any;
14
16
  assertions: any[];
@@ -1 +1 @@
1
- {"version":3,"file":"include.d.ts","sourceRoot":"","sources":["../src/include.js"],"names":[],"mappings":"AA2BA,gDAuEC;AAMD,uDA4BC;AAGD,yDAIC;AAaD,yEAUC;AA9ID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAsDoB,CAAC;;;;;;;;;;;;;;;;;EAjDlB"}
1
+ {"version":3,"file":"include.d.ts","sourceRoot":"","sources":["../src/include.js"],"names":[],"mappings":"AA2BA,gDAuEC;AAMD,uDA4BC;AAGD,yDAIC;AAaD,yEAUC;AA9ID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAsDsB,CAAC;;;;;;;;;;;;;;;;;EAjDpB"}
package/types/search.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  export function applySearch(schema: any, definition: any): void;
2
2
  export function searchValidation(options?: {}): {
3
3
  setup(): void;
4
- getFields(): any;
5
- append(arg: import("@bedrockio/yada/types/object").SchemaMap | import("@bedrockio/yada/types/Schema").default): /*elided*/ any;
6
4
  get(path?: string | Array<string>): any;
5
+ unwind(path?: string | Array<string>): any;
7
6
  pick(...names?: string[]): /*elided*/ any;
8
7
  omit(...names?: string[]): /*elided*/ any;
8
+ require(...fields: string[]): /*elided*/ any;
9
+ export(): any;
10
+ append(arg: import("@bedrockio/yada/types/object").SchemaMap | import("@bedrockio/yada/types/Schema").default): /*elided*/ any;
9
11
  format(name: any, fn: any): /*elided*/ any;
10
12
  toString(): any;
11
13
  assertions: any[];
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAsBA,gEAwDC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAWS,CAAA;;;;;;;;;;;;;;;;;EAcR"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.js"],"names":[],"mappings":"AAsBA,gEAwDC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAWY,CAAC;;;;;;;;;;;;;;;;;EAcZ"}
@@ -1,4 +1,3 @@
1
1
  export function applySoftDelete(schema: any): void;
2
- export function assertUnique(obj: any, options: any): Promise<void>;
3
- export function hasUniqueConstraints(schema: any): boolean;
2
+ export function assertUnique(options: any): Promise<void>;
4
3
  //# sourceMappingURL=soft-delete.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"soft-delete.d.ts","sourceRoot":"","sources":["../src/soft-delete.js"],"names":[],"mappings":"AAKA,mDAIC;AAwTD,oEAsBC;AAgDD,2DAKC"}
1
+ {"version":3,"file":"soft-delete.d.ts","sourceRoot":"","sources":["../src/soft-delete.js"],"names":[],"mappings":"AAKA,mDAIC;AAED,0DAsBC"}
@@ -84,7 +84,7 @@ export const OBJECT_ID_SCHEMA: {
84
84
  options(options: any): /*elided*/ any;
85
85
  validate(value: any, options?: {}): Promise<any>;
86
86
  clone(meta: any): /*elided*/ any;
87
- append(schema: any): /*elided*/ any;
87
+ append(schema: any): import("@bedrockio/yada/types/Schema").default;
88
88
  toOpenApi(extra: any): any;
89
89
  getAnyType(): {
90
90
  type: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"AAoFA,kDAEC;AAED,oEAgGC;AAsBD,wEA2BC;AAkVD;;;EAEC;AAED;;;EAOC;AApjBD;;;;;;;;;;;;;;;;eAwBE,CAAF;;;;;;;;;;;;iBAcmC,CAAC;kBAC3B,CAAC;kBAEH,CAAC;oBACA,CAAC;oBACF,CAAC;;;wBAkBkB,CAAC;8BAEf,CAAC;oBAGD,CAAC;oBACV,CAAC;oCAGG,CAAC;uBAAkC,CAAC;8BACb,CAAC;uBAEjB,CAAC;iBAEX,CAAJ;;;mBAa6B,CAAC;yBAEjB,CAAC;0BAEN,CAAF;yBAKE,CAAC;sBACgB,CAAC;yBACS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAhDxB,CAAC;;;;;;;;;;;;;;;;;;EA3CR"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.js"],"names":[],"mappings":"AAoFA,kDAEC;AAED,oEAkFC;AAsBD,wEAoBC;AAgWD;;;EAEC;AAED;;;EAOC;AA7iBD;;;;;;;;;;;;;;;;eAwBoB,CAAC;;;;;;;;;;;;iBAenB,CAAA;kBAEA,CAAF;kBAA4B,CAAC;oBACA,CAAC;oBAE5B,CAAC;;;wBAkBc,CAAC;8BAIjB,CAAC;oBAA+B,CAAC;oBACV,CAAC;oCAGG,CAAC;uBACpB,CAAC;8BAEH,CAAC;uBAAmC,CAAA;iBACrB,CAAC;;;mBAkBX,CAAC;yBAAoC,CAAC;0BACpB,CAAC;yBAEvB,CAAN;sBACW,CAAC;yBAEN,CAAJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eA9CC,CAAC;;;;;;;;;;;;;;;;;;EA5CD"}
package/.prettierrc.cjs DELETED
@@ -1 +0,0 @@
1
- module.exports = require('@bedrockio/prettier-config');