@kollors/deep-json-server 1.0.0-alpha.8 → 1.0.0-beta.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.
Files changed (112) hide show
  1. package/README.md +279 -217
  2. package/README.ru.md +282 -220
  3. package/dist/index.d.ts +1 -2
  4. package/dist/src/auth/contract.d.ts +3 -2
  5. package/dist/src/auth/contract.js.map +1 -1
  6. package/dist/src/auth/service.js +1 -1
  7. package/dist/src/auth/service.js.map +1 -1
  8. package/dist/src/auth/store.d.ts +1 -1
  9. package/dist/src/auth/store.js.map +1 -1
  10. package/dist/src/cli/index.d.ts +2 -2
  11. package/dist/src/cli/index.js +59 -80
  12. package/dist/src/cli/index.js.map +1 -1
  13. package/dist/src/core/constants.d.ts +1 -1
  14. package/dist/src/core/constants.js +1 -1
  15. package/dist/src/core/constants.js.map +1 -1
  16. package/dist/src/core/database.d.ts +6 -11
  17. package/dist/src/core/database.js +2 -2
  18. package/dist/src/core/database.js.map +1 -1
  19. package/dist/src/core/engine.d.ts +3 -15
  20. package/dist/src/core/engine.js +10 -69
  21. package/dist/src/core/engine.js.map +1 -1
  22. package/dist/src/core/lifecycle/mutation.js +4 -4
  23. package/dist/src/core/lifecycle/mutation.js.map +1 -1
  24. package/dist/src/core/lifecycle/options.d.ts +15 -0
  25. package/dist/src/core/lifecycle/options.js +13 -0
  26. package/dist/src/core/lifecycle/options.js.map +1 -1
  27. package/dist/src/core/model.d.ts +17 -2
  28. package/dist/src/core/model.js +28 -7
  29. package/dist/src/core/model.js.map +1 -1
  30. package/dist/src/core/mutations/write.js +4 -0
  31. package/dist/src/core/mutations/write.js.map +1 -1
  32. package/dist/src/core/paths.d.ts +1 -1
  33. package/dist/src/core/paths.js +5 -5
  34. package/dist/src/core/paths.js.map +1 -1
  35. package/dist/src/core/query/execute.d.ts +25 -0
  36. package/dist/src/core/query/execute.js +82 -0
  37. package/dist/src/core/query/execute.js.map +1 -0
  38. package/dist/src/core/query/filter.js +1 -1
  39. package/dist/src/core/query/filter.js.map +1 -1
  40. package/dist/src/core/query/options.js +2 -2
  41. package/dist/src/core/query/options.js.map +1 -1
  42. package/dist/src/core/records.d.ts +2 -1
  43. package/dist/src/core/records.js +5 -2
  44. package/dist/src/core/records.js.map +1 -1
  45. package/dist/src/core/storage.d.ts +2 -0
  46. package/dist/src/core/storage.js +2 -0
  47. package/dist/src/core/storage.js.map +1 -0
  48. package/dist/src/files/contract.d.ts +6 -7
  49. package/dist/src/files/contract.js.map +1 -1
  50. package/dist/src/files/disk-metadata.d.ts +9 -0
  51. package/dist/src/files/disk-metadata.js +44 -0
  52. package/dist/src/files/disk-metadata.js.map +1 -0
  53. package/dist/src/files/disk-paths.d.ts +15 -0
  54. package/dist/src/files/disk-paths.js +100 -0
  55. package/dist/src/files/disk-paths.js.map +1 -0
  56. package/dist/src/files/disk-store.js +21 -165
  57. package/dist/src/files/disk-store.js.map +1 -1
  58. package/dist/src/files/index.d.ts +1 -1
  59. package/dist/src/files/index.js +5 -2
  60. package/dist/src/files/index.js.map +1 -1
  61. package/dist/src/files/memory-store.js +3 -3
  62. package/dist/src/files/memory-store.js.map +1 -1
  63. package/dist/src/graphql/generate.d.ts +3 -2
  64. package/dist/src/graphql/generate.js +3 -1
  65. package/dist/src/graphql/generate.js.map +1 -1
  66. package/dist/src/graphql/preflight.js +2 -0
  67. package/dist/src/graphql/preflight.js.map +1 -1
  68. package/dist/src/graphql/resolvers.js +7 -3
  69. package/dist/src/graphql/resolvers.js.map +1 -1
  70. package/dist/src/graphql/routes.js +4 -1
  71. package/dist/src/graphql/routes.js.map +1 -1
  72. package/dist/src/graphql/schema.js +5 -2
  73. package/dist/src/graphql/schema.js.map +1 -1
  74. package/dist/src/openapi/document.js +30 -19
  75. package/dist/src/openapi/document.js.map +1 -1
  76. package/dist/src/openapi/generate.js +3 -1
  77. package/dist/src/openapi/generate.js.map +1 -1
  78. package/dist/src/openapi/options.d.ts +0 -2
  79. package/dist/src/openapi/registry.d.ts +11 -0
  80. package/dist/src/openapi/registry.js +20 -0
  81. package/dist/src/openapi/registry.js.map +1 -0
  82. package/dist/src/rest/options.d.ts +11 -3
  83. package/dist/src/rest/options.js +15 -0
  84. package/dist/src/rest/options.js.map +1 -1
  85. package/dist/src/rest/projection.d.ts +15 -3
  86. package/dist/src/rest/projection.js +49 -15
  87. package/dist/src/rest/projection.js.map +1 -1
  88. package/dist/src/rest/routes.js +18 -7
  89. package/dist/src/rest/routes.js.map +1 -1
  90. package/dist/src/server/bootstrap.d.ts +7 -0
  91. package/dist/src/server/bootstrap.js +48 -0
  92. package/dist/src/server/bootstrap.js.map +1 -0
  93. package/dist/src/server/config.d.ts +28 -30
  94. package/dist/src/server/config.js +82 -169
  95. package/dist/src/server/config.js.map +1 -1
  96. package/dist/src/server/create.d.ts +3 -5
  97. package/dist/src/server/create.js +24 -89
  98. package/dist/src/server/create.js.map +1 -1
  99. package/dist/src/server/features.d.ts +0 -11
  100. package/dist/src/server/features.js +0 -20
  101. package/dist/src/server/features.js.map +1 -1
  102. package/dist/src/server/http.d.ts +12 -0
  103. package/dist/src/server/http.js +40 -0
  104. package/dist/src/server/http.js.map +1 -0
  105. package/dist/src/server/model.d.ts +6 -0
  106. package/dist/src/server/model.js +14 -0
  107. package/dist/src/server/model.js.map +1 -0
  108. package/dist/src/server/openapi-options.d.ts +6 -0
  109. package/dist/src/server/openapi-options.js +15 -0
  110. package/dist/src/server/openapi-options.js.map +1 -0
  111. package/dist/src/server/public.d.ts +1 -2
  112. package/package.json +3 -3
