@e22m4u/js-repository 0.1.9 → 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 +21 -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 +1 -1
- 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 +17 -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 +4 -1
- package/docs/types/AnyObject.html +1 -1
- package/docs/types/BelongsToDefinition.html +1 -1
- package/docs/types/CountMethod.html +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +2 -0
- package/src/definition/model/properties/index.js +2 -0
- package/src/definition/model/properties/properties-definition-validator.js +8 -2
- package/src/definition/model/properties/properties-definition-validator.spec.js +10 -6
- package/src/definition/model/properties/property-uniqueness-validator.js +10 -0
- package/src/definition/model/properties/property-uniqueness-validator.spec.js +44 -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,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,5 +1,7 @@
|
|
|
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';
|
|
5
7
|
export * from './property-uniqueness-validator.js';
|
|
@@ -1,5 +1,7 @@
|
|
|
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';
|
|
5
7
|
export * from './property-uniqueness-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,14 +298,19 @@ export class PropertiesDefinitionValidator extends Service {
|
|
|
297
298
|
);
|
|
298
299
|
}
|
|
299
300
|
}
|
|
300
|
-
if (
|
|
301
|
+
if (
|
|
302
|
+
propDef.unique &&
|
|
303
|
+
!Object.values(PropertyUniqueness).includes(propDef.unique)
|
|
304
|
+
) {
|
|
301
305
|
throw new InvalidArgumentError(
|
|
302
306
|
'The provided option "unique" of the property %v in the model %v ' +
|
|
303
|
-
'should be
|
|
307
|
+
'should be one of values: %l, but %v given.',
|
|
304
308
|
propName,
|
|
305
309
|
modelName,
|
|
310
|
+
Object.values(PropertyUniqueness),
|
|
306
311
|
propDef.unique,
|
|
307
312
|
);
|
|
313
|
+
}
|
|
308
314
|
if (propDef.unique && propDef.primaryKey)
|
|
309
315
|
throw new InvalidArgumentError(
|
|
310
316
|
'The property %v of the model %v is a primary key, ' +
|
|
@@ -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';
|
|
@@ -490,7 +491,7 @@ describe('PropertiesDefinitionValidator', function () {
|
|
|
490
491
|
validate({myTransformer: true})();
|
|
491
492
|
});
|
|
492
493
|
|
|
493
|
-
it('expects provided the option "unique" to be
|
|
494
|
+
it('expects provided the option "unique" to be the PropertyUniqueness', function () {
|
|
494
495
|
const validate = v => {
|
|
495
496
|
const foo = {
|
|
496
497
|
type: DataType.STRING,
|
|
@@ -501,18 +502,20 @@ describe('PropertiesDefinitionValidator', function () {
|
|
|
501
502
|
const error = v =>
|
|
502
503
|
format(
|
|
503
504
|
'The provided option "unique" of the property "foo" in the model "model" ' +
|
|
504
|
-
'should be
|
|
505
|
+
'should be one of values: %l, but %s given.',
|
|
506
|
+
Object.values(PropertyUniqueness),
|
|
505
507
|
v,
|
|
506
508
|
);
|
|
507
509
|
expect(validate('str')).to.throw(error('"str"'));
|
|
508
510
|
expect(validate(10)).to.throw(error('10'));
|
|
509
511
|
expect(validate([])).to.throw(error('Array'));
|
|
510
512
|
expect(validate({})).to.throw(error('Object'));
|
|
511
|
-
validate(
|
|
512
|
-
validate(
|
|
513
|
+
validate(PropertyUniqueness.UNIQUE)();
|
|
514
|
+
validate(PropertyUniqueness.SPARSE)();
|
|
515
|
+
validate(PropertyUniqueness.NON_UNIQUE)();
|
|
513
516
|
});
|
|
514
517
|
|
|
515
|
-
it('expects the primary key should not have the option "unique"
|
|
518
|
+
it('expects the primary key should not have the option "unique"', function () {
|
|
516
519
|
const validate = v => () => {
|
|
517
520
|
const foo = {
|
|
518
521
|
type: DataType.ANY,
|
|
@@ -525,7 +528,8 @@ describe('PropertiesDefinitionValidator', function () {
|
|
|
525
528
|
'The property "foo" of the model "model" is a primary key, ' +
|
|
526
529
|
'so it should not have the option "unique" to be provided.',
|
|
527
530
|
);
|
|
528
|
-
expect(validate(
|
|
531
|
+
expect(validate(PropertyUniqueness.UNIQUE)).to.throw(error);
|
|
532
|
+
expect(validate(PropertyUniqueness.SPARSE)).to.throw(error);
|
|
529
533
|
validate(false)();
|
|
530
534
|
validate(undefined)();
|
|
531
535
|
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import {DataType} from './data-type.js';
|
|
1
2
|
import {Service} from '@e22m4u/js-service';
|
|
2
3
|
import {isPureObject} from '../../../utils/index.js';
|
|
4
|
+
import {PropertyUniqueness} from './property-uniqueness.js';
|
|
5
|
+
import {EmptyValuesDefiner} from './empty-values-definer.js';
|
|
3
6
|
import {InvalidArgumentError} from '../../../errors/index.js';
|
|
4
7
|
import {ModelDefinitionUtils} from '../model-definition-utils.js';
|
|
5
8
|
|
|
@@ -67,10 +70,17 @@ export class PropertyUniquenessValidator extends Service {
|
|
|
67
70
|
propValue,
|
|
68
71
|
);
|
|
69
72
|
let willBeReplaced = undefined;
|
|
73
|
+
const emptyValuesDefiner = this.getService(EmptyValuesDefiner);
|
|
70
74
|
for (const propName of propNames) {
|
|
71
75
|
const propDef = propDefs[propName];
|
|
72
76
|
if (!propDef || typeof propDef === 'string' || !propDef.unique) continue;
|
|
77
|
+
// sparse
|
|
73
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
|
+
}
|
|
74
84
|
// create
|
|
75
85
|
if (methodName === 'create') {
|
|
76
86
|
const count = await countMethod({[propName]: propValue});
|
|
@@ -2,6 +2,8 @@ import {expect} from 'chai';
|
|
|
2
2
|
import {DataType} from './data-type.js';
|
|
3
3
|
import {format} from '@e22m4u/js-format';
|
|
4
4
|
import {Schema} from '../../../schema.js';
|
|
5
|
+
import {PropertyUniqueness} from './property-uniqueness.js';
|
|
6
|
+
import {EmptyValuesDefiner} from './empty-values-definer.js';
|
|
5
7
|
import {PropertyUniquenessValidator} from './property-uniqueness-validator.js';
|
|
6
8
|
import {DEFAULT_PRIMARY_KEY_PROPERTY_NAME as DEF_PK} from '../model-definition-utils.js';
|
|
7
9
|
|
|
@@ -185,6 +187,48 @@ describe('PropertyUniquenessValidator', function () {
|
|
|
185
187
|
);
|
|
186
188
|
});
|
|
187
189
|
|
|
190
|
+
it('skips uniqueness checking for an empty value for "sparse" mode', async function () {
|
|
191
|
+
const schema = new Schema();
|
|
192
|
+
schema.defineModel({
|
|
193
|
+
name: 'model',
|
|
194
|
+
properties: {
|
|
195
|
+
foo: {
|
|
196
|
+
type: DataType.STRING,
|
|
197
|
+
unique: true,
|
|
198
|
+
},
|
|
199
|
+
bar: {
|
|
200
|
+
type: DataType.STRING,
|
|
201
|
+
unique: PropertyUniqueness.SPARSE,
|
|
202
|
+
},
|
|
203
|
+
baz: {
|
|
204
|
+
type: DataType.STRING,
|
|
205
|
+
unique: true,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
const S = schema.getService(PropertyUniquenessValidator);
|
|
210
|
+
let invoked = 0;
|
|
211
|
+
schema
|
|
212
|
+
.getService(EmptyValuesDefiner)
|
|
213
|
+
.setEmptyValuesOf(DataType.STRING, ['val2']);
|
|
214
|
+
const modelData = {
|
|
215
|
+
foo: 'val1',
|
|
216
|
+
bar: 'val2',
|
|
217
|
+
baz: 'val3',
|
|
218
|
+
};
|
|
219
|
+
const countMethod = where => {
|
|
220
|
+
invoked++;
|
|
221
|
+
if (invoked === 1) {
|
|
222
|
+
expect(where).to.be.eql({foo: 'val1'});
|
|
223
|
+
} else if (invoked === 2) {
|
|
224
|
+
expect(where).to.be.eql({baz: 'val3'});
|
|
225
|
+
}
|
|
226
|
+
return 0;
|
|
227
|
+
};
|
|
228
|
+
await S.validate(countMethod, 'create', 'model', modelData);
|
|
229
|
+
expect(invoked).to.be.eql(2);
|
|
230
|
+
});
|
|
231
|
+
|
|
188
232
|
describe('create', function () {
|
|
189
233
|
it('throws an error if the "countMethod" returns a positive number', async function () {
|
|
190
234
|
const schema = new Schema();
|
package/src/utils/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './is-ctor.js';
|
|
|
2
2
|
export * from './capitalize.js';
|
|
3
3
|
export * from './clone-deep.js';
|
|
4
4
|
export * from './singularize.js';
|
|
5
|
+
export * from './is-deep-equal.js';
|
|
5
6
|
export * from './get-ctor-name.js';
|
|
6
7
|
export * from './is-pure-object.js';
|
|
7
8
|
export * from './string-to-regexp.js';
|
package/src/utils/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export * from './is-ctor.js';
|
|
|
2
2
|
export * from './capitalize.js';
|
|
3
3
|
export * from './clone-deep.js';
|
|
4
4
|
export * from './singularize.js';
|
|
5
|
+
export * from './is-deep-equal.js';
|
|
5
6
|
export * from './get-ctor-name.js';
|
|
6
7
|
export * from './is-pure-object.js';
|
|
7
8
|
export * from './string-to-regexp.js';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is deep equal.
|
|
3
|
+
* https://github.com/pinglu85/BFEdevSolutions/blob/main/Coding-Problems/69.implement-deep-equal-isEqual.md
|
|
4
|
+
*
|
|
5
|
+
* @param {*} firstValue
|
|
6
|
+
* @param {*} secondValue
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function isDeepEqual(firstValue, secondValue) {
|
|
10
|
+
const cached = new WeakMap();
|
|
11
|
+
const compare = (a, b) => {
|
|
12
|
+
// Check if one of the two inputs is primitive by using typeof
|
|
13
|
+
// operator; since the typeof primitive null is object, check
|
|
14
|
+
// if one of the inputs is equal to null. If one of the two
|
|
15
|
+
// inputs is primitive, then I can compare them by reference.
|
|
16
|
+
if (a === null || b === null) return a === b;
|
|
17
|
+
if (typeof a !== 'object' || typeof b !== 'object') return a === b;
|
|
18
|
+
// Check if the data type of the two inputs are the same,
|
|
19
|
+
// both are arrays or objects. If they are not, return false.
|
|
20
|
+
const dataTypeA = Array.isArray(a) ? 'array' : 'object';
|
|
21
|
+
const dataTypeB = Array.isArray(b) ? 'array' : 'object';
|
|
22
|
+
if (dataTypeA !== dataTypeB) return false;
|
|
23
|
+
// Use Object.keys and Object.getOwnPropertySymbols to get
|
|
24
|
+
// all of enumerable and not-inherited properties of the two
|
|
25
|
+
// inputs. Compare their size respectively, if one of them
|
|
26
|
+
// is not equal, return false.
|
|
27
|
+
const keysA = Object.keys(a);
|
|
28
|
+
const keysB = Object.keys(b);
|
|
29
|
+
if (keysA.length !== keysB.length) return false;
|
|
30
|
+
const symbolsA = Object.getOwnPropertySymbols(a);
|
|
31
|
+
const symbolsB = Object.getOwnPropertySymbols(b);
|
|
32
|
+
if (symbolsA.length !== symbolsB.length) return false;
|
|
33
|
+
// To handle the circular reference, initialize a WeakMap
|
|
34
|
+
// that is going to keep track of the objects or arrays
|
|
35
|
+
// that have been seen, in which each key is an object
|
|
36
|
+
// or an array and each value is a set of objects or arrays,
|
|
37
|
+
// that have been compared to that object or array.
|
|
38
|
+
let setForA = cached.get(a);
|
|
39
|
+
if (setForA == null) {
|
|
40
|
+
setForA = new Set();
|
|
41
|
+
cached.set(a, setForA);
|
|
42
|
+
} else if (setForA.has(b)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
setForA.add(b);
|
|
46
|
+
let setForB = cached.get(b);
|
|
47
|
+
if (setForB == null) {
|
|
48
|
+
setForB = new Set();
|
|
49
|
+
cached.set(b, setForB);
|
|
50
|
+
} else if (setForB.has(a)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
setForB.add(a);
|
|
54
|
+
// Compare the property names and the values. Loop through
|
|
55
|
+
// all the properties of the first input data, check if
|
|
56
|
+
// the property name also exist in the second input data,
|
|
57
|
+
// if not, return false; otherwise recursively compare
|
|
58
|
+
// the property value.
|
|
59
|
+
const propertyNamesA = [...keysA, ...symbolsA];
|
|
60
|
+
for (const propertyNameA of propertyNamesA) {
|
|
61
|
+
if (!b.hasOwnProperty(propertyNameA)) return false;
|
|
62
|
+
const propertyValueA = a[propertyNameA];
|
|
63
|
+
const propertyValueB = b[propertyNameA];
|
|
64
|
+
if (!compare(propertyValueA, propertyValueB)) return false;
|
|
65
|
+
}
|
|
66
|
+
// If we get out of the loop without
|
|
67
|
+
// returning false, return true.
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
return compare(firstValue, secondValue);
|
|
71
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import {expect} from 'chai';
|
|
2
|
+
import {isDeepEqual} from './is-deep-equal.js';
|
|
3
|
+
|
|
4
|
+
const check = (a, b, expected) => {
|
|
5
|
+
expect(isDeepEqual(a, b)).to.be.eq(expected);
|
|
6
|
+
expect(isDeepEqual(b, a)).to.be.eq(expected);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
describe('isDeepEqual', function () {
|
|
10
|
+
describe('string', function () {
|
|
11
|
+
it('a non-empty string', function () {
|
|
12
|
+
check('str', 'str', true);
|
|
13
|
+
check('str', '', false);
|
|
14
|
+
check('str', 10, false);
|
|
15
|
+
check('str', 0, false);
|
|
16
|
+
check('str', -10, false);
|
|
17
|
+
check('str', true, false);
|
|
18
|
+
check('str', false, false);
|
|
19
|
+
check('str', undefined, false);
|
|
20
|
+
check('str', null, false);
|
|
21
|
+
check('str', {foo: 'bar'}, false);
|
|
22
|
+
check('str', {}, false);
|
|
23
|
+
check('str', [1, 2, 3], false);
|
|
24
|
+
check('str', [], false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('an empty string', function () {
|
|
28
|
+
check('', 'str', false);
|
|
29
|
+
check('', '', true);
|
|
30
|
+
check('', 10, false);
|
|
31
|
+
check('', 0, false);
|
|
32
|
+
check('', -10, false);
|
|
33
|
+
check('', true, false);
|
|
34
|
+
check('', false, false);
|
|
35
|
+
check('', undefined, false);
|
|
36
|
+
check('', null, false);
|
|
37
|
+
check('', {foo: 'bar'}, false);
|
|
38
|
+
check('', {}, false);
|
|
39
|
+
check('', [1, 2, 3], false);
|
|
40
|
+
check('', [], false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('number', function () {
|
|
45
|
+
it('a positive number', function () {
|
|
46
|
+
check(10, 'str', false);
|
|
47
|
+
check(10, '', false);
|
|
48
|
+
check(10, 10, true);
|
|
49
|
+
check(10, 0, false);
|
|
50
|
+
check(10, -10, false);
|
|
51
|
+
check(10, true, false);
|
|
52
|
+
check(10, false, false);
|
|
53
|
+
check(10, undefined, false);
|
|
54
|
+
check(10, null, false);
|
|
55
|
+
check(10, {foo: 'bar'}, false);
|
|
56
|
+
check(10, {}, false);
|
|
57
|
+
check(10, [1, 2, 3], false);
|
|
58
|
+
check(10, [], false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('zero', function () {
|
|
62
|
+
check(0, 'str', false);
|
|
63
|
+
check(0, '', false);
|
|
64
|
+
check(0, 10, false);
|
|
65
|
+
check(0, 0, true);
|
|
66
|
+
check(0, -10, false);
|
|
67
|
+
check(0, true, false);
|
|
68
|
+
check(0, false, false);
|
|
69
|
+
check(0, undefined, false);
|
|
70
|
+
check(0, null, false);
|
|
71
|
+
check(0, {foo: 'bar'}, false);
|
|
72
|
+
check(0, {}, false);
|
|
73
|
+
check(0, [1, 2, 3], false);
|
|
74
|
+
check(0, [], false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('a negative number', function () {
|
|
78
|
+
check(-10, 'str', false);
|
|
79
|
+
check(-10, '', false);
|
|
80
|
+
check(-10, 10, false);
|
|
81
|
+
check(-10, 0, false);
|
|
82
|
+
check(-10, -10, true);
|
|
83
|
+
check(-10, true, false);
|
|
84
|
+
check(-10, false, false);
|
|
85
|
+
check(-10, undefined, false);
|
|
86
|
+
check(-10, null, false);
|
|
87
|
+
check(-10, {foo: 'bar'}, false);
|
|
88
|
+
check(-10, {}, false);
|
|
89
|
+
check(-10, [1, 2, 3], false);
|
|
90
|
+
check(-10, [], false);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('boolean', function () {
|
|
95
|
+
it('true', function () {
|
|
96
|
+
check(true, 'str', false);
|
|
97
|
+
check(true, '', false);
|
|
98
|
+
check(true, 10, false);
|
|
99
|
+
check(true, 0, false);
|
|
100
|
+
check(true, -10, false);
|
|
101
|
+
check(true, true, true);
|
|
102
|
+
check(true, false, false);
|
|
103
|
+
check(true, undefined, false);
|
|
104
|
+
check(true, null, false);
|
|
105
|
+
check(true, {foo: 'bar'}, false);
|
|
106
|
+
check(true, {}, false);
|
|
107
|
+
check(true, [1, 2, 3], false);
|
|
108
|
+
check(true, [], false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('false', function () {
|
|
112
|
+
check(false, 'str', false);
|
|
113
|
+
check(false, '', false);
|
|
114
|
+
check(false, 10, false);
|
|
115
|
+
check(false, 0, false);
|
|
116
|
+
check(false, -10, false);
|
|
117
|
+
check(false, true, false);
|
|
118
|
+
check(false, false, true);
|
|
119
|
+
check(false, undefined, false);
|
|
120
|
+
check(false, null, false);
|
|
121
|
+
check(false, {foo: 'bar'}, false);
|
|
122
|
+
check(false, {}, false);
|
|
123
|
+
check(false, [1, 2, 3], false);
|
|
124
|
+
check(false, [], false);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('array', function () {
|
|
129
|
+
it('an array of numbers', function () {
|
|
130
|
+
check([1, 2, 3], 'str', false);
|
|
131
|
+
check([1, 2, 3], '', false);
|
|
132
|
+
check([1, 2, 3], 10, false);
|
|
133
|
+
check([1, 2, 3], 0, false);
|
|
134
|
+
check([1, 2, 3], -10, false);
|
|
135
|
+
check([1, 2, 3], true, false);
|
|
136
|
+
check([1, 2, 3], false, false);
|
|
137
|
+
check([1, 2, 3], undefined, false);
|
|
138
|
+
check([1, 2, 3], null, false);
|
|
139
|
+
check([1, 2, 3], {foo: 'bar'}, false);
|
|
140
|
+
check([1, 2, 3], {}, false);
|
|
141
|
+
check([1, 2, 3], [1, 2, 3], true);
|
|
142
|
+
check([1, 2, 3], [], false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('an empty array', function () {
|
|
146
|
+
check([], 'str', false);
|
|
147
|
+
check([], '', false);
|
|
148
|
+
check([], 10, false);
|
|
149
|
+
check([], 0, false);
|
|
150
|
+
check([], -10, false);
|
|
151
|
+
check([], true, false);
|
|
152
|
+
check([], false, false);
|
|
153
|
+
check([], undefined, false);
|
|
154
|
+
check([], null, false);
|
|
155
|
+
check([], {foo: 'bar'}, false);
|
|
156
|
+
check([], {}, false);
|
|
157
|
+
check([], [1, 2, 3], false);
|
|
158
|
+
check([], [], true);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('object', function () {
|
|
163
|
+
it('string key and string value', function () {
|
|
164
|
+
check({foo: 'bar'}, 'str', false);
|
|
165
|
+
check({foo: 'bar'}, '', false);
|
|
166
|
+
check({foo: 'bar'}, 10, false);
|
|
167
|
+
check({foo: 'bar'}, 0, false);
|
|
168
|
+
check({foo: 'bar'}, -10, false);
|
|
169
|
+
check({foo: 'bar'}, true, false);
|
|
170
|
+
check({foo: 'bar'}, false, false);
|
|
171
|
+
check({foo: 'bar'}, undefined, false);
|
|
172
|
+
check({foo: 'bar'}, null, false);
|
|
173
|
+
check({foo: 'bar'}, {foo: 'bar'}, true);
|
|
174
|
+
check({foo: 'bar'}, {}, false);
|
|
175
|
+
check({foo: 'bar'}, [1, 2, 3], false);
|
|
176
|
+
check({foo: 'bar'}, [], false);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('an empty object', function () {
|
|
180
|
+
check({}, 'str', false);
|
|
181
|
+
check({}, '', false);
|
|
182
|
+
check({}, 10, false);
|
|
183
|
+
check({}, 0, false);
|
|
184
|
+
check({}, -10, false);
|
|
185
|
+
check({}, true, false);
|
|
186
|
+
check({}, false, false);
|
|
187
|
+
check({}, undefined, false);
|
|
188
|
+
check({}, null, false);
|
|
189
|
+
check({}, {foo: 'bar'}, false);
|
|
190
|
+
check({}, {}, true);
|
|
191
|
+
check({}, [1, 2, 3], false);
|
|
192
|
+
check({}, [], false);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('null', function () {
|
|
196
|
+
check(null, 'str', false);
|
|
197
|
+
check(null, '', false);
|
|
198
|
+
check(null, 10, false);
|
|
199
|
+
check(null, 0, false);
|
|
200
|
+
check(null, -10, false);
|
|
201
|
+
check(null, true, false);
|
|
202
|
+
check(null, false, false);
|
|
203
|
+
check(null, undefined, false);
|
|
204
|
+
check(null, null, true);
|
|
205
|
+
check(null, {foo: 'bar'}, false);
|
|
206
|
+
check(null, {}, false);
|
|
207
|
+
check(null, [1, 2, 3], false);
|
|
208
|
+
check(null, [], false);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('circular reference to itself', function () {
|
|
212
|
+
const a = {foo: 'bar'};
|
|
213
|
+
const b = {baz: 'qux'};
|
|
214
|
+
const c = {foo: 'bar'};
|
|
215
|
+
a.itself = a;
|
|
216
|
+
b.itself = b;
|
|
217
|
+
c.itself = c;
|
|
218
|
+
check(a, b, false);
|
|
219
|
+
check(a, c, true);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('undefined', function () {
|
|
224
|
+
check(undefined, 'str', false);
|
|
225
|
+
check(undefined, '', false);
|
|
226
|
+
check(undefined, 10, false);
|
|
227
|
+
check(undefined, 0, false);
|
|
228
|
+
check(undefined, -10, false);
|
|
229
|
+
check(undefined, true, false);
|
|
230
|
+
check(undefined, false, false);
|
|
231
|
+
check(undefined, undefined, true);
|
|
232
|
+
check(undefined, null, false);
|
|
233
|
+
check(undefined, {foo: 'bar'}, false);
|
|
234
|
+
check(undefined, {}, false);
|
|
235
|
+
check(undefined, [1, 2, 3], false);
|
|
236
|
+
check(undefined, [], false);
|
|
237
|
+
});
|
|
238
|
+
});
|