@e22m4u/js-repository 0.0.42 → 0.1.0

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 (111) hide show
  1. package/.eslintignore +1 -0
  2. package/.husky/pre-commit +1 -0
  3. package/README.md +515 -577
  4. package/docs/.nojekyll +1 -0
  5. package/docs/assets/highlight.css +99 -0
  6. package/docs/assets/main.js +59 -0
  7. package/docs/assets/navigation.js +1 -0
  8. package/docs/assets/search.js +1 -0
  9. package/docs/assets/style.css +1394 -0
  10. package/docs/classes/Adapter.html +38 -0
  11. package/docs/classes/AdapterLoader.html +16 -0
  12. package/docs/classes/AdapterRegistry.html +16 -0
  13. package/docs/classes/BelongsToResolver.html +18 -0
  14. package/docs/classes/DatasourceDefinitionValidator.html +16 -0
  15. package/docs/classes/DefinitionRegistry.html +26 -0
  16. package/docs/classes/FieldsClauseTool.html +20 -0
  17. package/docs/classes/HasManyResolver.html +20 -0
  18. package/docs/classes/HasOneResolver.html +20 -0
  19. package/docs/classes/IncludeClauseTool.html +24 -0
  20. package/docs/classes/InvalidArgumentError.html +14 -0
  21. package/docs/classes/InvalidOperatorValueError.html +14 -0
  22. package/docs/classes/ModelDataSanitizer.html +16 -0
  23. package/docs/classes/ModelDataValidator.html +18 -0
  24. package/docs/classes/ModelDefinitionUtils.html +46 -0
  25. package/docs/classes/ModelDefinitionValidator.html +16 -0
  26. package/docs/classes/NotImplementedError.html +14 -0
  27. package/docs/classes/OperatorClauseTool.html +72 -0
  28. package/docs/classes/OrderClauseTool.html +20 -0
  29. package/docs/classes/PrimaryKeysDefinitionValidator.html +16 -0
  30. package/docs/classes/PropertiesDefinitionValidator.html +16 -0
  31. package/docs/classes/ReferencesManyResolver.html +16 -0
  32. package/docs/classes/RelationsDefinitionValidator.html +16 -0
  33. package/docs/classes/Repository.html +44 -0
  34. package/docs/classes/RepositoryRegistry.html +18 -0
  35. package/docs/classes/Schema.html +20 -0
  36. package/docs/classes/SliceClauseTool.html +20 -0
  37. package/docs/classes/WhereClauseTool.html +18 -0
  38. package/docs/enums/DataType.html +8 -0
  39. package/docs/enums/RelationType.html +6 -0
  40. package/docs/functions/capitalize.html +2 -0
  41. package/docs/functions/cloneDeep.html +2 -0
  42. package/docs/functions/excludeObjectKeys.html +2 -0
  43. package/docs/functions/getCtorName.html +2 -0
  44. package/docs/functions/getValueByPath.html +2 -0
  45. package/docs/functions/isCtor.html +2 -0
  46. package/docs/functions/isPureObject.html +2 -0
  47. package/docs/functions/selectObjectKeys.html +2 -0
  48. package/docs/functions/singularize.html +2 -0
  49. package/docs/functions/stringToRegexp.html +2 -0
  50. package/docs/index.html +284 -0
  51. package/docs/interfaces/AndClause.html +5 -0
  52. package/docs/interfaces/OrClause.html +5 -0
  53. package/docs/modules.html +80 -0
  54. package/docs/types/AnyObject.html +2 -0
  55. package/docs/types/BelongsToDefinition.html +6 -0
  56. package/docs/types/DEFAULT_PRIMARY_KEY_PROPERTY_NAME.html +2 -0
  57. package/docs/types/DatasourceDefinition.html +2 -0
  58. package/docs/types/FieldsClause.html +4 -0
  59. package/docs/types/FilterClause.html +2 -0
  60. package/docs/types/Flatten.html +1 -0
  61. package/docs/types/FullPropertyDefinition.html +2 -0
  62. package/docs/types/HasManyDefinition.html +4 -0
  63. package/docs/types/HasOneDefinition.html +4 -0
  64. package/docs/types/Identity.html +2 -0
  65. package/docs/types/IncludeClause.html +14 -0
  66. package/docs/types/ItemFilterClause.html +2 -0
  67. package/docs/types/ModelData.html +2 -0
  68. package/docs/types/ModelDefinition.html +2 -0
  69. package/docs/types/ModelId.html +2 -0
  70. package/docs/types/NestedIncludeClause.html +10 -0
  71. package/docs/types/NormalizedFieldsClause.html +4 -0
  72. package/docs/types/NormalizedIncludeClause.html +6 -0
  73. package/docs/types/OperatorClause.html +4 -0
  74. package/docs/types/OptionalUnlessRequiredId.html +2 -0
  75. package/docs/types/OrderClause.html +4 -0
  76. package/docs/types/PartialBy.html +2 -0
  77. package/docs/types/PartialWithoutId.html +2 -0
  78. package/docs/types/PolyBelongsToDefinition.html +6 -0
  79. package/docs/types/PolyHasManyDefinitionWithTargetKeys.html +4 -0
  80. package/docs/types/PolyHasManyDefinitionWithTargetRelationName.html +4 -0
  81. package/docs/types/PolyHasOneDefinitionWithTargetKeys.html +4 -0
  82. package/docs/types/PolyHasOneDefinitionWithTargetRelationName.html +4 -0
  83. package/docs/types/PropertiesClause.html +4 -0
  84. package/docs/types/PropertyDefinition.html +2 -0
  85. package/docs/types/PropertyDefinitionMap.html +2 -0
  86. package/docs/types/ReferencesManyDefinition.html +6 -0
  87. package/docs/types/RelationDefinition.html +4 -0
  88. package/docs/types/RelationDefinitionMap.html +2 -0
  89. package/docs/types/WhereClause.html +4 -0
  90. package/docs/types/WithoutId.html +2 -0
  91. package/package.json +14 -12
  92. package/src/adapter/adapter.d.ts +13 -0
  93. package/src/adapter/adapter.js +15 -0
  94. package/src/adapter/adapter.spec.js +8 -0
  95. package/src/adapter/builtin/memory-adapter.d.ts +13 -0
  96. package/src/adapter/builtin/memory-adapter.js +30 -0
  97. package/src/adapter/builtin/memory-adapter.spec.js +652 -0
  98. package/src/adapter/decorator/data-sanitizing-decorator.js +6 -0
  99. package/src/adapter/decorator/data-sanitizing-decorator.spec.js +13 -0
  100. package/src/adapter/decorator/data-validation-decorator.js +6 -0
  101. package/src/adapter/decorator/data-validation-decorator.spec.js +13 -0
  102. package/src/adapter/decorator/default-values-decorator.js +6 -0
  103. package/src/adapter/decorator/default-values-decorator.spec.js +16 -0
  104. package/src/adapter/decorator/fields-filtering-decorator.js +13 -0
  105. package/src/adapter/decorator/fields-filtering-decorator.spec.js +17 -0
  106. package/src/adapter/decorator/inclusion-decorator.js +13 -0
  107. package/src/adapter/decorator/inclusion-decorator.spec.js +17 -0
  108. package/src/definition/model/relations/relation-definition.d.ts +3 -3
  109. package/src/definition/model/relations/relations-definition-validator.js +4 -4
  110. package/src/repository/repository.js +2 -6
  111. package/typedoc.json +4 -0
