@e22m4u/js-repository 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +569 -80
- package/dist/cjs/index.cjs +319 -270
- package/package.json +1 -1
- package/src/adapter/adapter-loader.js +1 -1
- package/src/adapter/adapter-loader.spec.js +1 -1
- package/src/adapter/decorator/data-sanitizing-decorator.js +1 -1
- package/src/adapter/decorator/data-transformation-decorator.js +1 -1
- package/src/adapter/decorator/data-validation-decorator.js +1 -1
- package/src/adapter/decorator/default-values-decorator.js +1 -1
- package/src/adapter/decorator/fields-filtering-decorator.js +1 -1
- package/src/adapter/decorator/inclusion-decorator.js +1 -1
- package/src/adapter/decorator/property-uniqueness-decorator.js +1 -1
- package/src/definition/datasource/datasource-definition-validator.js +3 -3
- package/src/definition/datasource/datasource-definition-validator.spec.js +3 -3
- package/src/definition/model/model-data-sanitizer.js +2 -2
- package/src/definition/model/model-data-transformer.js +71 -13
- package/src/definition/model/model-data-transformer.spec.js +2073 -407
- package/src/definition/model/model-data-validator.js +125 -37
- package/src/definition/model/model-data-validator.spec.js +2138 -440
- package/src/definition/model/model-definition-utils.js +5 -5
- package/src/definition/model/model-definition-utils.spec.js +7 -7
- package/src/definition/model/model-definition-validator.js +7 -7
- package/src/definition/model/model-definition-validator.spec.js +10 -7
- package/src/definition/model/properties/properties-definition-validator.js +129 -54
- package/src/definition/model/properties/properties-definition-validator.spec.js +85 -25
- package/src/definition/model/properties/property-definition.d.ts +3 -3
- package/src/definition/model/properties/property-transformer/builtin/index.d.ts +0 -1
- package/src/definition/model/properties/property-transformer/builtin/index.js +0 -1
- package/src/definition/model/properties/property-transformer/builtin/to-lower-case-transformer.d.ts +1 -1
- package/src/definition/model/properties/property-transformer/builtin/to-lower-case-transformer.js +1 -1
- package/src/definition/model/properties/property-transformer/builtin/to-lower-case-transformer.spec.js +1 -1
- package/src/definition/model/properties/property-transformer/builtin/to-upper-case-transformer.d.ts +1 -1
- package/src/definition/model/properties/property-transformer/builtin/to-upper-case-transformer.js +1 -1
- package/src/definition/model/properties/property-transformer/builtin/to-upper-case-transformer.spec.js +1 -1
- package/src/definition/model/properties/property-transformer/builtin/trim-transformer.js +1 -1
- package/src/definition/model/properties/property-transformer/builtin/trim-transformer.spec.js +1 -1
- package/src/definition/model/properties/property-transformer/property-transformer-registry.js +2 -4
- package/src/definition/model/properties/property-transformer/property-transformer-registry.spec.js +2 -4
- package/src/definition/model/properties/property-transformer/property-transformer.d.ts +2 -2
- package/src/definition/model/properties/property-uniqueness-validator.js +4 -4
- package/src/definition/model/properties/property-uniqueness-validator.spec.js +4 -4
- package/src/definition/model/properties/property-validator/builtin/max-length-validator.d.ts +1 -1
- package/src/definition/model/properties/property-validator/builtin/max-length-validator.js +2 -2
- package/src/definition/model/properties/property-validator/builtin/max-length-validator.spec.js +2 -2
- package/src/definition/model/properties/property-validator/builtin/min-length-validator.d.ts +1 -1
- package/src/definition/model/properties/property-validator/builtin/min-length-validator.js +2 -2
- package/src/definition/model/properties/property-validator/builtin/min-length-validator.spec.js +2 -2
- package/src/definition/model/properties/property-validator/builtin/regexp-validator.d.ts +1 -1
- package/src/definition/model/properties/property-validator/builtin/regexp-validator.js +2 -2
- package/src/definition/model/properties/property-validator/builtin/regexp-validator.spec.js +2 -2
- package/src/definition/model/properties/property-validator/property-validator-registry.js +2 -2
- package/src/definition/model/properties/property-validator/property-validator-registry.spec.js +2 -2
- package/src/definition/model/properties/property-validator/property-validator.d.ts +2 -2
- package/src/definition/model/relations/relation-definition.d.ts +21 -21
- package/src/definition/model/relations/relations-definition-validator.js +23 -23
- package/src/definition/model/relations/relations-definition-validator.spec.js +24 -24
- package/src/errors/invalid-operator-value-error.js +1 -1
- package/src/errors/invalid-operator-value-error.spec.js +1 -1
- package/src/filter/fields-clause-tool.js +5 -5
- package/src/filter/fields-clause-tool.spec.js +16 -16
- package/src/filter/include-clause-tool.js +6 -6
- package/src/filter/include-clause-tool.spec.js +2 -2
- package/src/filter/operator-clause-tool.js +13 -13
- package/src/filter/operator-clause-tool.spec.js +13 -13
- package/src/filter/order-clause-tool.js +3 -3
- package/src/filter/order-clause-tool.spec.js +4 -4
- package/src/filter/slice-clause-tool.js +5 -5
- package/src/filter/slice-clause-tool.spec.js +5 -5
- package/src/filter/where-clause-tool.js +4 -4
- package/src/filter/where-clause-tool.spec.js +3 -3
- package/src/relations/belongs-to-resolver.js +14 -14
- package/src/relations/belongs-to-resolver.spec.js +14 -14
- package/src/relations/has-many-resolver.js +22 -22
- package/src/relations/has-many-resolver.spec.js +23 -23
- package/src/relations/has-one-resolver.js +22 -22
- package/src/relations/has-one-resolver.spec.js +23 -23
- package/src/relations/references-many-resolver.js +7 -7
- package/src/relations/references-many-resolver.spec.js +7 -7
- package/src/repository/repository-registry.js +1 -1
- package/src/utils/exclude-object-keys.js +1 -1
- package/src/utils/exclude-object-keys.spec.js +1 -1
- package/src/utils/model-name-to-model-key.js +1 -1
- package/src/utils/model-name-to-model-key.spec.js +7 -7
- package/src/utils/select-object-keys.js +3 -3
- package/src/utils/select-object-keys.spec.js +3 -3
- package/src/definition/model/properties/property-transformer/builtin/to-title-case-transformer.d.ts +0 -6
- package/src/definition/model/properties/property-transformer/builtin/to-title-case-transformer.js +0 -22
- package/src/definition/model/properties/property-transformer/builtin/to-title-case-transformer.spec.js +0 -41
package/README.md
CHANGED
|
@@ -6,12 +6,18 @@
|
|
|
6
6
|
- [Импорт](#импорт)
|
|
7
7
|
- [Описание](#описание)
|
|
8
8
|
- [Пример](#пример)
|
|
9
|
-
- [Схема
|
|
9
|
+
- [Схема](#схема)
|
|
10
10
|
- [Источник данных](#источник-данных)
|
|
11
11
|
- [Модель](#модель)
|
|
12
12
|
- [Свойства](#свойства)
|
|
13
13
|
- [Валидаторы](#валидаторы)
|
|
14
|
+
- [Глобальные валидаторы](#глобальные-валидаторы)
|
|
15
|
+
- [Регистрация глобальных валидаторов](#регистрация-глобальных-валидаторов)
|
|
16
|
+
- [Локальные валидаторы](#локальные-валидаторы)
|
|
14
17
|
- [Трансформеры](#трансформеры)
|
|
18
|
+
- [Глобальные трансформеры](#глобальные-трансформеры)
|
|
19
|
+
- [Регистрация глобальных трансформеров](#регистрация-глобальных-трансформеров)
|
|
20
|
+
- [Локальные трансформеры](#локальные-трансформеры)
|
|
15
21
|
- [Пустые значения](#пустые-значения)
|
|
16
22
|
- [Репозиторий](#репозиторий)
|
|
17
23
|
- [Фильтрация](#фильтрация)
|
|
@@ -27,7 +33,7 @@
|
|
|
27
33
|
npm install @e22m4u/js-repository
|
|
28
34
|
```
|
|
29
35
|
|
|
30
|
-
Опционально
|
|
36
|
+
Опционально устанавливается нужный адаптер.
|
|
31
37
|
|
|
32
38
|
| | описание |
|
|
33
39
|
|-----------|--------------------------------------------------------------------------------------------------------------------------------|
|
|
@@ -129,10 +135,10 @@ dbs.defineModel({
|
|
|
129
135
|
},
|
|
130
136
|
})
|
|
131
137
|
|
|
132
|
-
// получение репозитория модели
|
|
138
|
+
// получение репозитория модели
|
|
133
139
|
const countryRep = dbs.getRepository('country');
|
|
134
140
|
|
|
135
|
-
// добавление нового документа в коллекцию
|
|
141
|
+
// добавление нового документа в коллекцию
|
|
136
142
|
const country = await countryRep.create({
|
|
137
143
|
name: 'Russia',
|
|
138
144
|
population: 143400000,
|
|
@@ -141,16 +147,15 @@ const country = await countryRep.create({
|
|
|
141
147
|
// вывод нового документа
|
|
142
148
|
console.log(country);
|
|
143
149
|
// {
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
150
|
+
// id: 1,
|
|
151
|
+
// name: 'Russia',
|
|
152
|
+
// population: 143400000,
|
|
147
153
|
// }
|
|
148
154
|
```
|
|
149
155
|
|
|
150
|
-
## Схема
|
|
156
|
+
## Схема
|
|
151
157
|
|
|
152
|
-
Экземпляр класса `DatabaseSchema` хранит определения источников и
|
|
153
|
-
данных.
|
|
158
|
+
Экземпляр класса `DatabaseSchema` хранит определения источников данных и моделей.
|
|
154
159
|
|
|
155
160
|
**Методы**
|
|
156
161
|
|
|
@@ -285,7 +290,7 @@ dbs.defineModel({
|
|
|
285
290
|
- `columnType: string` тип колонки (определяется адаптером)
|
|
286
291
|
- `required: boolean` объявить свойство обязательным
|
|
287
292
|
- `default: any` значение по умолчанию
|
|
288
|
-
- `validate: string | array | object` см. [Валидаторы](#Валидаторы)
|
|
293
|
+
- `validate: string | Function | array | object` см. [Валидаторы](#Валидаторы)
|
|
289
294
|
- `unique: boolean | string` проверять значение на уникальность
|
|
290
295
|
|
|
291
296
|
**Параметр `unique`**
|
|
@@ -341,7 +346,7 @@ dbs.defineModel({
|
|
|
341
346
|
},
|
|
342
347
|
code: {
|
|
343
348
|
type: DataType.NUMBER, // тип свойства "number" (обязательно)
|
|
344
|
-
unique: PropertyUniqueness.
|
|
349
|
+
unique: PropertyUniqueness.STRICT, // проверять уникальность
|
|
345
350
|
},
|
|
346
351
|
},
|
|
347
352
|
});
|
|
@@ -369,19 +374,64 @@ dbs.defineModel({
|
|
|
369
374
|
|
|
370
375
|
## Валидаторы
|
|
371
376
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
377
|
+
Валидаторы используются для проверки значения свойства перед записью в базу.
|
|
378
|
+
Проверка значения валидатором выполняется сразу после проверки типа, указанного
|
|
379
|
+
в определении свойства модели. [Пустые значения](#пустые-значения) пропускают
|
|
380
|
+
проверку валидаторами, так как не имеют полезной нагрузки.
|
|
381
|
+
|
|
382
|
+
### Глобальные валидаторы
|
|
383
|
+
|
|
384
|
+
Модуль поставляется с набором глобальных валидаторов:
|
|
385
|
+
|
|
386
|
+
- `regexp` проверка по регулярному выражению,
|
|
387
|
+
*параметр: `string | RegExp` - регулярное выражение;*
|
|
388
|
+
|
|
389
|
+
- `maxLength` максимальная длина строки или массива,
|
|
390
|
+
*параметр: `number` - максимальная длина;*
|
|
391
|
+
|
|
392
|
+
- `minLength` минимальная длина строки или массива,
|
|
393
|
+
*параметр: `number` - минимальная длина;*
|
|
394
|
+
|
|
395
|
+
Валидаторы указанные ниже находятся в разработке:
|
|
396
|
+
|
|
397
|
+
- `isLowerCase` проверка регистра (только прописные буквы);
|
|
398
|
+
- `isUpperCase` проверка регистра (только строчные буквы);
|
|
399
|
+
- `isEmail` проверка формата электронного адреса;
|
|
400
|
+
|
|
401
|
+
**Примеры**
|
|
402
|
+
|
|
403
|
+
Использование глобального валидатора.
|
|
404
|
+
|
|
405
|
+
```js
|
|
406
|
+
dbs.defineModel({
|
|
407
|
+
name: 'user',
|
|
408
|
+
properties: {
|
|
409
|
+
email: {
|
|
410
|
+
type: DataType.STRING,
|
|
411
|
+
validate: 'isEmail',
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
```
|
|
376
416
|
|
|
377
|
-
|
|
378
|
-
- `maxLength: number` максимальная длинна строки или массива
|
|
379
|
-
- `regexp: string | RegExp` проверка по регулярному выражению
|
|
417
|
+
Использование глобальных валидаторов в виде массива.
|
|
380
418
|
|
|
381
|
-
|
|
419
|
+
```js
|
|
420
|
+
dbs.defineModel({
|
|
421
|
+
name: 'user',
|
|
422
|
+
properties: {
|
|
423
|
+
email: {
|
|
424
|
+
type: DataType.STRING,
|
|
425
|
+
validate: [
|
|
426
|
+
'isEmail',
|
|
427
|
+
'isLowerCase',
|
|
428
|
+
],
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
```
|
|
382
433
|
|
|
383
|
-
|
|
384
|
-
`validate`, который принимает объект с их названиями и настройками.
|
|
434
|
+
Использование глобальных валидаторов с передачей аргументов.
|
|
385
435
|
|
|
386
436
|
```js
|
|
387
437
|
dbs.defineModel({
|
|
@@ -389,46 +439,209 @@ dbs.defineModel({
|
|
|
389
439
|
properties: {
|
|
390
440
|
name: {
|
|
391
441
|
type: DataType.STRING,
|
|
392
|
-
validate: {
|
|
393
|
-
minLength: 2,
|
|
394
|
-
maxLength: 24,
|
|
442
|
+
validate: {
|
|
443
|
+
minLength: 2,
|
|
444
|
+
maxLength: 24,
|
|
445
|
+
regexp: /^[a-zA-Z-']+$/,
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Глобальные валидаторы без параметров могут принимать любые аргументы.
|
|
453
|
+
|
|
454
|
+
```js
|
|
455
|
+
dbs.defineModel({
|
|
456
|
+
name: 'user',
|
|
457
|
+
properties: {
|
|
458
|
+
email: {
|
|
459
|
+
type: DataType.STRING,
|
|
460
|
+
validate: {
|
|
461
|
+
maxLength: 100,
|
|
462
|
+
// так как валидатор "isEmail" не имеет параметров,
|
|
463
|
+
// его определение допускает передачу любого значения
|
|
464
|
+
// в качестве аргумента
|
|
465
|
+
isEmail: true,
|
|
395
466
|
},
|
|
396
467
|
},
|
|
397
468
|
},
|
|
398
469
|
});
|
|
399
470
|
```
|
|
400
471
|
|
|
401
|
-
###
|
|
472
|
+
### Регистрация глобальных валидаторов
|
|
402
473
|
|
|
403
474
|
Валидатором является функция, в которую передается значение соответствующего
|
|
404
475
|
поля перед записью в базу. Если во время проверки функция возвращает `false`,
|
|
405
476
|
то выбрасывается стандартная ошибка. Подмена стандартной ошибки возможна
|
|
406
|
-
с помощью выброса пользовательской ошибки непосредственно внутри
|
|
477
|
+
с помощью выброса пользовательской ошибки непосредственно внутри проверяющей
|
|
478
|
+
функции.
|
|
479
|
+
|
|
480
|
+
Регистрация глобального валидатора выполняется методом `addValidator` сервиса
|
|
481
|
+
`PropertyValidatorRegistry`, который принимает название валидатора и функцию
|
|
482
|
+
для проверки значения.
|
|
483
|
+
|
|
484
|
+
**Примеры**
|
|
485
|
+
|
|
486
|
+
Регистрация глобального валидатора для проверки формата UUID.
|
|
407
487
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
488
|
+
```js
|
|
489
|
+
import {createError} from 'http-errors';
|
|
490
|
+
import {format} from '@e22m4u/js-format';
|
|
491
|
+
import {Errorf} from '@e22m4u/js-format';
|
|
492
|
+
import {PropertyValidatorRegistry} from '@e22m4u/js-repository';
|
|
493
|
+
|
|
494
|
+
// получение экземпляра сервиса
|
|
495
|
+
const pvr = dbs.get(PropertyValidatorRegistry);
|
|
496
|
+
|
|
497
|
+
// регулярные выражения для разных версий UUID
|
|
498
|
+
const uuidRegex = {
|
|
499
|
+
any: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
500
|
+
v4: /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// регистрация глобального валидатора "isUuid",
|
|
504
|
+
// принимающего объект настроек со свойством "version"
|
|
505
|
+
pvr.addValidator('isUuid', (value, options, context) => {
|
|
506
|
+
// value - проверяемое значение;
|
|
507
|
+
// options - параметры валидатора;
|
|
508
|
+
// context - информация о проверяемом свойстве;
|
|
509
|
+
console.log(options);
|
|
510
|
+
// {
|
|
511
|
+
// version: 'v4'
|
|
512
|
+
// }
|
|
513
|
+
console.log(context);
|
|
514
|
+
// {
|
|
515
|
+
// validatorName: 'isUuid',
|
|
516
|
+
// modelName: 'device',
|
|
517
|
+
// propName: 'deviceId'
|
|
518
|
+
// }
|
|
519
|
+
|
|
520
|
+
// пустые значения не передаются в валидаторы
|
|
521
|
+
// (условие ниже никогда не сработает)
|
|
522
|
+
if (typeof value !== 'string') return false;
|
|
523
|
+
// поиск регулярного выражения для указанной
|
|
524
|
+
// версии UUID (из параметров валидатора)
|
|
525
|
+
const version = options?.version || 'any';
|
|
526
|
+
const regex = uuidRegex[version];
|
|
527
|
+
// если регулярное выражение не найдено,
|
|
528
|
+
// то выбрасывается внутренняя ошибка
|
|
529
|
+
if (!regex)
|
|
530
|
+
throw new Errorf(
|
|
531
|
+
'Invalid UUID version %v specified for validator.',
|
|
532
|
+
version,
|
|
533
|
+
);
|
|
534
|
+
// при неудачной проверке выбрасывается
|
|
535
|
+
// ошибка 400 BadRequest
|
|
536
|
+
if (!regex.test(value)) {
|
|
537
|
+
const versionString = version !== 'any' ? ` (version ${version})` : '';
|
|
538
|
+
throw createError(400, format(
|
|
539
|
+
'The property %v of the model %v must be a valid UUID%s.',
|
|
540
|
+
context.propName,
|
|
541
|
+
context.modelName,
|
|
542
|
+
versionString,
|
|
543
|
+
));
|
|
544
|
+
}
|
|
545
|
+
// при успешной проверке возвращается true,
|
|
546
|
+
// в противном случае выбрасывается стандартная
|
|
547
|
+
// ошибка проверки
|
|
548
|
+
return true;
|
|
549
|
+
});
|
|
550
|
+
```
|
|
411
551
|
|
|
412
|
-
|
|
552
|
+
Использование глобального валидатора в определении свойства.
|
|
413
553
|
|
|
414
554
|
```js
|
|
415
|
-
//
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
555
|
+
// определение модели "device"
|
|
556
|
+
dbs.defineModel({
|
|
557
|
+
name: 'device',
|
|
558
|
+
properties: {
|
|
559
|
+
deviceId: {
|
|
560
|
+
type: DataType.STRING,
|
|
561
|
+
required: true,
|
|
562
|
+
validate: {
|
|
563
|
+
// значение {version: 'v4'} будет передаваться
|
|
564
|
+
// вторым аргументом в функцию-валидатор
|
|
565
|
+
isUuid: {version: 'v4'},
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### Локальные валидаторы
|
|
573
|
+
|
|
574
|
+
Функция-валидатор может быть передана непосредственно в определении свойства
|
|
575
|
+
без предварительной регистрации. Для этого достаточно передать функцию
|
|
576
|
+
в параметр `validate` в качестве значения или элемента массива наряду
|
|
577
|
+
с другими валидаторами.
|
|
578
|
+
|
|
579
|
+
**Примеры**
|
|
580
|
+
|
|
581
|
+
Использование локального валидатора для проверки сложности пароля.
|
|
582
|
+
|
|
583
|
+
```js
|
|
584
|
+
// валидатор `passwordStrength` проверяет сложность пароля
|
|
585
|
+
function passwordStrength(value, options, context) {
|
|
586
|
+
// value - проверяемое значение;
|
|
587
|
+
// options - не используется;
|
|
588
|
+
// context - информация о свойстве;
|
|
589
|
+
console.log(context);
|
|
590
|
+
// {
|
|
591
|
+
// validatorName: 'passwordStrength',
|
|
592
|
+
// modelName: 'user',
|
|
593
|
+
// propName: 'password'
|
|
594
|
+
// }
|
|
595
|
+
const errors = [];
|
|
596
|
+
if (value.length < 8)
|
|
597
|
+
errors.push('must be at least 8 characters long');
|
|
598
|
+
if (!/\d/.test(value))
|
|
599
|
+
errors.push('must contain at least one number');
|
|
600
|
+
if (!/[a-zA-Z]/.test(value))
|
|
601
|
+
errors.push('must contain at least one letter');
|
|
602
|
+
// если одно из условий сработало,
|
|
603
|
+
// то выбрасывается ошибка
|
|
604
|
+
if (errors.length > 0)
|
|
605
|
+
throw createError(400, format(
|
|
606
|
+
'Value of the property %v of the model %v %s.',
|
|
607
|
+
context.propName,
|
|
608
|
+
context.modelName,
|
|
609
|
+
errors.join(', '),
|
|
610
|
+
));
|
|
611
|
+
// при успешной проверке возвращается true,
|
|
612
|
+
// в противном случае выбрасывается стандартная
|
|
613
|
+
// ошибка проверки
|
|
614
|
+
return true;
|
|
419
615
|
}
|
|
420
616
|
|
|
421
|
-
//
|
|
422
|
-
dbs.
|
|
617
|
+
// определение модели "user"
|
|
618
|
+
dbs.defineModel({
|
|
619
|
+
name: 'user',
|
|
620
|
+
properties: {
|
|
621
|
+
password: {
|
|
622
|
+
type: DataType.STRING,
|
|
623
|
+
required: true,
|
|
624
|
+
validate: passwordStrength, // <=
|
|
625
|
+
// или
|
|
626
|
+
// validate: [passwordStrength, ...]
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
Использование анонимной функции-валидатора для проверки слага.
|
|
423
633
|
|
|
424
|
-
|
|
425
|
-
//
|
|
634
|
+
```js
|
|
635
|
+
// определение модели "article"
|
|
426
636
|
dbs.defineModel({
|
|
427
|
-
name: '
|
|
637
|
+
name: 'article',
|
|
428
638
|
properties: {
|
|
429
|
-
|
|
639
|
+
slug: {
|
|
430
640
|
type: DataType.STRING,
|
|
431
|
-
validate:
|
|
641
|
+
validate: (value) => {
|
|
642
|
+
const re = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
643
|
+
return re.test(value);
|
|
644
|
+
},
|
|
432
645
|
},
|
|
433
646
|
},
|
|
434
647
|
});
|
|
@@ -436,39 +649,245 @@ dbs.defineModel({
|
|
|
436
649
|
|
|
437
650
|
## Трансформеры
|
|
438
651
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
652
|
+
Трансформеры используются для модификации значения свойства перед проверкой
|
|
653
|
+
типа и передачей данных в базу. Трансформеры позволяют автоматически очищать
|
|
654
|
+
или приводить данные к нужному формату. [Пустые значения](#Пустые-значения)
|
|
655
|
+
не передаются в трансформеры, так как не имеют полезной нагрузки.
|
|
656
|
+
|
|
657
|
+
### Глобальные трансформеры
|
|
658
|
+
|
|
659
|
+
Модуль поставляется с набором глобальных трансформеров:
|
|
660
|
+
|
|
661
|
+
- `trim` удаление пробельных символов с начала и конца строки;
|
|
662
|
+
- `toUpperCase` перевод строки в верхний регистр;
|
|
663
|
+
- `toLowerCase` перевод строки в нижний регистр;
|
|
664
|
+
|
|
665
|
+
Трансформеры указанные ниже находятся в разработке:
|
|
443
666
|
|
|
444
|
-
- `
|
|
445
|
-
|
|
446
|
-
- `toLowerCase` перевод строки в нижний регистр
|
|
447
|
-
- `toTitleCase` перевод строки в регистр заголовка
|
|
667
|
+
- `cut` усечение строки или массива до указанной длины,
|
|
668
|
+
*параметр: `number` - максимальная длина;*
|
|
448
669
|
|
|
449
|
-
|
|
670
|
+
- `truncate` усечение строки с добавлением троеточия,
|
|
671
|
+
*параметр: `number` - максимальная длина;*
|
|
450
672
|
|
|
451
|
-
|
|
452
|
-
`
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
673
|
+
- `capitalize` перевод первой буквы каждого слова в верхний регистр,
|
|
674
|
+
*параметр: `{firstWordOnly?: boolean}`;*
|
|
675
|
+
|
|
676
|
+
**Примеры**
|
|
677
|
+
|
|
678
|
+
Использование глобального трансформера.
|
|
456
679
|
|
|
457
680
|
```js
|
|
458
681
|
dbs.defineModel({
|
|
459
682
|
name: 'user',
|
|
460
683
|
properties: {
|
|
461
|
-
|
|
684
|
+
username: {
|
|
462
685
|
type: DataType.STRING,
|
|
463
|
-
transform:
|
|
464
|
-
|
|
465
|
-
|
|
686
|
+
transform: 'toLowerCase',
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
});
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
Использование глобальных трансформеров в виде массива.
|
|
693
|
+
|
|
694
|
+
```js
|
|
695
|
+
dbs.defineModel({
|
|
696
|
+
name: 'user',
|
|
697
|
+
properties: {
|
|
698
|
+
firstName: {
|
|
699
|
+
type: DataType.STRING,
|
|
700
|
+
transform: [
|
|
701
|
+
'trim',
|
|
702
|
+
'capitalize',
|
|
466
703
|
],
|
|
467
704
|
},
|
|
468
705
|
},
|
|
469
706
|
});
|
|
470
707
|
```
|
|
471
708
|
|
|
709
|
+
Использование глобальных трансформеров с передачей аргументов.
|
|
710
|
+
|
|
711
|
+
```js
|
|
712
|
+
dbs.defineModel({
|
|
713
|
+
name: 'article',
|
|
714
|
+
properties: {
|
|
715
|
+
annotation: {
|
|
716
|
+
type: DataType.STRING,
|
|
717
|
+
transform: {
|
|
718
|
+
truncate: 200,
|
|
719
|
+
capitalize: {firstWordOnly: true},
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
Глобальные трансформеры без параметров могут принимать любые аргументы.
|
|
727
|
+
|
|
728
|
+
```js
|
|
729
|
+
dbs.defineModel({
|
|
730
|
+
name: 'user',
|
|
731
|
+
properties: {
|
|
732
|
+
firstName: {
|
|
733
|
+
type: DataType.STRING,
|
|
734
|
+
transform: {
|
|
735
|
+
cut: 60,
|
|
736
|
+
// так как трансформер "trim" не имеет параметров,
|
|
737
|
+
// его определение допускает передачу любого значения
|
|
738
|
+
// в качестве аргумента
|
|
739
|
+
trim: true,
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### Регистрация глобальных трансформеров
|
|
747
|
+
|
|
748
|
+
Трансформером является функция, которая принимает значение свойства и возвращает
|
|
749
|
+
новое значение. Функция может быть как синхронной, так и асинхронной (возвращать
|
|
750
|
+
`Promise`).
|
|
751
|
+
|
|
752
|
+
Регистрация глобального трансформера выполняется методом `addTransformer`
|
|
753
|
+
сервиса `PropertyTransformerRegistry`, который принимает название трансформера
|
|
754
|
+
и саму функцию.
|
|
755
|
+
|
|
756
|
+
**Примеры**
|
|
757
|
+
|
|
758
|
+
Регистрация глобального трансформера для удаления HTML-тегов.
|
|
759
|
+
|
|
760
|
+
```js
|
|
761
|
+
import {PropertyTransformerRegistry} from '@e22m4u/js-repository';
|
|
762
|
+
|
|
763
|
+
// получение экземпляра сервиса
|
|
764
|
+
const ptr = dbs.get(PropertyTransformerRegistry);
|
|
765
|
+
|
|
766
|
+
// регистрация глобального трансформера "stripTags"
|
|
767
|
+
ptr.addTransformer('stripTags', (value, options, context) => {
|
|
768
|
+
// value - трансформируемое значение;
|
|
769
|
+
// options - настройки трансформера (если переданы);
|
|
770
|
+
// context - информация о свойстве;
|
|
771
|
+
console.log(context);
|
|
772
|
+
// {
|
|
773
|
+
// transformerName: 'stripTags',
|
|
774
|
+
// modelName: 'comment',
|
|
775
|
+
// propName: 'text'
|
|
776
|
+
// }
|
|
777
|
+
|
|
778
|
+
if (typeof value !== 'string')
|
|
779
|
+
return value; // возвращаем как есть, если не строка
|
|
780
|
+
|
|
781
|
+
return value.replace(/<[^>]*>?/gm, '');
|
|
782
|
+
});
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
Использование глобального трансформера в определении модели.
|
|
786
|
+
|
|
787
|
+
```js
|
|
788
|
+
dbs.defineModel({
|
|
789
|
+
name: 'comment',
|
|
790
|
+
properties: {
|
|
791
|
+
text: {
|
|
792
|
+
type: DataType.STRING,
|
|
793
|
+
transform: 'stripTags',
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
});
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### Локальные трансформеры
|
|
800
|
+
|
|
801
|
+
Функция-трансформер может быть передана непосредственно в определении свойства без предварительной регистрации. Для этого достаточно передать функцию в параметр `transform` в качестве значения или элемента массива.
|
|
802
|
+
|
|
803
|
+
**Примеры**
|
|
804
|
+
|
|
805
|
+
Использование локального трансформера для нормализации имен.
|
|
806
|
+
|
|
807
|
+
```js
|
|
808
|
+
// функция для нормализации имени
|
|
809
|
+
function normalizeName(value, options, context) {
|
|
810
|
+
// value - трансформируемое значение
|
|
811
|
+
// options - не используется
|
|
812
|
+
// context - информация о свойстве
|
|
813
|
+
if (!value || typeof value !== 'string') return value;
|
|
814
|
+
return value
|
|
815
|
+
.trim() // удаление пробелов в начале и конце
|
|
816
|
+
.toLowerCase() // перевод к нижнему регистру
|
|
817
|
+
.split(' ') // разделение на слова
|
|
818
|
+
// перевод к верхнему регистру первой буквы каждого слова
|
|
819
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
820
|
+
.join(' '); // сборка массива в строку
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// определение модели "user"
|
|
824
|
+
dbs.defineModel({
|
|
825
|
+
name: 'user',
|
|
826
|
+
properties: {
|
|
827
|
+
firstName: {
|
|
828
|
+
type: DataType.STRING,
|
|
829
|
+
transform: normalizeName, // <=
|
|
830
|
+
},
|
|
831
|
+
lastName: {
|
|
832
|
+
type: DataType.STRING,
|
|
833
|
+
transform: normalizeName, // <=
|
|
834
|
+
},
|
|
835
|
+
},
|
|
836
|
+
});
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
Использование локального асинхронного трансформера для хэширования пароля.
|
|
840
|
+
|
|
841
|
+
```js
|
|
842
|
+
import * as bcrypt from 'bcrypt';
|
|
843
|
+
|
|
844
|
+
// асинхронная функция для хеширования значения
|
|
845
|
+
async function hash(value, options, context) {
|
|
846
|
+
// value - трансформируемое значение
|
|
847
|
+
// options - не используется
|
|
848
|
+
// context - информация о свойстве
|
|
849
|
+
console.log(context);
|
|
850
|
+
// {
|
|
851
|
+
// transformerName: 'hash',
|
|
852
|
+
// modelName: 'user',
|
|
853
|
+
// propName: 'password'
|
|
854
|
+
// }
|
|
855
|
+
const saltRounds = 10;
|
|
856
|
+
return bcrypt.hash(value, saltRounds);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// определение модели "user"
|
|
860
|
+
dbs.defineModel({
|
|
861
|
+
name: 'user',
|
|
862
|
+
properties: {
|
|
863
|
+
password: {
|
|
864
|
+
type: DataType.STRING,
|
|
865
|
+
transform: hash, // <=
|
|
866
|
+
// или
|
|
867
|
+
// transform: [hash, ...]
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
});
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Использование анонимной функции-трансформера для коррекции слага.
|
|
874
|
+
|
|
875
|
+
```js
|
|
876
|
+
dbs.defineModel({
|
|
877
|
+
name: 'article',
|
|
878
|
+
properties: {
|
|
879
|
+
slug: {
|
|
880
|
+
type: DataType.STRING,
|
|
881
|
+
transform: (value) => {
|
|
882
|
+
if (typeof value !== 'string') return value;
|
|
883
|
+
return value.toLowerCase().replace(/\s+/g, '-');
|
|
884
|
+
},
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
});
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
|
|
472
891
|
## Пустые значения
|
|
473
892
|
|
|
474
893
|
Разные типы свойств имеют свои наборы пустых значений. Эти наборы
|
|
@@ -718,31 +1137,101 @@ const res = await rep.find({
|
|
|
718
1137
|
|
|
719
1138
|
## Связи
|
|
720
1139
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1140
|
+
Связи позволяют описывать отношения между моделями, что дает возможность
|
|
1141
|
+
автоматически встраивать связанные данные с помощью опции `include`
|
|
1142
|
+
в методах репозитория. Ниже приводится пример автоматического разрешения
|
|
1143
|
+
связи при использовании метода `findById`.
|
|
724
1144
|
|
|
725
|
-
|
|
1145
|
+
```js
|
|
1146
|
+
// запрос документа коллекции "users",
|
|
1147
|
+
// включая связанные данные (role и city)
|
|
1148
|
+
const user = await userRep.findById(1, {
|
|
1149
|
+
include: ['role', 'city'],
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
console.log(user);
|
|
1153
|
+
// {
|
|
1154
|
+
// id: 1,
|
|
1155
|
+
// name: 'John Doe',
|
|
1156
|
+
// roleId: 3,
|
|
1157
|
+
// role: {
|
|
1158
|
+
// id: 3,
|
|
1159
|
+
// name: 'Manager'
|
|
1160
|
+
// },
|
|
1161
|
+
// cityId: 24,
|
|
1162
|
+
// city: {
|
|
1163
|
+
// id: 24,
|
|
1164
|
+
// name: 'Moscow'
|
|
1165
|
+
// }
|
|
1166
|
+
// }
|
|
1167
|
+
```
|
|
726
1168
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1169
|
+
### Определение связи
|
|
1170
|
+
|
|
1171
|
+
Свойство `relations` в определении модели принимает объект, ключи которого
|
|
1172
|
+
являются названиями связей, а значения их параметрами. В дальнейшем название
|
|
1173
|
+
связи можно будет использовать в опции `include` методах репозитория.
|
|
1174
|
+
|
|
1175
|
+
```js
|
|
1176
|
+
import {DataType} from '@e22m4u/js-repository';
|
|
1177
|
+
import {RelationType} from '@e22m4u/js-repository';
|
|
1178
|
+
|
|
1179
|
+
dbs.defineModel({
|
|
1180
|
+
name: 'user',
|
|
1181
|
+
datasource: 'memory',
|
|
1182
|
+
properties: {
|
|
1183
|
+
name: DataType.STRING,
|
|
1184
|
+
},
|
|
1185
|
+
relations: {
|
|
1186
|
+
// связь role -> параметры
|
|
1187
|
+
role: {
|
|
1188
|
+
type: RelationType.BELONGS_TO,
|
|
1189
|
+
model: 'role',
|
|
1190
|
+
},
|
|
1191
|
+
// связь city -> параметры
|
|
1192
|
+
city: {
|
|
1193
|
+
type: RelationType.BELONGS_TO,
|
|
1194
|
+
model: 'city',
|
|
1195
|
+
},
|
|
1196
|
+
},
|
|
1197
|
+
});
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
**Основные параметры**
|
|
1201
|
+
|
|
1202
|
+
- `type: string` тип связи (обязательно);
|
|
1203
|
+
- `model: string` название целевой модели (обязательно для некоторых типов);
|
|
1204
|
+
- `foreignKey: string` свойство текущей модели для идентификатора цели;
|
|
1205
|
+
|
|
1206
|
+
*i. Для типов Belongs To и References Many значение параметра `foreignKey`
|
|
1207
|
+
можно опустить, так как генерируется автоматически по названию связи.*
|
|
1208
|
+
|
|
1209
|
+
**Полиморфный режим**
|
|
1210
|
+
|
|
1211
|
+
- `polymorphic: boolean|string` объявление полиморфной связи;
|
|
1212
|
+
- `discriminator: string` свойство текущей модели для названия цели;
|
|
732
1213
|
|
|
733
1214
|
*i. Полиморфный режим позволяет динамически определять целевую модель
|
|
734
1215
|
по ее названию, которое хранит документ в свойстве-дискриминаторе.*
|
|
735
1216
|
|
|
736
|
-
|
|
1217
|
+
**Типы связи**
|
|
737
1218
|
|
|
738
|
-
- `belongsTo` - текущая модель
|
|
739
|
-
- `hasOne` - обратная сторона `belongsTo` по принципу "один к одному"
|
|
740
|
-
- `hasMany` - обратная сторона `belongsTo` по принципу "один ко многим"
|
|
741
|
-
- `referencesMany` -
|
|
1219
|
+
- `belongsTo` - текущая модель ссылается на целевую по идентификатору;
|
|
1220
|
+
- `hasOne` - обратная сторона `belongsTo` по принципу "один к одному";
|
|
1221
|
+
- `hasMany` - обратная сторона `belongsTo` по принципу "один ко многим";
|
|
1222
|
+
- `referencesMany` - модель ссылается через массив идентификаторов;
|
|
1223
|
+
|
|
1224
|
+
Параметр `type` в определении связи принимает строку с названием типа. Чтобы исключить опечатку, рекомендуется использовать константы объекта `RelationType`
|
|
1225
|
+
указанные ниже.
|
|
1226
|
+
|
|
1227
|
+
- `RelationType.BELONGS_TO`
|
|
1228
|
+
- `RelationType.HAS_ONE`
|
|
1229
|
+
- `RelationType.HAS_MANY`
|
|
1230
|
+
- `RelationType.REFERENCES_MANY`
|
|
742
1231
|
|
|
743
1232
|
**Примеры**
|
|
744
1233
|
|
|
745
|
-
Объявление связи `belongsTo
|
|
1234
|
+
Объявление связи `belongsTo`.
|
|
746
1235
|
|
|
747
1236
|
```js
|
|
748
1237
|
dbs.defineModel({
|
|
@@ -760,7 +1249,7 @@ dbs.defineModel({
|
|
|
760
1249
|
});
|
|
761
1250
|
```
|
|
762
1251
|
|
|
763
|
-
Объявление связи `hasMany
|
|
1252
|
+
Объявление связи `hasMany`.
|
|
764
1253
|
|
|
765
1254
|
```js
|
|
766
1255
|
dbs.defineModel({
|
|
@@ -775,7 +1264,7 @@ dbs.defineModel({
|
|
|
775
1264
|
});
|
|
776
1265
|
```
|
|
777
1266
|
|
|
778
|
-
Объявление связи `referencesMany
|
|
1267
|
+
Объявление связи `referencesMany`.
|
|
779
1268
|
|
|
780
1269
|
```js
|
|
781
1270
|
dbs.defineModel({
|
|
@@ -793,7 +1282,7 @@ dbs.defineModel({
|
|
|
793
1282
|
});
|
|
794
1283
|
```
|
|
795
1284
|
|
|
796
|
-
Полиморфная версия `belongsTo
|
|
1285
|
+
Полиморфная версия `belongsTo`.
|
|
797
1286
|
|
|
798
1287
|
```js
|
|
799
1288
|
dbs.defineModel({
|
|
@@ -924,7 +1413,7 @@ dbs.defineModel({
|
|
|
924
1413
|
name: 'city',
|
|
925
1414
|
datasource: 'myDatasource',
|
|
926
1415
|
properties: {
|
|
927
|
-
|
|
1416
|
+
name: DataType.STRING,
|
|
928
1417
|
timeZone: DataType.STRING,
|
|
929
1418
|
},
|
|
930
1419
|
relations: {
|
|
@@ -938,7 +1427,7 @@ dbs.defineModel({
|
|
|
938
1427
|
// определение интерфейса "city"
|
|
939
1428
|
interface City {
|
|
940
1429
|
id: number;
|
|
941
|
-
|
|
1430
|
+
name?: string;
|
|
942
1431
|
timeZone?: string;
|
|
943
1432
|
countryId?: number;
|
|
944
1433
|
country?: Country;
|