@e22m4u/js-repository 0.1.8 → 0.1.10

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.
Files changed (128) hide show
  1. package/README.md +22 -1
  2. package/docs/assets/navigation.js +1 -1
  3. package/docs/assets/search.js +1 -1
  4. package/docs/classes/Adapter.html +1 -1
  5. package/docs/classes/AdapterLoader.html +1 -1
  6. package/docs/classes/AdapterRegistry.html +1 -1
  7. package/docs/classes/BelongsToResolver.html +1 -1
  8. package/docs/classes/DatasourceDefinitionValidator.html +1 -1
  9. package/docs/classes/DefinitionRegistry.html +1 -1
  10. package/docs/classes/EmptyValuesDefiner.html +18 -0
  11. package/docs/classes/FieldsClauseTool.html +1 -1
  12. package/docs/classes/HasManyResolver.html +1 -1
  13. package/docs/classes/HasOneResolver.html +1 -1
  14. package/docs/classes/IncludeClauseTool.html +1 -1
  15. package/docs/classes/InvalidArgumentError.html +1 -1
  16. package/docs/classes/InvalidOperatorValueError.html +1 -1
  17. package/docs/classes/ModelDataSanitizer.html +1 -1
  18. package/docs/classes/ModelDataTransformer.html +1 -1
  19. package/docs/classes/ModelDataValidator.html +1 -1
  20. package/docs/classes/ModelDefinitionUtils.html +1 -1
  21. package/docs/classes/ModelDefinitionValidator.html +1 -1
  22. package/docs/classes/NotImplementedError.html +1 -1
  23. package/docs/classes/OperatorClauseTool.html +1 -1
  24. package/docs/classes/OrderClauseTool.html +1 -1
  25. package/docs/classes/PrimaryKeysDefinitionValidator.html +1 -1
  26. package/docs/classes/PropertiesDefinitionValidator.html +1 -1
  27. package/docs/classes/PropertyTransformerRegistry.html +1 -1
  28. package/docs/classes/PropertyUniquenessValidator.html +16 -0
  29. package/docs/classes/PropertyValidatorRegistry.html +1 -1
  30. package/docs/classes/ReferencesManyResolver.html +1 -1
  31. package/docs/classes/RelationsDefinitionValidator.html +1 -1
  32. package/docs/classes/Repository.html +1 -1
  33. package/docs/classes/RepositoryRegistry.html +1 -1
  34. package/docs/classes/Schema.html +1 -1
  35. package/docs/classes/SliceClauseTool.html +1 -1
  36. package/docs/classes/WhereClauseTool.html +1 -1
  37. package/docs/enums/DataType.html +1 -1
  38. package/docs/enums/DecoratorTargetType.html +1 -1
  39. package/docs/enums/RelationType.html +1 -1
  40. package/docs/functions/capitalize.html +1 -1
  41. package/docs/functions/cloneDeep.html +1 -1
  42. package/docs/functions/excludeObjectKeys.html +1 -1
  43. package/docs/functions/getCtorName.html +1 -1
  44. package/docs/functions/getDecoratorTargetType.html +1 -1
  45. package/docs/functions/getValueByPath.html +1 -1
  46. package/docs/functions/isCtor.html +1 -1
  47. package/docs/functions/isDeepEqual.html +2 -0
  48. package/docs/functions/isPureObject.html +1 -1
  49. package/docs/functions/selectObjectKeys.html +1 -1
  50. package/docs/functions/singularize.html +1 -1
  51. package/docs/functions/stringToRegexp.html +1 -1
  52. package/docs/index.html +18 -3
  53. package/docs/interfaces/AndClause.html +1 -1
  54. package/docs/interfaces/Constructor.html +1 -1
  55. package/docs/interfaces/OrClause.html +1 -1
  56. package/docs/modules.html +6 -1
  57. package/docs/types/AnyObject.html +1 -1
  58. package/docs/types/BelongsToDefinition.html +1 -1
  59. package/docs/types/CountMethod.html +2 -0
  60. package/docs/types/DEFAULT_PRIMARY_KEY_PROPERTY_NAME.html +1 -1
  61. package/docs/types/DatasourceDefinition.html +1 -1
  62. package/docs/types/FieldsClause.html +1 -1
  63. package/docs/types/FilterClause.html +1 -1
  64. package/docs/types/Flatten.html +1 -1
  65. package/docs/types/FullPropertyDefinition.html +2 -2
  66. package/docs/types/HasManyDefinition.html +1 -1
  67. package/docs/types/HasOneDefinition.html +1 -1
  68. package/docs/types/Identity.html +1 -1
  69. package/docs/types/IncludeClause.html +1 -1
  70. package/docs/types/ItemFilterClause.html +1 -1
  71. package/docs/types/ModelData.html +1 -1
  72. package/docs/types/ModelDefinition.html +1 -1
  73. package/docs/types/ModelId.html +1 -1
  74. package/docs/types/NestedIncludeClause.html +1 -1
  75. package/docs/types/NormalizedFieldsClause.html +1 -1
  76. package/docs/types/NormalizedIncludeClause.html +1 -1
  77. package/docs/types/OperatorClause.html +1 -1
  78. package/docs/types/OptionalUnlessRequiredId.html +1 -1
  79. package/docs/types/OrderClause.html +1 -1
  80. package/docs/types/PartialBy.html +1 -1
  81. package/docs/types/PartialWithoutId.html +1 -1
  82. package/docs/types/PolyBelongsToDefinition.html +1 -1
  83. package/docs/types/PolyHasManyDefinitionWithTargetKeys.html +1 -1
  84. package/docs/types/PolyHasManyDefinitionWithTargetRelationName.html +1 -1
  85. package/docs/types/PolyHasOneDefinitionWithTargetKeys.html +1 -1
  86. package/docs/types/PolyHasOneDefinitionWithTargetRelationName.html +1 -1
  87. package/docs/types/PropertiesClause.html +1 -1
  88. package/docs/types/PropertyDefinition.html +1 -1
  89. package/docs/types/PropertyDefinitionMap.html +1 -1
  90. package/docs/types/PropertyTransformOptions.html +1 -1
  91. package/docs/types/PropertyTransformer.html +1 -1
  92. package/docs/types/PropertyTransformerContext.html +1 -1
  93. package/docs/types/PropertyUniqueness.html +2 -0
  94. package/docs/types/PropertyValidateOptions.html +1 -1
  95. package/docs/types/PropertyValidator.html +1 -1
  96. package/docs/types/PropertyValidatorContext.html +1 -1
  97. package/docs/types/ReferencesManyDefinition.html +1 -1
  98. package/docs/types/RelationDefinition.html +1 -1
  99. package/docs/types/RelationDefinitionMap.html +1 -1
  100. package/docs/types/WhereClause.html +1 -1
  101. package/docs/types/WithoutId.html +1 -1
  102. package/package.json +4 -4
  103. package/src/adapter/adapter.js +2 -0
  104. package/src/adapter/adapter.spec.js +8 -2
  105. package/src/adapter/decorator/index.d.ts +1 -0
  106. package/src/adapter/decorator/index.js +1 -0
  107. package/src/adapter/decorator/property-uniqueness-decorator.d.ts +14 -0
  108. package/src/adapter/decorator/property-uniqueness-decorator.js +76 -0
  109. package/src/adapter/decorator/property-uniqueness-decorator.spec.js +135 -0
  110. package/src/definition/definition-registry.spec.js +1 -0
  111. package/src/definition/model/properties/empty-values-definer.d.ts +23 -0
  112. package/src/definition/model/properties/empty-values-definer.js +66 -0
  113. package/src/definition/model/properties/empty-values-definer.spec.js +96 -0
  114. package/src/definition/model/properties/index.d.ts +3 -0
  115. package/src/definition/model/properties/index.js +3 -0
  116. package/src/definition/model/properties/properties-definition-validator.js +21 -0
  117. package/src/definition/model/properties/properties-definition-validator.spec.js +50 -5
  118. package/src/definition/model/properties/property-definition.d.ts +3 -0
  119. package/src/definition/model/properties/property-uniqueness-validator.d.ts +31 -0
  120. package/src/definition/model/properties/property-uniqueness-validator.js +141 -0
  121. package/src/definition/model/properties/property-uniqueness-validator.spec.js +987 -0
  122. package/src/definition/model/properties/property-uniqueness.d.ts +8 -0
  123. package/src/definition/model/properties/property-uniqueness.js +8 -0
  124. package/src/utils/index.d.ts +1 -0
  125. package/src/utils/index.js +1 -0
  126. package/src/utils/is-deep-equal.d.ts +10 -0
  127. package/src/utils/is-deep-equal.js +71 -0
  128. package/src/utils/is-deep-equal.spec.js +238 -0
