@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.
Files changed (125) hide show
  1. package/.eslintignore +0 -1
  2. package/.husky/pre-commit +0 -2
  3. package/LICENSE +1 -1
  4. package/README.md +53 -11
  5. package/package.json +26 -27
  6. package/src/adapter/decorator/data-transformation-decorator.js +10 -10
  7. package/src/adapter/decorator/data-transformation-decorator.spec.js +150 -52
  8. package/src/definition/model/model-data-transformer.d.ts +2 -1
  9. package/src/definition/model/model-data-transformer.js +26 -17
  10. package/src/definition/model/model-data-transformer.spec.js +118 -0
  11. package/src/definition/model/properties/property-transformer/property-transformer.d.ts +3 -1
  12. package/src/types.d.ts +7 -0
  13. package/src/utils/index.d.ts +2 -0
  14. package/src/utils/index.js +2 -0
  15. package/src/utils/is-promise.d.ts +10 -0
  16. package/src/utils/is-promise.js +13 -0
  17. package/src/utils/is-promise.spec.js +23 -0
  18. package/src/utils/transform-promise.d.ts +13 -0
  19. package/src/utils/transform-promise.js +15 -0
  20. package/src/utils/transform-promise.spec.js +20 -0
  21. package/docs/.nojekyll +0 -1
  22. package/docs/assets/highlight.css +0 -99
  23. package/docs/assets/main.js +0 -59
  24. package/docs/assets/navigation.js +0 -1
  25. package/docs/assets/search.js +0 -1
  26. package/docs/assets/style.css +0 -1414
  27. package/docs/classes/Adapter.html +0 -38
  28. package/docs/classes/AdapterLoader.html +0 -16
  29. package/docs/classes/AdapterRegistry.html +0 -16
  30. package/docs/classes/BelongsToResolver.html +0 -18
  31. package/docs/classes/DatasourceDefinitionValidator.html +0 -16
  32. package/docs/classes/DefinitionRegistry.html +0 -26
  33. package/docs/classes/EmptyValuesDefiner.html +0 -18
  34. package/docs/classes/FieldsClauseTool.html +0 -20
  35. package/docs/classes/HasManyResolver.html +0 -20
  36. package/docs/classes/HasOneResolver.html +0 -20
  37. package/docs/classes/IncludeClauseTool.html +0 -24
  38. package/docs/classes/InvalidArgumentError.html +0 -16
  39. package/docs/classes/InvalidOperatorValueError.html +0 -16
  40. package/docs/classes/ModelDataSanitizer.html +0 -16
  41. package/docs/classes/ModelDataTransformer.html +0 -16
  42. package/docs/classes/ModelDataValidator.html +0 -16
  43. package/docs/classes/ModelDefinitionUtils.html +0 -48
  44. package/docs/classes/ModelDefinitionValidator.html +0 -16
  45. package/docs/classes/NotImplementedError.html +0 -16
  46. package/docs/classes/OperatorClauseTool.html +0 -72
  47. package/docs/classes/OrderClauseTool.html +0 -20
  48. package/docs/classes/PrimaryKeysDefinitionValidator.html +0 -16
  49. package/docs/classes/PropertiesDefinitionValidator.html +0 -16
  50. package/docs/classes/PropertyTransformerRegistry.html +0 -20
  51. package/docs/classes/PropertyUniquenessValidator.html +0 -16
  52. package/docs/classes/PropertyValidatorRegistry.html +0 -20
  53. package/docs/classes/ReferencesManyResolver.html +0 -16
  54. package/docs/classes/RelationsDefinitionValidator.html +0 -16
  55. package/docs/classes/Repository.html +0 -48
  56. package/docs/classes/RepositoryRegistry.html +0 -18
  57. package/docs/classes/Schema.html +0 -20
  58. package/docs/classes/SliceClauseTool.html +0 -20
  59. package/docs/classes/WhereClauseTool.html +0 -18
  60. package/docs/enums/DataType.html +0 -8
  61. package/docs/enums/DecoratorTargetType.html +0 -11
  62. package/docs/enums/PropertyUniqueness.html +0 -5
  63. package/docs/enums/RelationType.html +0 -6
  64. package/docs/functions/capitalize.html +0 -2
  65. package/docs/functions/cloneDeep.html +0 -2
  66. package/docs/functions/excludeObjectKeys.html +0 -2
  67. package/docs/functions/getCtorName.html +0 -2
  68. package/docs/functions/getDecoratorTargetType.html +0 -2
  69. package/docs/functions/getValueByPath.html +0 -2
  70. package/docs/functions/isCtor.html +0 -3
  71. package/docs/functions/isDeepEqual.html +0 -2
  72. package/docs/functions/isPureObject.html +0 -2
  73. package/docs/functions/selectObjectKeys.html +0 -2
  74. package/docs/functions/singularize.html +0 -2
  75. package/docs/functions/stringToRegexp.html +0 -2
  76. package/docs/index.html +0 -373
  77. package/docs/interfaces/AndClause.html +0 -5
  78. package/docs/interfaces/Constructor.html +0 -4
  79. package/docs/interfaces/OrClause.html +0 -5
  80. package/docs/modules.html +0 -97
  81. package/docs/types/AnyObject.html +0 -2
  82. package/docs/types/BelongsToDefinition.html +0 -6
  83. package/docs/types/CountMethod.html +0 -2
  84. package/docs/types/DEFAULT_PRIMARY_KEY_PROPERTY_NAME.html +0 -2
  85. package/docs/types/DatasourceDefinition.html +0 -2
  86. package/docs/types/FieldsClause.html +0 -4
  87. package/docs/types/FilterClause.html +0 -2
  88. package/docs/types/Flatten.html +0 -1
  89. package/docs/types/FullPropertyDefinition.html +0 -2
  90. package/docs/types/HasManyDefinition.html +0 -4
  91. package/docs/types/HasOneDefinition.html +0 -4
  92. package/docs/types/Identity.html +0 -2
  93. package/docs/types/IncludeClause.html +0 -14
  94. package/docs/types/ItemFilterClause.html +0 -2
  95. package/docs/types/ModelData.html +0 -2
  96. package/docs/types/ModelDefinition.html +0 -2
  97. package/docs/types/ModelId.html +0 -2
  98. package/docs/types/NestedIncludeClause.html +0 -10
  99. package/docs/types/NormalizedFieldsClause.html +0 -4
  100. package/docs/types/NormalizedIncludeClause.html +0 -6
  101. package/docs/types/OperatorClause.html +0 -4
  102. package/docs/types/OptionalUnlessRequiredId.html +0 -2
  103. package/docs/types/OrderClause.html +0 -4
  104. package/docs/types/PartialBy.html +0 -2
  105. package/docs/types/PartialWithoutId.html +0 -2
  106. package/docs/types/PolyBelongsToDefinition.html +0 -6
  107. package/docs/types/PolyHasManyDefinitionWithTargetKeys.html +0 -4
  108. package/docs/types/PolyHasManyDefinitionWithTargetRelationName.html +0 -4
  109. package/docs/types/PolyHasOneDefinitionWithTargetKeys.html +0 -4
  110. package/docs/types/PolyHasOneDefinitionWithTargetRelationName.html +0 -4
  111. package/docs/types/PropertiesClause.html +0 -4
  112. package/docs/types/PropertyDefinition.html +0 -2
  113. package/docs/types/PropertyDefinitionMap.html +0 -2
  114. package/docs/types/PropertyTransformOptions.html +0 -2
  115. package/docs/types/PropertyTransformer.html +0 -2
  116. package/docs/types/PropertyTransformerContext.html +0 -2
  117. package/docs/types/PropertyValidateOptions.html +0 -2
  118. package/docs/types/PropertyValidator.html +0 -2
  119. package/docs/types/PropertyValidatorContext.html +0 -2
  120. package/docs/types/ReferencesManyDefinition.html +0 -6
  121. package/docs/types/RelationDefinition.html +0 -4
  122. package/docs/types/RelationDefinitionMap.html +0 -2
  123. package/docs/types/WhereClause.html +0 -4
  124. package/docs/types/WithoutId.html +0 -2
  125. package/typedoc.json +0 -4