package/README.ru.md CHANGED
@@ -4,15 +4,15 @@
4
4
 
5
5
  JSON-сервер для имитации API: REST, GraphQL, связанные записи, загрузка файлов и экспорт схем. Поддерживает вход пользователей, права владельца и администратора, даты записей и мягкое удаление. Требуется Node.js 22 или новее.
6
6
 
7
- **1.0.0-alpha.8 предварительная версия.** REST-запросы используют `scope=[поля, аргументы?]` на всех уровнях. При обновлении измените параметры запросов по примерам ниже; для перехода с 0.x также нужна новая схема моделей.
7
+ **Breaking changes: 1.0.0-beta.1.** `*` в REST `scope` теперь выбирает только скалярные поля. Массивы, объекты и связи указываются явно. При включённом auth права на запись доступны через виртуальное поле `actions`.
8
8
 
9
9
  ## Установка
10
10
 
11
11
  ```sh
12
- npm install @kollors/deep-json-server@alpha
12
+ npm install @kollors/deep-json-server@beta
13
13
  ```
14
14
 
15
- Для установки конкретной версии укажите `@1.0.0-alpha.8`.
15
+ Для установки конкретной версии укажите `@1.0.0-beta.1`.
16
16
 
17
17
  ## Быстрый старт
18
18
 
@@ -31,9 +31,7 @@ npm install @kollors/deep-json-server@alpha
31
31
  `server.config.js`:
32
32
 
33
33
  ```js
34
- export default {
35
- database: { path: './database.json' },
36
- };
34
+ export default { storage: 'file', database: { source: './database.json' } };
37
35
  ```
38
36
 