@@ -0,0 +1,135 @@
1
+ import chai from 'chai';
2
+ import {expect} from 'chai';
3
+ import {Adapter} from '../adapter.js';
4
+ import {Schema} from '../../schema.js';
5
+ import {PropertyUniquenessValidator} from '../../definition/index.js';
6
+
7
+ const S = new Schema();
8
+ S.defineModel({name: 'model'});
9
+
10
+ class TestAdapter extends Adapter {
11
+ // eslint-disable-next-line no-unused-vars
12
+ create(modelName, modelData, filter = undefined) {
13
+ return Promise.resolve(modelData);
14
+ }
15
+
16
+ // eslint-disable-next-line no-unused-vars
17
+ replaceById(modelName, id, modelData, filter = undefined) {
18
+ return Promise.resolve(modelData);
19
+ }
20
+
21
+ // eslint-disable-next-line no-unused-vars
22
+ replaceOrCreate(modelName, modelData, filter = undefined) {
23
+ return Promise.resolve(modelData);
24
+ }
25
+
26
+ // eslint-disable-next-line no-unused-vars
27
+ patch(modelName, modelData, where = undefined) {
28
+ return Promise.resolve(modelData);
29
+ }
30
+
31
+ // eslint-disable-next-line no-unused-vars
32
+ patchById(modelName, id, modelData, filter = undefined) {
33
+ return Promise.resolve(modelData);
34
+ }
35
+ }
36
+
37
+ const A = S.getService(TestAdapter);
38
+ const V = S.getService(PropertyUniquenessValidator);
39
+ const sandbox = chai.spy.sandbox();
40
+
41
+ describe('PropertyUniquenessDecorator', function () {
42
+ afterEach(function () {
43
+ sandbox.restore();
44
+ });
45
+
46
+ it('overrides the "create" method and validates a given data', async function () {
47
+ const data = {kind: 'data'};
48
+ sandbox.on(
49
+ V,
50
+ 'validate',
51
+ (countMethod, methodName, modelName, modelData, id = undefined) => {
52
+ expect(typeof countMethod).to.be.eq('function');
53
+ expect(methodName).to.be.eq('create');
54
+ expect(modelName).to.be.eq('model');
55
+ expect(modelData).to.be.eql(data);
56
+ expect(id).to.be.undefined;
57
+ },
58
+ );
59
+ const res = await A.create('model', data);
60
+ expect(res).to.be.eql(data);
61
+ expect(V.validate).to.be.called.once;
62
+ });
63
+
64
+ it('overrides the "replaceById" method and validates a given data', async function () {
65
+ const data = {kind: 'data'};
66
+ sandbox.on(
67
+ V,
68
+ 'validate',
69
+ (countMethod, methodName, modelName, modelData, id = undefined) => {
70
+ expect(typeof countMethod).to.be.eq('function');
71
+ expect(methodName).to.be.eq('replaceById');
72
+ expect(modelName).to.be.eq('model');
73
+ expect(modelData).to.be.eql(data);
74
+ expect(id).to.be.eq(1);
75
+ },
76
+ );
77
+ const res = await A.replaceById('model', 1, data);
78
+ expect(res).to.be.eql(data);
79
+ expect(V.validate).to.be.called.once;
80
+ });
81
+
82
+ it('overrides the "replaceOrCreate" method and validates a given data', async function () {
83
+ const data = {kind: 'data'};
84
+ sandbox.on(
85
+ V,
86
+ 'validate',
87
+ (countMethod, methodName, modelName, modelData, id = undefined) => {
88
+ expect(typeof countMethod).to.be.eq('function');
89
+ expect(methodName).to.be.eq('replaceOrCreate');
90
+ expect(modelName).to.be.eq('model');
91
+ expect(modelData).to.be.eql(data);
92
+ expect(id).to.be.undefined;
93
+ },
94
+ );
95
+ const res = await A.replaceOrCreate('model', data);
96
+ expect(res).to.be.eql(data);
97
+ expect(V.validate).to.be.called.once;
98
+ });
99
+
100
+ it('overrides the "patch" method and validates a given data', async function () {
101
+ const data = {kind: 'data'};
102
+ sandbox.on(
103
+ V,
104
+ 'validate',
105
+ (countMethod, methodName, modelName, modelData, id = undefined) => {
106
+ expect(typeof countMethod).to.be.eq('function');
107
+ expect(methodName).to.be.eq('patch');
108
+ expect(modelName).to.be.eq('model');
109
+ expect(modelData).to.be.eql(data);
110
+ expect(id).to.be.undefined;
111
+ },
112
+ );
113
+ const res = await A.patch('model', data);
114
+ expect(res).to.be.eql(data);
115
+ expect(V.validate).to.be.called.once;
116
+ });
117
+
118
+ it('overrides the "patchById" method and validates a given data', async function () {
119
+ const data = {kind: 'data'};
120
+ sandbox.on(
121
+ V,
122
+ 'validate',
123
+ (countMethod, methodName, modelName, modelData, id = undefined) => {
124
+ expect(typeof countMethod).to.be.eq('function');
125
+ expect(methodName).to.be.eq('patchById');
126
+ expect(modelName).to.be.eq('model');
127
+ expect(modelData).to.be.eql(data);
128
+ expect(id).to.be.eq(1);
129
+ },
130
+ );
131
+ const res = await A.patchById('model', 1, data);
132
+ expect(res).to.be.eql(data);
133
+ expect(V.validate).to.be.called.once;
134
+ });
135
+ });
@@ -8,6 +8,7 @@ const sandbox = chai.spy.sandbox();
8
8
 