package/.eslintignore CHANGED
@@ -1,2 +1 @@
1
1
  *.d.ts
2
- docs
package/.husky/pre-commit CHANGED
@@ -1,7 +1,5 @@
1
1
  npm run lint:fix
2
2
  npm run format
3
-
4
3
  npm run test
5
- npm run build:doc
6
4
 
7
5
  git add -A
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 e22m4u@gmail.com
3
+ Copyright (c) 2023 e22m4u@yandex.ru
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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
- <img alt="Схема" src="https://gitflic.ru/project/e22m4u/js-repository/blob/raw?file=assets%2Fmermaid-diagram.png&commit=39e53768b4ea62cafe60522e14d1fa9ddd42ebd5">
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
- // получение репозитория для модели "country"
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.13",
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://gitflic.ru/project/e22m4u/js-repository.git"
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://gitflic.ru/project/e22m4u/js-repository",
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": "^18.6.1",
40
- "@commitlint/config-conventional": "^18.6.2",
41
- "@types/chai": "^4.3.11",
42
- "@types/chai-as-promised": "^7.1.8",
43
- "@types/mocha": "^10.0.6",
44
- "@typescript-eslint/eslint-plugin": "^7.0.2",
45
- "@typescript-eslint/parser": "^7.0.2",
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.56.0",
52
- "eslint-config-prettier": "^9.1.0",
53
- "eslint-plugin-chai-expect": "^3.0.0",
54
- "eslint-plugin-jsdoc": "^48.1.0",
55
- "eslint-plugin-mocha": "^10.3.0",
56
- "husky": "^9.0.11",
57
- "mocha": "^10.3.0",
58
- "prettier": "^3.2.5",
59
- "ts-node": "^10.9.2",
60
- "typedoc": "^0.25.8",
61
- "typescript": "^5.3.3"
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 S = new Schema();
8
- S.defineModel({name: 'model'});
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 V = S.getService(ModelDataTransformer);
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
- it('overrides the "create" method and transforms a given data', async function () {
47
- const modelData = {kind: 'modelData'};
48
- const transformedData = {kind: 'transformedData'};
49
- sandbox.on(V, 'transform', () => transformedData);
50
- const res = await A.create('model', modelData);
51
- expect(res).to.be.eql(transformedData);
52
- expect(V.transform).to.be.called.once;
53
- expect(V.transform).to.be.called.with.exactly('model', modelData);
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
- it('overrides the "replaceById" method and transforms a given data', async function () {
57
- const modelData = {kind: 'modelData'};
58
- const transformedData = {kind: 'transformedData'};
59
- sandbox.on(V, 'transform', () => transformedData);
60
- const res = await A.replaceById('model', 1, modelData);
61
- expect(res).to.be.eql(transformedData);
62
- expect(V.transform).to.be.called.once;
63
- expect(V.transform).to.be.called.with.exactly('model', modelData);
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
- it('overrides the "replaceOrCreate" method and transforms a given data', async function () {
67
- const modelData = {kind: 'modelData'};
68
- const transformedData = {kind: 'transformedData'};
69
- sandbox.on(V, 'transform', () => transformedData);
70
- const res = await A.replaceOrCreate('model', modelData);
71
- expect(res).to.be.eql(transformedData);
72
- expect(V.transform).to.be.called.once;
73
- expect(V.transform).to.be.called.with.exactly('model', modelData);
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
- it('overrides the "patch" method and transforms a given data', async function () {
77
- const modelData = {kind: 'modelData'};
78
- const transformedData = {kind: 'transformedData'};
79
- sandbox.on(V, 'transform', () => transformedData);
80
- const res = await A.patch('model', modelData);
81
- expect(res).to.be.eql(transformedData);
82
- expect(V.transform).to.be.called.once;
83
- expect(V.transform).to.be.called.with.exactly('model', modelData, true);
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
- it('overrides the "patchById" method and transforms a given data', async function () {
87
- const modelData = {kind: 'modelData'};
88
- const transformedData = {kind: 'transformedData'};
89
- sandbox.on(V, 'transform', () => transformedData);
90
- const res = await A.patchById('model', 1, modelData);
91
- expect(res).to.be.eql(transformedData);
92
- expect(V.transform).to.be.called.once;
93
- expect(V.transform).to.be.called.with.exactly('model', modelData, true);
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.forEach(propName => {
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 newPropValue = this._transformPropertyValue(
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
- if (propValue !== newPropValue) {
51
- transformedData[propName] = newPropValue;
52
- }
53
- });
54
- return transformedData;
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
- (value, transformerName) => transformFn(value, transformerName),
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 ' +