package/README.md CHANGED
@@ -1,6 +1,23 @@
1
1
  ## @e22m4u/js-repository
2
2
 
3
- Абстракция для работы с базами данных для Node.js
3
+ Модуль для работы с базами данных для Node.js
4
+
5
+ [API](https://e22m4u.github.io/js-repository/modules.html)
6
+
7
+ - [Установка](#Установка)
8
+ - [Описание](#Описание)
9
+ - [Пример](#Пример)
10
+ - [Схема](#Схема)
11
+ - [Источник данных](#Источник-данных)
12
+ - [Модель](#Модель)
13
+ - [Свойства](#Свойства)
14
+ - [Репозиторий](#Репозиторий)
15
+ - [Фильтрация](#Фильтрация)
16
+ - [Связи](#Связи)
17
+ - [Расширение](#Расширение)
18
+ - [TypeScript](#TypeScript)
19
+ - [Тесты](#Тесты)
20
+ - [Лицензия](#Лицензия)
4
21
 
5
22
  ## Установка
6
23
 
@@ -8,638 +25,510 @@
8
25
  npm install @e22m4u/js-repository
9
26
  ```
10
27
 
11
- Опционально устанавливаем адаптер. Например, если используется
12
- *MongoDB*, то для подключения потребуется установить
13
- [адаптер mongodb](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter)
14
- как отдельную зависимость.
28
+ Опционально устанавливаем адаптер.
15
29
 
16
- ```bash
17
- npm install @e22m4u/js-repository-mongodb-adapter
18
- ```
30
+ | | описание |
31
+ |-----------|--------------------------------------------------------------------------------------------------------------------------------|
32
+ | `memory` | виртуальная база в памяти процесса (не требует установки) |
33
+ | `mongodb` | MongoDB - система управления NoSQL базами (*[установка](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter))* |
19
34
 
20
- **Список доступных адаптеров:**
35
+ ## Описание
21
36
 
22
- | адаптер | описание |
23
- |-----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
24
- | `memory` | виртуальная база в памяти процесса (для разработки и тестирования) |
25
- | `mongodb` | MongoDB - система управления NoSQL базами (*[требует установки](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter))* |
37
+ Модуль позволяет объединить несколько баз данных в единую абстракцию «Схема».
26
38
 
27
- ## Концепция
28
-
29
- Модуль позволяет спроектировать систему связанных данных, доступ к которым
30
- осуществляется посредством репозиториев. Каждый репозиторий имеет собственную
31
- модель данных, которая описывает структуру определенной коллекции в базе,
32
- а так же определяет связи к другим коллекциям.
39
+ - *Источник данных* - определяет способ подключения к базе
40
+ - *Модель* - описывает структуру документа и связи к другим моделям
41
+ - *Репозиторий* - выполняет операции чтения и записи документов модели
33
42
 
34
43
  ```mermaid
35
- flowchart LR
36
-
37
- A[Datasource]-->B[Data Model]-->С[Repository];
44
+ flowchart TD
45
+
46
+ A[Схема]
47
+ subgraph Базы данных
48
+ B[Источник данных 1]
49
+ C[Источник данных 2]
50
+ end
51
+ A-->B
52
+ A-->C
53
+
54
+ subgraph Коллекции
55
+ D[Модель A]
56
+ E[Модель Б]
57
+ F[Модель В]
58
+ G[Модель Г]
59
+ end
60
+ B-->D
61
+ B-->E
62
+ C-->F
63
+ C-->G
64
+
65
+ H[Репозиторий A]
66
+ I[Репозиторий Б]
67
+ J[Репозиторий В]
68
+ K[Репозиторий Г]
69
+ D-->H
70
+ E-->I
71
+ F-->J
72
+ G-->K
38
73
  ```
39
74
 
40
- ## Использование
75
+ ## Пример
41
76
 
42
- Определения источников и моделей хранятся в экземпляре класса `Schema`,
43
- и первым шагом будет создание данного экземпляра.
77
+ Определение источника данных, модели и добавление нового документа в коллекцию.
44
78
 
45
79
  ```js
46
80
  import {Schema} from '@e22m4u/js-repository';
81
+ import {DataType} from '@e22m4u/js-repository';
47
82
 
83
+ // создание экземпляра Schema
48
84
  const schema = new Schema();
85
+
86
+ // определение источника "myMemory"
87
+ schema.defineDatasource({
88
+ name: 'myMemory', // название нового источника
89
+ adapter: 'memory', // выбранный адаптер
90
+ });
91
+
92
+ // определение модели "country"
93
+ schema.defineModel({
94
+ name: 'country', // название новой модели
95
+ datasource: 'myMemory', // выбранный источник
96
+ properties: { // свойства модели
97
+ name: DataType.STRING, // тип "string"
98
+ population: DataType.NUMBER, // тип "number"
99
+ },
100
+ })
101
+
102
+ // получение репозитория для модели "country"
103
+ const countryRep = schema.getRepository('country');
104
+
105
+ // добавление нового документа в коллекцию "country"
106
+ const country = await countryRep.create({
107
+ name: 'Russia',
108
+ population: 143400000,
109
+ });
110
+
111
+ // вывод результата
112
+ console.log(country);
113
+ // {
114
+ // "id": 1,
115
+ // "name": "Russia",
116
+ // "population": 143400000,
117
+ // }
49
118
  ```
50
119
 
51
- Интерфейс `Schema` содержит три основных метода:
120
+ ## Схема
121
+
122
+ Экземпляр класса `Schema` хранит определения источников данных и моделей.
123
+
124
+ **Методы**
52
125
 
53
126
  - `defineDatasource(datasourceDef: object): this` - добавить источник
54
127
  - `defineModel(modelDef: object): this` - добавить модель
55
128
  - `getRepository(modelName: string): Repository` - получить репозиторий
56
129
 
57
- ### Источник данных
130
+ **Примеры**
58
131
 
59
- Источник описывает способ подключения к базе и используемый адаптер.
60
- Если адаптер имеет настройки, то они передаются вместе с объектом
61
- определения источника методом `defineDatasource`, как это показано
62
- ниже.
132
+ Импорт класса и создание экземпляра схемы.
63
133
 
64
134
  ```js
65
- schema.defineDatasource({
66
- name: 'myMongo', // название нового источника
67
- adapter: 'mongodb', // название выбранного адаптера
68
- // настройки адаптера mongodb
69
- host: '127.0.0.1',
70
- port: 27017,
71
- database: 'data'
72
- });
73
- ```
74
-
75
- **Параметры источника:**
135
+ import {Schema} from '@e22m4u/js-repository';
76
136
 
77
- - `name: string` уникальное название
78
- - `adapter: string` выбранный адаптер
137
+ const schema = new Schema();
138
+ ```
79
139
 
80
- При желании можно использовать встроенный адаптер `memory`, который хранит
81
- данные в памяти процесса. У него нет специальных настроек, и он отлично
82
- подходит для тестов и прототипирования.
140
+ Определение нового источника.
83
141
 
84
142
  ```js
85
143
  schema.defineDatasource({
86
- name: 'myMemory', // название источника
144
+ name: 'myMemory', // название нового источника
87
145
  adapter: 'memory', // выбранный адаптер
88
146
  });
89
147
  ```
90
148
 
91
- ### Модель данных
92
-
93
- Когда источники определены, можно перейти к описанию моделей данных.
94
- Модель может определять как структуру какого-либо объекта,
95
- так и являться отражением реальной коллекции базы.
96
-
97
- Представьте себе коллекцию торговых точек, у каждой из которых имеются
98
- координаты `lat` и `lng`. Мы можем заранее определить модель для
99
- объекта координат методом `defineModel` и использовать ее в других
100
- коллекциях.
149
+ Определение новой модели.
101
150
 
102
151
  ```js
103
152
  schema.defineModel({
104
- name: 'latLng', // название новой модели
105
- properties: { // поля модели
106
- lat: DataType.NUMBER, // поле широты
107
- lng: DataType.NUMBER, // поле долготы
153
+ name: 'product', // название новой модели
154
+ datasource: 'myMemory', // выбранный источник
155
+ properties: { // свойства модели
156
+ name: DataType.STRING,
157
+ weight: DataType.NUMBER,
108
158
  },
109
159
  });
110
160
  ```
111
161
 
112
- **Параметры модели:**
113
-
114
- - `name: string` уникальное название (обязательно)
115
- - `datasource: string` выбранный источник данных
116
- - `properties: object` определения полей модели
117
- - `relations: object` определения связей модели
118
-
119
- Параметр `properties` принимает объект, ключи которого являются именами
120
- полей, а значением тип поля или объект с дополнительными параметрами.
162
+ Получение репозитория по названию модели.
121
163
 
122
164
  ```js
123
- schema.defineModel({
124
- name: 'latLng',
125
- properties: {
126
- lat: DataType.NUMBER, // краткое определение поля "lat"
127
- lng: { // расширенное определение поля "lng"
128
- type: DataType.NUMBER, // тип допустимого значения
129
- required: true, // исключает null и undefined
130
- },
131
- },
132
- });
165
+ const productRep = schema.getRepository('product');
133
166
  ```
134
167
 
135
- **Типы данных:**
168
+ ## Источник данных
169
+
170
+ Источник хранит название выбранного адаптера и его настройки. Определить
171
+ новый источник можно методом `defineDatasource` экземпляра схемы.
172
+
173
+ **Параметры**
136
174
 
137
- - `DataType.ANY`
138
- - `DataType.STRING`
139
- - `DataType.NUMBER`
140
- - `DataType.BOOLEAN`
141
- - `DataType.ARRAY`
142
- - `DataType.OBJECT`
175
+ - `name: string` уникальное название
176
+ - `adapter: string` выбранный адаптер
177
+ - параметры адаптера (если имеются)
143
178
 
144
- Модель `latLng` всего лишь описывает структуру объекта координат, тогда
145
- как торговая точка должна иметь реальную таблицу в базе. По аналогии с
146
- предыдущим примером, добавим модель `place`, но дополнительно укажем
147
- источник данных в параметре `datasource`
179
+ **Примеры**
180
+
181
+ Определение нового источника.
148
182
 
149
183
  ```js
150
- schema.defineModel({
151
- name: 'place',
152
- datasource: 'myMemory', // выбранный источник данных
153
- properties: {
154
- name: DataType.STRING, // поле для названия торговой точки
155
- location: { // поле объекта координат
156
- type: DataType.OBJECT, // допускать только объекты
157
- model: 'latLng', // определение структуры объекта
158
- },
159
- },
184
+ schema.defineDatasource({
185
+ name: 'myMemory', // название нового источника
186
+ adapter: 'memory', // выбранный адаптер
160
187
  });
161
188
  ```
162
189
 
163
- В примере выше мы использовали модель `latLng` как структуру допустимого
164
- значения поля `location`. Возможный документ данной коллекции может
165
- выглядеть так:
190
+ Передача дополнительных параметров адаптера.
166
191
 
167
- ```json
168
- {
169
- "id": 1,
170
- "name": "Burger King",
171
- "location": {
172
- "lat": 32.412891,
173
- "lng": 34.7660061
174
- }
175
- }
192
+ ```js
193
+ schema.defineDatasource({
194
+ name: 'myMongodb',
195
+ adapter: 'mongodb',
196
+ // параметры адаптера "mongodb"
197
+ host: '127.0.0.1',
198
+ port: 27017,
199
+ database: 'myDatabase',
200
+ });
176
201
  ```
177
202
 
178
- Стоит обратить внимание, что мы могли бы не объявлять параметр `properties`,
179
- при этом теряя возможность проверки данных перед записью в базу.
203
+ ## Модель
204
+
205
+ Описывает структуру документа коллекции и связи к другим моделям. Определить
206
+ новую модель можно методом `defineModel` экземпляра схемы.
207
+
208
+ **Параметры**
209
+
210
+ - `name: string` название модели (обязательно)
211
+ - `base: string` название наследуемой модели
212
+ - `tableName: string` название коллекции в базе
213
+ - `datasource: string` выбранный источник данных
214
+ - `properties: object` определения свойств (см. [Свойства](#Свойства))
215
+ - `relations: object` определения связей (см. [Связи](#Связи))
216
+
217
+ **Примеры**
218
+
219
+ Определение модели со свойствами указанного типа.
180
220
 
181
221
  ```js
182
222
  schema.defineModel({
183
- name: 'place',
184
- adapter: 'myMemory',
185
- // параметр "properties" не указан
223
+ name: 'user', // название новой модели
224
+ properties: { // свойства модели
225
+ name: DataType.STRING,
226
+ age: DataType.NUMBER,
227
+ },
186
228
  });
187
229
  ```
188
230
 
189
- **Параметры поля:**
231
+ ## Свойства
232
+
233
+ Параметр `properties` находится в составе модели и принимает объект, ключи
234
+ которого являются свойствами этой модели, а значением тип свойства или объект
235
+ с дополнительными параметрами.
236
+
237
+ **Тип данных**
238
+
239
+ - `DataType.ANY` разрешено любое значение
240
+ - `DataType.STRING` только значение типа `string`
241
+ - `DataType.NUMBER` только значение типа `number`
242
+ - `DataType.BOOLEAN` только значение типа `boolean`
243
+ - `DataType.ARRAY` только значение типа `array`
244
+ - `DataType.OBJECT` только значение типа `object`
245
+
246
+ **Параметры**
190
247
 
191
248
  - `type: string` тип допустимого значения (обязательно)
192
249
  - `itemType: string` тип элемента массива (для `type: 'array'`)
193
250
  - `model: string` модель объекта (для `type: 'object'`)
194
- - `primaryKey: boolean` объявить поле первичным ключом
251
+ - `primaryKey: boolean` объявить свойство первичным ключом
195
252
  - `columnName: string` переопределение названия колонки
196
253
  - `columnType: string` тип колонки (определяется адаптером)
197
- - `required: boolean` объявить поле обязательным
254
+ - `required: boolean` объявить свойство обязательным
198
255
  - `default: any` значение по умолчанию
199
256
 
200
- ### Репозиторий
201
-
202
- В отличие от `latLng`, модель `place` имеет источник данных с названием
203
- `myMemory`, который был объявлен ранее. Наличие источника позволяет получить
204
- репозиторий по названию модели.
205
-
206
- ```js
207
- const rep = schema.getRepository('place');
208
- ```
209
-
210
- **Методы:**
211
-
212
- - `create(data, filter = undefined)`
213
- - `replaceById(id, data, filter = undefined)`
214
- - `replaceOrCreate(data, filter = undefined)`
215
- - `patch(data, where = undefined)`
216
- - `patchById(id, data, filter = undefined)`
217
- - `find(filter = undefined)`
218
- - `findOne(filter = undefined)`
219
- - `findById(id, filter = undefined)`
220
- - `delete(where = undefined)`
221
- - `deleteById(id)`
222
- - `exists(id)`
223
- - `count(where = undefined)`
257
+ **Примеры**
224
258
 
225
- #### create(data, filter = undefined)
226
-
227
- Создадим торговую точку методом `create` используя репозиторий из примера
228
- выше. Метод возвращает документ, который был записан в базу, включая присвоенный
229
- идентификатор.
259
+ Краткое определение свойств модели.
230
260
 
231
261
  ```js
232
- const place = await rep.create({
233
- "name": "Burger King",
234
- "location": {
235
- "lat": 32.412891,
236
- "lng": 34.7660061
262
+ schema.defineModel({
263
+ name: 'city',
264
+ properties: { // свойства модели
265
+ name: DataType.STRING, // тип свойства "string"
266
+ population: DataType.NUMBER, // тип свойства "number"
237
267
  },
238
268
  });
239
-
240
- console.log(place);
241
- // {
242
- // "id": 1,
243
- // "name": "Burger King",
244
- // "location": {
245
- // "lat": 32.412891,
246
- // "lng": 34.7660061
247
- // }
248
- // }
249
269
  ```
250
270
 
251
- #### replaceById(id, data, filter = undefined)
252
-
253
- Добавленный в базу документ можно полностью заменить зная его идентификатор.
254
- Воспользуемся методом `replaceById`, который перезапишет данные по значению
255
- первичного ключа.
271
+ Расширенное определение свойств модели.
256
272
 
257
273
  ```js
258
- // {
259
- // "id": 1,
260
- // "name": "Burger King",
261
- // "location": {
262
- // "lat": 32.412891,
263
- // "lng": 34.7660061
264
- // }
265
- // }
266
- const result = rep.replaceById(place.id, {
267
- name: 'Starbucks',
268
- address: 'Sukhumvit Alley'
274
+ schema.defineModel({
275
+ name: 'city',
276
+ properties: { // свойства модели
277
+ name: {
278
+ type: DataType.STRING, // тип свойства "string" (обязательно)
279
+ required: true, // исключение значений undefined и null
280
+ },
281
+ population: {
282
+ type: DataType.NUMBER, // тип свойства "number" (обязательно)
283
+ default: 0, // значение по умолчанию
284
+ },
285
+ },
269
286
  });
270
-
271
- console.log(result);
272
- // {
273
- // "id": 1,
274
- // "name": "Starbucks",
275
- // "address": "Sukhumvit Alley"
276
- // }
277
287
  ```
278
288
 
279
- #### replaceOrCreate(data, filter = undefined)
280
-
281
- Если вы знакомы с методом PUT в архитектуре REST, когда документ
282
- добавляется, если его не существовало, или же обновляется существующий,
283
- то `replaceOrCreate` работает схожим образом. Если параметр
284
- `data` передаваемый первым аргументом будет содержать идентификатор,
285
- то метод будет вести себя как `replaceById`, в противном случае будет
286
- создан новый документ.
289
+ Фабричное значение по умолчанию. Возвращаемое значение функции будет
290
+ определено в момент записи документа.
287
291
 
288
292
  ```js
289
- // {
290
- // "id": 1,
291
- // "name": "Starbucks",
292
- // "address": "Sukhumvit Alley"
293
- // }
294
- const result = rep.replaceOrCreate({
295
- id: 1,
296
- name: 'Airport',
297
- city: 'Antalya',
298
- code: 'AYT'
293
+ schema.defineModel({
294
+ name: 'article',
295
+ properties: { // свойства модели
296
+ tags: {
297
+ type: DataType.ARRAY, // тип свойства "array" (обязательно)
298
+ itemType: DataType.STRING, // тип элемента "string"
299
+ default: () => [], // фабричное значение
300
+ },
301
+ createdAt: {
302
+ type: DataType.STRING, // тип свойства "string" (обязательно)
303
+ default: () => new Date().toISOString(), // фабричное значение
304
+ },
305
+ },
299
306
  });
300
-
301
- console.log(result);
302
- // {
303
- // "id": 1,
304
- // "name": "Airport",
305
- // "city": "Antalya"
306
- // "code": "AYT"
307
- // }
308
307
  ```
309
308
 
310
- В примере выше был передан первичный ключ `id` для поиска и
311
- замены существующего документа. Теперь рассмотрим создание
312
- документа с новым идентификатором.
309
+ ## Репозиторий
313
310
 
314
- ```js
315
- const result = rep.replaceOrCreate({
316
- name: 'Airport',
317
- city: 'Bangkok',
318
- code: 'BKK',
319
- });
311
+ Выполняет операции чтения и записи документов определенной модели.
312
+ Получить репозиторий можно методом `getRepository` экземпляра схемы.
320
313
 
321
- console.log(result);
322
- // {
323
- // "id": 2,
324
- // "name": "Airport",
325
- // "city": "Bangkok",
326
- // "code": "BKK"
327
- // }
328
- ```
314
+ **Методы**
329
315
 
330
- #### patchById(id, data, filter = undefined)
316
+ - `create(data, filter = undefined)` добавить новый документ
317
+ - `replaceById(id, data, filter = undefined)` заменить весь документ
318
+ - `replaceOrCreate(data, filter = undefined)` заменить или создать новый
319
+ - `patchById(id, data, filter = undefined)` частично обновить документ
320
+ - `patch(data, where = undefined)` обновить все документы или по условию
321
+ - `find(filter = undefined)` найти все документы или по условию
322
+ - `findOne(filter = undefined)` найти первый документ или по условию
323
+ - `findById(id, filter = undefined)` найти документ по идентификатору
324
+ - `delete(where = undefined)` удалить все документы или по условию
325
+ - `deleteById(id)` удалить документ по идентификатору
326
+ - `exists(id)` проверить существование по идентификатору
327
+ - `count(where = undefined)` подсчет всех документов или по условию
331
328
 
332
- В отличие от `replaceById`, данный метод не удаляет поля, которые
333
- не были переданы, что позволяет обновить только часть документа,
334
- не затрагивая другие данные.
329
+ **Аргументы**
335
330
 
336
- ```js
337
- // {
338
- // "id": 2,
339
- // "name": "Airport",
340
- // "city": "Bangkok",
341
- // "code": "BKK"
342
- // }
343
- const result = rep.patchById(place.id, {
344
- city: 'Moscow',
345
- code: 'SVO'
346
- });
331
+ - `id: number|string` идентификатор (первичный ключ)
332
+ - `data: object` объект отражающий состав документа
333
+ - `where: object` параметры выборки (см. [Фильтрация](#Фильтрация))
334
+ - `filter: object` параметры возвращаемого результата (см. [Фильтрация](#Фильтрация))
347
335
 
348
- console.log(result);
349
- // {
350
- // "id": 2,
351
- // "name": "Airport",
352
- // "city": "Moscow",
353
- // "code": "SVO"
354
- // }
355
- ```
356
-
357
- ## Пример
336
+ **Примеры**
358
337
 
359
- Создаем модель `user`
338
+ Получение репозитория по названию модели.
360
339
 
361
340
  ```js
362
- schema.defineModel({
363
- name: 'user', // название модели
364
- adapter: 'myMemory', // выбранный источник
365
- properties: { // поля модели
366
- name: 'string',
367
- age: 'number',
368
- },
369
- });
341
+ const countryRep = schema.getRepository('country');
370
342
  ```
371
343
 
372
- Получаем репозиторий модели `user`
344
+ Добавление нового документа в коллекцию.
373
345
 
374
346
  ```js
375
- const userRep = schema.getRepository('user');
376
- ```
377
-
378
- Добавляем новую запись методом `create`
379
-
380
- ```js
381
- const fedor = await userRep.create({
382
- name: 'Fedor',
383
- age: 24,
347
+ const res = await countryRep.create({
348
+ name: 'Russia',
349
+ population: 143400000,
384
350
  });
385
351
 
386
- console.log(fedor);
352
+ console.log(res);
387
353
  // {
388
- // id: 1,
389
- // name: 'Fedor',
390
- // age: 24,
354
+ // "id": 1,
355
+ // "name": "Russia",
356
+ // "population": 143400000,
391
357
  // }
392
358
  ```
393
359
 
394
- Изменяем данные методом `patchById`
360
+ Поиск документа по идентификатору.
395
361
 
396
362
  ```js
397
- const result = await userRep.patchById(
398
- fedor.id,
399
- {age: 30},
400
- );
363
+ const res = await countryRep.findById(1);
401
364
 
402
- console.log(result);
365
+ console.log(res);
403
366
  // {
404
- // id: 1,
405
- // name: 'Fedor',
406
- // age: 30,
367
+ // "id": 1,
368
+ // "name": "Russia",
369
+ // "population": 143400000,
407
370
  // }
408
371
  ```
409
372
 
410
- Удаляем по идентификатору методом `deleteById`
373
+ Удаление документа по идентификатору.
411
374
 
412
375
  ```js
413
- await userRep.deleteById(fedor.id); // true
376
+ const res = await countryRep.deleteById(1);
377
+
378
+ console.log(res); // true
414
379
  ```
415
380
 
416
- ## Репозиторий
381
+ ## Фильтрация
417
382
 
418
- Выполняет операции чтения и записи определенной коллекции.
383
+ Некоторые методы репозитория принимают объект настроек влияющий
384
+ на возвращаемый результат. Максимально широкий набор таких настроек
385
+ имеет первый параметр метода `find`, где ожидается объект содержащий
386
+ набор опций указанных ниже.
419
387
 
420
- Методы:
388
+ - `where: object` объект выборки
389
+ - `order: string[]` указание порядка
390
+ - `limit: number` ограничение количества документов
391
+ - `skip: number` пропуск документов
392
+ - `fields: string[]` выбор необходимых свойств модели
393
+ - `include: object` включение связанных данных в результат
421
394
 
422
- - `create(data, filter = undefined)`
423
- - `replaceById(id, data, filter = undefined)`
424
- - `replaceOrCreate(data, filter = undefined)`
425
- - `patch(data, where = undefined)`
426
- - `patchById(id, data, filter = undefined)`
427
- - `find(filter = undefined)`
428
- - `findOne(filter = undefined)`
429
- - `findById(id, filter = undefined)`
430
- - `delete(where = undefined)`
431
- - `deleteById(id)`
432
- - `exists(id)`
433
- - `count(where = undefined)`
395
+ ### where
434
396
 
435
- Получение репозитория модели:
397
+ Параметр принимает объект с условиями выборки и поддерживает широкий
398
+ набор операторов сравнения.
436
399
 
437
- ```js
438
- import {Schema} from '@e22m4u/js-repository';
400
+ `{foo: 'bar'}` поиск по значению свойства `foo`
401
+ `{foo: {eq: 'bar'}}` оператор равенства `eq`
402
+ `{foo: {neq: 'bar'}}` оператор неравенства `neq`
403
+ `{foo: {gt: 5}}` оператор "больше" `gt`
404
+ `{foo: {lt: 10}}` оператор "меньше" `lt`
405
+ `{foo: {gte: 5}}` оператор "больше или равно" `gte`
406
+ `{foo: {lte: 10}}` оператор "меньше или равно" `lte`
407
+ `{foo: {inq: ['bar', 'baz']}}` равенство одного из значений `inq`
408
+ `{foo: {nin: ['bar', 'baz']}}` исключение значений массива `nin`
409
+ `{foo: {between: [5, 10]}}` оператор диапазона `between`
410
+ `{foo: {exists: true}}` оператор наличия значения `exists`
411
+ `{foo: {like: 'bar'}}` оператор поиска подстроки `like`
412
+ `{foo: {ilike: 'BaR'}}` регистронезависимая версия `ilike`
413
+ `{foo: {nlike: 'bar'}}` оператор исключения подстроки `nlike`
414
+ `{foo: {nilike: 'BaR'}}` регистронезависимая версия `nilike`
415
+ `{foo: {regexp: 'ba.+'}}` оператор регулярного выражения `regexp`
416
+ `{foo: {regexp: 'ba.+', flags: 'i'}}` флаги регулярного выражения
439
417
 
440
- const schema = new Schema();
441
- // создаем источник
442
- schema.defineDatasource({name: 'myDatasource', adapter: 'memory'});
443
- // создаем модель
444
- schema.defineModel({name: 'myModel', datasource: 'myDatasource'});
445
- // получаем репозиторий по названию модели
446
- const repositorty = schema.getRepository('myModel');
447
- ```
418
+ *i. Условия можно объединять операторами `and`, `or` и `nor`.*
448
419
 
449
- Переопределение конструктора:
420
+ **Примеры**
450
421
 
451
- ```js
452
- import {Schema} from '@e22m4u/js-repository';
453
- import {Repository} from '@e22m4u/js-repository';
454
- import {RepositoryRegistry} from '@e22m4u/js-repository';
422
+ Применение условий выборки при подсчете документов.
455
423
 
456
- class MyRepository extends Repository {
457
- /*...*/
458
- }
424
+ ```js
425
+ const res = await rep.count({
426
+ authorId: 251,
427
+ publishedAt: {
428
+ lte: '2023-12-02T14:00:00.000Z',
429
+ },
430
+ });
431
+ ```
459
432
 
460
- const schema = new Schema();
461
- schema.get(RepositoryRegistry).setRepositoryCtor(MyRepository);
462
- // теперь schema.getRepository(modelName) будет возвращать
463
- // экземпляр класса MyRepository
464
- ```
465
-
466
- ### Filter
467
-
468
- Большинство методов репозитория принимают объект `filter` для
469
- фильтрации возвращаемого ответа. Описание параметров объекта:
470
-
471
- - **where** *(условия выборки данных из базы)*
472
- примеры:
473
- `where: {foo: 'bar'}` поиск по значению поля `foo`
474
- `where: {foo: {eq: 'bar'}}` оператор равенства `eq`
475
- `where: {foo: {neq: 'bar'}}` оператор неравенства `neq`
476
- `where: {foo: {gt: 5}}` оператор "больше" `gt`
477
- `where: {foo: {lt: 10}}` оператор "меньше" `lt`
478
- `where: {foo: {gte: 5}}` оператор "больше или равно" `gte`
479
- `where: {foo: {lte: 10}}` оператор "меньше или равно" `lte`
480
- `where: {foo: {inq: ['bar', 'baz']}}` равенство одного из значений `inq`
481
- `where: {foo: {nin: ['bar', 'baz']}}` исключение значений массива `nin`
482
- `where: {foo: {between: [5, 10]}}` оператор диапазона `between`
483
- `where: {foo: {exists: true}}` оператор наличия значения `exists`
484
- `where: {foo: {like: 'bar'}}` оператор поиска подстроки `like`
485
- `where: {foo: {ilike: 'BaR'}}` регистронезависимая версия `ilike`
486
- `where: {foo: {nlike: 'bar'}}` оператор исключения подстроки `nlike`
487
- `where: {foo: {nilike: 'BaR'}}` регистронезависимая версия `nilike`
488
- `where: {foo: {regexp: 'ba.+'}}` оператор регулярного выражения `regexp`
489
- `where: {foo: {regexp: 'ba.+', flags: 'i'}}` флаги регулярного выражения
490
-
491
-
492
- - **order** *(упорядочить записи по полю)*
493
- примеры:
494
- `order: 'foo'` порядок по полю `foo`
495
- `order: 'foo ASC'` явное указание порядка
496
- `order: 'foo DESC'` инвертировать порядок
497
- `order: ['foo', 'bar ASC', 'baz DESC']` по нескольким полям
498
-
499
-
500
- - **limit** *(не более N записей)*
501
- примеры:
502
- `limit: 0` не ограничивать
503
- `limit: 14` не более 14-и
504
-
505
-
506
- - **skip** *(пропуск первых N записей)*
507
- примеры:
508
- `skip: 0` выборка без пропуска
509
- `skip: 10` пропустить 10 объектов выборки
510
-
511
-
512
- - **include** *(включение связанных данных в результат)*
513
- примеры:
514
- `include: 'foo'` включение связи `foo`
515
- `include: ['foo', 'bar']` включение `foo` и `bar`
516
- `include: {foo: 'bar'}` включение вложенной связи `foo`
433
+ Применение оператора `or` при удалении документов.
517
434
 
518
- ## Источник данных
435
+ ```js
436
+ const res = await rep.delete({
437
+ or: [
438
+ {draft: true},
439
+ {title: {like: 'draft'}},
440
+ ],
441
+ });
442
+ ```
519
443
 
520
- Определяет настройки и способ подключения к базе.
444
+ ### order
521
445
 
522
- Параметры:
446
+ Параметр упорядочивает выборку по указанным свойствам модели. Обратное
447
+ направление порядка можно задать постфиксом `DESC` в названии свойства.
523
448
 
524
- - `name: string` название нового источника
525
- - `adapter: string` выбранный адаптер базы данных
449
+ **Примеры**
526
450
 
527
- Пример:
451
+ Упорядочить по полю `createdAt`
528
452
 
529
453
  ```js
530
- schema.defineDatasource({
531
- name: 'myDatasource',
532
- adapter: 'memory',
454
+ const res = await rep.find({
455
+ order: 'createdAt',
533
456
  });
534
457
  ```
535
458
 
536
- Адаптер может иметь параметры, которые передаются
537
- при определении источника.
459
+ Упорядочить по полю `createdAt` в обратном порядке.
538
460
 
539
- Пример:
461
+ ```js
462
+ const res = await rep.find({
463
+ order: 'createdAt DESC',
464
+ });
465
+ ```
466
+
467
+ Упорядочить по нескольким свойствам в разных направлениях.
540
468
 
541
469
  ```js
542
- schema.defineDatasource({
543
- name: 'myDatasource',
544
- adapter: 'mongodb',
545
- // параметры адаптера:
546
- host: '127.0.0.1',
547
- port: 27017,
470
+ const res = await rep.find({
471
+ order: [
472
+ 'title',
473
+ 'price ASC',
474
+ 'featured DESC',
475
+ ],
548
476
  });
549
477
  ```
550
478
 
551
- ## Модель
479
+ *i. Направление порядка `ASC` указывать необязательно.*
552
480
 
553
- Описывает набор полей и связей к другим моделям.
481
+ ### include
554
482
 
555
- Параметры:
483
+ Параметр включает связанные документы в результат вызываемого метода.
484
+ Названия включаемых связей должны быть определены в текущей модели.
485
+ (см. [Связи](#Связи))
556
486
 
557
- - `name: string` название новой модели
558
- - `datasource: string` выбранный источник данных
559
- - `properties: object` определения полей модели
560
- - `relations: object` определения связей модели
487
+ **Примеры**
561
488
 
562
- Пример:
489
+ Включение связи по названию.
563
490
 
564
491
  ```js
565
- schema.defineModel({
566
- name: 'myModel',
567
- datasource: 'myDatasource',
568
- properties: {...}, // см. ниже
569
- relations: {...}, // см. ниже
492
+ const res = await rep.find({
493
+ include: 'city',
570
494
  });
571
495
  ```
572
496
 
573
- ## Поля
574
-
575
- Параметр `properties` описывает набор полей и их настройки.
576
-
577
- Типы:
578
-
579
- - `string`
580
- - `number`
581
- - `boolean`
582
- - `array`
583
- - `object`
584
- - `any`
585
-
586
- Пример:
497
+ Включение вложенных связей.
587
498
 
588
499
  ```js
589
- schema.defineModel({
590
- // ...
591
- properties: {
592
- prop1: 'string',
593
- prop2: 'number',
594
- prop3: 'boolean',
595
- prop4: 'array',
596
- prop5: 'object',
597
- prop6: 'any',
500
+ const res = await rep.find({
501
+ include: {
502
+ city: 'country',
598
503
  },
599
504
  });
600
505
  ```
601
506
 
602
- Расширенные параметры:
507
+ Включение нескольких связей массивом.
603
508
 
604
- - `type: string` тип хранимого значения
605
- - `itemType: string` тип элемента массива (для `type: 'array'`)
606
- - `model: string` модель объекта (для `type: 'object'`)
607
- - `primaryKey: boolean` объявить поле первичным ключом
608
- - `columnName: string` переопределение названия колонки
609
- - `columnType: string` тип колонки (определяется адаптером)
610
- - `required: boolean` объявить поле обязательным
611
- - `default: any` значение по умолчанию для `undefined`
509
+ ```js
510
+ const res = await rep.find({
511
+ include: [
512
+ 'city',
513
+ 'address',
514
+ 'employees'
515
+ ],
516
+ });
517
+ ```
612
518
 
613
- Пример:
519
+ Использование фильтрации включаемых документов.
614
520
 
615
521
  ```js
616
- schema.defineModel({
617
- // ...
618
- properties: {
619
- prop1: {
620
- type: 'string',
621
- primaryKey: true,
622
- },
623
- prop2: {
624
- type: 'boolean',
625
- required: true,
626
- },
627
- prop3: {
628
- type: 'number',
629
- default: 100,
630
- },
631
- prop3: {
632
- type: 'string',
633
- // фабричное значение
634
- default: () => new Date().toISOString(),
635
- },
636
- prop4: {
637
- type: 'array',
638
- itemType: 'string',
639
- },
640
- prop5: {
641
- type: 'object',
642
- model: 'objectModel',
522
+ const res = await rep.find({
523
+ include: {
524
+ relation: 'employees', // название связи
525
+ scope: { // фильтрация документов "employees"
526
+ where: {hidden: false}, // условия выборки
527
+ order: 'id', // порядок документов
528
+ limit: 10, // ограничение количества
529
+ skip: 5, // пропуск документов
530
+ fields: ['name', 'surname'], // только указанные поля
531
+ include: 'city', // включение связей для "employees"
643
532
  },
644
533
  },
645
534
  });
@@ -647,188 +536,237 @@ schema.defineModel({
647
536
 
648
537
  ## Связи
649
538
 
650
- Параметр `relations` описывает набор связей к другим моделям.
651
-
652
- Понятия:
653
-
654
- - источник связи
655
- *- модель в которой определена данная связь*
656
- - целевая модель
657
- *- модель на которую ссылается источник связи*
539
+ Параметр `relations` находится в составе определения модели и принимает
540
+ объект, ключ которого является названием связи, а значением объект
541
+ с параметрами.
658
542
 
659
- Типы:
660
-
661
- - `belongsTo` - ссылка на целевую модель находится в источнике
662
- - `hasOne` - ссылка на источник находится в целевой модели (one-to-one)
663
- - `hasMany` - ссылка на источник находится в целевой модели (one-to-many)
664
- - `referencesMany` - массив ссылок на целевую модель находится в источнике
665
-
666
- Параметры:
543
+ **Параметры**
667
544
 
668
545
  - `type: string` тип связи
669
- - `model: string` целевая модель
670
- - `foreignKey: string` поле для идентификатора цели
546
+ - `model: string` название целевой модели
547
+ - `foreignKey: string` свойство текущей модели для идентификатора цели
671
548
  - `polymorphic: boolean|string` объявить связь полиморфной*
672
- - `discriminator: string` поле для названия целевой модели (для `polymorphic: true`)
549
+ - `discriminator: string` свойство текущей модели для названия целевой*
550
+
551
+ *i. Полиморфный режим позволяет динамически определять целевую модель
552
+ по ее названию, которое хранит документ в свойстве-дискриминаторе.*
673
553
 
674
- *i. Полиморфный режим `belongsTo` позволяет динамически определять цель связи,
675
- где имя целевой модели хранится в отдельном поле, рядом с `foreignKey`*
554
+ **Тип связи**
676
555
 
677
- #### BelongsTo
556
+ - `belongsTo` - текущая модель содержит свойство для идентификатора цели
557
+ - `hasOne` - обратная сторона `belongsTo` по принципу "один к одному"
558
+ - `hasMany` - обратная сторона `belongsTo` по принципу "один ко многим"
559
+ - `referencesMany` - документ содержит массив с идентификаторами целевой модели
678
560
 
679
- Связь заказа к покупателю через поле `customerId`
561
+ **Примеры**
562
+
563
+ Объявление связи `belongsTo`
680
564
 
681
565
  ```js
682
566
  schema.defineModel({
683
- // ...
567
+ name: 'user',
684
568
  relations: {
685
- // ...
686
- customer: {
687
- type: 'belongsTo',
688
- model: 'customer',
689
- foreignKey: 'customerId', // опционально
569
+ role: { // название связи
570
+ type: RelationType.BELONGS_TO, // текущая модель ссылается на целевую
571
+ model: 'role', // название целевой модели
572
+ foreignKey: 'roleId', // внешний ключ (необязательно)
573
+ // если "foreignKey" не указан, то свойство внешнего
574
+ // ключа формируется согласно названию связи
575
+ // с добавлением постфикса "Id"
690
576
  },
691
577
  },
692
578
  });
693
579
  ```
694
580
 
695
- Полиморфная версия
581
+ Объявление связи `hasMany`
696
582
 
697
583
  ```js
698
584
  schema.defineModel({
699
- // ...
585
+ name: 'role',
700
586
  relations: {
701
- // ...
702
- customer: {
703
- type: 'belongsTo',
704
- polymorphic: true,
705
- foreignKey: 'customerId', // опционально
706
- discriminator: 'customerType', // опционально
587
+ users: { // название связи
588
+ type: RelationType.HAS_MANY, // целевая модель ссылается на текущую
589
+ model: 'user', // название целевой модели
590
+ foreignKey: 'roleId', // внешний ключ из целевой модели на текущую
707
591
  },
708
592
  },
709
593
  });
710
594
  ```
711
595
 
712
- #### HasOne (one-to-one)
713
-
714
- Связь покупателя к заказу, как обратная сторона `belongsTo`
596
+ Объявление связи `referencesMany`
715
597
 
716
598
  ```js
717
599
  schema.defineModel({
718
- // ...
600
+ name: 'article',
719
601
  relations: {
720
- // ...
721
- order: {
722
- type: 'hasOne',
723
- model: 'order',
724
- foreignKey: 'customerId', // поле целевой модели
602
+ categories: { // название связи
603
+ type: RelationType.REFERENCES_MANY, // связь через массив идентификаторов
604
+ model: 'category', // название целевой модели
605
+ foreignKey: 'categoryIds', // внешний ключ (необязательно)
606
+ // если "foreignKey" не указан, то свойство внешнего
607
+ // ключа формируется согласно названию связи
608
+ // с добавлением постфикса "Ids"
725
609
  },
726
610
  },
727
611
  });
728
612
  ```
729
613
 
730
- Обратная сторона полиморфной версии `belongsTo`
614
+ Полиморфная версия `belongsTo`
731
615
 
732
616
  ```js
733
617
  schema.defineModel({
734
- // ...
618
+ name: 'file',
735
619
  relations: {
736
- // ...
737
- order: {
738
- type: 'hasOne',
739
- model: 'order',
740
- polymorphic: 'customer', // имя связи целевой модели
620
+ reference: { // название связи
621
+ type: RelationType.BELONGS_TO, // текущая модель ссылается на целевую
622
+ // полиморфный режим позволяет хранить название целевой модели
623
+ // в свойстве-дискриминаторе, которое формируется согласно
624
+ // названию связи с постфиксом "Type", и в данном случае
625
+ // название целевой модели хранит "referenceType",
626
+ // а идентификатор документа "referenceId"
627
+ polymorphic: true,
741
628
  },
742
629
  },
743
630
  });
744
631
  ```
745
632
 
746
- Явное указание `foreignKey` и `discriminator`
633
+ Полиморфная версия `belongsTo` с указанием свойств.
747
634
 
748
635
  ```js
749
636
  schema.defineModel({
750
- // ...
637
+ name: 'file',
751
638
  relations: {
752
- // ...
753
- order: {
754
- type: 'hasOne',
755
- model: 'order',
756
- polymorphic: true, // true вместо имени модели
757
- foreignKey: 'customerId', // поле целевой модели
758
- discriminator: 'customerType', // поле целевой модели
639
+ reference: { // название связи
640
+ type: RelationType.BELONGS_TO, // текущая модель ссылается на целевую
641
+ polymorphic: true, // название целевой модели хранит дискриминатор
642
+ foreignKey: 'referenceId', // свойство для идентификатора цели
643
+ discriminator: 'referenceType', // свойство для названия целевой модели
759
644
  },
760
645
  },
761
- });
646
+ })
762
647
  ```
763
648
 
764
- #### HasMany (one-to-many)
765
-
766
- Связь покупателя к заказам, как обратная сторона `belongsTo`
649
+ Полиморфная версия `hasMany` с указанием названия связи целевой модели.
767
650
 
768
651
  ```js
769
652
  schema.defineModel({
770
- // ...
653
+ name: 'letter',
771
654
  relations: {
772
- // ...
773
- orders: {
774
- type: 'hasMany',
775
- model: 'order',
776
- foreignKey: 'customerId', // поле целевой модели
655
+ attachments: { // название связи
656
+ type: RelationType.HAS_MANY, // целевая модель ссылается на текущую
657
+ model: 'file', // название целевой модели
658
+ polymorphic: 'reference', // название полиморфной связи целевой модели
777
659
  },
778
660
  },
779
- });
661
+ })
780
662
  ```
781
663
 
782
- Обратная сторона полиморфной версии `belongsTo`
664
+ Полиморфная версия `hasMany` с указанием свойств целевой модели.
783
665
 
784
666
  ```js
785
667
  schema.defineModel({
786
- // ...
668
+ name: 'letter',
787
669
  relations: {
788
- // ...
789
- orders: {
790
- type: 'hasMany',
791
- model: 'order',
792
- polymorphic: 'customer', // имя связи целевой модели
670
+ attachments: { // название связи
671
+ type: RelationType.HAS_MANY, // целевая модель ссылается на текущую
672
+ model: 'file', // название целевой модели
673
+ polymorphic: true, // название текущей модели находится в дискриминаторе
674
+ foreignKey: 'referenceId', // свойство целевой модели для идентификатора
675
+ discriminator: 'referenceType', // свойство целевой модели для названия текущей
793
676
  },
794
677
  },
795
- });
678
+ })
796
679
  ```
797
680
 
798
- Явное указание `foreignKey` и `discriminator`
681
+ ## Расширение
682
+
683
+ Метод `getRepository` экземпляра схемы проверяет наличие существующего
684
+ репозитория для указанной модели и возвращает его. В противном случае
685
+ создается новый экземпляр, который будет сохранен для последующих
686
+ обращений к методу.
799
687
 
800
688
  ```js
801
- schema.defineModel({
802
- // ...
803
- relations: {
804
- // ...
805
- orders: {
806
- type: 'hasMany',
807
- model: 'order',
808
- polymorphic: true, // true вместо имени модели
809
- foreignKey: 'customerId', // поле целевой модели
810
- discriminator: 'customerType', // поле целевой модели
811
- },
812
- },
813
- });
814
- ```
689
+ import {Schema} from '@e22m4u/js-repository';
690
+ import {Repository} from '@e22m4u/js-repository';
815
691
 
816
- #### ReferencesMany
692
+ // const schema = new Schema();
693
+ // schema.defineDatasource ...
694
+ // schema.defineModel ...
695
+
696
+ const rep1 = schema.getRepository('model');
697
+ const rep2 = schema.getRepository('model');
698
+ console.log(rep1 === rep2); // true
699
+ ```
817
700
 
818
- Связь покупателя к заказам через поле `orderIds`
701
+ Подмена стандартного конструктора репозитория выполняется методом
702
+ `setRepositoryCtor` сервиса `RepositoryRegistry`, который находится
703
+ в контейнере экземпляра схемы. После чего все новые репозитории будут
704
+ создаваться указанным конструктором вместо стандартного.
819
705
 
820
706
  ```js
707
+ import {Schema} from '@e22m4u/js-repository';
708
+ import {Repository} from '@e22m4u/js-repository';
709
+ import {RepositoryRegistry} from '@e22m4u/js-repository';
710
+
711
+ class MyRepository extends Repository {
712
+ /*...*/
713
+ }
714
+
715
+ // const schema = new Schema();
716
+ // schema.defineDatasource ...
717
+ // schema.defineModel ...
718
+
719
+ schema.get(RepositoryRegistry).setRepositoryCtor(MyRepository);
720
+ const rep = schema.getRepository('model');
721
+ console.log(rep instanceof MyRepository); // true
722
+ ```
723
+
724
+ *i. Так как экземпляры репозитория кэшируется, то замену конструктора
725
+ следует выполнять до обращения к методу `getRepository`.*
726
+
727
+ ## TypeScript
728
+
729
+ Получение типизированного репозитория с указанием интерфейса модели.
730
+
731
+ ```ts
732
+ import {Schema} from '@e22m4u/js-repository';
733
+ import {DataType} from '@e22m4u/js-repository';
734
+ import {RelationType} from '@e22m4u/js-repository';
735
+
736
+ // const schema = new Schema();
737
+ // schema.defineDatasource ...
738
+ // schema.defineModel ...
739
+
740
+ // определение модели "city"
821
741
  schema.defineModel({
822
- // ...
742
+ name: 'city',
743
+ datasource: 'myDatasource',
744
+ properties: {
745
+ title: DataType.STRING,
746
+ timeZone: DataType.STRING,
747
+ },
823
748
  relations: {
824
- // ...
825
- orders: {
826
- type: 'referencesMany',
827
- model: 'order',
828
- foreignKey: 'orderIds', // опционально
749
+ country: {
750
+ type: RelationType.BELONGS_TO,
751
+ model: 'country',
829
752
  },
830
753
  },
831
754
  });
755
+
756
+ // определение интерфейса "city"
757
+ interface City {
758
+ id: number;
759
+ title?: string;
760
+ timeZone?: string;
761
+ countryId?: number;
762
+ country?: Country;
763
+ // открыть свойства (опционально)
764
+ [property: string]: unknown;
765
+ }
766
+
767
+ // получаем репозиторий по названию модели
768
+ // указывая ее тип и тип идентификатора
769
+ const cityRep = schema.getRepository<City, number>('city');
832
770
  ```
833
771
 
834
772
  ## Тесты