9
9
  describe('DefinitionRegistry', function () {
10
10
  let S;
11
+
11
12
  beforeEach(function () {
12
13
  S = new DefinitionRegistry();
13
14
  });
@@ -0,0 +1,23 @@
1
+ import {DataType} from './data-type.js';
2
+ import {Service} from '@e22m4u/js-service';
3
+
4
+ /**
5
+ * Empty values definer.
6
+ */
7
+ export class EmptyValuesDefiner extends Service {
8
+ /**
9
+ * Set empty values of.
10
+ *
11
+ * @param dataType
12
+ * @param emptyValues
13
+ */
14
+ setEmptyValuesOf(dataType: DataType, emptyValues: unknown[]): this;
15
+
16
+ /**
17
+ * Is empty.
18
+ *
19
+ * @param dataType
20
+ * @param value
21
+ */
22
+ isEmpty(dataType: DataType, value: unknown): boolean;
23
+ }
@@ -0,0 +1,66 @@
1
+ import {DataType} from './data-type.js';
2
+ import {Service} from '@e22m4u/js-service';
3
+ import {isDeepEqual} from '../../../utils/index.js';
4
+ import {InvalidArgumentError} from '../../../errors/index.js';
5
+
6
+ /**
7
+ * Empty values definer.
8
+ */
9
+ export class EmptyValuesDefiner extends Service {
10
+ /**
11
+ * Empty values map.
12
+ *
13
+ * @type {Map<string, *[]>}
14
+ */
15
+ _emptyValuesMap = new Map([
16
+ [DataType.ANY, [undefined, null]],
17
+ [DataType.STRING, [undefined, null, '']],
18
+ [DataType.NUMBER, [undefined, null, 0]],
19
+ [DataType.BOOLEAN, [undefined, null]],
20
+ [DataType.ARRAY, [undefined, null, []]],
21
+ [DataType.OBJECT, [undefined, null, {}]],
22
+ ]);
23
+
24
+ /**
25
+ * Set empty values of data type.
26
+ *
27
+ * @param {string} dataType
28
+ * @param {*[]} emptyValues
29
+ * @returns {EmptyValuesDefiner}
30
+ */
31
+ setEmptyValuesOf(dataType, emptyValues) {
32
+ if (!Object.values(DataType).includes(dataType))
33
+ throw new InvalidArgumentError(
34
+ 'The argument "dataType" of the EmptyValuesDefiner.setEmptyValuesOf ' +
35
+ 'must be one of data types: %l, but %v given.',
36
+ Object.values(DataType),
37
+ dataType,
38
+ );
39
+ if (!Array.isArray(emptyValues))
40
+ throw new InvalidArgumentError(
41
+ 'The argument "emptyValues" of the EmptyValuesDefiner.setEmptyValuesOf ' +
42
+ 'must be an Array, but %v given.',
43
+ emptyValues,
44
+ );
45
+ this._emptyValuesMap.set(dataType, emptyValues);
46
+ return this;
47
+ }
48
+
49
+ /**
50
+ * Is empty.
51
+ *
52
+ * @param {string} dataType
53
+ * @param {*} value
54
+ * @returns {boolean}
55
+ */
56
+ isEmpty(dataType, value) {
57
+ if (!Object.values(DataType).includes(dataType))
58
+ throw new InvalidArgumentError(
59
+ 'The argument "dataType" of the EmptyValuesDefiner.isEmpty ' +
60
+ 'must be one of data types: %l, but %v given.',
61
+ Object.values(DataType),
62
+ dataType,
63
+ );
64
+ return this._emptyValuesMap.get(dataType).some(v => isDeepEqual(v, value));
65
+ }
66
+ }
@@ -0,0 +1,96 @@
1
+ import {expect} from 'chai';
2
+ import {DataType} from './data-type.js';
3
+ import {format} from '@e22m4u/js-format';
4
+ import {Schema} from '../../../schema.js';
5
+ import {EmptyValuesDefiner} from './empty-values-definer.js';
6
+
7
+ const getEmptyValues = (definer, dataType) => {
8
+ return definer['_emptyValuesMap'].get(dataType);
9
+ };
10
+
11
+ describe('EmptyValuesDefiner', function () {
12
+ describe('_emptyValuesMap', function () {
13
+ it('has default values', function () {
14
+ const schema = new Schema();
15
+ const S = schema.getService(EmptyValuesDefiner);
16
+ expect(Array.from(S['_emptyValuesMap'])).to.be.eql([
17
+ [DataType.ANY, [undefined, null]],
18
+ [DataType.STRING, [undefined, null, '']],
19
+ [DataType.NUMBER, [undefined, null, 0]],
20
+ [DataType.BOOLEAN, [undefined, null]],
21
+ [DataType.ARRAY, [undefined, null, []]],
22
+ [DataType.OBJECT, [undefined, null, {}]],
23
+ ]);
24
+ });
25
+ });
26
+
27
+ describe('setEmptyValuesOf', function () {
28
+ it('requires the parameter "dataType" to be a DataType enum', function () {
29
+ const schema = new Schema();
30
+ const S = schema.getService(EmptyValuesDefiner);
31
+ const throwable = v => () => S.setEmptyValuesOf(v, []);
32
+ const error = v =>
33
+ format(
34
+ 'The argument "dataType" of the EmptyValuesDefiner.setEmptyValuesOf ' +
35
+ 'must be one of data types: %l, but %s given.',
36
+ Object.values(DataType),
37
+ v,
38
+ );
39
+ expect(throwable('str')).to.throw(error('"str"'));
40
+ expect(throwable('')).to.throw(error('""'));
41
+ expect(throwable(10)).to.throw(error('10'));
42
+ expect(throwable(0)).to.throw(error('0'));
43
+ expect(throwable(true)).to.throw(error('true'));
44
+ expect(throwable(false)).to.throw(error('false'));
45
+ expect(throwable(undefined)).to.throw(error('undefined'));
46
+ expect(throwable(null)).to.throw(error('null'));
47
+ expect(throwable({})).to.throw(error('Object'));
48
+ expect(throwable([])).to.throw(error('Array'));
49
+ throwable(DataType.ANY)();
50
+ });
51
+
52
+ it('requires the parameter "emptyValues" to be an Array', function () {
53
+ const schema = new Schema();
54
+ const S = schema.getService(EmptyValuesDefiner);
55
+ const throwable = v => () => S.setEmptyValuesOf(DataType.ANY, v);
56
+ const error = v =>
57
+ format(
58
+ 'The argument "emptyValues" of the EmptyValuesDefiner.setEmptyValuesOf ' +
59
+ 'must be an Array, but %s given.',
60
+ v,
61
+ );
62
+ expect(throwable('str')).to.throw(error('"str"'));
63
+ expect(throwable('')).to.throw(error('""'));
64
+ expect(throwable(10)).to.throw(error('10'));
65
+ expect(throwable(0)).to.throw(error('0'));
66
+ expect(throwable(true)).to.throw(error('true'));
67
+ expect(throwable(false)).to.throw(error('false'));
68
+ expect(throwable(undefined)).to.throw(error('undefined'));
69
+ expect(throwable(null)).to.throw(error('null'));
70
+ expect(throwable({})).to.throw(error('Object'));
71
+ throwable([])();
72
+ throwable([1, 2, 3])();
73
+ });
74
+
75
+ it('overrides default values of the given data type', function () {
76
+ const schema = new Schema();
77
+ const S = schema.getService(EmptyValuesDefiner);
78
+ expect(getEmptyValues(S, DataType.ANY)).eql([undefined, null]);
79
+ S.setEmptyValuesOf(DataType.ANY, [1, 2, 3]);
80
+ expect(getEmptyValues(S, DataType.ANY)).eql([1, 2, 3]);
81
+ });
82
+ });
83
+
84
+ describe('isEmpty', function () {
85
+ it('returns true if the given value exists in the given type', function () {
86
+ const schema = new Schema();
87
+ const S = schema.getService(EmptyValuesDefiner);
88
+ S.setEmptyValuesOf(DataType.ANY, []);
89
+ expect(S.isEmpty(DataType.ANY, 'foo')).to.be.false;
90
+ S.setEmptyValuesOf(DataType.ANY, ['bar']);
91
+ expect(S.isEmpty(DataType.ANY, 'foo')).to.be.false;
92
+ S.setEmptyValuesOf(DataType.ANY, ['bar', 'foo']);
93
+ expect(S.isEmpty(DataType.ANY, 'foo')).to.be.true;
94
+ });
95
+ });
96
+ });
@@ -1,6 +1,9 @@
1
1
  export * from './data-type.js';
2
2
  export * from './property-definition.js';
3
+ export * from './property-uniqueness.js';
4
+ export * from './empty-values-definer.js';
3
5
  export * from './property-validator/index.js';
4
6
  export * from './property-transformer/index.js';
7
+ export * from './property-uniqueness-validator.js';
5
8
  export * from './properties-definition-validator.js';
6
9
  export * from './primary-keys-definition-validator.js';
@@ -1,6 +1,9 @@
1
1
  export * from './data-type.js';
2
2
  export * from './property-definition.js';
3
+ export * from './property-uniqueness.js';
4
+ export * from './empty-values-definer.js';
3
5
  export * from './property-validator/index.js';
4
6
  export * from './property-transformer/index.js';
7
+ export * from './property-uniqueness-validator.js';
5
8
  export * from './properties-definition-validator.js';
6
9
  export * from './primary-keys-definition-validator.js';
@@ -1,6 +1,7 @@
1
1
  import {Service} from '@e22m4u/js-service';
2
2
  import {DataType as Type} from './data-type.js';
3
3
  import {capitalize} from '../../../utils/index.js';
4
+ import {PropertyUniqueness} from './property-uniqueness.js';
4
5
  import {InvalidArgumentError} from '../../../errors/index.js';
5
6
  import {PropertyValidatorRegistry} from './property-validator/index.js';
6
7
  import {PropertyTransformerRegistry} from './property-transformer/index.js';
@@ -297,5 +298,25 @@ export class PropertiesDefinitionValidator extends Service {
297
298
  );
298
299
  }
299
300
  }
