@e22m4u/js-repository 0.1.26 → 0.2.2

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.
@@ -2348,8 +2348,19 @@ var init_model_definition_utils = __esm({
2348
2348
  propNames.forEach((propName) => {
2349
2349
  if (!(propName in convertedData)) return;
2350
2350
  const colName = this.getColumnNameByPropertyName(modelName, propName);
2351
- if (propName === colName) return;
2352
- const propValue = convertedData[propName];
2351
+ let propValue = convertedData[propName];
2352
+ const propDef = propDefs[propName];
2353
+ if (propValue !== null && typeof propValue === "object" && !Array.isArray(propValue) && propDef !== null && typeof propDef === "object" && propDef.type === DataType.OBJECT && propDef.model) {
2354
+ propValue = this.convertPropertyNamesToColumnNames(
2355
+ propDef.model,
2356
+ propValue
2357
+ );
2358
+ }
2359
+ if (Array.isArray(propValue) && propDef !== null && typeof propDef === "object" && propDef.type === DataType.ARRAY && propDef.itemModel) {
2360
+ propValue = propValue.map((el) => {
2361
+ return el !== null && typeof el === "object" && !Array.isArray(el) ? this.convertPropertyNamesToColumnNames(propDef.itemModel, el) : el;
2362
+ });
2363
+ }
2353
2364
  delete convertedData[propName];
2354
2365
  convertedData[colName] = propValue;
2355
2366
  });
@@ -2368,8 +2379,20 @@ var init_model_definition_utils = __esm({
2368
2379
  const convertedData = cloneDeep(tableData);
2369
2380
  propNames.forEach((propName) => {
2370
2381
  const colName = this.getColumnNameByPropertyName(modelName, propName);
2371
- if (!(colName in convertedData) || colName === propName) return;
2372
- const colValue = convertedData[colName];
2382
+ if (!(colName in convertedData)) return;
2383
+ let colValue = convertedData[colName];
2384
+ const propDef = propDefs[propName];
2385
+ if (colValue !== null && typeof colValue === "object" && !Array.isArray(colValue) && propDef !== null && typeof propDef === "object" && propDef.type === DataType.OBJECT && propDef.model) {
2386
+ colValue = this.convertColumnNamesToPropertyNames(
2387
+ propDef.model,
2388
+ colValue
2389
+ );
2390
+ }
2391
+ if (Array.isArray(colValue) && propDef !== null && typeof propDef === "object" && propDef.type === DataType.ARRAY && propDef.itemModel) {
2392
+ colValue = colValue.map((el) => {
2393
+ return el !== null && typeof el === "object" && !Array.isArray(el) ? this.convertColumnNamesToPropertyNames(propDef.itemModel, el) : el;
2394
+ });
2395
+ }
2373
2396
  delete convertedData[colName];
2374
2397
  convertedData[propName] = colValue;
2375
2398
  });
@@ -2838,6 +2861,14 @@ var init_properties_definition_validator = __esm({
2838
2861
  propDef.itemType
2839
2862
  );
2840
2863
  }
2864
+ if (propDef.itemModel && typeof propDef.itemModel !== "string") {
2865
+ throw new InvalidArgumentError(
2866
+ 'The provided option "itemModel" of the property %v in the model %v should be a String, but %v given.',
2867
+ propName,
2868
+ modelName,
2869
+ propDef.itemModel
2870
+ );
2871
+ }
2841
2872
  if (propDef.model && typeof propDef.model !== "string")
2842
2873
  throw new InvalidArgumentError(
2843
2874
  'The provided option "model" of the property %v in the model %v should be a String, but %v given.',
@@ -2893,28 +2924,41 @@ var init_properties_definition_validator = __esm({
2893
2924
  );
2894
2925
  if (propDef.itemType && propDef.type !== DataType.ARRAY)
2895
2926
  throw new InvalidArgumentError(
2896
- 'The property %v of the model %v has the non-array type, so it should not have the option "itemType" to be provided.',
2927
+ 'The property %v of the model %v has a non-array type, so it should not have the option "itemType" to be provided.',
2897
2928
  propName,
2898
2929
  modelName,
2899
2930
  propDef.type
2900
2931
  );
2901
- if (propDef.model && propDef.type !== DataType.OBJECT && propDef.itemType !== DataType.OBJECT) {
2902
- if (propDef.type !== DataType.ARRAY) {
2932
+ if (propDef.itemModel && propDef.type !== DataType.ARRAY)
2933
+ throw new InvalidArgumentError(
2934
+ 'The option "itemModel" is not supported for %s property type, so the property %v of the model %v should not have the option "itemModel" to be provided.',
2935
+ capitalize(propDef.type),
2936
+ propName,
2937
+ modelName
2938
+ );
2939
+ if (propDef.itemModel && propDef.itemType !== DataType.OBJECT) {
2940
+ if (propDef.itemType) {
2903
2941
  throw new InvalidArgumentError(
2904
- 'The option "model" is not supported for %s property type, so the property %v of the model %v should not have the option "model" to be provided.',
2905
- capitalize(propDef.type),
2942
+ 'The provided option "itemModel" requires the option "itemType" to be explicitly set to Object, but the property %v of the model %v has specified item type as %s.',
2906
2943
  propName,
2907
- modelName
2944
+ modelName,
2945
+ capitalize(propDef.itemType)
2908
2946
  );
2909
2947
  } else {
2910
2948
  throw new InvalidArgumentError(
2911
- 'The option "model" is not supported for Array property type of %s, so the property %v of the model %v should not have the option "model" to be provided.',
2912
- capitalize(propDef.itemType),
2949
+ 'The provided option "itemModel" requires the option "itemType" to be explicitly set to Object, but the property %v of the model %v does not have specified item type.',
2913
2950
  propName,
2914
2951
  modelName
2915
2952
  );
2916
2953
  }
2917
2954
  }
2955
+ if (propDef.model && propDef.type !== DataType.OBJECT)
2956
+ throw new InvalidArgumentError(
2957
+ 'The option "model" is not supported for %s property type, so the property %v of the model %v should not have the option "model" to be provided.',
2958
+ capitalize(propDef.type),
2959
+ propName,
2960
+ modelName
2961
+ );
2918
2962
  if (propDef.validate != null) {
2919
2963
  const propertyValidatorRegistry = this.getService(
2920
2964
  PropertyValidatorRegistry
@@ -3186,11 +3230,15 @@ var init_model_data_validator = __esm({
3186
3230
  );
3187
3231
  break;
3188
3232
  // OBJECT
3189
- case DataType.OBJECT:
3233
+ case DataType.OBJECT: {
3190
3234
  if (!isPureObject(propValue)) throw createError("an Object");
3191
- if (typeof propDef === "object" && propDef.model)
3192
- this.validate(propDef.model, propValue);
3235
+ if (typeof propDef === "object") {
3236
+ const modelOptionField = isArrayValue ? "itemModel" : "model";
3237
+ const modelOptionValue = propDef[modelOptionField];
3238
+ if (modelOptionValue) this.validate(modelOptionValue, propValue);
3239
+ }
3193
3240
  break;
3241
+ }
3194
3242
  }
3195
3243
  }
3196
3244
  /**
@@ -5321,7 +5369,7 @@ var init_decorator = __esm({
5321
5369
  });
5322
5370
 
5323
5371
  // src/adapter/adapter.js
5324
- var import_js_service32, _Adapter, Adapter;
5372
+ var import_js_service32, ADAPTER_CLASS_NAME, _Adapter, Adapter;
5325
5373
  var init_adapter = __esm({
5326
5374
  "src/adapter/adapter.js"() {
5327
5375
  "use strict";
@@ -5334,6 +5382,7 @@ var init_adapter = __esm({
5334
5382
  init_decorator();
5335
5383
  init_decorator();
5336
5384
  init_decorator();
5385
+ ADAPTER_CLASS_NAME = "Adapter";
5337
5386
  _Adapter = class _Adapter extends import_js_service32.Service {
5338
5387
  /**
5339
5388
  * Settings.
@@ -5526,7 +5575,7 @@ var init_adapter = __esm({
5526
5575
  *
5527
5576
  * @type {string}
5528
5577
  */
5529
- __publicField(_Adapter, "kind", "Adapter");
5578
+ __publicField(_Adapter, "kinds", [...import_js_service32.Service.kinds, ADAPTER_CLASS_NAME]);
5530
5579
  Adapter = _Adapter;
5531
5580
  }
5532
5581
  });
@@ -5928,7 +5977,7 @@ function findAdapterCtorInModule(module2) {
5928
5977
  let adapterCtor;
5929
5978
  if (!module2 || typeof module2 !== "object" || Array.isArray(module2)) return;
5930
5979
  for (const ctor of Object.values(module2)) {
5931
- if (typeof ctor === "function" && ctor.kind === Adapter.kind) {
5980
+ if (typeof ctor === "function" && Array.isArray(ctor.kinds) && Adapter.kinds.includes(ADAPTER_CLASS_NAME)) {
5932
5981
  adapterCtor = ctor;
5933
5982
  break;
5934
5983
  }
@@ -5941,6 +5990,7 @@ var init_adapter_loader = __esm({
5941
5990
  "use strict";
5942
5991
  init_adapter();
5943
5992
  import_js_service33 = require("@e22m4u/js-service");
5993
+ init_adapter();
5944
5994
  init_errors();
5945
5995
  init_();
5946
5996
  _AdapterLoader = class _AdapterLoader extends import_js_service33.Service {
@@ -6303,6 +6353,7 @@ var init_repository2 = __esm({
6303
6353
  // src/index.js
6304
6354
  var src_exports = {};
6305
6355
  __export(src_exports, {
6356
+ ADAPTER_CLASS_NAME: () => ADAPTER_CLASS_NAME,
6306
6357
  Adapter: () => Adapter,
6307
6358
  AdapterLoader: () => AdapterLoader,
6308
6359
  AdapterRegistry: () => AdapterRegistry,
@@ -6407,6 +6458,7 @@ init_definition();
6407
6458
  init_repository2();
6408
6459
  // Annotate the CommonJS export names for ESM import in node:
6409
6460
  0 && (module.exports = {
6461
+ ADAPTER_CLASS_NAME,
6410
6462
  Adapter,
6411
6463
  AdapterLoader,
6412
6464
  AdapterRegistry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e22m4u/js-repository",
3
- "version": "0.1.26",
3
+ "version": "0.2.2",
4
4
  "description": "Модуль для работы с базами данных для Node.js",
5
5
  "type": "module",
6
6
  "types": "./src/index.d.ts",
@@ -40,32 +40,32 @@
40
40
  "homepage": "https://github.com/e22m4u/js-repository",
41
41
  "peerDependencies": {
42
42
  "@e22m4u/js-format": "0.1.x",
43
- "@e22m4u/js-service": "0.1.x"
43
+ "@e22m4u/js-service": "0.2.x"
44
44
  },
45
45
  "devDependencies": {
46
- "@commitlint/cli": "~19.5.0",
47
- "@commitlint/config-conventional": "~19.5.0",
46
+ "@commitlint/cli": "~19.6.0",
47
+ "@commitlint/config-conventional": "~19.6.0",
48
48
  "@types/chai": "~5.0.1",
49
49
  "@types/chai-as-promised": "~8.0.1",
50
50
  "@types/chai-spies": "~1.0.6",
51
- "@types/mocha": "~10.0.9",
51
+ "@types/mocha": "~10.0.10",
52
52
  "c8": "~10.1.2",
53
53
  "chai": "~5.1.2",
54
- "chai-as-promised": "~8.0.0",
54
+ "chai-as-promised": "~8.0.1",
55
55
  "chai-spies": "~1.1.0",
56
56
  "chai-subset": "~1.6.0",
57
57
  "esbuild": "~0.24.0",
58
- "eslint": "~9.14.0",
58
+ "eslint": "~9.15.0",
59
59
  "eslint-config-prettier": "~9.1.0",
60
60
  "eslint-plugin-chai-expect": "~3.1.0",
61
- "eslint-plugin-jsdoc": "~50.4.3",
61
+ "eslint-plugin-jsdoc": "~50.5.0",
62
62
  "eslint-plugin-mocha": "~10.5.0",
63
- "husky": "~9.1.6",
63
+ "husky": "~9.1.7",
64
64
  "mocha": "~10.8.2",
65
65
  "prettier": "~3.3.3",
66
- "rimraf": "^6.0.1",
66
+ "rimraf": "~6.0.1",
67
67
  "tsx": "~4.19.2",
68
68
  "typescript": "~5.6.3",
69
- "typescript-eslint": "~8.13.0"
69
+ "typescript-eslint": "~8.15.0"
70
70
  }
71
71
  }
@@ -1,5 +1,6 @@
1
1
  import {Adapter} from './adapter.js';
2
2
  import {Service} from '@e22m4u/js-service';
3
+ import {ADAPTER_CLASS_NAME} from './adapter.js';
3
4
  import {InvalidArgumentError} from '../errors/index.js';
4
5
 
5
6
  /**
@@ -54,7 +55,11 @@ function findAdapterCtorInModule(module) {
54
55
  let adapterCtor;
55
56
  if (!module || typeof module !== 'object' || Array.isArray(module)) return;
56
57
  for (const ctor of Object.values(module)) {
57
- if (typeof ctor === 'function' && ctor.kind === Adapter.kind) {
58
+ if (
59
+ typeof ctor === 'function' &&
60
+ Array.isArray(ctor.kinds) &&
61
+ Adapter.kinds.includes(ADAPTER_CLASS_NAME)
62
+ ) {
58
63
  adapterCtor = ctor;
59
64
  break;
60
65
  }
@@ -10,6 +10,13 @@ import {FieldsFilteringDecorator} from './decorator/index.js';
10
10
  import {DataTransformationDecorator} from './decorator/index.js';
11
11
  import {PropertyUniquenessDecorator} from './decorator/index.js';
12
12
 
13
+ /**
14
+ * Adapter class name.
15
+ *
16
+ * @type {string}
17
+ */
18
+ export const ADAPTER_CLASS_NAME = 'Adapter';
19
+
13
20
  /**
14
21
  * Adapter.
15
22
  */
@@ -19,7 +26,7 @@ export class Adapter extends Service {
19
26
  *
20
27
  * @type {string}
21
28
  */
22
- static kind = 'Adapter';
29
+ static kinds = [...Service.kinds, ADAPTER_CLASS_NAME];
23
30
 
24
31
  /**
25
32
  * Settings.
@@ -3,6 +3,7 @@ import {chai} from '../chai.js';
3
3
  import {Schema} from '../schema.js';
4
4
  import {Adapter} from './adapter.js';
5
5
  import {Service} from '@e22m4u/js-service';
6
+ import {ADAPTER_CLASS_NAME} from './adapter.js';
6
7
  import {ServiceContainer} from '@e22m4u/js-service';
7
8
  import {InclusionDecorator} from './decorator/index.js';
8
9
  import {DefaultValuesDecorator} from './decorator/index.js';
@@ -15,12 +16,11 @@ import {PropertyUniquenessDecorator} from './decorator/index.js';
15
16
  const sandbox = chai.spy.sandbox();
16
17
 
17
18
  describe('Adapter', function () {
18
- it('exposes static property "kind"', function () {
19
- expect(Adapter.kind).to.be.eq(Adapter.name);
20
- const MyAdapter1 = class extends Adapter {};
21
- expect(MyAdapter1.kind).to.be.eq(Adapter.name);
22
- class MyAdapter2 extends Adapter {}
23
- expect(MyAdapter2.kind).to.be.eq(Adapter.name);
19
+ it('exposes static property "kinds"', function () {
20
+ const kinds = [...Service.kinds, ADAPTER_CLASS_NAME];
21
+ expect(Adapter.kinds).to.be.eql(kinds);
22
+ const MyAdapter = class extends Adapter {};
23
+ expect(MyAdapter.kinds).to.be.eql(kinds);
24
24
  });
25
25
 
26
26
  describe('constructor', function () {
@@ -157,11 +157,15 @@ export class ModelDataValidator extends Service {
157
157
  );
158
158
  break;
159
159
  // OBJECT
160
- case DataType.OBJECT:
160
+ case DataType.OBJECT: {
161
161
  if (!isPureObject(propValue)) throw createError('an Object');
162
- if (typeof propDef === 'object' && propDef.model)
163
- this.validate(propDef.model, propValue);
162
+ if (typeof propDef === 'object') {
163
+ const modelOptionField = isArrayValue ? 'itemModel' : 'model';
164
+ const modelOptionValue = propDef[modelOptionField];
165
+ if (modelOptionValue) this.validate(modelOptionValue, propValue);
166
+ }
164
167
  break;
168
+ }
165
169
  }
166
170
  }
167
171
 
@@ -1651,7 +1651,22 @@ describe('ModelDataValidator', function () {
1651
1651
  );
1652
1652
  });
1653
1653
 
1654
- describe('the "model" option', function () {
1654
+ describe('the "itemModel" option', function () {
1655
+ it('does not throw an error if the option "itemModel" is not specified in case of Object item type', function () {
1656
+ const S = new Schema();
1657
+ S.defineModel({
1658
+ name: 'model',
1659
+ properties: {
1660
+ foo: {
1661
+ type: DataType.ARRAY,
1662
+ itemType: DataType.OBJECT,
1663
+ },
1664
+ },
1665
+ });
1666
+ const value = {foo: [{a: 1}, {b: 2}]};
1667
+ S.getService(ModelDataValidator).validate('model', value);
1668
+ });
1669
+
1655
1670
  it('throws an error when the given object element has an invalid model', function () {
1656
1671
  const S = new Schema();
1657
1672
  S.defineModel({
@@ -1667,7 +1682,7 @@ describe('ModelDataValidator', function () {
1667
1682
  bar: {
1668
1683
  type: DataType.ARRAY,
1669
1684
  itemType: DataType.OBJECT,
1670
- model: 'modelA',
1685
+ itemModel: 'modelA',
1671
1686
  },
1672
1687
  },
1673
1688
  });
@@ -1696,7 +1711,7 @@ describe('ModelDataValidator', function () {
1696
1711
  bar: {
1697
1712
  type: DataType.ARRAY,
1698
1713
  itemType: DataType.OBJECT,
1699
- model: 'modelA',
1714
+ itemModel: 'modelA',
1700
1715
  },
1701
1716
  },
1702
1717
  });
@@ -179,8 +179,43 @@ export class ModelDefinitionUtils extends Service {
179
179
  propNames.forEach(propName => {
180
180
  if (!(propName in convertedData)) return;
181
181
  const colName = this.getColumnNameByPropertyName(modelName, propName);
182
- if (propName === colName) return;
183
- const propValue = convertedData[propName];
182
+ let propValue = convertedData[propName];
183
+ // если значением является объект, то проверяем
184
+ // тип свойства и наличие модели для замены
185
+ // полей данного объекта
186
+ const propDef = propDefs[propName];
187
+ if (
188
+ propValue !== null &&
189
+ typeof propValue === 'object' &&
190
+ !Array.isArray(propValue) &&
191
+ propDef !== null &&
192
+ typeof propDef === 'object' &&
193
+ propDef.type === DataType.OBJECT &&
194
+ propDef.model
195
+ ) {
196
+ propValue = this.convertPropertyNamesToColumnNames(
197
+ propDef.model,
198
+ propValue,
199
+ );
200
+ }
201
+ // если значением является массив, то проверяем
202
+ // тип свойства и наличие модели элементов массива
203
+ // для замены полей каждого объекта
204
+ if (
205
+ Array.isArray(propValue) &&
206
+ propDef !== null &&
207
+ typeof propDef === 'object' &&
208
+ propDef.type === DataType.ARRAY &&
209
+ propDef.itemModel
210
+ ) {
211
+ propValue = propValue.map(el => {
212
+ // если элементом массива является объект,
213
+ // то конвертируем поля согласно модели
214
+ return el !== null && typeof el === 'object' && !Array.isArray(el)
215
+ ? this.convertPropertyNamesToColumnNames(propDef.itemModel, el)
216
+ : el;
217
+ });
218
+ }
184
219
  delete convertedData[propName];
185
220
  convertedData[colName] = propValue;
186
221
  });
@@ -201,8 +236,44 @@ export class ModelDefinitionUtils extends Service {
201
236
  const convertedData = cloneDeep(tableData);
202
237
  propNames.forEach(propName => {
203
238
  const colName = this.getColumnNameByPropertyName(modelName, propName);
204
- if (!(colName in convertedData) || colName === propName) return;
205
- const colValue = convertedData[colName];
239
+ if (!(colName in convertedData)) return;
240
+ let colValue = convertedData[colName];
241
+ // если значением является объект, то проверяем
242
+ // тип свойства и наличие модели для замены
243
+ // полей данного объекта
244
+ const propDef = propDefs[propName];
245
+ if (
246
+ colValue !== null &&
247
+ typeof colValue === 'object' &&
248
+ !Array.isArray(colValue) &&
249
+ propDef !== null &&
250
+ typeof propDef === 'object' &&
251
+ propDef.type === DataType.OBJECT &&
252
+ propDef.model
253
+ ) {
254
+ colValue = this.convertColumnNamesToPropertyNames(
255
+ propDef.model,
256
+ colValue,
257
+ );
258
+ }
259
+ // если значением является массив, то проверяем
260
+ // тип свойства и наличие модели элементов массива
261
+ // для замены полей каждого объекта
262
+ if (
263
+ Array.isArray(colValue) &&
264
+ propDef !== null &&
265
+ typeof propDef === 'object' &&
266
+ propDef.type === DataType.ARRAY &&
267
+ propDef.itemModel
268
+ ) {
269
+ colValue = colValue.map(el => {
270
+ // если элементом массива является объект,
271
+ // то конвертируем поля согласно модели
272
+ return el !== null && typeof el === 'object' && !Array.isArray(el)
273
+ ? this.convertColumnNamesToPropertyNames(propDef.itemModel, el)
274
+ : el;
275
+ });
276
+ }
206
277
  delete convertedData[colName];
207
278
  convertedData[propName] = colValue;
208
279
  });
@@ -690,6 +690,191 @@ describe('ModelDefinitionUtils', function () {
690
690
  .convertPropertyNamesToColumnNames('modelB', {foo: 'string'});
691
691
  expect(result).to.be.eql({fooColumn: 'string'});
692
692
  });
693
+
694
+ describe('embedded object with model', function () {
695
+ it('does nothing if no property definitions', function () {
696
+ const schema = new Schema();
697
+ schema.defineModel({
698
+ name: 'modelA',
699
+ properties: {
700
+ embedded: {
701
+ type: DataType.OBJECT,
702
+ model: 'modelB',
703
+ },
704
+ },
705
+ });
706
+ schema.defineModel({
707
+ name: 'modelB',
708
+ });
709
+ const result = schema
710
+ .getService(ModelDefinitionUtils)
711
+ .convertPropertyNamesToColumnNames('modelA', {
712
+ embedded: {foo: 'string'},
713
+ });
714
+ expect(result).to.be.eql({embedded: {foo: 'string'}});
715
+ });
716
+
717
+ it('does nothing if no column name specified', function () {
718
+ const schema = new Schema();
719
+ schema.defineModel({
720
+ name: 'modelA',
721
+ properties: {
722
+ embedded: {
723
+ type: DataType.OBJECT,
724
+ model: 'modelB',
725
+ },
726
+ },
727
+ });
728
+ schema.defineModel({
729
+ name: 'modelB',
730
+ properties: {
731
+ foo: DataType.STRING,
732
+ bar: DataType.NUMBER,
733
+ },
734
+ });
735
+ const result = schema
736
+ .getService(ModelDefinitionUtils)
737
+ .convertPropertyNamesToColumnNames('modelA', {
738
+ embedded: {foo: 'string', bar: 10},
739
+ });
740
+ expect(result).to.be.eql({embedded: {foo: 'string', bar: 10}});
741
+ });
742
+
743
+ it('replaces property names by column names', function () {
744
+ const schema = new Schema();
745
+ schema.defineModel({
746
+ name: 'modelA',
747
+ properties: {
748
+ embedded: {
749
+ type: DataType.OBJECT,
750
+ model: 'modelB',
751
+ },
752
+ },
753
+ });
754
+ schema.defineModel({
755
+ name: 'modelB',
756
+ properties: {
757
+ foo: {
758
+ type: DataType.STRING,
759
+ columnName: 'fooColumn',
760
+ },
761
+ bar: {
762
+ type: DataType.NUMBER,
763
+ columnName: 'barColumn',
764
+ },
765
+ },
766
+ });
767
+ const result = schema
768
+ .getService(ModelDefinitionUtils)
769
+ .convertPropertyNamesToColumnNames('modelA', {
770
+ embedded: {foo: 'string', bar: 10},
771
+ });
772
+ expect(result).to.be.eql({
773
+ embedded: {fooColumn: 'string', barColumn: 10},
774
+ });
775
+ });
776
+ });
777
+
778
+ describe('embedded array with items model', function () {
779
+ it('does nothing if no property definitions', function () {
780
+ const schema = new Schema();
781
+ schema.defineModel({
782
+ name: 'modelA',
783
+ properties: {
784
+ embedded: {
785
+ type: DataType.ARRAY,
786
+ itemType: DataType.OBJECT,
787
+ itemModel: 'modelB',
788
+ },
789
+ },
790
+ });
791
+ schema.defineModel({
792
+ name: 'modelB',
793
+ });
794
+ const result = schema
795
+ .getService(ModelDefinitionUtils)
796
+ .convertPropertyNamesToColumnNames('modelA', {
797
+ embedded: [{foo: 'val'}, {bar: 10}],
798
+ });
799
+ expect(result).to.be.eql({embedded: [{foo: 'val'}, {bar: 10}]});
800
+ });
801
+
802
+ it('does nothing if no column name specified', function () {
803
+ const schema = new Schema();
804
+ schema.defineModel({
805
+ name: 'modelA',
806
+ properties: {
807
+ embedded: {
808
+ type: DataType.ARRAY,
809
+ itemType: DataType.OBJECT,
810
+ itemModel: 'modelB',
811
+ },
812
+ },
813
+ });
814
+ schema.defineModel({
815
+ name: 'modelB',
816
+ properties: {
817
+ foo: DataType.STRING,
818
+ bar: DataType.NUMBER,
819
+ },
820
+ });
821
+ const result = schema
822
+ .getService(ModelDefinitionUtils)
823
+ .convertPropertyNamesToColumnNames('modelA', {
824
+ embedded: [
825
+ {foo: 'val1', bar: 10},
826
+ {foo: 'val2', bar: 20},
827
+ ],
828
+ });
829
+ expect(result).to.be.eql({
830
+ embedded: [
831
+ {foo: 'val1', bar: 10},
832
+ {foo: 'val2', bar: 20},
833
+ ],
834
+ });
835
+ });
836
+
837
+ it('replaces property names by column names', function () {
838
+ const schema = new Schema();
839
+ schema.defineModel({
840
+ name: 'modelA',
841
+ properties: {
842
+ embedded: {
843
+ type: DataType.ARRAY,
844
+ itemType: DataType.OBJECT,
845
+ itemModel: 'modelB',
846
+ },
847
+ },
848
+ });
849
+ schema.defineModel({
850
+ name: 'modelB',
851
+ properties: {
852
+ foo: {
853
+ type: DataType.STRING,
854
+ columnName: 'fooColumn',
855
+ },
856
+ bar: {
857
+ type: DataType.NUMBER,
858
+ columnName: 'barColumn',
859
+ },
860
+ },
861
+ });
862
+ const result = schema
863
+ .getService(ModelDefinitionUtils)
864
+ .convertPropertyNamesToColumnNames('modelA', {
865
+ embedded: [
866
+ {foo: 'val1', bar: 10},
867
+ {foo: 'val2', bar: 20},
868
+ ],
869
+ });
870
+ expect(result).to.be.eql({
871
+ embedded: [
872
+ {fooColumn: 'val1', barColumn: 10},
873
+ {fooColumn: 'val2', barColumn: 20},
874
+ ],
875
+ });
876
+ });
877
+ });
693
878
  });
694
879
 
695
880
  describe('convertColumnNamesToPropertyNames', function () {
@@ -764,6 +949,189 @@ describe('ModelDefinitionUtils', function () {
764
949
  .convertColumnNamesToPropertyNames('modelA', {fooColumn: 'string'});
765
950
  expect(result).to.be.eql({foo: 'string'});
766
951
  });
952
+
953
+ describe('embedded object with model', function () {
954
+ it('does nothing if no property definitions', function () {
955
+ const schema = new Schema();
956
+ schema.defineModel({
957
+ name: 'modelA',
958
+ properties: {
959
+ embedded: {
960
+ type: DataType.OBJECT,
961
+ model: 'modelB',
962
+ },
963
+ },
964
+ });
965
+ schema.defineModel({
966
+ name: 'modelB',
967
+ });
968
+ const result = schema
969
+ .getService(ModelDefinitionUtils)
970
+ .convertColumnNamesToPropertyNames('modelA', {
971
+ embedded: {foo: 'string'},
972
+ });
973
+ expect(result).to.be.eql({embedded: {foo: 'string'}});
974
+ });
975
+
976
+ it('does nothing if no column name specified', function () {
977
+ const schema = new Schema();
978
+ schema.defineModel({
979
+ name: 'modelA',
980
+ properties: {
981
+ embedded: {
982
+ type: DataType.OBJECT,
983
+ model: 'modelB',
984
+ },
985
+ },
986
+ });
987
+ schema.defineModel({
988
+ name: 'modelB',
989
+ properties: {
990
+ foo: DataType.STRING,
991
+ bar: DataType.NUMBER,
992
+ },
993
+ });
994
+ const result = schema
995
+ .getService(ModelDefinitionUtils)
996
+ .convertColumnNamesToPropertyNames('modelA', {
997
+ embedded: {foo: 'string', bar: 10},
998
+ });
999
+ expect(result).to.be.eql({embedded: {foo: 'string', bar: 10}});
1000
+ });
1001
+
1002
+ it('replaces property names by column names', function () {
1003
+ const schema = new Schema();
1004
+ schema.defineModel({
1005
+ name: 'modelA',
1006
+ properties: {
1007
+ embedded: {
1008
+ type: DataType.OBJECT,
1009
+ model: 'modelB',
1010
+ },
1011
+ },
1012
+ });
1013
+ schema.defineModel({
1014
+ name: 'modelB',
1015
+ properties: {
1016
+ foo: {
1017
+ type: DataType.STRING,
1018
+ columnName: 'fooColumn',
1019
+ },
1020
+ bar: {
1021
+ type: DataType.NUMBER,
1022
+ columnName: 'barColumn',
1023
+ },
1024
+ },
1025
+ });
1026
+ const result = schema
1027
+ .getService(ModelDefinitionUtils)
1028
+ .convertColumnNamesToPropertyNames('modelA', {
1029
+ embedded: {fooColumn: 'string', barColumn: 10},
1030
+ });
1031
+ expect(result).to.be.eql({embedded: {foo: 'string', bar: 10}});
1032
+ });
1033
+ });
1034
+
1035
+ describe('embedded array with items model', function () {
1036
+ it('does nothing if no property definitions', function () {
1037
+ const schema = new Schema();
1038
+ schema.defineModel({
1039
+ name: 'modelA',
1040
+ properties: {
1041
+ embedded: {
1042
+ type: DataType.ARRAY,
1043
+ itemType: DataType.OBJECT,
1044
+ itemModel: 'modelB',
1045
+ },
1046
+ },
1047
+ });
1048
+ schema.defineModel({
1049
+ name: 'modelB',
1050
+ });
1051
+ const result = schema
1052
+ .getService(ModelDefinitionUtils)
1053
+ .convertColumnNamesToPropertyNames('modelA', {
1054
+ embedded: [{foo: 'val'}, {bar: 10}],
1055
+ });
1056
+ expect(result).to.be.eql({embedded: [{foo: 'val'}, {bar: 10}]});
1057
+ });
1058
+
1059
+ it('does nothing if no column name specified', function () {
1060
+ const schema = new Schema();
1061
+ schema.defineModel({
1062
+ name: 'modelA',
1063
+ properties: {
1064
+ embedded: {
1065
+ type: DataType.ARRAY,
1066
+ itemType: DataType.OBJECT,
1067
+ itemModel: 'modelB',
1068
+ },
1069
+ },
1070
+ });
1071
+ schema.defineModel({
1072
+ name: 'modelB',
1073
+ properties: {
1074
+ foo: DataType.STRING,
1075
+ bar: DataType.NUMBER,
1076
+ },
1077
+ });
1078
+ const result = schema
1079
+ .getService(ModelDefinitionUtils)
1080
+ .convertColumnNamesToPropertyNames('modelA', {
1081
+ embedded: [
1082
+ {foo: 'val1', bar: 10},
1083
+ {foo: 'val2', bar: 20},
1084
+ ],
1085
+ });
1086
+ expect(result).to.be.eql({
1087
+ embedded: [
1088
+ {foo: 'val1', bar: 10},
1089
+ {foo: 'val2', bar: 20},
1090
+ ],
1091
+ });
1092
+ });
1093
+
1094
+ it('replaces property names by column names', function () {
1095
+ const schema = new Schema();
1096
+ schema.defineModel({
1097
+ name: 'modelA',
1098
+ properties: {
1099
+ embedded: {
1100
+ type: DataType.ARRAY,
1101
+ itemType: DataType.OBJECT,
1102
+ itemModel: 'modelB',
1103
+ },
1104
+ },
1105
+ });
1106
+ schema.defineModel({
1107
+ name: 'modelB',
1108
+ properties: {
1109
+ foo: {
1110
+ type: DataType.STRING,
1111
+ columnName: 'fooColumn',
1112
+ },
1113
+ bar: {
1114
+ type: DataType.NUMBER,
1115
+ columnName: 'barColumn',
1116
+ },
1117
+ },
1118
+ });
1119
+ const result = schema
1120
+ .getService(ModelDefinitionUtils)
1121
+ .convertColumnNamesToPropertyNames('modelA', {
1122
+ embedded: [
1123
+ {fooColumn: 'val1', barColumn: 10},
1124
+ {fooColumn: 'val2', barColumn: 20},
1125
+ ],
1126
+ });
1127
+ expect(result).to.be.eql({
1128
+ embedded: [
1129
+ {foo: 'val1', bar: 10},
1130
+ {foo: 'val2', bar: 20},
1131
+ ],
1132
+ });
1133
+ });
1134
+ });
767
1135
  });
768
1136
 
769
1137
  describe('getDataTypeByPropertyName', function () {
@@ -112,6 +112,15 @@ export class PropertiesDefinitionValidator extends Service {
112
112
  propDef.itemType,
113
113
  );
114
114
  }
115
+ if (propDef.itemModel && typeof propDef.itemModel !== 'string') {
116
+ throw new InvalidArgumentError(
117
+ 'The provided option "itemModel" of the property %v in the model %v ' +
118
+ 'should be a String, but %v given.',
119
+ propName,
120
+ modelName,
121
+ propDef.itemModel,
122
+ );
123
+ }
115
124
  if (propDef.model && typeof propDef.model !== 'string')
116
125
  throw new InvalidArgumentError(
117
126
  'The provided option "model" of the property %v in the model %v ' +
@@ -175,37 +184,50 @@ export class PropertiesDefinitionValidator extends Service {
175
184
  );
176
185
  if (propDef.itemType && propDef.type !== Type.ARRAY)
177
186
  throw new InvalidArgumentError(
178
- 'The property %v of the model %v has the non-array type, ' +
187
+ 'The property %v of the model %v has a non-array type, ' +
179
188
  'so it should not have the option "itemType" to be provided.',
180
189
  propName,
181
190
  modelName,
182
191
  propDef.type,
183
192
  );
184
- if (
185
- propDef.model &&
186
- propDef.type !== Type.OBJECT &&
187
- propDef.itemType !== Type.OBJECT
188
- ) {
189
- if (propDef.type !== Type.ARRAY) {
193
+ if (propDef.itemModel && propDef.type !== Type.ARRAY)
194
+ throw new InvalidArgumentError(
195
+ 'The option "itemModel" is not supported for %s property type, ' +
196
+ 'so the property %v of the model %v should not have ' +
197
+ 'the option "itemModel" to be provided.',
198
+ capitalize(propDef.type),
199
+ propName,
200
+ modelName,
201
+ );
202
+ if (propDef.itemModel && propDef.itemType !== Type.OBJECT) {
203
+ if (propDef.itemType) {
190
204
  throw new InvalidArgumentError(
191
- 'The option "model" is not supported for %s property type, ' +
192
- 'so the property %v of the model %v should not have ' +
193
- 'the option "model" to be provided.',
194
- capitalize(propDef.type),
205
+ 'The provided option "itemModel" requires the option "itemType" ' +
206
+ 'to be explicitly set to Object, but the property %v of ' +
207
+ 'the model %v has specified item type as %s.',
195
208
  propName,
196
209
  modelName,
210
+ capitalize(propDef.itemType),
197
211
  );
198
212
  } else {
199
213
  throw new InvalidArgumentError(
200
- 'The option "model" is not supported for Array property type of %s, ' +
201
- 'so the property %v of the model %v should not have ' +
202
- 'the option "model" to be provided.',
203
- capitalize(propDef.itemType),
214
+ 'The provided option "itemModel" requires the option "itemType" ' +
215
+ 'to be explicitly set to Object, but the property %v of ' +
216
+ 'the model %v does not have specified item type.',
204
217
  propName,
205
218
  modelName,
206
219
  );
207
220
  }
208
221
  }
222
+ if (propDef.model && propDef.type !== Type.OBJECT)
223
+ throw new InvalidArgumentError(
224
+ 'The option "model" is not supported for %s property type, ' +
225
+ 'so the property %v of the model %v should not have ' +
226
+ 'the option "model" to be provided.',
227
+ capitalize(propDef.type),
228
+ propName,
229
+ modelName,
230
+ );
209
231
  if (propDef.validate != null) {
210
232
  const propertyValidatorRegistry = this.getService(
211
233
  PropertyValidatorRegistry,
@@ -133,7 +133,7 @@ describe('PropertiesDefinitionValidator', function () {
133
133
  validate(DataType.STRING)();
134
134
  });
135
135
 
136
- it('expects provided the option "itemType" to be a DataType', function () {
136
+ it('expects the provided option "itemType" to be a DataType', function () {
137
137
  const validate = v => {
138
138
  const foo = {type: DataType.ARRAY, itemType: v};
139
139
  return () => S.validate('model', {foo});
@@ -153,7 +153,29 @@ describe('PropertiesDefinitionValidator', function () {
153
153
  validate(DataType.STRING)();
154
154
  });
155
155
 
156
- it('expects provided the option "model" to be a string', function () {
156
+ it('expects the provided option "itemModel" to be a string', function () {
157
+ const validate = v => {
158
+ const foo = {
159
+ type: DataType.ARRAY,
160
+ itemType: DataType.OBJECT,
161
+ itemModel: v,
162
+ };
163
+ return () => S.validate('model', {foo});
164
+ };
165
+ const error = v =>
166
+ format(
167
+ 'The provided option "itemModel" of the property "foo" ' +
168
+ 'in the model "model" should be a String, but %s given.',
169
+ v,
170
+ );
171
+ expect(validate(10)).to.throw(error('10'));
172
+ expect(validate(true)).to.throw(error('true'));
173
+ expect(validate([])).to.throw(error('Array'));
174
+ expect(validate({})).to.throw(error('Object'));
175
+ validate('model')();
176
+ });
177
+
178
+ it('expects the provided option "model" to be a string', function () {
157
179
  const validate = v => {
158
180
  const foo = {
159
181
  type: DataType.OBJECT,
@@ -174,7 +196,7 @@ describe('PropertiesDefinitionValidator', function () {
174
196
  validate('model')();
175
197
  });
176
198
 
177
- it('expects provided the option "primaryKey" to be a boolean', function () {
199
+ it('expects the provided option "primaryKey" to be a boolean', function () {
178
200
  const validate = v => {
179
201
  const foo = {
180
202
  type: DataType.STRING,
@@ -195,7 +217,7 @@ describe('PropertiesDefinitionValidator', function () {
195
217
  validate(false)();
196
218
  });
197
219
 
198
- it('expects provided the option "columnName" to be a string', function () {
220
+ it('expects the provided option "columnName" to be a string', function () {
199
221
  const validate = v => {
200
222
  const foo = {
201
223
  type: DataType.STRING,
@@ -216,7 +238,7 @@ describe('PropertiesDefinitionValidator', function () {
216
238
  validate('columnName')();
217
239
  });
218
240
 
219
- it('expects provided the option "columnType" to be a string', function () {
241
+ it('expects the provided option "columnType" to be a string', function () {
220
242
  const validate = v => {
221
243
  const foo = {
222
244
  type: DataType.STRING,
@@ -237,7 +259,7 @@ describe('PropertiesDefinitionValidator', function () {
237
259
  validate('columnType')();
238
260
  });
239
261
 
240
- it('expects provided the option "required" to be a boolean', function () {
262
+ it('expects the provided option "required" to be a boolean', function () {
241
263
  const validate = v => {
242
264
  const foo = {
243
265
  type: DataType.STRING,
@@ -332,7 +354,7 @@ describe('PropertiesDefinitionValidator', function () {
332
354
  S.validate('model', {foo});
333
355
  };
334
356
  const error =
335
- 'The property "foo" of the model "model" has the non-array type, ' +
357
+ 'The property "foo" of the model "model" has a non-array type, ' +
336
358
  'so it should not have the option "itemType" to be provided.';
337
359
  expect(validate(DataType.ANY)).to.throw(error);
338
360
  expect(validate(DataType.STRING)).to.throw(error);
@@ -364,27 +386,34 @@ describe('PropertiesDefinitionValidator', function () {
364
386
  validate(DataType.OBJECT)();
365
387
  });
366
388
 
367
- it('the option "model" requires the "object" item type', function () {
389
+ it('the option "itemModel" requires the "object" item type', function () {
368
390
  const validate = v => () => {
369
391
  const foo = {
370
392
  type: DataType.ARRAY,
371
393
  itemType: v,
372
- model: 'model',
394
+ itemModel: 'model',
373
395
  };
374
396
  S.validate('model', {foo});
375
397
  };
376
- const error = v =>
398
+ const errorForNonEmpty = v =>
377
399
  format(
378
- 'The option "model" is not supported for Array property type of %s, ' +
379
- 'so the property "foo" of the model "model" should not have ' +
380
- 'the option "model" to be provided.',
400
+ 'The provided option "itemModel" requires the option "itemType" ' +
401
+ 'to be explicitly set to Object, but the property "foo" of ' +
402
+ 'the model "model" has specified item type as %s.',
381
403
  v,
382
404
  );
383
- expect(validate(DataType.ANY)).to.throw(error('Any'));
384
- expect(validate(DataType.STRING)).to.throw(error('String'));
385
- expect(validate(DataType.NUMBER)).to.throw(error('Number'));
386
- expect(validate(DataType.BOOLEAN)).to.throw(error('Boolean'));
387
- expect(validate(DataType.ARRAY)).to.throw(error('Array'));
405
+ const errorForEmpty = format(
406
+ 'The provided option "itemModel" requires the option "itemType" ' +
407
+ 'to be explicitly set to Object, but the property "foo" of ' +
408
+ 'the model "model" does not have specified item type.',
409
+ );
410
+ expect(validate(DataType.ANY)).to.throw(errorForNonEmpty('Any'));
411
+ expect(validate(DataType.STRING)).to.throw(errorForNonEmpty('String'));
412
+ expect(validate(DataType.NUMBER)).to.throw(errorForNonEmpty('Number'));
413
+ expect(validate(DataType.BOOLEAN)).to.throw(errorForNonEmpty('Boolean'));
414
+ expect(validate(DataType.ARRAY)).to.throw(errorForNonEmpty('Array'));
415
+ expect(validate(undefined)).to.throw(errorForEmpty);
416
+ expect(validate(null)).to.throw(errorForEmpty);
388
417
  validate(DataType.OBJECT)();
389
418
  });
390
419
 
@@ -491,7 +520,7 @@ describe('PropertiesDefinitionValidator', function () {
491
520
  validate({myTransformer: true})();
492
521
  });
493
522
 
494
- it('expects provided the option "unique" to be a Boolean or the PropertyUniqueness', function () {
523
+ it('expects the provided option "unique" to be a Boolean or the PropertyUniqueness', function () {
495
524
  const validate = v => {
496
525
  const foo = {
497
526
  type: DataType.STRING,
@@ -9,6 +9,7 @@ import {PropertyTransformOptions} from './property-transformer/index.js';
9
9
  export declare type FullPropertyDefinition = {
10
10
  type: DataType;
11
11
  itemType?: DataType;
12
+ itemModel?: string;
12
13
  model?: string;
13
14
  primaryKey?: boolean;
14
15
  columnName?: string;