39
37
  ```sh
@@ -46,47 +44,53 @@ npx deep-json-server server.config.js
46
44
 
47
45
  ## Конфигурация
48
46
 
49
- | Ключ | Назначение |
47
+ При `storage: 'file'` все источники и схема задаются путями, при `'memory'` — данными в памяти. Режимы нельзя смешивать. Наличие секций `auth`, `files`, `graphql` и `openapi` включает соответствующие модули. `graphql: {}` и `openapi: {}` включают только HTTP-маршруты со стандартными адресами; для экспорта добавьте `target`.
48
+
49
+ ```js
50
+ export default {
51
+ storage: 'file',
52
+ database: { source: './database.json', schema: './schema.json' },
53
+ auth: { source: './users.json', expiresIn: 3600 },
54
+ files: { source: './uploads' },
55
+ graphql: { target: './generated/schema.graphql' },
56
+ openapi: { target: './generated/openapi.yaml' },
57
+ server: { host: '127.0.0.1', port: 4001 },
58
+ };
59
+ ```
60
+
61
+ | Настройка | Назначение |
50
62
  |---|---|
51
- | `database.path` / `database.data` | Для запуска укажите один вариант: JSON-файл или объект коллекций в памяти |
52
- | `database.schema` | Объект моделей или путь к JSON-файлу; необязателен для REST |
53
- | `database.timestamps` | Добавить даты создания и изменения; по умолчанию `false` |
54
- | `database.softDelete` | Сохранять удалённые записи с возможностью восстановления; по умолчанию `false` |
55
- | `openapi.enabled` | Включить HTTP-маршрут спецификации; по умолчанию `false` |
56
- | `openapi.endpoint` | Путь спецификации; по умолчанию `/openapi.json` |
57
- | `openapi.path` | Путь экспорта YAML |
58
- | `openapi.info` | Необязательный объект метаданных: обязательные `title` и `version`, необязательный `description` |
59
- | `graphql.enabled` | Включить GraphQL HTTP API; по умолчанию `false` |
60
- | `graphql.endpoint` | Путь GraphQL; по умолчанию `/graphql` |
61
- | `graphql.path` | Путь экспорта GraphQL SDL |
62
- | `auth.users` | Путь к JSON-массиву учётных записей или массив в памяти |
63
- | `auth.expiresIn` | Срок действия сессии в секундах; по умолчанию 3600 |
63
+ | `storage` | Обязательный режим: `file` или `memory`; общий для всех источников и схемы |
64
+ | `database.source` | Путь к JSON базы или объект коллекций |
65
+ | `database.schema` | Путь к JSON схемы или объект схемы; необязателен для REST |
66
+ | `auth.source` | Путь к JSON пользователей или массив пользователей |
67
+ | `auth.expiresIn` | Срок сессии в секундах; по умолчанию 3600 |
68
+ | `files.source` | Каталог файлов или массив начальных файлов |
69
+ | `files.metadata` | Только для `file`: путь JSON метаданных; по умолчанию `.files.json` внутри `files.source` |
70
+ | `graphql.endpoint` | HTTP-маршрут; по умолчанию `/graphql` |
71
+ | `graphql.target` | Файл для экспорта GraphQL SDL |
72
+ | `openapi.endpoint` | HTTP-маршрут; по умолчанию `/openapi.json` |
73
+ | `openapi.target` | Файл для экспорта OpenAPI |
74
+ | `openapi.info` | Метаданные: обязательные `title`, `version`, необязательный `description` |
64
75
  | `server.host`, `server.port` | По умолчанию `127.0.0.1`, `4001`; CLI также читает `HOST`/`PORT` |
65
76
  | `server.pageSize`, `server.maxPageSize` | По умолчанию 10 и 100; размер по умолчанию ограничен максимумом |
66
- | `server.cors`, `server.logger` | По умолчанию `true`; logger также принимает настройки Fastify |
77
+ | `server.cors`, `server.logger` | По умолчанию `true`; logger принимает также настройки Fastify |
67
78
  | `server.maxFileSize` | По умолчанию 100 МиБ |
68
- | `files.data` | Бинарные файлы в памяти |
69
- | `files.directory`, `files.metadata` | Каталог файлов и JSON метаданных; необходимы оба |
70
-
71
- Относительные пути отсчитываются от каталога конфигурационного файла. При вызове `createServer()` с объектом конфигурации — от рабочего каталога. Сервер работает с копией переданных данных в памяти и метаданных.
72
79
 
73
- Значение `server.port: 0` позволяет системе выбрать свободный порт. OpenAPI на HTTP-эндпоинте использует относительный адрес сервера.
80
+ Относительные пути разрешаются от каталога файла конфигурации; при вызове `createServer(config)` — от рабочего каталога. Данные в памяти, включая схему, копируются. Порт `0` позволяет системе выбрать свободный порт.
74
81
 
75
82
  ### CLI
76
83
 
77
- | Флаг CLI | Действие |
84
+ | Флаг | Действие |
78
85
  |---|---|
79
- | `--files` | Включить файловые маршруты; нужна секция `files` |
80
- | `--timestamps` | Включить даты записей глобально |
81
- | `--soft-delete` | Включить мягкое удаление глобально |
82
- | `--graphql` | Включить GraphQL API |
83
- | `--openapi` | Включить HTTP-маршрут OpenAPI |
86
+ | `--generate` | Экспортировать схемы и запустить сервер |
87
+ | `--generate-only` | Экспортировать схемы и завершить работу |
84
88
  | `--host <host>` | Адрес сервера |
85
89
  | `--port <port>` | Порт сервера |
86
- | `--help`, `-h` | Справка |
87
- | `--version`, `-v` | Версия пакета |
90
+ | `--help, -h` | Справка |
91
+ | `--version, -v` | Версия пакета |
88
92
 
89
- Приоритет адреса и порта: CLI → конфигурация → `HOST`/`PORT` → значения по умолчанию. Файловые маршруты включаются при наличии секции `files`, а вход и проверка прав — при наличии `auth`. Настройки модели могут переопределять глобальные `timestamps` и `softDelete`.
93
+ Приоритет адреса и порта: CLI → конфигурация → `HOST`/`PORT` → значения по умолчанию. Без флагов генерации запускается только сервер. `--generate` и `--generate-only` нельзя передавать вместе.
90
94
 
91
95
  ## Схема моделей
92
96
 
@@ -94,28 +98,52 @@ npx deep-json-server server.config.js
94
98
 
95
99
  ```json