301
+ if (
302
+ propDef.unique &&
303
+ !Object.values(PropertyUniqueness).includes(propDef.unique)
304
+ ) {
305
+ throw new InvalidArgumentError(
306
+ 'The provided option "unique" of the property %v in the model %v ' +
307
+ 'should be one of values: %l, but %v given.',
308
+ propName,
309
+ modelName,
310
+ Object.values(PropertyUniqueness),
311
+ propDef.unique,
312
+ );
313
+ }
314
+ if (propDef.unique && propDef.primaryKey)
315
+ throw new InvalidArgumentError(
316
+ 'The property %v of the model %v is a primary key, ' +
317
+ 'so it should not have the option "unique" to be provided.',
318
+ propName,
319
+ modelName,
320
+ );
300
321
  }
301
322
  }
@@ -2,6 +2,7 @@ import chai from 'chai';
2
2
  import {expect} from 'chai';
3
3
  import {DataType} from './data-type.js';
4
4
  import {format} from '@e22m4u/js-format';
5
+ import {PropertyUniqueness} from './property-uniqueness.js';
5
6
  import {PropertyValidatorRegistry} from './property-validator/index.js';
6
7
  import {PropertyTransformerRegistry} from './property-transformer/index.js';
7
8
  import {PropertiesDefinitionValidator} from './properties-definition-validator.js';
