@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.
- package/README.md +22 -1
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/Adapter.html +1 -1
- package/docs/classes/AdapterLoader.html +1 -1
- package/docs/classes/AdapterRegistry.html +1 -1
- package/docs/classes/BelongsToResolver.html +1 -1
- package/docs/classes/DatasourceDefinitionValidator.html +1 -1
- package/docs/classes/DefinitionRegistry.html +1 -1
- package/docs/classes/EmptyValuesDefiner.html +18 -0
- package/docs/classes/FieldsClauseTool.html +1 -1
- package/docs/classes/HasManyResolver.html +1 -1
- package/docs/classes/HasOneResolver.html +1 -1
- package/docs/classes/IncludeClauseTool.html +1 -1
- package/docs/classes/InvalidArgumentError.html +1 -1
- package/docs/classes/InvalidOperatorValueError.html +1 -1
- package/docs/classes/ModelDataSanitizer.html +1 -1
- package/docs/classes/ModelDataTransformer.html +1 -1
- package/docs/classes/ModelDataValidator.html +1 -1
- package/docs/classes/ModelDefinitionUtils.html +1 -1
- package/docs/classes/ModelDefinitionValidator.html +1 -1
- package/docs/classes/NotImplementedError.html +1 -1
- package/docs/classes/OperatorClauseTool.html +1 -1
- package/docs/classes/OrderClauseTool.html +1 -1
- package/docs/classes/PrimaryKeysDefinitionValidator.html +1 -1
- package/docs/classes/PropertiesDefinitionValidator.html +1 -1
- package/docs/classes/PropertyTransformerRegistry.html +1 -1
- package/docs/classes/PropertyUniquenessValidator.html +16 -0
- package/docs/classes/PropertyValidatorRegistry.html +1 -1
- package/docs/classes/ReferencesManyResolver.html +1 -1
- package/docs/classes/RelationsDefinitionValidator.html +1 -1
- package/docs/classes/Repository.html +1 -1
- package/docs/classes/RepositoryRegistry.html +1 -1
- package/docs/classes/Schema.html +1 -1
- package/docs/classes/SliceClauseTool.html +1 -1
- package/docs/classes/WhereClauseTool.html +1 -1
- package/docs/enums/DataType.html +1 -1
- package/docs/enums/DecoratorTargetType.html +1 -1
- package/docs/enums/RelationType.html +1 -1
- package/docs/functions/capitalize.html +1 -1
- package/docs/functions/cloneDeep.html +1 -1
- package/docs/functions/excludeObjectKeys.html +1 -1
- package/docs/functions/getCtorName.html +1 -1
- package/docs/functions/getDecoratorTargetType.html +1 -1
- package/docs/functions/getValueByPath.html +1 -1
- package/docs/functions/isCtor.html +1 -1
- package/docs/functions/isDeepEqual.html +2 -0
- package/docs/functions/isPureObject.html +1 -1
- package/docs/functions/selectObjectKeys.html +1 -1
- package/docs/functions/singularize.html +1 -1
- package/docs/functions/stringToRegexp.html +1 -1
- package/docs/index.html +18 -3
- package/docs/interfaces/AndClause.html +1 -1
- package/docs/interfaces/Constructor.html +1 -1
- package/docs/interfaces/OrClause.html +1 -1
- package/docs/modules.html +6 -1
- package/docs/types/AnyObject.html +1 -1
- package/docs/types/BelongsToDefinition.html +1 -1
- package/docs/types/CountMethod.html +2 -0
- package/docs/types/DEFAULT_PRIMARY_KEY_PROPERTY_NAME.html +1 -1
- package/docs/types/DatasourceDefinition.html +1 -1
- package/docs/types/FieldsClause.html +1 -1
- package/docs/types/FilterClause.html +1 -1
- package/docs/types/Flatten.html +1 -1
- package/docs/types/FullPropertyDefinition.html +2 -2
- package/docs/types/HasManyDefinition.html +1 -1
- package/docs/types/HasOneDefinition.html +1 -1
- package/docs/types/Identity.html +1 -1
- package/docs/types/IncludeClause.html +1 -1
- package/docs/types/ItemFilterClause.html +1 -1
- package/docs/types/ModelData.html +1 -1
- package/docs/types/ModelDefinition.html +1 -1
- package/docs/types/ModelId.html +1 -1
- package/docs/types/NestedIncludeClause.html +1 -1
- package/docs/types/NormalizedFieldsClause.html +1 -1
- package/docs/types/NormalizedIncludeClause.html +1 -1
- package/docs/types/OperatorClause.html +1 -1
- package/docs/types/OptionalUnlessRequiredId.html +1 -1
- package/docs/types/OrderClause.html +1 -1
- package/docs/types/PartialBy.html +1 -1
- package/docs/types/PartialWithoutId.html +1 -1
- package/docs/types/PolyBelongsToDefinition.html +1 -1
- package/docs/types/PolyHasManyDefinitionWithTargetKeys.html +1 -1
- package/docs/types/PolyHasManyDefinitionWithTargetRelationName.html +1 -1
- package/docs/types/PolyHasOneDefinitionWithTargetKeys.html +1 -1
- package/docs/types/PolyHasOneDefinitionWithTargetRelationName.html +1 -1
- package/docs/types/PropertiesClause.html +1 -1
- package/docs/types/PropertyDefinition.html +1 -1
- package/docs/types/PropertyDefinitionMap.html +1 -1
- package/docs/types/PropertyTransformOptions.html +1 -1
- package/docs/types/PropertyTransformer.html +1 -1
- package/docs/types/PropertyTransformerContext.html +1 -1
- package/docs/types/PropertyUniqueness.html +2 -0
- package/docs/types/PropertyValidateOptions.html +1 -1
- package/docs/types/PropertyValidator.html +1 -1
- package/docs/types/PropertyValidatorContext.html +1 -1
- package/docs/types/ReferencesManyDefinition.html +1 -1
- package/docs/types/RelationDefinition.html +1 -1
- package/docs/types/RelationDefinitionMap.html +1 -1
- package/docs/types/WhereClause.html +1 -1
- package/docs/types/WithoutId.html +1 -1
- package/package.json +4 -4
- package/src/adapter/adapter.js +2 -0
- package/src/adapter/adapter.spec.js +8 -2
- package/src/adapter/decorator/index.d.ts +1 -0
- package/src/adapter/decorator/index.js +1 -0
- package/src/adapter/decorator/property-uniqueness-decorator.d.ts +14 -0
- package/src/adapter/decorator/property-uniqueness-decorator.js +76 -0
- package/src/adapter/decorator/property-uniqueness-decorator.spec.js +135 -0
- package/src/definition/definition-registry.spec.js +1 -0
- package/src/definition/model/properties/empty-values-definer.d.ts +23 -0
- package/src/definition/model/properties/empty-values-definer.js +66 -0
- package/src/definition/model/properties/empty-values-definer.spec.js +96 -0
- package/src/definition/model/properties/index.d.ts +3 -0
- package/src/definition/model/properties/index.js +3 -0
- package/src/definition/model/properties/properties-definition-validator.js +21 -0
- package/src/definition/model/properties/properties-definition-validator.spec.js +50 -5
- package/src/definition/model/properties/property-definition.d.ts +3 -0
- package/src/definition/model/properties/property-uniqueness-validator.d.ts +31 -0
- package/src/definition/model/properties/property-uniqueness-validator.js +141 -0
- package/src/definition/model/properties/property-uniqueness-validator.spec.js +987 -0
- package/src/definition/model/properties/property-uniqueness.d.ts +8 -0
- package/src/definition/model/properties/property-uniqueness.js +8 -0
- package/src/utils/index.d.ts +1 -0
- package/src/utils/index.js +1 -0
- package/src/utils/is-deep-equal.d.ts +10 -0
- package/src/utils/is-deep-equal.js +71 -0
- 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
|
+
});
|
|
@@ -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
|
+
}
|