@e22m4u/js-repository 0.1.13 → 0.1.15
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/.eslintignore +0 -1
- package/.husky/pre-commit +0 -2
- package/LICENSE +1 -1
- package/README.md +53 -11
- package/package.json +26 -27
- package/src/adapter/decorator/data-transformation-decorator.js +10 -10
- package/src/adapter/decorator/data-transformation-decorator.spec.js +150 -52
- package/src/definition/model/model-data-transformer.d.ts +2 -1
- package/src/definition/model/model-data-transformer.js +26 -17
- package/src/definition/model/model-data-transformer.spec.js +118 -0
- package/src/definition/model/properties/property-transformer/property-transformer.d.ts +3 -1
- package/src/types.d.ts +7 -0
- package/src/utils/index.d.ts +2 -0
- package/src/utils/index.js +2 -0
- package/src/utils/is-promise.d.ts +10 -0
- package/src/utils/is-promise.js +13 -0
- package/src/utils/is-promise.spec.js +23 -0
- package/src/utils/transform-promise.d.ts +13 -0
- package/src/utils/transform-promise.js +15 -0
- package/src/utils/transform-promise.spec.js +20 -0
- package/docs/.nojekyll +0 -1
- package/docs/assets/highlight.css +0 -99
- package/docs/assets/main.js +0 -59
- package/docs/assets/navigation.js +0 -1
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1414
- package/docs/classes/Adapter.html +0 -38
- package/docs/classes/AdapterLoader.html +0 -16
- package/docs/classes/AdapterRegistry.html +0 -16
- package/docs/classes/BelongsToResolver.html +0 -18
- package/docs/classes/DatasourceDefinitionValidator.html +0 -16
- package/docs/classes/DefinitionRegistry.html +0 -26
- package/docs/classes/EmptyValuesDefiner.html +0 -18
- package/docs/classes/FieldsClauseTool.html +0 -20
- package/docs/classes/HasManyResolver.html +0 -20
- package/docs/classes/HasOneResolver.html +0 -20
- package/docs/classes/IncludeClauseTool.html +0 -24
- package/docs/classes/InvalidArgumentError.html +0 -16
- package/docs/classes/InvalidOperatorValueError.html +0 -16
- package/docs/classes/ModelDataSanitizer.html +0 -16
- package/docs/classes/ModelDataTransformer.html +0 -16
- package/docs/classes/ModelDataValidator.html +0 -16
- package/docs/classes/ModelDefinitionUtils.html +0 -48
- package/docs/classes/ModelDefinitionValidator.html +0 -16
- package/docs/classes/NotImplementedError.html +0 -16
- package/docs/classes/OperatorClauseTool.html +0 -72
- package/docs/classes/OrderClauseTool.html +0 -20
- package/docs/classes/PrimaryKeysDefinitionValidator.html +0 -16
- package/docs/classes/PropertiesDefinitionValidator.html +0 -16
- package/docs/classes/PropertyTransformerRegistry.html +0 -20
- package/docs/classes/PropertyUniquenessValidator.html +0 -16
- package/docs/classes/PropertyValidatorRegistry.html +0 -20
- package/docs/classes/ReferencesManyResolver.html +0 -16
- package/docs/classes/RelationsDefinitionValidator.html +0 -16
- package/docs/classes/Repository.html +0 -48
- package/docs/classes/RepositoryRegistry.html +0 -18
- package/docs/classes/Schema.html +0 -20
- package/docs/classes/SliceClauseTool.html +0 -20
- package/docs/classes/WhereClauseTool.html +0 -18
- package/docs/enums/DataType.html +0 -8
- package/docs/enums/DecoratorTargetType.html +0 -11
- package/docs/enums/PropertyUniqueness.html +0 -5
- package/docs/enums/RelationType.html +0 -6
- package/docs/functions/capitalize.html +0 -2
- package/docs/functions/cloneDeep.html +0 -2
- package/docs/functions/excludeObjectKeys.html +0 -2
- package/docs/functions/getCtorName.html +0 -2
- package/docs/functions/getDecoratorTargetType.html +0 -2
- package/docs/functions/getValueByPath.html +0 -2
- package/docs/functions/isCtor.html +0 -3
- package/docs/functions/isDeepEqual.html +0 -2
- package/docs/functions/isPureObject.html +0 -2
- package/docs/functions/selectObjectKeys.html +0 -2
- package/docs/functions/singularize.html +0 -2
- package/docs/functions/stringToRegexp.html +0 -2
- package/docs/index.html +0 -373
- package/docs/interfaces/AndClause.html +0 -5
- package/docs/interfaces/Constructor.html +0 -4
- package/docs/interfaces/OrClause.html +0 -5
- package/docs/modules.html +0 -97
- package/docs/types/AnyObject.html +0 -2
- package/docs/types/BelongsToDefinition.html +0 -6
- package/docs/types/CountMethod.html +0 -2
- package/docs/types/DEFAULT_PRIMARY_KEY_PROPERTY_NAME.html +0 -2
- package/docs/types/DatasourceDefinition.html +0 -2
- package/docs/types/FieldsClause.html +0 -4
- package/docs/types/FilterClause.html +0 -2
- package/docs/types/Flatten.html +0 -1
- package/docs/types/FullPropertyDefinition.html +0 -2
- package/docs/types/HasManyDefinition.html +0 -4
- package/docs/types/HasOneDefinition.html +0 -4
- package/docs/types/Identity.html +0 -2
- package/docs/types/IncludeClause.html +0 -14
- package/docs/types/ItemFilterClause.html +0 -2
- package/docs/types/ModelData.html +0 -2
- package/docs/types/ModelDefinition.html +0 -2
- package/docs/types/ModelId.html +0 -2
- package/docs/types/NestedIncludeClause.html +0 -10
- package/docs/types/NormalizedFieldsClause.html +0 -4
- package/docs/types/NormalizedIncludeClause.html +0 -6
- package/docs/types/OperatorClause.html +0 -4
- package/docs/types/OptionalUnlessRequiredId.html +0 -2
- package/docs/types/OrderClause.html +0 -4
- package/docs/types/PartialBy.html +0 -2
- package/docs/types/PartialWithoutId.html +0 -2
- package/docs/types/PolyBelongsToDefinition.html +0 -6
- package/docs/types/PolyHasManyDefinitionWithTargetKeys.html +0 -4
- package/docs/types/PolyHasManyDefinitionWithTargetRelationName.html +0 -4
- package/docs/types/PolyHasOneDefinitionWithTargetKeys.html +0 -4
- package/docs/types/PolyHasOneDefinitionWithTargetRelationName.html +0 -4
- package/docs/types/PropertiesClause.html +0 -4
- package/docs/types/PropertyDefinition.html +0 -2
- package/docs/types/PropertyDefinitionMap.html +0 -2
- package/docs/types/PropertyTransformOptions.html +0 -2
- package/docs/types/PropertyTransformer.html +0 -2
- package/docs/types/PropertyTransformerContext.html +0 -2
- package/docs/types/PropertyValidateOptions.html +0 -2
- package/docs/types/PropertyValidator.html +0 -2
- package/docs/types/PropertyValidatorContext.html +0 -2
- package/docs/types/ReferencesManyDefinition.html +0 -6
- package/docs/types/RelationDefinition.html +0 -4
- package/docs/types/RelationDefinitionMap.html +0 -2
- package/docs/types/WhereClause.html +0 -4
- package/docs/types/WithoutId.html +0 -2
- package/typedoc.json +0 -4
package/.eslintignore
CHANGED
package/.husky/pre-commit
CHANGED
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
- [Свойства](#Свойства)
|
|
12
12
|
- [Валидаторы](#Валидаторы)
|
|
13
13
|
- [Трансформеры](#Трансформеры)
|
|
14
|
-
- [
|
|
14
|
+
- [Пустые значения](#Пустые-значения)
|
|
15
15
|
- [Репозиторий](#Репозиторий)
|
|
16
16
|
- [Фильтрация](#Фильтрация)
|
|
17
17
|
- [Связи](#Связи)
|
|
@@ -35,13 +35,55 @@ npm install @e22m4u/js-repository
|
|
|
35
35
|
|
|
36
36
|
## Описание
|
|
37
37
|
|
|
38
|
-
Модуль позволяет
|
|
38
|
+
Модуль позволяет абстрагироваться от различных интерфейсов баз данных,
|
|
39
|
+
представляя их как именованные *источники данных*, подключаемые к *моделям*.
|
|
40
|
+
*Модель* же описывает таблицу базы, колонки которой являются свойствами
|
|
41
|
+
модели. Свойства модели могут иметь определенный *тип* допустимого значения,
|
|
42
|
+
набор *валидаторов* и *трансформеров*, через которые проходят данные перед
|
|
43
|
+
записью в базу. Кроме того, *модель* может определять классические связи
|
|
44
|
+
«один к одному», «один ко многим» и другие типы отношений между моделями.
|
|
45
|
+
|
|
46
|
+
Непосредственно чтение и запись данных производится с помощью *репозитория*,
|
|
47
|
+
который имеет каждая модель с объявленным *источником данных*. Репозиторий
|
|
48
|
+
может фильтровать запрашиваемые документы, выполнять валидацию свойств
|
|
49
|
+
согласно определению модели, и встраивать связанные данные в результат
|
|
50
|
+
выборки.
|
|
39
51
|
|
|
40
52
|
- *Источник данных* - определяет способ подключения к базе
|
|
41
53
|
- *Модель* - описывает структуру документа и связи к другим моделям
|
|
42
54
|
- *Репозиторий* - выполняет операции чтения и записи документов модели
|
|
43
55
|
|
|
44
|
-
|
|
56
|
+
```mermaid
|
|
57
|
+
flowchart TD
|
|
58
|
+
|
|
59
|
+
A[Схема]
|
|
60
|
+
subgraph Базы данных
|
|
61
|
+
B[Источник данных 1]
|
|
62
|
+
C[Источник данных 2]
|
|
63
|
+
end
|
|
64
|
+
A-->B
|
|
65
|
+
A-->C
|
|
66
|
+
|
|
67
|
+
subgraph Коллекции
|
|
68
|
+
D[Модель A]
|
|
69
|
+
E[Модель Б]
|
|
70
|
+
F[Модель В]
|
|
71
|
+
G[Модель Г]
|
|
72
|
+
end
|
|
73
|
+
B-->D
|
|
74
|
+
B-->E
|
|
75
|
+
C-->F
|
|
76
|
+
C-->G
|
|
77
|
+
|
|
78
|
+
H[Репозиторий A]
|
|
79
|
+
I[Репозиторий Б]
|
|
80
|
+
J[Репозиторий В]
|
|
81
|
+
K[Репозиторий Г]
|
|
82
|
+
D-->H
|
|
83
|
+
E-->I
|
|
84
|
+
F-->J
|
|
85
|
+
G-->K
|
|
86
|
+
```
|
|
45
87
|
|
|
46
88
|
## Пример
|
|
47
89
|
|
|
@@ -70,7 +112,7 @@ schema.defineModel({
|
|
|
70
112
|
},
|
|
71
113
|
})
|
|
72
114
|
|
|
73
|
-
// получение репозитория
|
|
115
|
+
// получение репозитория модели "country"
|
|
74
116
|
const countryRep = schema.getRepository('country');
|
|
75
117
|
|
|
76
118
|
// добавление нового документа в коллекцию "country"
|
|
@@ -79,7 +121,7 @@ const country = await countryRep.create({
|
|
|
79
121
|
population: 143400000,
|
|
80
122
|
});
|
|
81
123
|
|
|
82
|
-
// вывод
|
|
124
|
+
// вывод нового документа
|
|
83
125
|
console.log(country);
|
|
84
126
|
// {
|
|
85
127
|
// "id": 1,
|
|
@@ -230,17 +272,17 @@ schema.defineModel({
|
|
|
230
272
|
**unique**
|
|
231
273
|
|
|
232
274
|
Если значением параметра `unique` является `true` или `'strict'`, то выполняется
|
|
233
|
-
строгая проверка на уникальность. В этом режиме [пустые значения](
|
|
275
|
+
строгая проверка на уникальность. В этом режиме [пустые значения](#Пустые-значения)
|
|
234
276
|
так же подлежат проверке, где `null` и `undefined` не могут повторяться более одного
|
|
235
277
|
раза.
|
|
236
278
|
|
|
237
279
|
Режим `'sparse'` проверяет только значения с полезной нагрузкой, исключая
|
|
238
|
-
[пустые значения](
|
|
280
|
+
[пустые значения](#Пустые-значения), список которых отличается в зависимости
|
|
239
281
|
от типа свойства. Например, для типа `string` пустым значением будет `undefined`,
|
|
240
282
|
`null` и `''` (пустая строка).
|
|
241
283
|
|
|
242
284
|
- `unique: true | 'strict'` строгая проверка на уникальность
|
|
243
|
-
- `unique: 'sparse'` исключить из проверки [пустые значения](
|
|
285
|
+
- `unique: 'sparse'` исключить из проверки [пустые значения](#Пустые-значения)
|
|
244
286
|
- `unique: false | 'nonUnique'` не проверять на уникальность (по умолчанию)
|
|
245
287
|
|
|
246
288
|
**Примеры**
|
|
@@ -303,7 +345,7 @@ schema.defineModel({
|
|
|
303
345
|
|
|
304
346
|
Кроме проверки типа, дополнительные условия можно задать с помощью
|
|
305
347
|
валидаторов, через которые будет проходить значение свойства перед
|
|
306
|
-
записью в базу. Исключением являются [пустые значения](
|
|
348
|
+
записью в базу. Исключением являются [пустые значения](#Пустые-значения),
|
|
307
349
|
которые не подлежат проверке.
|
|
308
350
|
|
|
309
351
|
- `minLength: number` минимальная длинна строки или массива
|
|
@@ -335,7 +377,7 @@ schema.defineModel({
|
|
|
335
377
|
С помощью трансформеров производится модификация значений определенных
|
|
336
378
|
полей перед записью в базу. Трансформеры позволяют указать какие изменения
|
|
337
379
|
нужно производить с входящими данными. Исключением являются
|
|
338
|
-
[пустые значения](
|
|
380
|
+
[пустые значения](#Пустые-значения), которые не подлежат трансформации.
|
|
339
381
|
|
|
340
382
|
- `trim` удаление пробельных символов с начала и конца строки
|
|
341
383
|
- `toUpperCase` перевод строки в верхний регистр
|
|
@@ -365,7 +407,7 @@ schema.defineModel({
|
|
|
365
407
|
});
|
|
366
408
|
```
|
|
367
409
|
|
|
368
|
-
##
|
|
410
|
+
## Пустые значения
|
|
369
411
|
|
|
370
412
|
Разные типы свойств имеют свои наборы пустых значений. Эти наборы
|
|
371
413
|
используются для определения наличия полезной нагрузки в значении
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e22m4u/js-repository",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Модуль для работы с базами данных для Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -13,12 +13,11 @@
|
|
|
13
13
|
"format": "prettier --write \"./src/**/*.js\"",
|
|
14
14
|
"test": "npm run lint && c8 --reporter=text-summary mocha",
|
|
15
15
|
"test:coverage": "npm run lint && c8 --reporter=text mocha",
|
|
16
|
-
"build:doc": "tsc && npx typedoc",
|
|
17
16
|
"prepare": "husky"
|
|
18
17
|
},
|
|
19
18
|
"repository": {
|
|
20
19
|
"type": "git",
|
|
21
|
-
"url": "https://
|
|
20
|
+
"url": "https://github.com/e22m4u/js-repository.git"
|
|
22
21
|
},
|
|
23
22
|
"keywords": [
|
|
24
23
|
"Repository",
|
|
@@ -30,34 +29,34 @@
|
|
|
30
29
|
],
|
|
31
30
|
"author": "e22m4u <e22m4u@yandex.ru>",
|
|
32
31
|
"license": "MIT",
|
|
33
|
-
"homepage": "https://
|
|
32
|
+
"homepage": "https://github.com/e22m4u/js-repository",
|
|
34
33
|
"dependencies": {
|
|
35
34
|
"@e22m4u/js-format": "0.0.9",
|
|
36
35
|
"@e22m4u/js-service": "0.0.8"
|
|
37
36
|
},
|
|
38
37
|
"devDependencies": {
|
|
39
|
-
"@commitlint/cli": "
|
|
40
|
-
"@commitlint/config-conventional": "
|
|
41
|
-
"@types/chai": "
|
|
42
|
-
"@types/chai-as-promised": "
|
|
43
|
-
"@types/
|
|
44
|
-
"@
|
|
45
|
-
"@typescript-eslint/
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"chai
|
|
49
|
-
"chai-
|
|
50
|
-
"chai-
|
|
51
|
-
"
|
|
52
|
-
"eslint
|
|
53
|
-
"eslint-
|
|
54
|
-
"eslint-plugin-
|
|
55
|
-
"eslint-plugin-
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"typescript": "
|
|
38
|
+
"@commitlint/cli": "~19.2.0",
|
|
39
|
+
"@commitlint/config-conventional": "~19.1.0",
|
|
40
|
+
"@types/chai": "~4.3.12",
|
|
41
|
+
"@types/chai-as-promised": "~7.1.8",
|
|
42
|
+
"@types/chai-spies": "~1.0.6",
|
|
43
|
+
"@types/mocha": "~10.0.6",
|
|
44
|
+
"@typescript-eslint/eslint-plugin": "~7.2.0",
|
|
45
|
+
"@typescript-eslint/parser": "~7.2.0",
|
|
46
|
+
"c8": "~9.1.0",
|
|
47
|
+
"chai": "~4.4.1",
|
|
48
|
+
"chai-as-promised": "~7.1.1",
|
|
49
|
+
"chai-spies": "~1.1.0",
|
|
50
|
+
"chai-subset": "~1.6.0",
|
|
51
|
+
"eslint": "~8.57.0",
|
|
52
|
+
"eslint-config-prettier": "~9.1.0",
|
|
53
|
+
"eslint-plugin-chai-expect": "~3.0.0",
|
|
54
|
+
"eslint-plugin-jsdoc": "~48.2.1",
|
|
55
|
+
"eslint-plugin-mocha": "~10.4.1",
|
|
56
|
+
"husky": "~9.0.11",
|
|
57
|
+
"mocha": "~10.3.0",
|
|
58
|
+
"prettier": "~3.2.5",
|
|
59
|
+
"ts-node": "~10.9.2",
|
|
60
|
+
"typescript": "~5.4.2"
|
|
62
61
|
}
|
|
63
62
|
}
|
|
@@ -22,32 +22,32 @@ export class DataTransformationDecorator extends Service {
|
|
|
22
22
|
const transformer = this.getService(ModelDataTransformer);
|
|
23
23
|
|
|
24
24
|
const create = adapter.create;
|
|
25
|
-
adapter.create = function (modelName, modelData, filter) {
|
|
26
|
-
modelData = transformer.transform(modelName, modelData);
|
|
25
|
+
adapter.create = async function (modelName, modelData, filter) {
|
|
26
|
+
modelData = await transformer.transform(modelName, modelData);
|
|
27
27
|
return create.call(this, modelName, modelData, filter);
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
const replaceById = adapter.replaceById;
|
|
31
|
-
adapter.replaceById = function (modelName, id, modelData, filter) {
|
|
32
|
-
modelData = transformer.transform(modelName, modelData);
|
|
31
|
+
adapter.replaceById = async function (modelName, id, modelData, filter) {
|
|
32
|
+
modelData = await transformer.transform(modelName, modelData);
|
|
33
33
|
return replaceById.call(this, modelName, id, modelData, filter);
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
const replaceOrCreate = adapter.replaceOrCreate;
|
|
37
|
-
adapter.replaceOrCreate = function (modelName, modelData, filter) {
|
|
38
|
-
modelData = transformer.transform(modelName, modelData);
|
|
37
|
+
adapter.replaceOrCreate = async function (modelName, modelData, filter) {
|
|
38
|
+
modelData = await transformer.transform(modelName, modelData);
|
|
39
39
|
return replaceOrCreate.call(this, modelName, modelData, filter);
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
const patch = adapter.patch;
|
|
43
|
-
adapter.patch = function (modelName, modelData, where) {
|
|
44
|
-
modelData = transformer.transform(modelName, modelData, true);
|
|
43
|
+
adapter.patch = async function (modelName, modelData, where) {
|
|
44
|
+
modelData = await transformer.transform(modelName, modelData, true);
|
|
45
45
|
return patch.call(this, modelName, modelData, where);
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
const patchById = adapter.patchById;
|
|
49
|
-
adapter.patchById = function (modelName, id, modelData, filter) {
|
|
50
|
-
modelData = transformer.transform(modelName, modelData, true);
|
|
49
|
+
adapter.patchById = async function (modelName, id, modelData, filter) {
|
|
50
|
+
modelData = await transformer.transform(modelName, modelData, true);
|
|
51
51
|
return patchById.call(this, modelName, id, modelData, filter);
|
|
52
52
|
};
|
|
53
53
|
}
|
|
@@ -4,38 +4,52 @@ import {Adapter} from '../adapter.js';
|
|
|
4
4
|
import {Schema} from '../../schema.js';
|
|
5
5
|
import {ModelDataTransformer} from '../../definition/index.js';
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const MODEL_NAME = 'myModel';
|
|
8
|
+
const MODEL_DATA = {kind: 'modelData'};
|
|
9
|
+
const TRANSFORMED_DATA = {kind: 'transformedData'};
|
|
10
|
+
const WHERE_CLAUSE = {kind: {existed: true}};
|
|
11
|
+
const FILTER_CLAUSE = {where: WHERE_CLAUSE};
|
|
12
|
+
const DUMMY_ID = 1;
|
|
9
13
|
|
|
10
14
|
class TestAdapter extends Adapter {
|
|
11
|
-
// eslint-disable-next-line no-unused-vars
|
|
12
15
|
create(modelName, modelData, filter = undefined) {
|
|
16
|
+
expect(modelName).to.be.eq(MODEL_NAME);
|
|
17
|
+
expect(modelData).to.be.eql(TRANSFORMED_DATA);
|
|
18
|
+
expect(filter).to.be.eql(FILTER_CLAUSE);
|
|
13
19
|
return Promise.resolve(modelData);
|
|
14
20
|
}
|
|
15
|
-
|
|
16
|
-
// eslint-disable-next-line no-unused-vars
|
|
17
21
|
replaceById(modelName, id, modelData, filter = undefined) {
|
|
22
|
+
expect(modelName).to.be.eq(MODEL_NAME);
|
|
23
|
+
expect(id).to.be.eq(DUMMY_ID);
|
|
24
|
+
expect(modelData).to.be.eql(TRANSFORMED_DATA);
|
|
25
|
+
expect(filter).to.be.eql(FILTER_CLAUSE);
|
|
18
26
|
return Promise.resolve(modelData);
|
|
19
27
|
}
|
|
20
|
-
|
|
21
|
-
// eslint-disable-next-line no-unused-vars
|
|
22
28
|
replaceOrCreate(modelName, modelData, filter = undefined) {
|
|
29
|
+
expect(modelName).to.be.eq(MODEL_NAME);
|
|
30
|
+
expect(modelData).to.be.eql(TRANSFORMED_DATA);
|
|
31
|
+
expect(filter).to.be.eql(FILTER_CLAUSE);
|
|
23
32
|
return Promise.resolve(modelData);
|
|
24
33
|
}
|
|
25
|
-
|
|
26
|
-
// eslint-disable-next-line no-unused-vars
|
|
27
34
|
patch(modelName, modelData, where = undefined) {
|
|
35
|
+
expect(modelName).to.be.eq(MODEL_NAME);
|
|
36
|
+
expect(modelData).to.be.eql(TRANSFORMED_DATA);
|
|
37
|
+
expect(where).to.be.eql(WHERE_CLAUSE);
|
|
28
38
|
return Promise.resolve(modelData);
|
|
29
39
|
}
|
|
30
|
-
|
|
31
|
-
// eslint-disable-next-line no-unused-vars
|
|
32
40
|
patchById(modelName, id, modelData, filter = undefined) {
|
|
41
|
+
expect(modelName).to.be.eq(MODEL_NAME);
|
|
42
|
+
expect(id).to.be.eq(DUMMY_ID);
|
|
43
|
+
expect(modelData).to.be.eql(TRANSFORMED_DATA);
|
|
44
|
+
expect(filter).to.be.eql(FILTER_CLAUSE);
|
|
33
45
|
return Promise.resolve(modelData);
|
|
34
46
|
}
|
|
35
47
|
}
|
|
36
48
|
|
|
49
|
+
const S = new Schema();
|
|
50
|
+
S.defineModel({name: MODEL_NAME});
|
|
37
51
|
const A = S.getService(TestAdapter);
|
|
38
|
-
const
|
|
52
|
+
const T = S.getService(ModelDataTransformer);
|
|
39
53
|
const sandbox = chai.spy.sandbox();
|
|
40
54
|
|
|
41
55
|
describe('DataTransformationDecorator', function () {
|
|
@@ -43,53 +57,137 @@ describe('DataTransformationDecorator', function () {
|
|
|
43
57
|
sandbox.restore();
|
|
44
58
|
});
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
describe('overrides the "create" method', function () {
|
|
61
|
+
it('transforms the given data', async function () {
|
|
62
|
+
sandbox.on(T, 'transform', () => TRANSFORMED_DATA);
|
|
63
|
+
const res = await A.create(MODEL_NAME, MODEL_DATA, FILTER_CLAUSE);
|
|
64
|
+
expect(res).to.be.eql(TRANSFORMED_DATA);
|
|
65
|
+
expect(T.transform).to.be.called.once;
|
|
66
|
+
expect(T.transform).to.be.called.with.exactly(MODEL_NAME, MODEL_DATA);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('resolves the transformation promise', async function () {
|
|
70
|
+
sandbox.on(T, 'transform', () => Promise.resolve(TRANSFORMED_DATA));
|
|
71
|
+
const res = await A.create(MODEL_NAME, MODEL_DATA, FILTER_CLAUSE);
|
|
72
|
+
expect(res).to.be.eql(TRANSFORMED_DATA);
|
|
73
|
+
expect(T.transform).to.be.called.once;
|
|
74
|
+
expect(T.transform).to.be.called.with.exactly(MODEL_NAME, MODEL_DATA);
|
|
75
|
+
});
|
|
54
76
|
});
|
|
55
77
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
describe('overrides the "replaceById" method', function () {
|
|
79
|
+
it('transforms the given data', async function () {
|
|
80
|
+
sandbox.on(T, 'transform', () => TRANSFORMED_DATA);
|
|
81
|
+
const res = await A.replaceById(
|
|
82
|
+
MODEL_NAME,
|
|
83
|
+
DUMMY_ID,
|
|
84
|
+
MODEL_DATA,
|
|
85
|
+
FILTER_CLAUSE,
|
|
86
|
+
);
|
|
87
|
+
expect(res).to.be.eql(TRANSFORMED_DATA);
|
|
88
|
+
expect(T.transform).to.be.called.once;
|
|
89
|
+
expect(T.transform).to.be.called.with.exactly(MODEL_NAME, MODEL_DATA);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('resolves the transformation promise', async function () {
|
|
93
|
+
sandbox.on(T, 'transform', () => Promise.resolve(TRANSFORMED_DATA));
|
|
94
|
+
const res = await A.replaceById(
|
|
95
|
+
MODEL_NAME,
|
|
96
|
+
DUMMY_ID,
|
|
97
|
+
MODEL_DATA,
|
|
98
|
+
FILTER_CLAUSE,
|
|
99
|
+
);
|
|
100
|
+
expect(res).to.be.eql(TRANSFORMED_DATA);
|
|
101
|
+
expect(T.transform).to.be.called.once;
|
|
102
|
+
expect(T.transform).to.be.called.with.exactly(MODEL_NAME, MODEL_DATA);
|
|
103
|
+
});
|
|
64
104
|
});
|
|
65
105
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
106
|
+
describe('overrides the "replaceOrCreate" method', function () {
|
|
107
|
+
it('transforms the given data', async function () {
|
|
108
|
+
sandbox.on(T, 'transform', () => TRANSFORMED_DATA);
|
|
109
|
+
const res = await A.replaceOrCreate(
|
|
110
|
+
MODEL_NAME,
|
|
111
|
+
MODEL_DATA,
|
|
112
|
+
FILTER_CLAUSE,
|
|
113
|
+
);
|
|
114
|
+
expect(res).to.be.eql(TRANSFORMED_DATA);
|
|
115
|
+
expect(T.transform).to.be.called.once;
|
|
116
|
+
expect(T.transform).to.be.called.with.exactly(MODEL_NAME, MODEL_DATA);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('resolves the transformation promise', async function () {
|
|
120
|
+
sandbox.on(T, 'transform', () => Promise.resolve(TRANSFORMED_DATA));
|
|
121
|
+
const res = await A.replaceOrCreate(
|
|
122
|
+
MODEL_NAME,
|
|
123
|
+
MODEL_DATA,
|
|
124
|
+
FILTER_CLAUSE,
|
|
125
|
+
);
|
|
126
|
+
expect(res).to.be.eql(TRANSFORMED_DATA);
|
|
127
|
+
expect(T.transform).to.be.called.once;
|
|
128
|
+
expect(T.transform).to.be.called.with.exactly(MODEL_NAME, MODEL_DATA);
|
|
129
|
+
});
|
|
74
130
|
});
|
|
75
131
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
132
|
+
describe('overrides the "patch" method', function () {
|
|
133
|
+
it('transforms the given data', async function () {
|
|
134
|
+
sandbox.on(T, 'transform', () => TRANSFORMED_DATA);
|
|
135
|
+
const res = await A.patch(MODEL_NAME, MODEL_DATA, WHERE_CLAUSE);
|
|
136
|
+
expect(res).to.be.eql(TRANSFORMED_DATA);
|
|
137
|
+
expect(T.transform).to.be.called.once;
|
|
138
|
+
expect(T.transform).to.be.called.with.exactly(
|
|
139
|
+
MODEL_NAME,
|
|
140
|
+
MODEL_DATA,
|
|
141
|
+
true,
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('resolves the transformation promise', async function () {
|
|
146
|
+
sandbox.on(T, 'transform', () => Promise.resolve(TRANSFORMED_DATA));
|
|
147
|
+
const res = await A.patch(MODEL_NAME, MODEL_DATA, WHERE_CLAUSE);
|
|
148
|
+
expect(res).to.be.eql(TRANSFORMED_DATA);
|
|
149
|
+
expect(T.transform).to.be.called.once;
|
|
150
|
+
expect(T.transform).to.be.called.with.exactly(
|
|
151
|
+
MODEL_NAME,
|
|
152
|
+
MODEL_DATA,
|
|
153
|
+
true,
|
|
154
|
+
);
|
|
155
|
+
});
|
|
84
156
|
});
|
|
85
157
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
158
|
+
describe('overrides the "patchById" method', function () {
|
|
159
|
+
it('transforms the given data', async function () {
|
|
160
|
+
sandbox.on(T, 'transform', () => TRANSFORMED_DATA);
|
|
161
|
+
const res = await A.patchById(
|
|
162
|
+
MODEL_NAME,
|
|
163
|
+
DUMMY_ID,
|
|
164
|
+
MODEL_DATA,
|
|
165
|
+
FILTER_CLAUSE,
|
|
166
|
+
);
|
|
167
|
+
expect(res).to.be.eql(TRANSFORMED_DATA);
|
|
168
|
+
expect(T.transform).to.be.called.once;
|
|
169
|
+
expect(T.transform).to.be.called.with.exactly(
|
|
170
|
+
MODEL_NAME,
|
|
171
|
+
MODEL_DATA,
|
|
172
|
+
true,
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('resolves the transformation promise', async function () {
|
|
177
|
+
sandbox.on(T, 'transform', () => Promise.resolve(TRANSFORMED_DATA));
|
|
178
|
+
const res = await A.patchById(
|
|
179
|
+
MODEL_NAME,
|
|
180
|
+
DUMMY_ID,
|
|
181
|
+
MODEL_DATA,
|
|
182
|
+
FILTER_CLAUSE,
|
|
183
|
+
);
|
|
184
|
+
expect(res).to.be.eql(TRANSFORMED_DATA);
|
|
185
|
+
expect(T.transform).to.be.called.once;
|
|
186
|
+
expect(T.transform).to.be.called.with.exactly(
|
|
187
|
+
MODEL_NAME,
|
|
188
|
+
MODEL_DATA,
|
|
189
|
+
true,
|
|
190
|
+
);
|
|
191
|
+
});
|
|
94
192
|
});
|
|
95
193
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {ModelData} from '../../types.js';
|
|
2
2
|
import {Service} from '@e22m4u/js-service';
|
|
3
|
+
import {ValueOrPromise} from '../../types.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Model data transformer.
|
|
@@ -11,5 +12,5 @@ export declare class ModelDataTransformer extends Service {
|
|
|
11
12
|
* @param modelName
|
|
12
13
|
* @param modelData
|
|
13
14
|
*/
|
|
14
|
-
transform(modelName: string, modelData: ModelData): ModelData
|
|
15
|
+
transform(modelName: string, modelData: ModelData): ValueOrPromise<ModelData>;
|
|
15
16
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {Service} from '@e22m4u/js-service';
|
|
2
2
|
import {cloneDeep} from '../../utils/index.js';
|
|
3
3
|
import {isPureObject} from '../../utils/index.js';
|
|
4
|
+
import {transformPromise} from '../../utils/index.js';
|
|
4
5
|
import {EmptyValuesDefiner} from './properties/index.js';
|
|
5
6
|
import {InvalidArgumentError} from '../../errors/index.js';
|
|
6
7
|
import {ModelDefinitionUtils} from './model-definition-utils.js';
|
|
@@ -16,7 +17,7 @@ export class ModelDataTransformer extends Service {
|
|
|
16
17
|
* @param {string} modelName
|
|
17
18
|
* @param {object} modelData
|
|
18
19
|
* @param {boolean} isPartial
|
|
19
|
-
* @returns {object}
|
|
20
|
+
* @returns {object|Promise<object>}
|
|
20
21
|
*/
|
|
21
22
|
transform(modelName, modelData, isPartial = false) {
|
|
22
23
|
if (!isPureObject(modelData))
|
|
@@ -33,25 +34,27 @@ export class ModelDataTransformer extends Service {
|
|
|
33
34
|
);
|
|
34
35
|
const propNames = Object.keys(isPartial ? modelData : propDefs);
|
|
35
36
|
const transformedData = cloneDeep(modelData);
|
|
36
|
-
propNames.
|
|
37
|
+
return propNames.reduce((transformedDataOrPromise, propName) => {
|
|
37
38
|
const propDef = propDefs[propName];
|
|
38
|
-
if (!propDef) return;
|
|
39
|
+
if (!propDef) return transformedDataOrPromise;
|
|
39
40
|
const propType =
|
|
40
41
|
modelDefinitionUtils.getDataTypeFromPropertyDefinition(propDef);
|
|
41
42
|
const propValue = modelData[propName];
|
|
42
43
|
const isEmpty = emptyValuesDefiner.isEmpty(propType, propValue);
|
|
43
|
-
if (isEmpty) return;
|
|
44
|
-
const
|
|
44
|
+
if (isEmpty) return transformedDataOrPromise;
|
|
45
|
+
const newPropValueOrPromise = this._transformPropertyValue(
|
|
45
46
|
modelName,
|
|
46
47
|
propName,
|
|
47
48
|
propDef,
|
|
48
49
|
propValue,
|
|
49
50
|
);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
return transformPromise(newPropValueOrPromise, newPropValue => {
|
|
52
|
+
return transformPromise(transformedDataOrPromise, resolvedData => {
|
|
53
|
+
if (newPropValue !== propValue) resolvedData[propName] = newPropValue;
|
|
54
|
+
return resolvedData;
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}, transformedData);
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
/**
|
|
@@ -80,15 +83,21 @@ export class ModelDataTransformer extends Service {
|
|
|
80
83
|
if (transformDef && typeof transformDef === 'string') {
|
|
81
84
|
return transformFn(propValue, transformDef);
|
|
82
85
|
} else if (Array.isArray(transformDef)) {
|
|
83
|
-
return transformDef.reduce(
|
|
84
|
-
(
|
|
86
|
+
return transformDef.reduce((valueOrPromise, transformerName) => {
|
|
87
|
+
return transformPromise(valueOrPromise, value => {
|
|
88
|
+
return transformFn(value, transformerName);
|
|
89
|
+
});
|
|
90
|
+
}, propValue);
|
|
91
|
+
} else if (transformDef !== null && typeof transformDef === 'object') {
|
|
92
|
+
return Object.keys(transformDef).reduce(
|
|
93
|
+
(valueOrPromise, transformerName) => {
|
|
94
|
+
const transformerOptions = transformDef[transformerName];
|
|
95
|
+
return transformPromise(valueOrPromise, value => {
|
|
96
|
+
return transformFn(value, transformerName, transformerOptions);
|
|
97
|
+
});
|
|
98
|
+
},
|
|
85
99
|
propValue,
|
|
86
100
|
);
|
|
87
|
-
} else if (transformDef !== null && typeof transformDef === 'object') {
|
|
88
|
-
return Object.keys(transformDef).reduce((value, transformerName) => {
|
|
89
|
-
const transformerOptions = transformDef[transformerName];
|
|
90
|
-
return transformFn(value, transformerName, transformerOptions);
|
|
91
|
-
}, propValue);
|
|
92
101
|
} else {
|
|
93
102
|
throw new InvalidArgumentError(
|
|
94
103
|
'The provided option "transform" of the property %v in the model %v ' +
|