@@ -250,6 +251,7 @@ describe('PropertiesDefinitionValidator', function () {
250
251
  'should be a Boolean, but %s given.',
251
252
  v,
252
253
  );
254
+ expect(validate('str')).to.throw(error('"str"'));
253
255
  expect(validate(10)).to.throw(error('10'));
254
256
  expect(validate([])).to.throw(error('Array'));
255
257
  expect(validate({})).to.throw(error('Object'));
@@ -277,7 +279,7 @@ describe('PropertiesDefinitionValidator', function () {
277
279
  expect(validate([])).to.throw(error);
278
280
  expect(validate({})).to.throw(error);
279
281
  expect(validate(null)).to.throw(error);
280
- validate(undefined);
282
+ validate(undefined)();
281
283
  });
282
284
 
283
285
  it('expects the primary key should not have the option "required" to be true', function () {
@@ -294,8 +296,8 @@ describe('PropertiesDefinitionValidator', function () {
294
296
  'so it should not have the option "required" to be provided.',
295
297
  );
296
298
  expect(validate(true)).to.throw(error);
297
- validate(false);
298
- validate(undefined);
299
+ validate(false)();
300
+ validate(undefined)();
299
301
  });
300
302
 
301
303
  it('expects the primary key should not have the option "default" to be provided', function () {
@@ -318,7 +320,7 @@ describe('PropertiesDefinitionValidator', function () {
318
320
  expect(validate([])).to.throw(error);
319
321
  expect(validate({})).to.throw(error);
320
322
  expect(validate(null)).to.throw(error);
321
- validate(undefined);
323
+ validate(undefined)();
322
324
  });
323
325
 
324
326
  it('expects a non-array property should not have the option "itemType" to be provided', function () {
@@ -337,7 +339,7 @@ describe('PropertiesDefinitionValidator', function () {
337
339
  expect(validate(DataType.NUMBER)).to.throw(error);
338
340
  expect(validate(DataType.BOOLEAN)).to.throw(error);
339
341
  expect(validate(DataType.OBJECT)).to.throw(error);
340
- validate(DataType.ARRAY);
342
+ validate(DataType.ARRAY)();
341
343
  });
342
344
 
343
345
  it('the option "model" requires the "object" property type', function () {
@@ -488,5 +490,48 @@ describe('PropertiesDefinitionValidator', function () {
488
490
  validate(['myTransformer'])();
489
491
  validate({myTransformer: true})();
490
492
  });
493
+
494
+ it('expects provided the option "unique" to be the PropertyUniqueness', function () {
495
+ const validate = v => {
496
+ const foo = {
497
+ type: DataType.STRING,
498
+ unique: v,
499
+ };
500
+ return () => S.validate('model', {foo});
501
+ };
502
+ const error = v =>
503
+ format(
504
+ 'The provided option "unique" of the property "foo" in the model "model" ' +
505
+ 'should be one of values: %l, but %s given.',
506
+ Object.values(PropertyUniqueness),
507
+ v,
508
+ );
509
+ expect(validate('str')).to.throw(error('"str"'));
510
+ expect(validate(10)).to.throw(error('10'));
511
+ expect(validate([])).to.throw(error('Array'));
512
+ expect(validate({})).to.throw(error('Object'));
513
+ validate(PropertyUniqueness.UNIQUE)();
514
+ validate(PropertyUniqueness.SPARSE)();
515
+ validate(PropertyUniqueness.NON_UNIQUE)();
516
+ });
517
+
518
+ it('expects the primary key should not have the option "unique"', function () {
519
+ const validate = v => () => {
520
+ const foo = {
521
+ type: DataType.ANY,
522
+ primaryKey: true,
523
+ unique: v,
524
+ };
525
+ S.validate('model', {foo});
526
+ };
527
+ const error = format(
528
+ 'The property "foo" of the model "model" is a primary key, ' +
529
+ 'so it should not have the option "unique" to be provided.',
530
+ );
531
+ expect(validate(PropertyUniqueness.UNIQUE)).to.throw(error);
532
+ expect(validate(PropertyUniqueness.SPARSE)).to.throw(error);
533
+ validate(false)();
534
+ validate(undefined)();
535
+ });
491
536
  });
492
537
  });
@@ -1,5 +1,6 @@
1
1
  import {DataType} from './data-type.js';
2
2
  import {PropertyValidateOptions} from './property-validator/index.js';
3
+ import {PropertyTransformOptions} from './property-transformer/index.js';
3
4
 
4
5
  /**
5
6
  * Full property definition.
@@ -14,6 +15,8 @@ export declare type FullPropertyDefinition = {
14
15
  required?: boolean;
15
16
  default?: unknown;
16
17
  validate?: PropertyValidateOptions;
18
+ transform?: PropertyTransformOptions;
19
+ unique?: boolean;
17
20
  };
18
21
 
19
22
  /**
@@ -0,0 +1,31 @@
1
+ import {ModelId} from '../../../types.js';
2
+ import {Service} from '@e22m4u/js-service';
3
+ import {ModelData} from '../../../types.js';
4
+ import {WhereClause} from '../../../filter/index.js';
5
+
6
+ /**
7
+ * Count method.
8
+ */
9
+ type CountMethod = (where: WhereClause) => Promise<number>;
10
+
11
+ /**
12
+ * Property uniqueness validator.
13
+ */
14
+ export declare class PropertyUniquenessValidator extends Service {
15
+ /**
16
+ * Validate.
17
+ *
18
+ * @param countMethod
19
+ * @param methodName
20
+ * @param modelName
21
+ * @param modelData
22
+ * @param modelId
23
+ */
24
+ validate(
25
+ countMethod: CountMethod,
26
+ methodName: string,
27
+ modelName: string,
28
+ modelData: ModelData,
29
+ modelId?: ModelId,
30
+ ): Promise<void>;
31
+ }
@@ -0,0 +1,141 @@
1
+ import {DataType} from './data-type.js';
2
+ import {Service} from '@e22m4u/js-service';
3
+ import {isPureObject} from '../../../utils/index.js';
4
+ import {PropertyUniqueness} from './property-uniqueness.js';
5
+ import {EmptyValuesDefiner} from './empty-values-definer.js';
6
+ import {InvalidArgumentError} from '../../../errors/index.js';
7
+ import {ModelDefinitionUtils} from '../model-definition-utils.js';
8
+
9
+ /**
10
+ * Property uniqueness validator.
11
+ */
12
+ export class PropertyUniquenessValidator extends Service {
13
+ /**
14
+ * Validate.
15
+ *
16
+ * @param {Function} countMethod
17
+ * @param {string} methodName
18
+ * @param {string} modelName
19
+ * @param {object} modelData
20
+ * @param {*} modelId
21
+ * @returns {Promise<undefined>}
22
+ */
23
+ async validate(
24
+ countMethod,
25
+ methodName,
26
+ modelName,
27
+ modelData,
28
+ modelId = undefined,
29
+ ) {
30
+ if (typeof countMethod !== 'function')
31
+ throw new InvalidArgumentError(
32
+ 'The parameter "countMethod" of the PropertyUniquenessValidator ' +
33
+ 'must be a Function, but %v given.',
34
+ countMethod,
35
+ );
36
+ if (!methodName || typeof methodName !== 'string')
37
+ throw new InvalidArgumentError(
38
+ 'The parameter "methodName" of the PropertyUniquenessValidator ' +
39
+ 'must be a non-empty String, but %v given.',
40
+ methodName,
41
+ );
42
+ if (!modelName || typeof modelName !== 'string')
43
+ throw new InvalidArgumentError(
44
+ 'The parameter "modelName" of the PropertyUniquenessValidator ' +
45
+ 'must be a non-empty String, but %v given.',
46
+ modelName,
47
+ );
48
+ if (!isPureObject(modelData))
49
+ throw new InvalidArgumentError(
50
+ 'The data of the model %v should be an Object, but %v given.',
51
+ modelName,
52
+ modelData,
53
+ );
54
+ const propDefs =
55
+ this.getService(
56
+ ModelDefinitionUtils,
57
+ ).getPropertiesDefinitionInBaseModelHierarchy(modelName);
58
+ const isPartial = methodName === 'patch' || methodName === 'patchById';
59
+ const propNames = Object.keys(isPartial ? modelData : propDefs);
60
+ const idProp =
61
+ this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
62
+ modelName,
63
+ );
64
+ const createError = (propName, propValue) =>
65
+ new InvalidArgumentError(
66
+ 'An existing document of the model %v already has ' +
67
+ 'the property %v with the value %v and should be unique.',
68
+ modelName,
69
+ propName,
70
+ propValue,
71
+ );
72
+ let willBeReplaced = undefined;
73
+ const emptyValuesDefiner = this.getService(EmptyValuesDefiner);
74
+ for (const propName of propNames) {
75
+ const propDef = propDefs[propName];
76
+ if (!propDef || typeof propDef === 'string' || !propDef.unique) continue;
77
+ // sparse
78
+ const propValue = modelData[propName];
79
+ if (propDef.unique === PropertyUniqueness.SPARSE) {
80
+ const propType = propDef.type || DataType.ANY;
81
+ const isEmpty = emptyValuesDefiner.isEmpty(propType, propValue);
82
+ if (isEmpty) continue;
83
+ }
84
+ // create
85
+ if (methodName === 'create') {
86
+ const count = await countMethod({[propName]: propValue});
87
+ if (count > 0) throw createError(propName, propValue);
88
+ }
89
+ // replaceById
90
+ else if (methodName === 'replaceById') {
91
+ const count = await countMethod({
92
+ [idProp]: {neq: modelId},
93
+ [propName]: propValue,
94
+ });
95
+ if (count > 0) throw createError(propName, propValue);
96
+ }
97
+ // replaceOrCreate
98
+ else if (methodName === 'replaceOrCreate') {
99
+ const idFromData = modelData[idProp];
100
+ if (willBeReplaced == null && idFromData != null) {
101
+ const count = await countMethod({[idProp]: idFromData});
102
+ willBeReplaced = count > 0;
103
+ }
104
+ // replaceById by replaceOrCreate
105
+ if (willBeReplaced) {
106
+ const count = await countMethod({
107
+ [idProp]: {neq: idFromData},
108
+ [propName]: propValue,
109
+ });
110
+ if (count > 0) throw createError(propName, propValue);
111
+ }
112
+ // create by replaceOrCreate
113
+ else {
114
+ const count = await countMethod({[propName]: propValue});
115
+ if (count > 0) throw createError(propName, propValue);
116
+ }
117
+ }
118
+ // patch
119
+ else if (methodName === 'patch') {
120
+ const count = await countMethod({[propName]: propValue});
121
+ if (count > 0) throw createError(propName, propValue);
122
+ }
123
+ // patchById
124
+ else if (methodName === 'patchById') {
125
+ const count = await countMethod({
126
+ [idProp]: {neq: modelId},
127
+ [propName]: propValue,
128
+ });
129
+ if (count > 0) throw createError(propName, propValue);
130
+ }
131
+ // unsupported method
132
+ else {
133
+ throw new InvalidArgumentError(
134
+ 'The PropertyUniquenessValidator does not ' +
135
+ 'support the adapter method %v.',
136
+ methodName,
137
+ );
138
+ }
139
+ }
140
+ }
141
+ }