96
100
  {
97
- "Country": {
98
- "collection": "countries",
99
- "api": ["openapi", "graphql"],
100
- "fields": {
101
- "id": { "type": "string", "primary": true, "generated": "uuid" },
102
- "name": { "type": "string", "required": true },
103
- "users": { "type": "User[]", "target": "countryId" }
104
- }
105
- },
106
- "User": {
107
- "collection": "users",
108
- "api": ["openapi", "graphql"],
109
- "fields": {
110
- "id": { "type": "string", "primary": true, "generated": "uuid" },
111
- "fullName": { "type": "string", "required": true },
112
- "country": { "type": "Country", "source": "countryId" }
101
+ "api": [
102
+ "openapi",
103
+ "graphql"
104
+ ],
105
+ "models": {
106
+ "Country": {
107
+ "collection": "countries",
108
+ "fields": {
109
+ "id": {
110
+ "type": "string",
111
+ "primary": true,
112
+ "generated": "uuid"
113
+ },
114
+ "name": {
115
+ "type": "string",
116
+ "required": true
117
+ },
118
+ "users": {
119
+ "type": "User[]",
120
+ "target": "countryId"
121
+ }
122
+ }
123
+ },
124
+ "User": {
125
+ "collection": "users",
126
+ "fields": {
127
+ "id": {
128
+ "type": "string",
129
+ "primary": true,
130
+ "generated": "uuid"
131
+ },
132
+ "fullName": {
133
+ "type": "string",
134
+ "required": true
135
+ },
136
+ "country": {
137
+ "type": "Country",
138
+ "source": "countryId"
139
+ }
140
+ }
113
141
  }
114
142
  }
115
143
  }
116
144
  ```
117
145
 
118
- По умолчанию `api` содержит `["openapi", "graphql"]`. Пустой массив исключает модель из экспорта и GraphQL, но REST продолжает работать. Связанные модели должны разрешать тот же формат экспорта. Имена должны быть допустимыми идентификаторами; конфликты генерируемых типов и операций вызывают ошибку. Имена `and`, `or`, `not` зарезервированы фильтрами.
146
+ Модели находятся в `models`. В корне схемы можно задать `api`, `timestamps` и `softDelete`; у модели эти параметры переопределяют общие значения. Для `api` приоритет такой: модель → корень схемы → секции конфигурации. Массив заменяется целиком; `[]` исключает модель из GraphQL и OpenAPI, но REST продолжает работать. Явный список не включает отсутствующий модуль. Связанные модели должны разрешать тот же формат. Имена моделей должны быть допустимыми идентификаторами; конфликты типов и операций вызывают ошибку. Имена `and`, `or`, `not` зарезервированы фильтрами.
119
147
 
120
148
  | Возможность | Со схемой | Без схемы |
121
149
  |---|---|---|
@@ -126,7 +154,7 @@ npx deep-json-server server.config.js
126
154
 
127
155
  При явной схеме неизвестные поля и коллекции запрещены. Исключение — хранимые ключи, выведенные из связей. Исходная база проверяется при загрузке. Генерация использует описание моделей.
128
156
 
129
- REST без схемы создаёт ключ `id` и сохраняет произвольные JSON-поля. Для фильтров и выбора отдельных полей используются имена в формате идентификаторов; остальные поля возвращаются через `scope=[{"*":true}]`. Поля с разными типами значений можно читать, но фильтрация, сортировка и пагинация неоднородных списков требуют явной схемы.
157
+ REST без схемы создаёт ключ `id` и сохраняет произвольные JSON-поля. Для фильтров и выбора отдельных полей используются имена в формате идентификаторов. `scope=[{"*":true}]` возвращает скаляры верхнего уровня; массивы и объекты выбираются по имени. Поля с разными типами значений можно запросить явно, но фильтрация, сортировка и пагинация неоднородных списков требуют явной схемы.
130
158
 
131
159
  У модели обязательны `collection` — имя коллекции в базе и REST-пути — и `fields` — описание полей. Имя модели (`User`) задаёт имена типов и операций GraphQL. `api` управляет доступностью форматов, а `timestamps` и `softDelete` переопределяют глобальные настройки для этой модели.
132
160
 
@@ -184,6 +212,110 @@ REST без схемы создаёт ключ `id` и сохраняет про
184
212
 
185
213
  Каскадное удаление выполняется целиком, включая циклические связи. Ошибка проверки отменяет всю операцию. Правила `onDelete` действуют и на явно объявленные обратные связи; при описании обоих направлений учитывайте оба правила.
186
214
 
215
+ ## Пример базы данных
216
+
217
+ ```json
218
+ {
219
+ "countries": [
220
+ {
221
+ "id": "1",
222
+ "isArchived": false,
223
+ "name": "Ардения"
224
+ },
225
+ {
226
+ "id": "2",
227
+ "isArchived": false,
228
+ "name": "Велория"
229
+ }
230
+ ],
231
+ "genres": [
232
+ {
233
+ "id": "1",
234
+ "isArchived": false,
235
+ "name": "Криминал",
236
+ "parentIds": []
237
+ },
238
+ {
239
+ "id": "2",
240
+ "isArchived": false,
241
+ "name": "Гангстер",
242
+ "parentIds": ["1"]
243
+ },
244
+ {
245
+ "id": "3",
246
+ "isArchived": false,
247
+ "name": "Драма",
248
+ "parentIds": []
249
+ },
250
+ {
251
+ "id": "4",
252
+ "isArchived": false,
253
+ "name": "Комедия",
254
+ "parentIds": []
255
+ }
256
+ ],
257
+ "movies": [
258
+ {
259
+ "actors": [
260
+ {
261
+ "genreIds": ["2", "3"],
262
+ "id": "movie-1-actor-1",
263
+ "userId": "1"
264
+ },
265
+ {
266
+ "genreIds": ["3"],
267
+ "id": "movie-1-actor-2",
268
+ "userId": "2"
269
+ }
270
+ ],
271
+ "coverSrc": "https://example.com/covers/shadows-of-ardenia.jpg",
272
+ "description": "Наследница портового города раскрывает заговор двух соперничающих семей.",
273
+ "id": "1",
274
+ "isArchived": false,
275
+ "publisherIds": ["2"],
276
+ "title": "Тени Ардении"
277
+ },
278
+ {
279
+ "actors": [],
280
+ "coverSrc": "https://example.com/covers/northern-star.jpg",
281
+ "description": "Ночной администратор старого отеля случайно становится участником поисков пропавшей картины.",
282
+ "id": "2",
283
+ "isArchived": false,
284
+ "publisherIds": ["1"],
285
+ "title": "Полночь в «Северной звезде»"
286
+ }
287
+ ],
288
+ "publishers": [
289
+ {
290
+ "id": "1",
291
+ "isArchived": false,
292
+ "name": "Northlight Studio"
293
+ },
294
+ {
295
+ "id": "2",
296
+ "isArchived": false,
297
+ "name": "Aurora Pictures"
298
+ }
299
+ ],
300
+ "users": [
301
+ {
302
+ "bornAt": "1988-03-14",
303
+ "countryId": "1",
304
+ "fullName": "Мира Волкова",
305
+ "id": "1",
306
+ "isArchived": false
307
+ },
308
+ {
309
+ "bornAt": "1991-11-02",
310
+ "countryId": "2",
311
+ "fullName": "Леон Ветров",
312
+ "id": "2",
313
+ "isArchived": false
314
+ }
315
+ ]
316
+ }
317
+ ```
318
+
187
319
  ## Запросы и ответы
188
320
 
189
321
  Примеры с фильмами, актёрами и жанрами используют полную [схему из examples](examples/schema.json). Для их запуска используйте [конфигурацию примера](examples/server.config.js):
@@ -252,10 +384,7 @@ const scope = [
252
384
  fullName: true,
253
385
  movies: [
254
386
  { id: true, title: true },
255
- {
256
- order: [{ field: 'title', direction: 'ASC' }],
257
- pager: { page: 1, pageSize: 5 },
258
- },
387
+ { order: [{ field: 'title', direction: 'ASC' }], pager: { page: 1, pageSize: 5 } },
259
388
  ],
260
389
  },
261
390
  {
@@ -268,7 +397,7 @@ const params = new URLSearchParams({ scope: JSON.stringify(scope) });
268
397
  const response = await fetch(`/users?${params}`);
269
398
  ```
270
399
 
271
- Обычные поля выбираются через `true`, объекты и связи — через свой массив `scope`. Если аргументы не нужны, в массиве остаётся только объект полей. `"*": true` включает собственные поля и хранимые ключи, кроме `writeOnly`; связи выбираются явно.
400
+ Скаляры и массивы примитивов выбираются через `true`, объекты и связи — через свой массив `scope`. Если аргументы не нужны, в массиве остаётся только объект полей. `"*": true` включает только скалярные поля. Массивы, объекты, связи и поля `writeOnly` в него не входят.
272
401
 
273
402
  Например, собственные поля фильма, пользователи актёров и отсортированные жанры:
274
403
 
@@ -289,9 +418,9 @@ const response = await fetch(`/users?${params}`);
289
418
  ]
290
419
  ```
291
420
 
292
- Без `scope` возвращаются собственные поля, как при `[{"*":true}]`. Пустой выбор `[{}]` возвращает объект без полей. Списки сохраняют структуру `{ data, total }`.
421
+ Без `scope` возвращаются скалярные поля, как при `[{"*":true}]`. Пустой выбор `[{}]` возвращает объект без полей. Списки сохраняют структуру `{ data, total }`.
293
422
 
294
- Аргументы доступны только у списков. В запросе отдельной записи и в ответе мутации их можно задать для вложенных списков. Параметры проверяются даже на пустых данных; ошибка в выборе ответа отменяет изменения записи. Некорректный `scope` возвращает `400`. Максимальная длина JSON — 10 000 символов, глубина выбора — 32 уровня.
423
+ Аргументы доступны только у списков. Вместо них список может использовать `{ "union": [scope, ...] }`: каждая часть — обычный scope списка, части выполняются в порядке массива, а для каждого первичного ключа остаётся первая запись. Это работает и у вложенных списков. В запросе отдельной записи и в ответе мутации аргументы можно задать для вложенных списков. Параметры проверяются даже на пустых данных; ошибка в выборе ответа отменяет изменения записи. Некорректный `scope` возвращает `400`. Максимальная длина JSON — 10 000 символов, глубина выбора — 32 уровня.
295
424
 
296
425
  ### Вложенная запись
297
426
 
@@ -346,10 +475,10 @@ mutation {
346
475
 
347
476
  ### GraphQL
348
477
 
349
- Укажите `database.schema` и включите `graphql.enabled: true` в конфигурации или запустите сервер с `--graphql`:
478
+ Укажите `database.schema` и добавьте секцию `graphql: {}` в конфигурацию:
350
479
 
351
480
  ```sh
352
- npx deep-json-server --graphql server.config.js
481
+ npx deep-json-server server.config.js
353
482
  ```
354
483
 
355
484
  Отправляйте запросы на `/graphql` методом POST с `Content-Type: application/json` и телом `{ "query": "…", "variables": {} }`. Путь можно изменить через `graphql.endpoint`.
@@ -386,29 +515,27 @@ query {
386
515
 
387
516
  ## OpenAPI и экспорт схем
388
517
 
389
- Экспорт использует OpenAPI 3.0.3. Для `scope` назначение позиций массива описано текстом; их порядок проверяет сервер.
390
-
391
- Для получения спецификации по HTTP укажите `database.schema` и включите `openapi.enabled: true` или запустите сервер с `--openapi`. По умолчанию JSON доступен по адресу `/openapi.json`; путь задаётся в `openapi.endpoint`. Спецификацию можно открыть в отдельно установленном Swagger UI или импортировать в API-клиент.
392
-
393
- Для генерации укажите формат и файл конфигурации:
394
-
395
- ```sh
396
- npx deep-json-server generate openapi server.config.js
397
- npx deep-json-server generate graphql server.config.js
398
- npx deep-json-server generate openapi,graphql server.config.js
399
- ```
518
+ Экспорт использует OpenAPI 3.0.3. Добавьте `openapi: {}` и `database.schema`, чтобы получать спецификацию по HTTP на `/openapi.json`. Путь меняется через `openapi.endpoint`. Спецификацию можно открыть в Swagger UI или импортировать в API-клиент.
400
519
 
401
- Генерация выполняется без запуска сервера и чтения записей базы. Команда читает `database.schema` и сохраняет схемы в `openapi.path` и `graphql.path`. Флаги `enabled` управляют HTTP-маршрутами и для экспорта не требуются. Для генерации достаточно такой конфигурации:
520
+ Для сохранения схем задайте пути экспорта:
402
521
 
403
522
  ```js
404
523
  export default {
405
- database: { schema: './schema.json' },
406
- openapi: { path: './generated/openapi.yaml' },
407
- graphql: { path: './generated/schema.graphql' },
524
+ storage: 'file',
525
+ database: { source: './database.json', schema: './schema.json' },
526
+ openapi: { target: './generated/openapi.yaml' },
527
+ graphql: { target: './generated/schema.graphql' },
408
528
  };
409
529
  ```
410
530
 
411
- Для каждого формата нужен отдельный файл. Команда отклонит путь, который перезапишет конфигурацию, базу, схему, учётные записи auth или метаданные файлов.
531
+ ```bash
532
+ npx deep-json-server server.config.js --generate-only
533
+ npx deep-json-server server.config.js --generate
534
+ ```
535
+
536
+ `--generate-only` экспортирует и завершает работу, `--generate` после экспорта запускает сервер. Форматы определяются наличием секций. Для каждого выбранного формата обязателен свой `target`. Если секций нет, отсутствует `target` или генерация завершилась ошибкой, команда возвращает ошибку и сервер не запускается.
537
+
538
+ Экспорт не открывает базу, учётные записи и файлы. Перед записью проверяются все выбранные `target`; они не могут совпадать с конфигурацией, базой, схемой, пользователями, счётчиками или метаданными файлов.
412
539
 
413
540
  ## Аутентификация
414
541
 
@@ -422,23 +549,30 @@ import { hashPassword } from '@kollors/deep-json-server/auth';
422
549
 
423
550
  const password = process.env.DJS_PASSWORD;
424
551
  if (!password) throw new Error('Set DJS_PASSWORD');
425
- await writeFile('./auth.json', JSON.stringify([
426
- { id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true },
427
- ], null, 2), { flag: 'wx', mode: 0o600 });
552
+ await writeFile(
553
+ './auth.json',
554
+ JSON.stringify(
555
+ [{ id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true }],
556
+ null,
557
+ 2,
558
+ ),
559
+ { flag: 'wx', mode: 0o600 },
560
+ );
428
561
  ```
429
562
 
430
563
  Задайте `DJS_PASSWORD` и выполните `node setup-auth.mjs`. Добавьте файл в конфигурацию сервера:
431
564
 
432
565
  ```js
433
566
  export default {
434
- database: { path: './database.json' },
435
- auth: { users: './auth.json', expiresIn: 3600 },
567
+ storage: 'file',
568
+ database: { source: './database.json' },
569
+ auth: { source: './auth.json', expiresIn: 3600 },
436
570
  };
437
571
  ```
438
572
 
439
573
  Запустите `npx deep-json-server server.config.js`. Каждой исходной учётной записи нужны уникальный строковый `id`, уникальный `username` и `passwordHash`, созданный функцией выше. `isAdmin` по умолчанию равен `false`. Пароли хешируются через scrypt со случайной солью.
440
574
 
441
- В `auth.users` можно передать массив вместо пути:
575
+ При `storage: 'memory'` передайте массив в `auth.source`:
442
576
 
443
577
  ```js
444
578
  import { hashPassword } from '@kollors/deep-json-server/auth';
@@ -447,16 +581,17 @@ const password = process.env.DJS_PASSWORD;
447
581
  if (!password) throw new Error('Set DJS_PASSWORD');
448
582
 
449
583
  export default {
450
- database: { data: { items: [] } },
584
+ storage: 'memory',
585
+ database: { source: { items: [] } },
451
586
  auth: {
452
- users: [
587
+ source: [
453
588
  { id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true },
454
589
  ],
455
590
  },
456
591
  };
457
592
  ```
458
593
 
459
- Учётные записи auth хранятся отдельно от коллекций базы. При строке в `auth.users` регистрация, смена пароля и статуса сохраняются в указанный JSON-файл через тот же `lowdb`, что и основная база. При массиве изменения остаются во внутренней копии в памяти и исчезают после перезапуска; исходный массив не меняется. Выбор не зависит от `database.path` или `database.data`. Ошибка записи файла отменяет изменение и сохраняет действующие сессии. Файл читается при запуске; после ручного редактирования перезапустите сервер.
594
+ Учётные записи auth хранятся отдельно от базы. В файловом режиме изменения сохраняются в `auth.source`; в режиме памяти они исчезают после перезапуска. Переданный массив пользователей не изменяется. После ручного изменения файла перезапустите сервер.
460
595
 
461
596
  | REST-запрос | JSON тела | Ответ |
462
597
  |---|---|---|
@@ -475,40 +610,70 @@ export default {
475
610
 
476
611
  Недействительный или истёкший токен возвращает `401`, недостаток прав — `403`, отсутствующий пользователь при разрешённой операции — `404`. Ошибки тела запроса возвращают `400`. Вход, регистрация и смена пароля могут вернуть `429` при превышении числа одновременных вычислений паролей; вход также ограничивает число активных сессий. Сессии хранятся в памяти и исчезают после перезапуска. Выход завершает только сессию переданного токена.
477
612
 
478
- OpenAPI описывает все маршруты auth и требования Bearer-токена. В Swagger UI токен из ответа на вход можно вставить в **Authorize**. Для экспорта схем включите auth в конфигурации и выполните `generate openapi server.config.js`; файл учётных записей при генерации не читается. Методы auth доступны через REST. В GraphQL тот же токен проверяется при изменении записей. Для GraphQL и OpenAPI нужна `database.schema`.
613
+ При включённом auth у каждой записи модели доступен виртуальный объект `actions`. Он вычисляется для текущего пользователя и не сохраняется в базе. В REST его нужно явно запросить через `scope`:
479
614
 
480
- ## Даты записей, удаление и владельцы
615
+ ```json
616
+ [
617
+ {
618
+ "id": true,
619
+ "actions": [{ "*": true }]
620
+ }
621
+ ]
622
+ ```
481
623
 
482
- Глобальные настройки задаются в `database`:
624
+ Объект содержит `update`, `replace` и `delete`. Владелец или администратор получает `true`, а анонимный или посторонний пользователь — `false`. Записи без `createdById` изменяет только администратор. Без токена чтение остаётся открытым, а флаги равны `false`. Переданный недействительный токен возвращает `401` в REST или `UNAUTHENTICATED` в GraphQL.
483
625
 
484
- ```js
485
- export default {
486
- database: {
487
- path: './database.json',
488
- schema: './schema.json',
489
- timestamps: true,
490
- softDelete: true,
491
- },
492
- auth: { users: './auth.json' },
493
- };
626
+ В GraphQL то же поле выбирается обычным способом:
627
+
628
+ ```graphql
629
+ query {
630
+ itemList {
631
+ data {
632
+ id
633
+ actions {
634
+ update
635
+ replace
636
+ delete
637
+ }
638
+ }
639
+ }
640
+ }
494
641
  ```
495
642
 
496
- В модели можно переопределить `timestamps` и `softDelete` рядом с `collection` и `fields`. Отсутствующее значение наследуется из глобальных настроек; `true` или `false` переопределяет его. CLI имеет приоритет над глобальным конфигом, а настройки модели — над обоими. Без схемы глобальные значения действуют на все коллекции. Например, эта модель отключает даты и сохраняет удалённые записи независимо от глобальных настроек:
643
+ `actions` доступен у корневых и связанных записей моделей, включая результаты мутаций. Его нельзя записывать, фильтровать или сортировать. Обычные вложенные объекты и ответы методов auth это поле не получают.
644
+
645
+ OpenAPI описывает маршруты auth, необязательную Bearer-аутентификацию при чтении записей, обязательную при изменениях и поле ответа `actions`. В Swagger UI токен из ответа на вход можно вставить в **Authorize**. Для экспорта схем включите auth в конфигурации и выполните `npx deep-json-server server.config.js --generate-only`; файл учётных записей при генерации не читается. Методы auth доступны через REST. В GraphQL тот же токен проверяется при чтении прав и изменении записей. Для GraphQL и OpenAPI нужна `database.schema`.
646
+
647
+ ## Даты записей, удаление и владельцы
648
+
649
+ Общие настройки задаются в корне схемы. В этом примере модель `Note` наследует мягкое удаление и отключает даты:
497
650
 
498
651
  ```json
499
652
  {
500
- "Note": {
501
- "collection": "notes",
502
- "timestamps": false,
503
- "softDelete": true,
504
- "fields": {
505
- "id": { "type": "string", "primary": true, "generated": "uuid" },
506
- "text": { "type": "string", "required": true }
653
+ "timestamps": true,
654
+ "softDelete": true,
655
+ "models": {
656
+ "Note": {
657
+ "collection": "notes",
658
+ "timestamps": false,
659
+ "fields": {
660
+ "id": {
661
+ "type": "string",
662
+ "primary": true,
663
+ "generated": "uuid"
664
+ },
665
+ "text": {
666
+ "type": "string",
667
+ "required": true
668
+ }
669
+ }
507
670
  }
508
671
  }
509
672
  }
510
673
  ```
511
674
 
675
+ Приоритет: модель → корень схемы → `false`. Явное `false` отключает унаследованную настройку. Без схемы даты и мягкое удаление выключены.
676
+
512
677
  | Поле | Когда включено | Значение |
513
678
  |---|---|---|
514
679
  | `createdAt`, `updatedAt` | `timestamps` | Даты создания и последнего изменения |
@@ -545,8 +710,9 @@ REST возвращает 401 при отсутствии действитель
545
710
 
546
711
  ```js
547
712
  export default {
548
- database: { path: './database.json' },
549
- files: { directory: './uploads', metadata: './files.json' },
713
+ storage: 'file',
714
+ database: { source: './database.json' },
715
+ files: { source: './uploads' },
550
716
  };
551
717
  ```
552
718
 
@@ -556,7 +722,7 @@ export default {
556
722
  npx deep-json-server server.config.js
557
723
  ```
558
724
 
559
- Для временных тестов вместо этого используйте `files.data`. Каждая начальная запись содержит `name`, `mimeType`, бинарное `content` в виде `Uint8Array` и необязательный `directory`. Загруженные файлы в таком режиме остаются в памяти до завершения процесса.
725
+ Для временных тестов выберите `storage: 'memory'` и передайте массив в `files.source`. Каждая начальная запись содержит `name`, `mimeType`, бинарное `content` в виде `Uint8Array` и необязательный `directory`. Загруженные файлы в таком режиме остаются в памяти до завершения процесса.
560
726
 
561
727
  Один файл отправляется непосредственно в теле запроса. `Content-Name` содержит URI-кодированное имя файла, `Content-Type` — его MIME-тип, а необязательный `Content-Directory` — URI-кодированный относительный путь к директории:
562
728
 
@@ -607,9 +773,9 @@ Content-Type: application/json
607
773
  }
608
774
  ```
609
775
 
610
- `PATCH` возвращает обновлённые метаданные со статусом `200`; если по новому пути уже существует файл, сервер возвращает `409`. `DELETE` отвечает статусом `204` без тела. Если файл не найден, любая операция по пути возвращает `404`. Пути в URL задаются относительно `files.directory`, а все возвращаемые URL — относительно адреса сервера.
776
+ `PATCH` возвращает обновлённые метаданные со статусом `200`; если по новому пути уже существует файл, сервер возвращает `409`. `DELETE` отвечает статусом `204` без тела. Если файл не найден, любая операция по пути возвращает `404`. Пути в URL задаются относительно `files.source`, а все возвращаемые URL — относительно адреса сервера.
611
777
 
612
- При хранении на диске бинарный файл находится по пути `<files.directory>/<directory>/<name>`. Метаданные содержат `directory`, `mimeType` и `name`; размер сервер читает из файла, а URL формирует сам. Директории и файл метаданных создаются по мере необходимости.
778
+ При хранении на диске бинарный файл находится по пути `<files.source>/<directory>/<name>`. Метаданные по умолчанию хранятся в `<files.source>/.files.json`; другой путь можно задать через `files.metadata`. Директории и файл метаданных создаются по мере необходимости.
613
779
 
614
780
  Используйте один процесс сервера для дисковой базы и файлового хранилища. Перед ручным изменением файлов или метаданных остановите его. Пути в хранилище не могут содержать символические ссылки. Загрузка и переименование не могут перезаписать базу, счётчики, схему, учётные записи auth, загруженную конфигурацию или файл метаданных.
615
781
 
@@ -629,7 +795,7 @@ await server.listen();
629
795
 
630
796
  Методы `openapi()` и `graphql()` возвращают схемы и требуют `database.schema`. `fastify()` возвращает экземпляр сервера для настройки и запуска. База и включённые сервисы инициализируются при `ready()`, `listen()` или первом `inject()`; ошибка инициализации останавливает запуск.
631
797
 
632
- Второй аргумент переопределяет подключение модулей, например `createServer(config, { files: false, graphql: true })`. Допустимы флаги `files`, `graphql`, `openapi` и `auth`. Для включения auth или файлов нужна соответствующая секция конфигурации; для GraphQL и OpenAPI схема моделей.
798
+ `createServer(config)` принимает один аргумент. Наличие секций управляет модулями так же, как при запуске через CLI. Для методов `openapi()` и `graphql()` нужна соответствующая секция. Методы возвращают схему и не записывают файлы.
633
799
 
634
800
  Эти функции доступны и через общий импорт `@kollors/deep-json-server`. Адаптеры сервера загружаются при включении. Генераторы можно использовать отдельно:
635
801
 
@@ -643,114 +809,10 @@ await writeOpenapi(document, './generated/openapi.yaml');
643
809
  await writeGraphql(sdl, './generated/schema.graphql');
644
810
  ```
645
811
 
646
- Оба генератора принимают `timestamps`, `softDelete` и `auth` для описания полей записей. При `{ auth: true }` OpenAPI также добавляет REST-маршруты auth и требования токена для изменения записей. Функция `hashPassword()` доступна и через общий импорт пакета.
812
+ Отдельные генераторы принимают путь или объект схемы и не требуют конфигурации сервера. Выбранная функция задаёт формат по умолчанию; `api` в схеме и моделях может его ограничить. `timestamps` и `softDelete` берутся из схемы. Опция `{ auth: true }` добавляет поля владельца и `actions`; OpenAPI также описывает маршруты auth и требования токена. `hashPassword()` доступна и через общий импорт пакета.
647
813
 
648
814
  `generateOpenapi()` также принимает `host`, `port`, `pageSize`, `maxPageSize` и `info`. Вместо пути можно передать объект схемы. Сервер и генераторы работают с собственной копией модели. Размеры страниц должны быть положительными целыми числами; `pageSize` не может превышать `maxPageSize`.
649
815
 
650
- ## Пример базы данных
651
-
652
- ```json
653
- {
654
- "countries": [
655
- {
656
- "id": "1",
657
- "isArchived": false,
658
- "name": "Ардения"
659
- },
660
- {
661
- "id": "2",
662
- "isArchived": false,
663
- "name": "Велория"
664
- }
665
- ],
666
- "genres": [
667
- {
668
- "id": "1",
669
- "isArchived": false,
670
- "name": "Криминал",
671
- "parentIds": []
672
- },
673
- {
674
- "id": "2",
675
- "isArchived": false,
676
- "name": "Гангстер",
677
- "parentIds": ["1"]
678
- },
679
- {
680
- "id": "3",
681
- "isArchived": false,
682
- "name": "Драма",
683
- "parentIds": []
684
- },
685
- {
686
- "id": "4",
687
- "isArchived": false,
688
- "name": "Комедия",
689
- "parentIds": []
690
- }
691
- ],
692
- "movies": [
693
- {
694
- "actors": [
695
- {
696
- "genreIds": ["2", "3"],
697
- "id": "movie-1-actor-1",
698
- "userId": "1"
699
- },
700
- {
701
- "genreIds": ["3"],
702
- "id": "movie-1-actor-2",
703
- "userId": "2"
704
- }
705
- ],
706
- "coverSrc": "https://example.com/covers/shadows-of-ardenia.jpg",
707
- "description": "Наследница портового города раскрывает заговор двух соперничающих семей.",
708
- "id": "1",
709
- "isArchived": false,
710
- "publisherIds": ["2"],
711
- "title": "Тени Ардении"
712
- },
713
- {
714
- "actors": [],
715
- "coverSrc": "https://example.com/covers/northern-star.jpg",
716
- "description": "Ночной администратор старого отеля случайно становится участником поисков пропавшей картины.",
717
- "id": "2",
718
- "isArchived": false,
719
- "publisherIds": ["1"],
720
- "title": "Полночь в «Северной звезде»"
721
- }
722
- ],
723
- "publishers": [
724
- {
725
- "id": "1",
726
- "isArchived": false,
727
- "name": "Northlight Studio"
728
- },
729
- {
730
- "id": "2",
731
- "isArchived": false,
732
- "name": "Aurora Pictures"
733
- }
734
- ],
735
- "users": [
736
- {
737
- "bornAt": "1988-03-14",
738
- "countryId": "1",
739
- "fullName": "Мира Волкова",
740
- "id": "1",
741
- "isArchived": false
742
- },
743
- {
744
- "bornAt": "1991-11-02",
745
- "countryId": "2",
746
- "fullName": "Леон Ветров",
747
- "id": "2",
748
- "isArchived": false
749
- }
750
- ]
751
- }
752
- ```
753
-
754
816
  ## Хранение данных
755
817
 
756
818
  Изменения выполняются последовательно внутри экземпляра сервера и проверяются на копии данных до сохранения. Для одного файла базы используйте один процесс сервера. Счётчики `increment` хранятся рядом с базой в `<путь базы>.counters.json`; сохраняйте этот файл вместе с базой. Номера резервируются до записи данных: сбой может оставить пропуск, но не приводит к повторной выдаче номера.
@@ -766,6 +828,6 @@ npm run verify
766
828
 
767
829
  Команда проверяет типы, стиль кода, покрытие тестами и установку пакета из архива.
768
830
 
769
- Для публикации новой альфы обновите версию в `package.json`, `package-lock.json` и `src/core/constants.ts`, затем отправьте изменения в `main`. GitHub Actions создаст тег версии и опубликует пакет в канал npm `alpha` через trusted publishing. Уже опубликованная версия пропускается. Если тег создан, а публикация не завершилась, повторный запуск использует этот тег и проверяет соответствие ему файлов пакета. Отправка тега версии также запускает публикацию; стабильные версии публикуются в `latest`.
831
+ Для публикации предварительной версии обновите номер в `package.json`, `package-lock.json` и `src/core/constants.ts`, затем отправьте коммит в `main`. GitHub Actions создаст тег `v<версия>` и опубликует пакет через trusted publishing в канал `alpha`, `beta` или `rc`. Стабильные версии публикуются в `latest` из явно отправленного тега версии.
770
832
 
771
833
  Лицензия: MIT.