@e22m4u/js-repository 0.5.7 → 0.6.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.
package/README.md CHANGED
@@ -19,6 +19,7 @@
19
19
  - [Регистрация глобальных трансформеров](#регистрация-глобальных-трансформеров)
20
20
  - [Локальные трансформеры](#локальные-трансформеры)
21
21
  - [Пустые значения](#пустые-значения)
22
+ - [Переопределение пустых значений](#переопределение-пустых-значений)
22
23
  - [Репозиторий](#репозиторий)
23
24
  - [Фильтрация](#фильтрация)
24
25
  - [Связи](#связи)
@@ -35,9 +36,9 @@ npm install @e22m4u/js-repository
35
36
 
36
37
  Опционально устанавливается нужный адаптер.
37
38
 
38
- | | описание |
39
+ | адаптер | описание |
39
40
  |-----------|--------------------------------------------------------------------------------------------------------------------------------|
40
- | `memory` | виртуальная база в памяти процесса (не требует установки) |
41
+ | `memory` | Виртуальная база в памяти процесса (не требует установки) |
41
42
  | `mongodb` | MongoDB - система управления NoSQL базами (*[установка](https://www.npmjs.com/package/@e22m4u/js-repository-mongodb-adapter))* |
42
43
 
43
44
  ## Импорт
@@ -907,6 +908,62 @@ dbs.defineModel({
907
908
  | `'array'` | `undefined`, `null`, `[]` |
908
909
  | `'object'` | `undefined`, `null`, `{}` |
909
910
 
911
+ ### Переопределение пустых значений
912
+
913
+ Набор пустых значений для любого типа данных можно переопределить. Управление
914
+ этими наборами осуществляется через специальный сервис, который предоставляет
915
+ модуль
916
+ [@e22m4u/js-empty-values](https://www.npmjs.com/package/@e22m4u/js-empty-values)
917
+ (не требует установки).
918
+
919
+ **EmptyValuesService**
920
+
921
+ Для переопределения пустых значений необходимо получить экземпляр класса
922
+ `EmptyValuesService` из контейнера схемы и вызвать метод, который принимает
923
+ тип данных и массив новых значений.
924
+
925
+ Интерфейс:
926
+
927
+ ```ts
928
+ class EmptyValuesService {
929
+ /**
930
+ * Установить пустые значения
931
+ * для определенного типа данных.
932
+ *
933
+ * @param dataType Тип данных.
934
+ * @param emptyValues Массив новых пустых значений.
935
+ */
936
+ setEmptyValuesOf(
937
+ dataType: DataType,
938
+ emptyValues: unknown[],
939
+ ): this;
940
+ }
941
+ ```
942
+
943
+ **Пример**
944
+
945
+ По умолчанию, для числовых свойств значение `0` считается пустым. Следующий
946
+ пример демонстрирует, как изменить это поведение, оставив в качестве пустых
947
+ значений только `undefined` и `null`.
948
+
949
+ ```js
950
+ import {DataType} from '@e22m4u/js-repository';
951
+ import {DatabaseSchema} from '@e22m4u/js-repository';
952
+ import {EmptyValuesService} from '@e22m4u/js-empty-values';
953
+
954
+ const dbs = new DatabaseSchema();
955
+
956
+ // получение сервиса для работы с пустыми значениями
957
+ const emptyValuesService = dbs.getService(EmptyValuesService);
958
+
959
+ // переопределение пустых значений для типа DataType.NUMBER
960
+ emptyValuesService.setEmptyValuesOf(DataType.NUMBER, [undefined, null]);
961
+ ```
962
+
963
+ После этого, значение `0` для свойств типа `DataType.NUMBER` больше не будет
964
+ считаться пустым и будет проходить проверки валидаторами, а также не будет
965
+ заменяться значением по умолчанию.
966
+
910
967
  ## Репозиторий
911
968
 
912
969
  Выполняет операции чтения и записи документов определенной модели.
@@ -930,9 +987,9 @@ dbs.defineModel({
930
987
  **Аргументы**
931
988
 
932
989
  - `id: number|string` идентификатор (первичный ключ)
933
- - `data: object` объект отражающий состав документа
934
- - `where: object` параметры выборки (см. [Фильтрация](#Фильтрация))
935
- - `filter: object` параметры возвращаемого результата (см. [Фильтрация](#Фильтрация))
990
+ - `data: object` данные документа (используется при записи)
991
+ - `where: object` условия фильтрации (см. [Фильтрация](#Фильтрация))
992
+ - `filter: object` параметры выборки (см. [Фильтрация](#Фильтрация))
936
993
 
937
994
  **Примеры**
938
995
 
@@ -986,12 +1043,72 @@ console.log(res); // true
986
1043
  имеет первый параметр метода `find`, где ожидается объект содержащий
987
1044
  набор опций указанных ниже.
988
1045
 
989
- - `where: object` объект выборки
990
- - `order: string[]` указание порядка
991
- - `limit: number` ограничение количества документов
992
- - `skip: number` пропуск документов
993
- - `fields: string[]` выбор необходимых свойств модели
994
- - `include: object` включение связанных данных в результат
1046
+ - `where: object` условия фильтрации по свойствам документа;
1047
+ - `order: string|string[]` сортировка по указанным свойствам;
1048
+ - `limit: number` ограничение количества документов;
1049
+ - `skip: number` пропуск документов (пагинация);
1050
+ - `fields: string|string[]` выбор необходимых свойств модели;
1051
+ - `include: object` включение связанных данных в результат;
1052
+
1053
+ **Примеры**
1054
+
1055
+ Объект фильтрации позволяет комбинировать различные опции для построения
1056
+ сложных запросов. Представим, что нам нужно выбрать из коллекции новости
1057
+ для отображения на сайте по следующему набору критериев:
1058
+
1059
+ - Заголовок должен содержать слово «Moscow» без учета регистра.
1060
+ - Дата публикации должна быть не ранее 15 октября 2025 года.
1061
+ - Новость должна содержать один из тегов «world» или «politic».
1062
+ - Новость не должна быть скрытой `hidden: false`.
1063
+ - Результат должен быть отсортирован по дате публикации.
1064
+ - Нужно пропустить первые 24 записи и выбрать следующие 12.
1065
+ - Выборка должна включать только поля `title`, `annotation` и `body`.
1066
+ - К каждой новости добавить связанные данные `author` и `category`.
1067
+
1068
+ ```js
1069
+ // для запроса используется метод репозитория "find"
1070
+ // с передачей объекта фильтрации первым аргументом
1071
+ const news = await newsRepository.find({
1072
+ where: {
1073
+ title: {regexp: 'Moscow', flags: 'i'},
1074
+ publishedAt: {gte: '2025-10-15T00:00:00.000Z'},
1075
+ tags: {inq: ['world', 'politic']},
1076
+ hidden: false,
1077
+ },
1078
+ order: 'publishedAt DESC',
1079
+ limit: 12,
1080
+ skip: 24,
1081
+ fields: ['title', 'annotation', 'body'],
1082
+ include: ['author', 'category'],
1083
+ })
1084
+ ```
1085
+
1086
+ Пример получения профиля пользователя со списком его последних постов.
1087
+
1088
+ - Найти пользователя по уникальному имени в свойстве `username`.
1089
+ - Из данных пользователя вернуть только имя, URL аватара и биографию.
1090
+ - Включить в результат 5 *опубликованных* постов данного пользователя.
1091
+ - Для каждого поста вернуть только заголовок и дату создания.
1092
+ - К каждому посту также прикрепить данные о категории.
1093
+
1094
+ ```js
1095
+ // для запроса используется метод "findOne"
1096
+ // и вложенные фильтры в опции "include"
1097
+ const userProfile = await userRepository.findOne({
1098
+ where: {username: 'john.doe'},
1099
+ fields: ['username', 'avatarUrl', 'bio'],
1100
+ include: {
1101
+ relation: 'posts',
1102
+ scope: {
1103
+ where: {status: 'published'},
1104
+ order: 'createdAt DESC',
1105
+ limit: 5,
1106
+ fields: ['title', 'createdAt'],
1107
+ include: 'category',
1108
+ },
1109
+ },
1110
+ });
1111
+ ```
995
1112
 
996
1113
  ### where
997
1114
 
@@ -1013,8 +1130,8 @@ console.log(res); // true
1013
1130
  `{foo: {ilike: 'BaR'}}` регистронезависимая версия `ilike`
1014
1131
  `{foo: {nlike: 'bar'}}` оператор исключения подстроки `nlike`
1015
1132
  `{foo: {nilike: 'BaR'}}` регистронезависимая версия `nilike`
1016
- `{foo: {regexp: 'ba.+'}}` оператор регулярного выражения `regexp`
1017
- `{foo: {regexp: 'ba.+', flags: 'i'}}` флаги регулярного выражения
1133
+ `{foo: {regexp: '^ba.+'}}` оператор регулярного выражения `regexp`
1134
+ `{foo: {regexp: '^ba.+', flags: 'i'}}` флаги регулярного выражения
1018
1135
 
1019
1136
  *i. Условия можно объединять операторами `and`, `or` и `nor`.*
1020
1137
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e22m4u/js-repository",
3
- "version": "0.5.7",
3
+ "version": "0.6.0",
4
4
  "description": "Реализация репозитория для работы с базами данных в Node.js",
5
5
  "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
6
6
  "license": "MIT",
@@ -41,12 +41,12 @@
41
41
  "dependencies": {
42
42
  "@e22m4u/js-empty-values": "~0.1.2",
43
43
  "@e22m4u/js-format": "~0.2.0",
44
- "@e22m4u/js-service": "~0.4.2"
44
+ "@e22m4u/js-service": "~0.4.3"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@commitlint/cli": "~20.1.0",
48
48
  "@commitlint/config-conventional": "~20.0.0",
49
- "@types/chai": "~5.2.2",
49
+ "@types/chai": "~5.2.3",
50
50
  "@types/chai-as-promised": "~8.0.2",
51
51
  "@types/chai-spies": "~1.0.6",
52
52
  "@types/mocha": "~10.0.10",
@@ -54,11 +54,11 @@
54
54
  "chai": "~6.2.0",
55
55
  "chai-as-promised": "~8.0.2",
56
56
  "chai-spies": "~1.1.0",
57
- "esbuild": "~0.25.10",
58
- "eslint": "~9.37.0",
57
+ "esbuild": "~0.25.11",
58
+ "eslint": "~9.38.0",
59
59
  "eslint-config-prettier": "~10.1.8",
60
60
  "eslint-plugin-chai-expect": "~3.1.0",
61
- "eslint-plugin-jsdoc": "~61.0.0",
61
+ "eslint-plugin-jsdoc": "~61.1.5",
62
62
  "eslint-plugin-mocha": "~11.2.0",
63
63
  "husky": "~9.1.7",
64
64
  "mocha": "~11.7.4",
@@ -66,6 +66,6 @@
66
66
  "rimraf": "~6.0.1",
67
67
  "ts-node": "~10.9.2",
68
68
  "typescript": "~5.9.3",
69
- "typescript-eslint": "~8.46.0"
69
+ "typescript-eslint": "~8.46.2"
70
70
  }
71
71
  }
@@ -1,19 +1,24 @@
1
+ import {ModelData} from '../types.js';
2
+
1
3
  /**
2
4
  * Filter clause.
3
5
  */
4
- export declare type FilterClause = {
5
- where?: WhereClause;
6
- order?: OrderClause;
6
+ export declare type FilterClause<M extends object = ModelData> = {
7
+ where?: WhereClause<M>;
8
+ order?: OrderClause<M>;
7
9
  limit?: number;
8
10
  skip?: number;
9
- fields?: FieldsClause;
10
- include?: IncludeClause;
11
+ fields?: FieldsClause<M>;
12
+ include?: IncludeClause<M>;
11
13
  };
12
14
 
13
15
  /**
14
16
  * Item filter clause.
15
17
  */
16
- export declare type ItemFilterClause = Pick<FilterClause, 'fields' | 'include'>;
18
+ export declare type ItemFilterClause<M extends object = ModelData> = Pick<
19
+ FilterClause<M>,
20
+ 'fields' | 'include'
21
+ >;
17
22
 
18
23
  /**
19
24
  * Where clause.
@@ -42,10 +47,21 @@ export declare type ItemFilterClause = Pick<FilterClause, 'fields' | 'include'>;
42
47
  * {or: [...]}
43
48
  * ```
44
49
  */
45
- export declare type WhereClause =
46
- & Partial<AndClause>
47
- & Partial<OrClause>
48
- & PropertiesClause;
50
+ export declare type WhereClause<M extends object = ModelData> = Partial<
51
+ AndClause<M>
52
+ > &
53
+ Partial<OrClause<M>> &
54
+ PropertiesClause<M>;
55
+
56
+ /**
57
+ * Primitive values.
58
+ */
59
+ export declare type PrimitiveValue =
60
+ | string
61
+ | number
62
+ | boolean
63
+ | null
64
+ | undefined;
49
65
 
50
66
  /**
51
67
  * Properties clause.
@@ -59,16 +75,8 @@ export declare type WhereClause =
59
75
  * }
60
76
  * ```
61
77
  */
62
- export type PropertiesClause = {
63
- [property: string]:
64
- | OperatorClause
65
- | string
66
- | number
67
- | boolean
68
- | RegExp
69
- | null
70
- | undefined
71
- | object;
78
+ export declare type PropertiesClause<M extends object = ModelData> = {
79
+ [property in keyof M]?: OperatorClause | PrimitiveValue | RegExp;
72
80
  };
73
81
 
74
82
  /**
@@ -95,14 +103,14 @@ export type PropertiesClause = {
95
103
  * ```
96
104
  */
97
105
  export declare type OperatorClause = {
98
- eq?: unknown;
99
- neq?: unknown;
106
+ eq?: PrimitiveValue;
107
+ neq?: PrimitiveValue;
100
108
  gt?: string | number;
101
109
  gte?: string | number;
102
110
  lt?: string | number;
103
111
  lte?: string | number;
104
- inq?: unknown[];
105
- nin?: unknown[];
112
+ inq?: PrimitiveValue[];
113
+ nin?: PrimitiveValue[];
106
114
  between?: readonly [string | number, string | number];
107
115
  exists?: boolean;
108
116
  like?: string | RegExp;
@@ -123,8 +131,8 @@ export declare type OperatorClause = {
123
131
  * }
124
132
  * ```
125
133
  */
126
- export interface AndClause {
127
- and: WhereClause[];
134
+ export interface AndClause<M extends object = ModelData> {
135
+ and: WhereClause<M>[];
128
136
  }
129
137
 
130
138
  /**
@@ -137,10 +145,24 @@ export interface AndClause {
137
145
  * }
138
146
  * ```
139
147
  */
140
- export interface OrClause {
141
- or: WhereClause[];
148
+ export interface OrClause<M extends object = ModelData> {
149
+ or: WhereClause<M>[];
142
150
  }
143
151
 
152
+ /**
153
+ * Order clause item.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * 'prop'
158
+ * 'prop ASC'
159
+ * 'prop DESC';
160
+ * ```
161
+ */
162
+ export declare type OrderClauseItem<M extends object = ModelData> = {
163
+ [prop in keyof M]: prop | `${prop & string} ASC` | `${prop & string} DESC`;
164
+ }[keyof M];
165
+
144
166
  /**
145
167
  * Order clause.
146
168
  *
@@ -153,7 +175,9 @@ export interface OrClause {
153
175
  * ['prop1 ASC', 'prop2 DESC'];
154
176
  * ```
155
177
  */
156
- export type OrderClause = string | string[];
178
+ export declare type OrderClause<M extends object = ModelData> =
179
+ | OrderClauseItem<M>
180
+ | OrderClauseItem<M>[];
157
181
 
158
182
  /**
159
183
  * Fields.
@@ -164,7 +188,9 @@ export type OrderClause = string | string[];
164
188
  * ['prop1', 'prop2']
165
189
  * ```
166
190
  */
167
- export type FieldsClause = string | NormalizedFieldsClause;
191
+ export declare type FieldsClause<M extends object = ModelData> =
192
+ | keyof M
193
+ | NormalizedFieldsClause<M>;
168
194
 
169
195
  /**
170
196
  * Normalized fields clause.
@@ -177,7 +203,8 @@ export type FieldsClause = string | NormalizedFieldsClause;
177
203
  * ]
178
204
  * ```
179
205
  */
180
- export type NormalizedFieldsClause = string[];
206
+ export declare type NormalizedFieldsClause<M extends object = ModelData> =
207
+ (keyof M)[];
181
208
 
182
209
  /**
183
210
  * Include clause.
@@ -236,10 +263,11 @@ export type NormalizedFieldsClause = string[];
236
263
  * }
237
264
  * ```
238
265
  */
239
- export declare type IncludeClause =
240
- | string
241
- | NestedIncludeClause
242
- | NormalizedIncludeClause
266
+ export declare type IncludeClause<M extends object = ModelData> =
267
+ | keyof M
268
+ | (keyof M)[]
269
+ | NestedIncludeClause<M>
270
+ | NormalizedIncludeClause<M>
243
271
  | IncludeClause[];
244
272
 
245
273
  /**
@@ -286,8 +314,8 @@ export declare type IncludeClause =
286
314
  * }
287
315
  * ```
288
316
  */
289
- export declare type NestedIncludeClause = {
290
- [property: string]: IncludeClause;
317
+ export declare type NestedIncludeClause<M extends object = ModelData> = {
318
+ [property in keyof M]?: IncludeClause;
291
319
  };
292
320
 
293
321
  /**
@@ -315,7 +343,7 @@ export declare type NestedIncludeClause = {
315
343
  * }
316
344
  * ```
317
345
  */
318
- export declare type NormalizedIncludeClause = {
319
- relation: string;
346
+ export declare interface NormalizedIncludeClause<M extends object = ModelData> {
347
+ relation: keyof M;
320
348
  scope?: FilterClause;
321
- };
349
+ }
@@ -17,7 +17,7 @@ export declare class Repository<
17
17
  Data extends object = ModelData,
18
18
  IdType extends ModelId = ModelId,
19
19
  IdName extends string = typeof DEFAULT_PRIMARY_KEY_PROPERTY_NAME,
20
- FlatData extends ModelData = Flatten<Data>,
20
+ FlatData extends object = Flatten<Data>,
21
21
  > extends Service {
22
22
  // it fixes unused generic bug
23
23
  private _Data?: Data;
@@ -56,7 +56,7 @@ export declare class Repository<
56
56
  */
57
57
  create(
58
58
  data: OptionalUnlessRequiredId<IdName, FlatData>,
59
- filter?: ItemFilterClause,
59
+ filter?: ItemFilterClause<FlatData>,
60
60
  ): Promise<FlatData>;
61
61
 
62
62
  /**
@@ -68,8 +68,8 @@ export declare class Repository<
68
68
  */
69
69
  replaceById(
70
70
  id: IdType,
71
- data: WithoutId<IdName, FlatData>,
72
- filter?: ItemFilterClause,
71
+ data: WithoutId<FlatData, IdName>,
72
+ filter?: ItemFilterClause<FlatData>,
73
73
  ): Promise<FlatData>;
74
74
 
75
75
  /**
@@ -79,8 +79,8 @@ export declare class Repository<
79
79
  * @param filter
80
80
  */
81
81
  replaceOrCreate(
82
- data: OptionalUnlessRequiredId<IdName, Data>,
83
- filter?: ItemFilterClause,
82
+ data: OptionalUnlessRequiredId<IdName, FlatData>,
83
+ filter?: ItemFilterClause<FlatData>,
84
84
  ): Promise<FlatData>;
85
85
 
86
86
  /**
@@ -90,8 +90,8 @@ export declare class Repository<
90
90
  * @param where
91
91
  */
92
92
  patch(
93
- data: PartialWithoutId<IdName, Data>,
94
- where?: WhereClause,
93
+ data: PartialWithoutId<FlatData, IdName>,
94
+ where?: WhereClause<FlatData>,
95
95
  ): Promise<number>;
96
96
 
97
97
  /**
@@ -103,8 +103,8 @@ export declare class Repository<
103
103
  */
104
104
  patchById(
105
105
  id: IdType,
106
- data: PartialWithoutId<IdName, Data>,
107
- filter?: ItemFilterClause,
106
+ data: PartialWithoutId<FlatData, IdName>,
107
+ filter?: ItemFilterClause<FlatData>,
108
108
  ): Promise<FlatData>;
109
109
 
110
110
  /**
@@ -112,14 +112,14 @@ export declare class Repository<
112
112
  *
113
113
  * @param filter
114
114
  */
115
- find(filter?: FilterClause): Promise<FlatData[]>;
115
+ find(filter?: FilterClause<FlatData>): Promise<FlatData[]>;
116
116
 
117
117
  /**
118
118
  * Find one.
119
119
  *
120
120
  * @param filter
121
121
  */
122
- findOne(filter?: FilterClause): Promise<FlatData | undefined>;
122
+ findOne(filter?: FilterClause<FlatData>): Promise<FlatData | undefined>;
123
123
 
124
124
  /**
125
125
  * Find by id.
@@ -127,14 +127,14 @@ export declare class Repository<
127
127
  * @param id
128
128
  * @param filter
129
129
  */
130
- findById(id: IdType, filter?: ItemFilterClause): Promise<FlatData>;
130
+ findById(id: IdType, filter?: ItemFilterClause<FlatData>): Promise<FlatData>;
131
131
 
132
132
  /**
133
133
  * Delete.
134
134
  *
135
135
  * @param where
136
136
  */
137
- delete(where?: WhereClause): Promise<number>;
137
+ delete(where?: WhereClause<FlatData>): Promise<number>;
138
138
 
139
139
  /**
140
140
  * Delete by id.
@@ -155,27 +155,29 @@ export declare class Repository<
155
155
  *
156
156
  * @param where
157
157
  */
158
- count(where?: WhereClause): Promise<number>;
158
+ count(where?: WhereClause<FlatData>): Promise<number>;
159
159
  }
160
160
 
161
161
  /**
162
162
  * Removes id field.
163
163
  */
164
- type WithoutId<IdName extends string, Data extends object> = Flatten<
165
- Omit<Data, IdName>
166
- >;
164
+ export declare type WithoutId<
165
+ Data extends object,
166
+ IdName extends string = 'id',
167
+ > = Flatten<Omit<Data, IdName>>;
167
168
 
168
169
  /**
169
170
  * Makes fields as optional and remove id field.
170
171
  */
171
- type PartialWithoutId<IdName extends string, Data extends object> = Flatten<
172
- Partial<Omit<Data, IdName>>
173
- >;
172
+ export declare type PartialWithoutId<
173
+ Data extends object,
174
+ IdName extends string = 'id',
175
+ > = Flatten<Partial<Omit<Data, IdName>>>;
174
176
 
175
177
  /**
176
178
  * Makes the required id field as optional.
177
179
  */
178
- type OptionalUnlessRequiredId<
180
+ export declare type OptionalUnlessRequiredId<
179
181
  IdName extends string,
180
182
  Data extends object,
181
183
  > = Flatten<Data extends {[K in IdName]: any} ? PartialBy<Data, IdName> : Data>;
package/src/types.d.ts CHANGED
@@ -14,9 +14,7 @@ export declare type PartialBy<T, K extends keyof T> = Omit<T, K> &
14
14
  /**
15
15
  * Model data.
16
16
  */
17
- export declare type ModelData = {
18
- [property: string]: unknown;
19
- };
17
+ export declare type ModelData = Record<string, unknown>;
20
18
 
21
19
  /**
22
20
  * Model id.