@bedrockio/model 0.9.1 → 0.10.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/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 0.10.1
2
+
3
+ - Fix to not expose details on unique constraint errors.
4
+
5
+ ## 0.10.0
6
+
7
+ - Unique constraints now run sequentially and will not run on nested validations
8
+ unless their parent fields are passed.
9
+
1
10
  ## 0.9.1
2
11
 
3
12
  - 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,17 @@ 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
+ toJSON() {
29
+ return {
30
+ type: 'unique',
31
+ message: this.message
32
+ };
33
+ }
34
+ }
35
+ exports.UniqueConstraintError = UniqueConstraintError;
@@ -5,16 +5,52 @@ 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
+ model,
38
+ field,
39
+ value
40
+ });
41
+ }
42
+ }
43
+ function getUniqueErrorMessage(model, field) {
44
+ const {
45
+ modelName
46
+ } = model;
47
+ if (modelName === 'User' && !field.includes('.')) {
48
+ const name = field === 'phone' ? 'phone number' : field;
49
+ return `A user with that ${name} already exists.`;
50
+ } else {
51
+ return `"${field}" already exists.`;
52
+ }
53
+ }
18
54
 
19
55
  // Soft Delete Querying
20
56
 
@@ -235,20 +271,24 @@ function getWithDeletedQuery() {
235
271
  // Unique Constraints
236
272
 
237
273
  function applyUniqueConstraints(schema) {
238
- const hasUnique = hasUniqueConstraints(schema);
239
- if (!hasUnique) {
274
+ const uniquePaths = getUniqueConstraints(schema);
275
+ if (!uniquePaths.length) {
240
276
  return;
241
277
  }
242
278
  schema.pre('save', async function () {
243
- await assertUnique(this, {
244
- operation: this.isNew ? 'create' : 'update',
245
- model: this.constructor,
246
- schema
247
- });
279
+ for (let path of uniquePaths) {
280
+ await assertUnique({
281
+ path,
282
+ id: this.id,
283
+ value: this.get(path),
284
+ model: this.constructor
285
+ });
286
+ }
248
287
  });
249
288
  schema.pre(/^(update|replace)/, async function () {
250
289
  await assertUniqueForQuery(this, {
251
- schema
290
+ schema,
291
+ uniquePaths
252
292
  });
253
293
  });
254
294
  schema.pre('insertMany', async function (next, obj) {
@@ -258,54 +298,39 @@ function applyUniqueConstraints(schema) {
258
298
  // as the last argument, however as we are passing an async
259
299
  // function it appears to not stop the middleware if we
260
300
  // don't call it directly.
261
- await assertUnique(obj, {
262
- operation: 'create',
301
+
302
+ await runUniqueConstraints(obj, {
263
303
  model: this,
264
- schema
304
+ uniquePaths
265
305
  });
266
306
  });
267
307
  }
268
- async function assertUnique(obj, options) {
308
+ async function runUniqueConstraints(arg, options) {
269
309
  const {
270
- operation,
271
- model,
272
- schema
310
+ uniquePaths,
311
+ model
273
312
  } = 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
313
+ // Updates or inserts
314
+ const operations = Array.isArray(arg) ? arg : [arg];
315
+ for (let operation of operations) {
316
+ for (let path of uniquePaths) {
317
+ const value = operation[path];
318
+ if (value) {
319
+ await assertUnique({
320
+ path,
321
+ value,
322
+ model
323
+ });
284
324
  }
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}.`);
325
+ }
297
326
  }
298
327
  }
299
- function getId(arg) {
300
- const id = arg.id || arg._id;
301
- return id ? String(id) : null;
302
- }
303
328
 
304
329
  // Asserts than an update or insert query will not
305
330
  // result in duplicate unique fields being present
306
331
  // within non-deleted documents.
307
332
  async function assertUniqueForQuery(query, options) {
308
- let update = query.getUpdate();
333
+ const update = query.getUpdate();
309
334
  const operation = getOperationForQuery(update);
310
335
  // Note: No need to check unique constraints
311
336
  // if the operation is a delete.
@@ -314,6 +339,7 @@ async function assertUniqueForQuery(query, options) {
314
339
  model
315
340
  } = query;
316
341
  const filter = query.getFilter();
342
+ let updates;
317
343
  if (operation === 'restore') {
318
344
  // A restore operation is functionally identical to a new
319
345
  // insert so we need to fetch the deleted documents with
@@ -321,18 +347,30 @@ async function assertUniqueForQuery(query, options) {
321
347
  const docs = await model.findWithDeleted(filter, {}, {
322
348
  lean: true
323
349
  });
324
- update = docs.map(doc => {
350
+ updates = docs.map(doc => {
325
351
  return {
326
352
  ...doc,
327
353
  ...update
328
354
  };
329
355
  });
356
+ } else {
357
+ updates = [update];
358
+ }
359
+ const {
360
+ uniquePaths
361
+ } = options;
362
+ for (let update of updates) {
363
+ for (let path of uniquePaths) {
364
+ const value = update[path];
365
+ if (value) {
366
+ await assertUnique({
367
+ path,
368
+ value,
369
+ model
370
+ });
371
+ }
372
+ }
330
373
  }
331
- await assertUnique(update, {
332
- ...options,
333
- operation,
334
- model
335
- });
336
374
  }
337
375
  }
338
376
  function getOperationForQuery(update) {
@@ -344,9 +382,9 @@ function getOperationForQuery(update) {
344
382
  return 'update';
345
383
  }
346
384
  }
347
- function hasUniqueConstraints(schema) {
385
+ function getUniqueConstraints(schema) {
348
386
  const paths = [...Object.keys(schema.paths), ...Object.keys(schema.subpaths)];
349
- return paths.some(key => {
387
+ return paths.filter(key => {
350
388
  return isUniquePath(schema, key);
351
389
  });
352
390
  }
@@ -354,67 +392,6 @@ function isUniquePath(schema, key) {
354
392
  return schema.path(key)?.options?.softUnique === true;
355
393
  }
356
394
 
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
395
  // Hook Patch
419
396
 
420
397
  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.1",
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,17 @@ 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
+
23
+ toJSON() {
24
+ return {
25
+ type: 'unique',
26
+ message: this.message,
27
+ };
28
+ }
29
+ }
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,41 @@ 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
+ model,
31
+ field,
32
+ value,
33
+ });
34
+ }
35
+ }
36
+
37
+ function getUniqueErrorMessage(model, field) {
38
+ const { modelName } = model;
39
+ if (modelName === 'User' && !field.includes('.')) {
40
+ const name = field === 'phone' ? 'phone number' : field;
41
+ return `A user with that ${name} already exists.`;
42
+ } else {
43
+ return `"${field}" already exists.`;
44
+ }
45
+ }
46
+
12
47
  // Soft Delete Querying
13
48
 
14
49
  function applyQueries(schema) {
@@ -58,7 +93,7 @@ function applyQueries(schema) {
58
93
  deleted: false,
59
94
  },
60
95
  update,
61
- ...omitCallback(rest)
96
+ ...omitCallback(rest),
62
97
  );
63
98
  return wrapQuery(query, async (promise) => {
64
99
  const res = await promise;
@@ -77,7 +112,7 @@ function applyQueries(schema) {
77
112
  deleted: false,
78
113
  },
79
114
  update,
80
- ...omitCallback(rest)
115
+ ...omitCallback(rest),
81
116
  );
82
117
  return wrapQuery(query, async (promise) => {
83
118
  const res = await promise;
@@ -95,7 +130,7 @@ function applyQueries(schema) {
95
130
  deleted: false,
96
131
  },
97
132
  getDelete(),
98
- ...omitCallback(rest)
133
+ ...omitCallback(rest),
99
134
  );
100
135
  });
101
136
 
@@ -106,7 +141,7 @@ function applyQueries(schema) {
106
141
  deleted: true,
107
142
  },
108
143
  getRestore(),
109
- ...omitCallback(rest)
144
+ ...omitCallback(rest),
110
145
  );
111
146
  return wrapQuery(query, async (promise) => {
112
147
  const res = await promise;
@@ -124,7 +159,7 @@ function applyQueries(schema) {
124
159
  deleted: true,
125
160
  },
126
161
  getRestore(),
127
- ...omitCallback(rest)
162
+ ...omitCallback(rest),
128
163
  );
129
164
  return wrapQuery(query, async (promise) => {
130
165
  const res = await promise;
@@ -139,7 +174,7 @@ function applyQueries(schema) {
139
174
  // Following Mongoose patterns here
140
175
  const query = new this.Query({}, {}, this, this.collection).deleteOne(
141
176
  conditions,
142
- ...omitCallback(rest)
177
+ ...omitCallback(rest),
143
178
  );
144
179
  return wrapQuery(query, async (promise) => {
145
180
  const res = await promise;
@@ -154,7 +189,7 @@ function applyQueries(schema) {
154
189
  // Following Mongoose patterns here
155
190
  const query = new this.Query({}, {}, this, this.collection).deleteMany(
156
191
  conditions,
157
- ...omitCallback(rest)
192
+ ...omitCallback(rest),
158
193
  );
159
194
  return wrapQuery(query, async (promise) => {
160
195
  const res = await promise;
@@ -205,7 +240,7 @@ function applyQueries(schema) {
205
240
  deleted: true,
206
241
  };
207
242
  return this.countDocuments(filter, ...omitCallback(rest));
208
- }
243
+ },
209
244
  );
210
245
 
211
246
  schema.static('findWithDeleted', function findWithDeleted(filter, ...rest) {
@@ -224,7 +259,7 @@ function applyQueries(schema) {
224
259
  ...getWithDeletedQuery(),
225
260
  };
226
261
  return this.findOne(filter, ...omitCallback(rest));
227
- }
262
+ },
228
263
  );
229
264
 
230
265
  schema.static(
@@ -235,7 +270,7 @@ function applyQueries(schema) {
235
270
  ...getWithDeletedQuery(),
236
271
  };
237
272
  return this.findOne(filter, ...omitCallback(rest));
238
- }
273
+ },
239
274
  );
240
275
 
241
276
  schema.static(
@@ -246,7 +281,7 @@ function applyQueries(schema) {
246
281
  ...getWithDeletedQuery(),
247
282
  };
248
283
  return this.exists(filter, ...omitCallback(rest));
249
- }
284
+ },
250
285
  );
251
286
 
252
287
  schema.static(
@@ -257,7 +292,7 @@ function applyQueries(schema) {
257
292
  ...getWithDeletedQuery(),
258
293
  };
259
294
  return this.countDocuments(filter, ...omitCallback(rest));
260
- }
295
+ },
261
296
  );
262
297
  }
263
298
 
@@ -284,23 +319,27 @@ function getWithDeletedQuery() {
284
319
  // Unique Constraints
285
320
 
286
321
  function applyUniqueConstraints(schema) {
287
- const hasUnique = hasUniqueConstraints(schema);
322
+ const uniquePaths = getUniqueConstraints(schema);
288
323
 
289
- if (!hasUnique) {
324
+ if (!uniquePaths.length) {
290
325
  return;
291
326
  }
292
327
 
293
328
  schema.pre('save', async function () {
294
- await assertUnique(this, {
295
- operation: this.isNew ? 'create' : 'update',
296
- model: this.constructor,
297
- schema,
298
- });
329
+ for (let path of uniquePaths) {
330
+ await assertUnique({
331
+ path,
332
+ id: this.id,
333
+ value: this.get(path),
334
+ model: this.constructor,
335
+ });
336
+ }
299
337
  });
300
338
 
301
339
  schema.pre(/^(update|replace)/, async function () {
302
340
  await assertUniqueForQuery(this, {
303
341
  schema,
342
+ uniquePaths,
304
343
  });
305
344
  });
306
345
 
@@ -311,71 +350,73 @@ function applyUniqueConstraints(schema) {
311
350
  // as the last argument, however as we are passing an async
312
351
  // function it appears to not stop the middleware if we
313
352
  // don't call it directly.
314
- await assertUnique(obj, {
315
- operation: 'create',
353
+
354
+ await runUniqueConstraints(obj, {
316
355
  model: this,
317
- schema,
356
+ uniquePaths,
318
357
  });
319
358
  });
320
359
  }
321
360
 
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
- );
361
+ async function runUniqueConstraints(arg, options) {
362
+ const { uniquePaths, model } = options;
363
+ // Updates or inserts
364
+ const operations = Array.isArray(arg) ? arg : [arg];
365
+ for (let operation of operations) {
366
+ for (let path of uniquePaths) {
367
+ const value = operation[path];
368
+ if (value) {
369
+ await assertUnique({
370
+ path,
371
+ value,
372
+ model,
373
+ });
374
+ }
375
+ }
343
376
  }
344
377
  }
345
378
 
346
- function getId(arg) {
347
- const id = arg.id || arg._id;
348
- return id ? String(id) : null;
349
- }
350
-
351
379
  // Asserts than an update or insert query will not
352
380
  // result in duplicate unique fields being present
353
381
  // within non-deleted documents.
354
382
  async function assertUniqueForQuery(query, options) {
355
- let update = query.getUpdate();
383
+ const update = query.getUpdate();
356
384
  const operation = getOperationForQuery(update);
357
385
  // Note: No need to check unique constraints
358
386
  // if the operation is a delete.
359
387
  if (operation === 'restore' || operation === 'update') {
360
388
  const { model } = query;
361
389
  const filter = query.getFilter();
390
+
391
+ let updates;
362
392
  if (operation === 'restore') {
363
393
  // A restore operation is functionally identical to a new
364
394
  // insert so we need to fetch the deleted documents with
365
395
  // all fields available to check against.
366
396
  const docs = await model.findWithDeleted(filter, {}, { lean: true });
367
- update = docs.map((doc) => {
397
+ updates = docs.map((doc) => {
368
398
  return {
369
399
  ...doc,
370
400
  ...update,
371
401
  };
372
402
  });
403
+ } else {
404
+ updates = [update];
405
+ }
406
+
407
+ const { uniquePaths } = options;
408
+ for (let update of updates) {
409
+ for (let path of uniquePaths) {
410
+ const value = update[path];
411
+ if (value) {
412
+ await assertUnique({
413
+ path,
414
+ value,
415
+ model,
416
+ });
417
+ }
418
+ }
373
419
  }
374
- await assertUnique(update, {
375
- ...options,
376
- operation,
377
- model,
378
- });
379
420
  }
380
421
  }
381
422
 
@@ -389,9 +430,9 @@ function getOperationForQuery(update) {
389
430
  }
390
431
  }
391
432
 
392
- export function hasUniqueConstraints(schema) {
433
+ function getUniqueConstraints(schema) {
393
434
  const paths = [...Object.keys(schema.paths), ...Object.keys(schema.subpaths)];
394
- return paths.some((key) => {
435
+ return paths.filter((key) => {
395
436
  return isUniquePath(schema, key);
396
437
  });
397
438
  }
@@ -400,61 +441,6 @@ function isUniquePath(schema, key) {
400
441
  return schema.path(key)?.options?.softUnique === true;
401
442
  }
402
443
 
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
444
  // Hook Patch
459
445
 
460
446
  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,12 @@ 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
+ toJSON(): {
15
+ type: string;
16
+ message: string;
17
+ };
18
+ }
11
19
  //# 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;IAGxB;;;MAKC;CACF"}
@@ -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,0DAuBC"}
@@ -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');