@bedrockio/model 0.10.0 → 0.11.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,11 @@
1
+ ## 0.11.0
2
+
3
+ - Added clone module.
4
+
5
+ ## 0.10.1
6
+
7
+ - Fix to not expose details on unique constraint errors.
8
+
1
9
  ## 0.10.0
2
10
 
3
11
  - Unique constraints now run sequentially and will not run on nested validations
package/README.md CHANGED
@@ -23,6 +23,7 @@ Bedrock utilities for model creation.
23
23
  - [Access Control](#access-control)
24
24
  - [Assign](#assign)
25
25
  - [Upsert](#upsert)
26
+ - [Clone](#clone)
26
27
  - [Slugs](#slugs)
27
28
  - [Testing](#testing)
28
29
  - [Troubleshooting](#troubleshooting)
@@ -1667,6 +1668,16 @@ if (!shop) {
1667
1668
  }
1668
1669
  ```
1669
1670
 
1671
+ ### Clone
1672
+
1673
+ Adds a single `clone` method on documents. This is an async method mostly for
1674
+ testing that will immediately create a copy of the document. It makes up for
1675
+ some of the shortcomings of the Mongoose `$clone` method:
1676
+
1677
+ - A new `id` will be generated.
1678
+ - Populated and self-referencing documents are handled.
1679
+ - Unique fields will be augmented to not collide.
1680
+
1670
1681
  ### Slugs
1671
1682
 
1672
1683
  A common requirement is to allow slugs on documents to serve as ids for human
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.applyClone = applyClone;
7
+ // This module attempts to fix Mongoose $clone
8
+ // which has a number of issues including:
9
+ // - Keeps the same _id making it impossible to use
10
+ // the clone separate to the original document.
11
+ // - Fails on circular references.
12
+ // - Clones other internals like __v.
13
+ // - Cannot deal with unique fields.
14
+ //
15
+ function applyClone(schema) {
16
+ schema.method('clone', async function clone() {
17
+ return await cloneDocument(this);
18
+ });
19
+ }
20
+ async function cloneDocument(doc) {
21
+ const Model = doc.constructor;
22
+ const clone = new Model();
23
+ for (let [key, typedef] of Object.entries(Model.schema.obj)) {
24
+ let value = doc.get(key);
25
+ const {
26
+ unique,
27
+ softUnique
28
+ } = typedef;
29
+ if (value && (unique || softUnique)) {
30
+ value = getUniqueValue(value);
31
+ }
32
+ clone.set(key, value);
33
+ }
34
+ await clone.save();
35
+ return clone;
36
+ }
37
+ let counter = 1;
38
+ function getUniqueValue(value) {
39
+ const type = typeof value;
40
+ if (type === 'string') {
41
+ return getUniqueString(value);
42
+ } else if (type === 'number') {
43
+ return value + getCounter();
44
+ } else {
45
+ throw new Error(`Unique behavior not defined for ${type}.`);
46
+ }
47
+ }
48
+ function getUniqueString(str) {
49
+ const split = str.split('@');
50
+ split[0] += getCounter();
51
+ return split.join('@');
52
+ }
53
+ function getCounter() {
54
+ return counter++;
55
+ }
@@ -25,5 +25,11 @@ class UniqueConstraintError extends Error {
25
25
  super(message);
26
26
  this.details = details;
27
27
  }
28
+ toJSON() {
29
+ return {
30
+ type: 'unique',
31
+ message: this.message
32
+ };
33
+ }
28
34
  }
29
35
  exports.UniqueConstraintError = UniqueConstraintError;
@@ -11,6 +11,7 @@ var _utils = require("./utils");
11
11
  var _serialization = require("./serialization");
12
12
  var _slug = require("./slug");
13
13
  var _cache = require("./cache");
14
+ var _clone = require("./clone");
14
15
  var _search = require("./search");
15
16
  var _assign = require("./assign");
16
17
  var _upsert = require("./upsert");
@@ -56,6 +57,7 @@ function createSchema(definition, options = {}) {
56
57
  (0, _deleteHooks.applyDeleteHooks)(schema, definition);
57
58
  (0, _search.applySearch)(schema, definition);
58
59
  (0, _cache.applyCache)(schema, definition);
60
+ (0, _clone.applyClone)(schema);
59
61
  (0, _disallowed.applyDisallowed)(schema);
60
62
  (0, _include.applyInclude)(schema);
61
63
  (0, _hydrate.applyHydrate)(schema);
@@ -34,8 +34,9 @@ async function assertUnique(options) {
34
34
  if (exists) {
35
35
  const message = getUniqueErrorMessage(model, field);
36
36
  throw new _errors.UniqueConstraintError(message, {
37
- ...options,
38
- field
37
+ model,
38
+ field,
39
+ value
39
40
  });
40
41
  }
41
42
  }
package/eslint.config.js CHANGED
@@ -1,3 +1,2 @@
1
1
  import { jest, recommended, nodeImports } from '@bedrockio/eslint-plugin';
2
-
3
2
  export default [jest, recommended, nodeImports];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrockio/model",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Bedrock utilities for model creation.",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/clone.js ADDED
@@ -0,0 +1,55 @@
1
+ // This module attempts to fix Mongoose $clone
2
+ // which has a number of issues including:
3
+ // - Keeps the same _id making it impossible to use
4
+ // the clone separate to the original document.
5
+ // - Fails on circular references.
6
+ // - Clones other internals like __v.
7
+ // - Cannot deal with unique fields.
8
+ //
9
+ export function applyClone(schema) {
10
+ schema.method('clone', async function clone() {
11
+ return await cloneDocument(this);
12
+ });
13
+ }
14
+
15
+ async function cloneDocument(doc) {
16
+ const Model = doc.constructor;
17
+ const clone = new Model();
18
+
19
+ for (let [key, typedef] of Object.entries(Model.schema.obj)) {
20
+ let value = doc.get(key);
21
+ const { unique, softUnique } = typedef;
22
+ if (value && (unique || softUnique)) {
23
+ value = getUniqueValue(value);
24
+ }
25
+
26
+ clone.set(key, value);
27
+ }
28
+
29
+ await clone.save();
30
+
31
+ return clone;
32
+ }
33
+
34
+ let counter = 1;
35
+
36
+ function getUniqueValue(value) {
37
+ const type = typeof value;
38
+ if (type === 'string') {
39
+ return getUniqueString(value);
40
+ } else if (type === 'number') {
41
+ return value + getCounter();
42
+ } else {
43
+ throw new Error(`Unique behavior not defined for ${type}.`);
44
+ }
45
+ }
46
+
47
+ function getUniqueString(str) {
48
+ const split = str.split('@');
49
+ split[0] += getCounter();
50
+ return split.join('@');
51
+ }
52
+
53
+ function getCounter() {
54
+ return counter++;
55
+ }
package/src/errors.js CHANGED
@@ -19,4 +19,11 @@ export class UniqueConstraintError extends Error {
19
19
  super(message);
20
20
  this.details = details;
21
21
  }
22
+
23
+ toJSON() {
24
+ return {
25
+ type: 'unique',
26
+ message: this.message,
27
+ };
28
+ }
22
29
  }
package/src/schema.js CHANGED
@@ -6,6 +6,7 @@ import { isSchemaTypedef } from './utils';
6
6
  import { serializeOptions } from './serialization';
7
7
  import { applySlug } from './slug';
8
8
  import { applyCache } from './cache';
9
+ import { applyClone } from './clone';
9
10
  import { applySearch } from './search';
10
11
  import { applyAssign } from './assign';
11
12
  import { applyUpsert } from './upsert';
@@ -60,6 +61,7 @@ export function createSchema(definition, options = {}) {
60
61
  applyDeleteHooks(schema, definition);
61
62
  applySearch(schema, definition);
62
63
  applyCache(schema, definition);
64
+ applyClone(schema);
63
65
  applyDisallowed(schema);
64
66
  applyInclude(schema);
65
67
  applyHydrate(schema);
@@ -27,8 +27,9 @@ export async function assertUnique(options) {
27
27
  if (exists) {
28
28
  const message = getUniqueErrorMessage(model, field);
29
29
  throw new UniqueConstraintError(message, {
30
- ...options,
30
+ model,
31
31
  field,
32
+ value,
32
33
  });
33
34
  }
34
35
  }
@@ -0,0 +1,2 @@
1
+ export function applyClone(schema: any): void;
2
+ //# sourceMappingURL=clone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clone.d.ts","sourceRoot":"","sources":["../src/clone.js"],"names":[],"mappings":"AAQA,8CAIC"}
package/types/errors.d.ts CHANGED
@@ -11,5 +11,9 @@ export class ReferenceError extends Error {
11
11
  export class UniqueConstraintError extends Error {
12
12
  constructor(message: any, details: any);
13
13
  details: any;
14
+ toJSON(): {
15
+ type: string;
16
+ message: string;
17
+ };
14
18
  }
15
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;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"}
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.js"],"names":[],"mappings":"AAuBA;;;;;;;GAOG;AACH,yCAJW,MAAM,YACN,QAAQ,CAAC,aAAa;;;;;;;YA8C5B,CAAC;WACF,CAAA;mBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;SAgHtB,CAAD;gBAA4B,CAAA;SAAY,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aArH1C;AAED,iEAsBC;qBA9FoB,UAAU"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.js"],"names":[],"mappings":"AAwBA;;;;;;;GAOG;AACH,yCAJW,MAAM,YACN,QAAQ,CAAC,aAAa;;;;;;;YA6CM,CAAC;WACtC,CAAF;mBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;SAiHV,CAAA;gBAA2B,CAAC;SAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aApHnD;AAED,iEAsBC;qBAhGoB,UAAU"}
@@ -1 +1 @@
1
- {"version":3,"file":"soft-delete.d.ts","sourceRoot":"","sources":["../src/soft-delete.js"],"names":[],"mappings":"AAKA,mDAIC;AAED,0DAsBC"}
1
+ {"version":3,"file":"soft-delete.d.ts","sourceRoot":"","sources":["../src/soft-delete.js"],"names":[],"mappings":"AAKA,mDAIC;AAED,0DAuBC"}