@kollors/deep-json-server 0.5.0 → 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.ru.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [GitHub](https://github.com/kollors/deep-json-server) | [npm](https://www.npmjs.com/package/@kollors/deep-json-server)
6
6
 
7
- Небольшой JSON REST mock-сервер с CRUD, пагинацией, глубокой фильтрацией и рекурсивной загрузкой связей. База данных хранится в одном читаемом JSON-файле, а мягкие связи определяются по соглашениям о нейминге ключей: `countryId`, `genreIds`, `publisherIds` и так далее.
7
+ Небольшой моковый REST-сервер с CRUD, пагинацией, глубокими фильтрами, рекурсивной загрузкой связей, бинарными файлами и генерацией OpenAPI. Данные могут храниться в JSON-файлах или памяти, а связи определяются по соглашениям о нейминге ключей: `countryId`, `genreIds`, `publisherIds` и так далее.
8
8
 
9
9
  ## Установка
10
10
 
@@ -14,6 +14,43 @@
14
14
  npm install --save-dev @kollors/deep-json-server
15
15
  ```
16
16
 
17
+ ## Быстрый старт
18
+
19
+ До запуска создайте файл базы `mock/database.json`:
20
+
21
+ ```json
22
+ {
23
+ "movies": [
24
+ { "id": "1", "title": "Тени Ардении" }
25
+ ]
26
+ }
27
+ ```
28
+
29
+ Рядом с `package.json` создайте ESM-модуль `server.config.js`:
30
+
31
+ ```js
32
+ export default {
33
+ database: {
34
+ path: 'mock/database.json',
35
+ },
36
+ };
37
+ ```
38
+
39
+ Запустите сервер:
40
+
41
+ ```bash
42
+ npx deep-json-server server.config.js
43
+ ```
44
+
45
+ По умолчанию API доступен по адресу `http://127.0.0.1:4001`. Например, `GET http://127.0.0.1:4001/movies` вернёт страницу:
46
+
47
+ ```json
48
+ {
49
+ "data": [{ "id": "1", "title": "Тени Ардении" }],
50
+ "total": 1
51
+ }
52
+ ```
53
+
17
54
  ## Конфигурация и запуск
18
55
 
19
56
  Создайте ESM-модуль `server.config.js`. В примере ниже включены настройки всех возможностей:
@@ -35,6 +72,9 @@ export default {
35
72
  },
36
73
  server: {
37
74
  host: '127.0.0.1',
75
+ logger: true,
76
+ maxFileSize: 100 * 1024 * 1024,
77
+ maxPageSize: 1000,
38
78
  port: 4001,
39
79
  },
40
80
  };
@@ -42,25 +82,51 @@ export default {
42
82
 
43
83
  Ключи конфигурации:
44
84
 
45
- | Ключ | Когда обязателен | Назначение |
85
+ | Ключ | Условие | Назначение |
46
86
  | --- | --- | --- |
47
- | `database.path` | Всегда | Существующий JSON-файл базы данных |
48
- | `database.schema` | Необязателен | JSON-настройки проверки запросов и OpenAPI-схем |
49
- | `files.directory` | С `--files` | Директория для бинарного содержимого |
50
- | `files.metadata` | С `--files` | JSON-файл с метаданными загруженных файлов |
51
- | `openapi.path` | С `--openapi` | Генерируемый YAML-файл OpenAPI |
52
- | `server.host` | Необязателен | Адрес прослушивания; затем используются `HOST` и `127.0.0.1` |
53
- | `server.port` | Необязателен | Порт; затем используются `PORT` и `4001` |
87
+ | `database.path` | Требуется ровно один из `path` или `data` | Существующий JSON-файл базы данных |
88
+ | `database.data` | Требуется ровно один из `path` или `data` | Объект базы данных, хранящийся в памяти |
89
+ | `database.schema` | Необязателен | Путь к JSON-настройкам либо объект с настройками проверки запросов и OpenAPI |
90
+ | `files.directory` | Вместе с `files.metadata` | Директория для бинарного содержимого на диске |
91
+ | `files.metadata` | Вместе с `files.directory` | JSON-файл с метаданными файлов на диске |
92
+ | `files.data` | Вместо пары `directory` и `metadata` | Файлы в памяти с содержимым в `Uint8Array` |
93
+ | `openapi.path` | Обязателен для CLI-флагов `--openapi` и `--openapi-only` | Генерируемый YAML-файл OpenAPI; программный API может вернуть документ без этого пути |
94
+ | `server.host` | Необязателен | Адрес для CLI, `server.openapi()` и вызова `server.fastify().listen()` без аргументов; по умолчанию `127.0.0.1` |
95
+ | `server.logger` | Необязателен | Настройки логгера Fastify; по умолчанию `true` |
96
+ | `server.maxFileSize` | Необязателен | Максимальный размер загружаемого файла в байтах при включённых файловых маршрутах; по умолчанию 100 МиБ |
97
+ | `server.maxPageSize` | Необязателен | Максимальное значение `_perPage` в API и OpenAPI; по умолчанию `1000` |
98
+ | `server.port` | Необязателен | Порт для CLI, `server.openapi()` и вызова `server.fastify().listen()` без аргументов; по умолчанию `4001` |
99
+
100
+ `server.port` должен быть целым числом от `0` до `65535`. Значение `0` позволяет Fastify выбрать свободный порт при запуске, но не подходит для генерации URL в OpenAPI, где допустимы порты от `1` до `65535`. `server.maxFileSize` и `server.maxPageSize` должны быть положительными целыми числами.
54
101
 
55
102
  Все относительные пути вычисляются от директории с `server.config.js`, а не от текущей рабочей директории. Неизвестные ключи, пустые пути и значения некорректных типов отклоняются до запуска. Конфиг является исполняемым JavaScript: в нём можно читать переменные окружения, импортировать другие модули и вычислять значения перед экспортом объекта. Для `.js`-конфига с `export default` проект должен быть ESM (`"type": "module"`); в CommonJS-проекте сохраните тот же конфиг как `server.config.mjs`.
56
103
 
57
- Добавьте нужные команды в `package.json`:
104
+ В том же конфиге все данные можно разместить в памяти. `database.path` и `database.data` взаимоисключающие, а `database.schema` принимает путь или объект. Аналогично, `files.data` нельзя сочетать с `files.directory` или `files.metadata`:
105
+
106
+ ```js
107
+ export default {
108
+ database: {
109
+ data: { movies: [{ id: '1', title: 'Тени Ардении' }] },
110
+ schema: { $info: { title: 'API фильмов', version: '1.0.0' } },
111
+ },
112
+ files: {
113
+ data: [{ content: new Uint8Array([1, 2, 3]), id: 'file-1', mimeType: 'application/octet-stream', name: 'example.bin' }],
114
+ },
115
+ };
116
+ ```
117
+
118
+ Значения в памяти клонируются при инициализации. Поэтому операции с CRUD и файлами не изменяют экспортированный объект конфига, а их результаты исчезают после завершения процесса.
119
+
120
+ Добавьте нужные команды в `package.json`. Здесь `mock:openapi:files` сначала обновляет OpenAPI, а затем оставляет сервер запущенным с файловыми маршрутами:
58
121
 
59
122
  ```json
60
123
  {
61
124
  "scripts": {
62
- "mock": "deep-json-server --files server.config.js",
63
- "openapi": "deep-json-server --openapi --files server.config.js"
125
+ "mock": "deep-json-server server.config.js",
126
+ "mock:files": "deep-json-server --files server.config.js",
127
+ "mock:openapi:files": "deep-json-server --files --openapi server.config.js",
128
+ "openapi": "deep-json-server --openapi-only server.config.js",
129
+ "openapi:files": "deep-json-server --files --openapi-only server.config.js"
64
130
  }
65
131
  }
66
132
  ```
@@ -71,24 +137,26 @@ export default {
71
137
  | --- | --- |
72
138
  | `deep-json-server server.config.js` | Запускает CRUD-сервер без файловых маршрутов |
73
139
  | `deep-json-server --files server.config.js` | Запускает CRUD-сервер с файловыми маршрутами |
74
- | `deep-json-server --openapi server.config.js` | Генерирует OpenAPI и завершает работу |
75
- | `deep-json-server --openapi --files server.config.js` | Генерирует OpenAPI с файловыми маршрутами и завершает работу |
140
+ | `deep-json-server --openapi server.config.js` | Генерирует OpenAPI и запускает CRUD-сервер |
141
+ | `deep-json-server --files --openapi server.config.js` | Генерирует OpenAPI с файловыми маршрутами и запускает сервер с ними |
142
+ | `deep-json-server --openapi-only server.config.js` | Генерирует OpenAPI и завершает работу |
143
+ | `deep-json-server --files --openapi-only server.config.js` | Генерирует OpenAPI с файловыми маршрутами и завершает работу |
76
144
 
77
- `--openapi` никогда не запускает HTTP-сервер. Флаг `--files` независим: без него файловые маршруты не регистрируются и не добавляются в OpenAPI. Команда `deep-json-server --help` выводит краткую справку по CLI.
145
+ Флаг `--files` независим: без него файловые маршруты не регистрируются и не добавляются в OpenAPI, даже если секция `files` присутствует в конфиге. Параметры `--openapi` и `--openapi-only` взаимоисключающие. Команда `deep-json-server --help` выводит краткую справку по CLI.
78
146
 
79
147
  ## Пример базы данных
80
148
 
81
- Пример основан на каталоге фильмов. `Гангстерский фильм` демонстрирует связь с родительским жанром.
149
+ Ниже приведён пример каталога фильмов с тестовыми данными. `Гангстер` связан с родительским жанром `Криминал`.
82
150
 
83
151
  ```json
84
152
  {
85
153
  "countries": [
86
- { "id": "1", "isArchived": false, "name": "Россия" },
87
- { "id": "2", "isArchived": false, "name": "США" }
154
+ { "id": "1", "isArchived": false, "name": "Ардения" },
155
+ { "id": "2", "isArchived": false, "name": "Велория" }
88
156
  ],
89
157
  "genres": [
90
158
  { "id": "1", "isArchived": false, "name": "Криминал", "parentIds": [] },
91
- { "id": "2", "isArchived": false, "name": "Гангстерский фильм", "parentIds": ["1"] },
159
+ { "id": "2", "isArchived": false, "name": "Гангстер", "parentIds": ["1"] },
92
160
  { "id": "3", "isArchived": false, "name": "Драма", "parentIds": [] },
93
161
  { "id": "4", "isArchived": false, "name": "Комедия", "parentIds": [] }
94
162
  ],
@@ -98,39 +166,39 @@ export default {
98
166
  { "genreIds": ["2", "3"], "id": "movie-1-actor-1", "userId": "1" },
99
167
  { "genreIds": ["3"], "id": "movie-1-actor-2", "userId": "2" }
100
168
  ],
101
- "coverSrc": "https://image.tmdb.org/t/p/w500/3bhkrj58Vtu7enYsRolD1fZdja1.jpg",
102
- "description": "История семьи Корлеоне и передачи власти от одного поколения другому.",
169
+ "coverSrc": "https://example.com/covers/shadows-of-ardenia.jpg",
170
+ "description": "Наследница портового города раскрывает заговор двух соперничающих семей.",
103
171
  "id": "1",
104
172
  "isArchived": false,
105
173
  "publisherIds": ["2"],
106
- "title": "Крёстный отец"
174
+ "title": "Тени Ардении"
107
175
  },
108
176
  {
109
177
  "actors": [],
110
- "coverSrc": "https://image.tmdb.org/t/p/w500/eWdyYQreja6JGCzqHWXpWHDrrPo.jpg",
111
- "description": "Приключения консьержа и его юного помощника в знаменитом европейском отеле.",
178
+ "coverSrc": "https://example.com/covers/northern-star.jpg",
179
+ "description": "Ночной администратор старого отеля случайно становится участником поисков пропавшей картины.",
112
180
  "id": "2",
113
181
  "isArchived": false,
114
182
  "publisherIds": ["1"],
115
- "title": "Отель «Гранд Будапешт»"
183
+ "title": "Полночь в «Северной звезде»"
116
184
  }
117
185
  ],
118
186
  "publishers": [
119
- { "id": "1", "isArchived": false, "name": "A24" },
120
- { "id": "2", "isArchived": false, "name": "Paramount Pictures" }
187
+ { "id": "1", "isArchived": false, "name": "Northlight Studio" },
188
+ { "id": "2", "isArchived": false, "name": "Aurora Pictures" }
121
189
  ],
122
190
  "users": [
123
191
  {
124
- "bornAt": "1989-01-25",
192
+ "bornAt": "1988-03-14",
125
193
  "countryId": "1",
126
- "fullName": "Александр Петров",
194
+ "fullName": "Мира Волкова",
127
195
  "id": "1",
128
196
  "isArchived": false
129
197
  },
130
198
  {
131
- "bornAt": "1984-09-05",
132
- "countryId": "1",
133
- "fullName": "Юлия Пересильд",
199
+ "bornAt": "1991-11-02",
200
+ "countryId": "2",
201
+ "fullName": "Леон Ветров",
134
202
  "id": "2",
135
203
  "isArchived": false
136
204
  }
@@ -149,14 +217,14 @@ PATCH /movies/:id
149
217
  DELETE /movies/:id
150
218
  ```
151
219
 
152
- `POST` генерирует строковый ID. `PUT` полностью заменяет выбранную запись, а `PATCH` изменяет только переданные поля; обе операции сохраняют существующий ID и его тип. Поле `id` в теле любого запроса не может переопределить ID, которым управляет сервер. Все операции записи — `POST`, `PUT`, `PATCH` и `DELETE` — выполняются последовательно и сохраняются в JSON-файле.
220
+ `POST` генерирует строковый ID. `PUT` полностью заменяет выбранную запись, а `PATCH` изменяет только переданные поля; обе операции сохраняют существующий ID и его тип. Поле `id` в теле любого запроса не может переопределить ID, которым управляет сервер. Все операции записи — `POST`, `PUT`, `PATCH` и `DELETE` — выполняются последовательно; дисковое хранилище записывает их в JSON, а хранилище в памяти сохраняет до завершения процесса.
153
221
 
154
222
  Файл базы должен существовать до запуска. Имена ресурсов могут содержать латинские буквы, цифры, `_` и `-` и должны начинаться с буквы. Каждый ресурс является массивом JSON-объектов. У каждой записи должен быть непустой строковый или конечный числовой `id`; ID должны быть уникальны внутри ресурса при сравнении как строки, поэтому `1` и `"1"` не могут существовать одновременно. Перед каждым GET-запросом и изменением сервер заново читает файл, поэтому корректные внешние правки становятся видны без перезапуска.
155
223
 
156
224
  Успешная операция записи возвращает созданную, заменённую, обновлённую или удалённую запись. Ошибки используют подходящий HTTP-статус и следующий JSON-формат:
157
225
 
158
226
  ```json
159
- { "error": "Понятное описание ошибки" }
227
+ { "error": "..." }
160
228
  ```
161
229
 
162
230
  ## Пагинация и сортировка
@@ -165,21 +233,18 @@ DELETE /movies/:id
165
233
  GET /movies?_page=1&_perPage=10&_sort=-id,title
166
234
  ```
167
235
 
168
- GET-запрос к коллекции всегда возвращает объект страницы. По умолчанию `_page` равен `1`, а `_perPage` — `10`:
236
+ GET-запрос к коллекции всегда возвращает объект с массивом текущей страницы и общим количеством записей после фильтрации. По умолчанию `_page` равен `1`, а `_perPage` — `10`:
169
237
 
170
238
  ```json
171
239
  {
172
240
  "data": [],
173
- "first": 1,
174
- "items": 0,
175
- "last": 1,
176
- "next": null,
177
- "pages": 1,
178
- "prev": null
241
+ "total": 0
179
242
  }
180
243
  ```
181
244
 
182
- Оба параметра пагинации должны быть положительными целыми числами. По умолчанию `_perPage` не может превышать `1000`; лимит можно изменить программным параметром `maxPageSize`. При некорректном значении сервер возвращает `400`, а не исправляет его автоматически. Страница после последней возвращает пустой массив `data`, а `prev` указывает на последнюю доступную страницу.
245
+ `data` содержит записи только запрошенной страницы. `total` содержит количество всех записей, соответствующих фильтру, до применения пагинации. Номер последней страницы при необходимости вычисляется на клиенте как `Math.max(1, Math.ceil(total / pageSize))`.
246
+
247
+ Оба параметра пагинации должны быть положительными целыми числами. По умолчанию `_perPage` не может превышать `1000`; лимит меняется через `server.maxPageSize` в конфиге, переданном CLI или `createServer()`. При некорректном значении сервер возвращает `400`, а не исправляет его автоматически. Страница после последней возвращает пустой массив `data`, но сохраняет фактическое значение `total`.
183
248
 
184
249
  `_sort` принимает пути полей через запятую. Правила применяются слева направо; префикс `-` включает сортировку по убыванию. Через точку можно обратиться к полю вложенного объекта, включая поля, добавленные через `_embed`, например `GET /users?_embed=country&_sort=country.name,-id`. Неизвестные и небезопасные поля сортировки возвращают `400`.
185
250
 
@@ -188,7 +253,7 @@ GET-запрос к коллекции всегда возвращает объ
188
253
  Передайте JSON-объект через `_where`:
189
254
 
190
255
  ```http
191
- GET /movies?_where={"title":{"contains":"отец"}}
256
+ GET /movies?_where={"title":{"contains":"тени"}}
192
257
  ```
193
258
 
194
259
  Можно фильтровать вложенные объекты и массивы на любой глубине. Условия внутри одного объекта по умолчанию объединяются через `AND`:
@@ -196,7 +261,7 @@ GET /movies?_where={"title":{"contains":"отец"}}
196
261
  ```json
197
262
  {
198
263
  "actors": { "some": { "userId": { "eq": "1" } } },
199
- "title": { "contains": "отец" }
264
+ "title": { "contains": "тени" }
200
265
  }
201
266
  ```
202
267
 
@@ -207,7 +272,7 @@ GET /movies?_where={"title":{"contains":"отец"}}
207
272
  "and": [
208
273
  {
209
274
  "or": [
210
- { "title": { "contains": "отец" } },
275
+ { "title": { "contains": "тени" } },
211
276
  { "actors": { "some": { "userId": { "eq": "2" } } } }
212
277
  ]
213
278
  },
@@ -231,12 +296,14 @@ GET /movies?_where={"title":{"contains":"отец"}}
231
296
  Также можно использовать простые query-параметры:
232
297
 
233
298
  ```http
234
- GET /movies?title:contains=отец
299
+ GET /movies?title:contains=тени
235
300
  ```
236
301
 
237
302
  В простых фильтрах распознаются JSON-примитивы: числа, `true`, `false` и `null`. Значения с ведущими нулями, например `001`, остаются строками. Неизвестные операторы, некорректные логические условия и отсутствующие в непустом ресурсе пути фильтра возвращают `400`.
238
303
 
239
- Для простого фильтра `in` перечислите значения через запятую: `GET /movies?id:in=1,2`. Если передан `_where`, он становится полным фильтром, а остальные простые параметры фильтрации игнорируются. В примерах JSON оставлен читаемым; при ручном формировании URL HTTP-клиент должен закодировать значение `_where`.
304
+ Несколько простых query-фильтров объединяются через `AND`. Для оператора `in` перечислите значения через запятую: `GET /movies?id:in=1,2`. Для поля-массива `in` означает, что хотя бы один элемент поля совпадает хотя бы с одним переданным значением. `every` для пустого массива возвращает `true`, а `some` `false`.
305
+
306
+ Если передан `_where`, он становится полным фильтром, а остальные простые параметры фильтрации игнорируются. В примерах JSON оставлен читаемым; при ручном формировании URL HTTP-клиент должен закодировать значение `_where`, например через `encodeURIComponent(JSON.stringify(where))`.
240
307
 
241
308
  Фильтрация выполняется после `_embed`. Поэтому фильтр может обращаться к полям добавленной связи, если в том же запросе указан соответствующий `_embed`; сохранённые поля `...Id` и `...Ids` всегда можно фильтровать напрямую.
242
309
 
@@ -248,7 +315,7 @@ GET /movies?title:contains=отец
248
315
  GET /movies/1?_embed=actors.user.country&_embed=actors.genres&_embed=publishers
249
316
  ```
250
317
 
251
- Ответ будет содержать актёров, пользователя и жанры каждого актёра, страну пользователя и издателей. Исходные поля с ID остаются в ответе, а файл базы не изменяется. Глубина вложения не ограничена:
318
+ Ответ будет содержать актёров, пользователя и жанры каждого актёра, страну пользователя и издателей. Исходные поля с ID остаются в ответе, а файл базы не изменяется. Сервер не устанавливает фиксированный лимит глубины, но каждый требуемый уровень должен быть явно указан в конечном пути `_embed`:
252
319
 
253
320
  ```http
254
321
  GET /movies/1?_embed=actors.user.country
@@ -285,35 +352,37 @@ GET /countries/1?_embed=users
285
352
  deep-json-server --files server.config.js
286
353
  ```
287
354
 
355
+ Для временных тестов вместо этого используйте `files.data`. Каждая начальная запись содержит `id`, `name`, `mimeType` и бинарное `content` в виде `Uint8Array`; `size` и `url` вычисляются автоматически. Загруженные файлы в таком режиме остаются в памяти до завершения процесса.
356
+
288
357
  Один файл отправляется непосредственно в теле запроса. Оба заголовка обязательны: `Content-Name` содержит относительное логическое имя, закодированное через `encodeURIComponent`, а `Content-Type` — MIME-тип файла:
289
358
 
290
359
  ```http
291
360
  POST /_files
292
- Content-Name: posters%2Fthe-godfather.jpg
361
+ Content-Name: posters%2Fshadows-of-ardenia.jpg
293
362
  Content-Type: image/jpeg
294
363
 
295
364
  <binary body>
296
365
  ```
297
366
 
298
- Ответ содержит метаданные и постоянный URL:
367
+ Успешная загрузка возвращает статус `201`, метаданные и постоянный URL:
299
368
 
300
369
  ```json
301
370
  {
302
371
  "id": "generated-id",
303
372
  "mimeType": "image/jpeg",
304
- "name": "posters/the-godfather.jpg",
373
+ "name": "posters/shadows-of-ardenia.jpg",
305
374
  "size": 182340,
306
375
  "url": "/_files/generated-id"
307
376
  }
308
377
  ```
309
378
 
310
- `GET /_files/:id` возвращает исходные байты, а `DELETE /_files/:id` удаляет бинарное содержимое вместе с метаданными. Возвращаемый `url` задаётся относительно адреса mock-сервера. Сервер автоматически создаёт настроенные директории, хранит бинарное содержимое под сгенерированными ID без привязки к исходному имени и записывает логические имена и остальные метаданные в `files.metadata`. Изначально файл метаданных может отсутствовать: он создаётся при первой загрузке. Не редактируйте его во время работы сервера.
379
+ `GET /_files/:id` возвращает исходные байты со статусом `200`, а `DELETE /_files/:id` удаляет бинарное содержимое вместе с метаданными и возвращает удалённые метаданные со статусом `200`. Возвращаемый `url` задаётся относительно адреса mock-сервера. Сервер автоматически создаёт настроенные директории, хранит бинарное содержимое под сгенерированными ID без привязки к исходному имени и записывает логические имена и остальные метаданные в `files.metadata`. Изначально файл метаданных может отсутствовать: он создаётся при первой загрузке. Не редактируйте его во время работы сервера.
311
380
 
312
- Используется бинарное тело запроса, а не `multipart/form-data`, поэтому `XMLHttpRequest.upload.onprogress` может показывать прогресс при непосредственной отправке `File` через `xhr.send(file)`. Максимальный размер по умолчанию равен 100 МиБ и настраивается программным параметром `maxFileSize`. Небезопасный или абсолютный путь в `Content-Name` возвращает `400`, превышение лимита — `413`, а некорректный или неподдерживаемый `Content-Type` — `400` или `415`.
381
+ Используется бинарное тело запроса, а не `multipart/form-data`, поэтому `XMLHttpRequest.upload.onprogress` может показывать прогресс при непосредственной отправке `File` через `xhr.send(file)`. Максимальный размер по умолчанию равен 100 МиБ и настраивается через `server.maxFileSize`. Отсутствующий или небезопасный `Content-Name` возвращает `400`, превышение лимита — `413`, а отсутствующий, некорректный или не поддерживаемый Fastify `Content-Type` — `400` либо `415` в зависимости от этапа проверки.
313
382
 
314
383
  ## Схема базы данных и генерация OpenAPI
315
384
 
316
- Необязательный JSON-файл, указанный в `database.schema`, например `mock/database-schema.json`, настраивает автоматически выведенные схемы. Он читается как при обычном запуске сервера, так и при генерации OpenAPI:
385
+ Необязательный путь или объект в `database.schema` настраивает автоматически выведенные схемы. Это собственный формат настроек Deep JSON Server, а не стандартный документ JSON Schema: `$schema` здесь является объектом с настройками ресурсов. Например, файл `mock/database-schema.json` может содержать:
317
386
 
318
387
  ```json
319
388
  {
@@ -349,12 +418,16 @@ Content-Type: image/jpeg
349
418
  | `$schema.<resource>.formats` | Форматы OpenAPI для автоматически найденных или явно описанных строковых полей, например `date`, `date-time` или `uri` |
350
419
  | `$schema.<resource>.properties` | Рекурсивные OpenAPI-совместимые схемы полей, объединяемые с автоматически найденными |
351
420
 
421
+ `formats` — сокращённая запись для назначения `format` уже существующему строковому полю. `properties` позволяет полностью описать поле, в том числе его `type`, `format`, ограничения и вложенные свойства, либо добавить поле, которого нет в данных. Если одно поле получает `format` через оба механизма, значение из `formats` применяется последним.
422
+
352
423
  Укажите `openapi.path` в конфиге сервера, затем сгенерируйте OpenAPI 3.0.3 и завершите работу:
353
424
 
354
425
  ```bash
355
- deep-json-server --openapi --files server.config.js
426
+ deep-json-server --openapi-only server.config.js
356
427
  ```
357
428
 
429
+ Чтобы включить в документ файловые маршруты, настройте секцию `files` и добавьте флаг `--files`: `deep-json-server --files --openapi-only server.config.js`.
430
+
358
431
  Генератор определяет ресурсы и типы полей по всем записям базы. По умолчанию все найденные поля необязательные, а поле `id` верхнего уровня всегда обязательное в схемах ответа и исключается из схем создания и обновления. Остальные обязательные поля перечисляются в `required`. Вложенный обязательный путь делает обязательным само вложенное свойство, но не все его родительские пути; при необходимости родителя нужно перечислить отдельно.
359
432
 
360
433
  Разные типы значений определяются независимо и объединяются через `oneOf`. Перед генерацией проверяются `$info`, имена ресурсов и схем, а также структура `properties`; пути из `required` и `formats` должны существовать в итоговой схеме.
@@ -377,7 +450,7 @@ deep-json-server --openapi --files server.config.js
377
450
 
378
451
  Пустой ресурс всё равно получает обязательное строковое поле `id`, поскольку сервер создаёт строковые ID. При совпадении имён схем или `operationId` генерация завершается понятной ошибкой; коллизию имён схем можно устранить с помощью явного `name`. Директория результата создаётся автоматически, а настроенный YAML-файл заменяется при каждой генерации.
379
452
 
380
- `$info` становится объектом `info` в OpenAPI, а настройки ресурсов находятся внутри `$schema`. Поле `servers` в OpenAPI формируется автоматически из `server.host` и `server.port`, резервных переменных окружения `HOST` и `PORT` или адреса по умолчанию `http://127.0.0.1:4001`.
453
+ `$info` становится объектом `info` в OpenAPI, а настройки ресурсов находятся внутри `$schema`. В CLI поле `servers` формируется из `server.host` и `server.port`, затем из резервных переменных окружения `HOST` и `PORT`, а при их отсутствии используется `http://127.0.0.1:4001`. При прямом вызове `createServer()` переменные окружения автоматически не читаются: `server.openapi()` использует значения конфига или тот же адрес по умолчанию.
381
454
 
382
455
  Используйте `name`, если ресурсу нужно явно задать имя схемы вместо автоматически полученного имени в единственном числе:
383
456
 
@@ -391,73 +464,87 @@ deep-json-server --openapi --files server.config.js
391
464
  }
392
465
  ```
393
466
 
394
- В сгенерированном документе описаны CRUD, пагинация, сортировка, глубокие фильтры, `_embed`, а также прямые и обратные связи в ответах, определённые по полям `...Id` и `...Ids`. При наличии `--files` в него также добавляются маршруты бинарной загрузки, получения и удаления файлов. Числовой ID базы описывается как `integer | string`, поскольку последующий `POST` создаст строковый ID в том же ресурсе. Файл можно передать, например, в RTK Query OpenAPI Codegen. OpenAPI создаётся только с параметром `--openapi`; обычный запуск сервера файл не перезаписывает.
467
+ В сгенерированном документе описаны CRUD, пагинация, сортировка, глубокие фильтры, `_embed`, а также прямые и обратные связи в ответах, определённые по полям `...Id` и `...Ids`. При наличии `--files` в него также добавляются маршруты бинарной загрузки, получения и удаления файлов. Числовой ID базы описывается как `integer | string`, поскольку последующий `POST` создаст строковый ID в том же ресурсе. Файл можно передать, например, в RTK Query OpenAPI Codegen. OpenAPI создаётся только с параметром `--openapi` или `--openapi-only`; обычный запуск сервера файл не перезаписывает.
395
468
 
396
469
  При обычном запуске тела запросов проверяются по тем же автоматически выведенным и настроенным схемам. Для `POST` и `PUT` проверяются обязательные поля из `required`; `PATCH` проверяет только фактически переданные поля. Настройки `formats` и `properties` применяются ко всем трём методам. Не перечисленные дополнительные поля объекта остаются разрешёнными. Некорректные тела возвращают `400`.
397
470
 
398
- ## Назначение и безопасность
399
-
400
- Deep JSON Server предназначен для локальной разработки и автоматических тестов. В нём нет аутентификации и авторизации, CORS разрешён для любого источника, принятые изменения напрямую сохраняются в настроенные файлы, а ссылочная целостность не проверяется. Оставляйте адрес loopback по умолчанию, если внешняя среда не предоставляет собственный контроль доступа; не открывайте сервер и файловые маршруты для недоверенной сети.
401
-
402
471
  ## Программный API
403
472
 
404
473
  ```js
405
- import { createOpenApiDocument, createServer, generateOpenApi, startServer } from '@kollors/deep-json-server';
406
-
407
- // Создание экземпляра без открытия порта, например для тестов.
408
- const server = await createServer({
409
- databasePath: 'mock/database.json',
410
- filesDirectoryPath: 'mock/files',
411
- filesMetadataPath: 'mock/files/_database.json',
412
- logger: false,
413
- maxFileSize: 100 * 1024 * 1024,
414
- maxPageSize: 1000,
415
- schemaPath: 'mock/database-schema.json',
416
- });
474
+ import { createServer } from '@kollors/deep-json-server';
417
475
 
418
- const response = await server.inject({ method: 'GET', url: '/movies' });
476
+ const config = {
477
+ database: {
478
+ path: 'mock/database.json',
479
+ schema: 'mock/database-schema.json',
480
+ },
481
+ files: {
482
+ directory: 'mock/files',
483
+ metadata: 'mock/files/_database.json',
484
+ },
485
+ openapi: {
486
+ path: 'mock/openapi-schema.yaml',
487
+ },
488
+ server: {
489
+ host: '127.0.0.1',
490
+ logger: false,
491
+ maxFileSize: 100 * 1024 * 1024,
492
+ maxPageSize: 1000,
493
+ port: 4001,
494
+ },
495
+ };
419
496
 
420
- await server.close();
497
+ // Запрос без открытия сетевого порта — удобно для автоматических тестов.
498
+ const server = await createServer(config);
499
+ const fastify = server.fastify();
500
+ const response = await fastify.inject({ method: 'GET', url: '/movies' });
421
501
 
422
- // Создание экземпляра и начало прослушивания.
423
- const listeningServer = await startServer({
424
- databasePath: 'mock/database.json',
425
- host: '127.0.0.1',
426
- port: 4001,
427
- schemaPath: 'mock/database-schema.json',
428
- });
502
+ console.log(response.json());
503
+
504
+ // Возвращает документ и записывает его в config.openapi.path.
505
+ const document = await server.openapi();
429
506
 
430
- await listeningServer.close();
507
+ await fastify.close();
431
508
 
432
- // Чтение файлов базы и схемы с последующей записью OpenAPI YAML.
433
- await generateOpenApi({
434
- databasePath: 'mock/database.json',
435
- files: true,
436
- host: '127.0.0.1',
437
- outputPath: 'mock/openapi-schema.yaml',
438
- port: 4001,
439
- schemaPath: 'mock/database-schema.json',
509
+ // Запуск сетевого сервера. Вызов без аргументов использует server.host и server.port.
510
+ const runningServer = await createServer(config);
511
+ const runningFastify = runningServer.fastify();
512
+
513
+ await runningFastify.listen();
514
+
515
+ // Позже, при завершении приложения:
516
+ await runningFastify.close();
517
+
518
+ // База, схема и файлы полностью в памяти.
519
+ const memoryServer = await createServer({
520
+ database: {
521
+ data: { movies: [{ id: '1', title: 'Тени Ардении' }] },
522
+ schema: { $info: { title: 'API фильмов', version: '1.0.0' } },
523
+ },
524
+ files: {
525
+ data: [{ content: new Uint8Array([1, 2, 3]), id: 'file-1', mimeType: 'application/octet-stream', name: 'example.bin' }],
526
+ },
440
527
  });
441
528
 
442
- // Создание такого же OpenAPI-документа полностью в памяти.
443
- const document = createOpenApiDocument(
444
- { movies: [{ id: '1', title: 'Крёстный отец' }] },
445
- { $info: { title: 'API фильмов', version: '1.0.0' } },
446
- { files: true, host: '127.0.0.1', port: 4001 },
447
- );
529
+ const memoryFastify = memoryServer.fastify();
530
+ const memoryResponse = await memoryFastify.inject({ method: 'GET', url: '/movies/1' });
531
+
532
+ console.log(memoryResponse.json());
533
+
534
+ await memoryFastify.close();
448
535
  ```
449
536
 
450
- Параметры программного API:
537
+ `createServer()` принимает точно ту же структуру конфига, что и `server.config.js`. Функция загружает и клонирует настроенные источники, а затем возвращает фасад с двумя операциями:
451
538
 
452
- | Параметр | Где используется | Назначение |
539
+ | Член | Назначение |
453
540
  | --- | --- | --- |
454
- | `databasePath` | `createServer`, `startServer`, `generateOpenApi` | Обязательный путь к JSON-базе |
455
- | `schemaPath` | Те же три функции | Необязательный путь к схеме базы |
456
- | `filesDirectoryPath`, `filesMetadataPath` | `createServer`, `startServer` | Необязательная пара, включающая файловые маршруты |
457
- | `files` | `generateOpenApi`, `createOpenApiDocument` | Нужно ли добавлять файловые маршруты в OpenAPI |
458
- | `host`, `port` | `startServer`, `generateOpenApi`, `createOpenApiDocument` | Адрес прослушивания или URL в поле `servers` |
459
- | `logger` | `createServer`, `startServer` | Настройки логгера Fastify; значение по умолчанию `true` |
460
- | `maxPageSize`, `maxFileSize` | `createServer`, `startServer` | Ограничения сервера; по умолчанию 1000 записей и 100 МиБ |
461
- | `outputPath` | `generateOpenApi` | Обязательный путь к генерируемому YAML-файлу |
462
-
463
- `createServer()` возвращает экземпляр Fastify без открытия сетевого порта, поэтому удобен вместе с `server.inject()` в тестах. `startServer()` дополнительно начинает прослушивание. `generateOpenApi()` читает файлы и записывает YAML, а `createOpenApiDocument()` работает с объектами базы и схемы в памяти и ничего не записывает. Эти функции не читают `server.config.js`: параметры нужно передавать явно. Пакет содержит сгенерированные TypeScript-декларации для всех экспортируемых функций.
541
+ | `server.fastify()` | Лениво создаёт и кэширует настоящий экземпляр Fastify; все нативные методы доступны, а `listen()` без аргументов использует `server.host` и `server.port` |
542
+ | `server.openapi()` | Возвращает документ OpenAPI и дополнительно записывает его, если настроен `openapi.path` |
543
+
544
+ При программном использовании файловые маршруты включаются при наличии секции `files`. Сигнатура второго аргумента — `{ files?: boolean }`: передайте `{ files: false }`, чтобы оставить настроенное хранилище выключенным, или `{ files: true }`, чтобы потребовать секцию `files` и включить маршруты. `server.openapi()` использует то же состояние возможности, что и `server.fastify()`.
545
+
546
+ Вызов `server.fastify().listen()` без аргументов использует `server.host` и `server.port`, а при их отсутствии — `127.0.0.1:4001`. Явные параметры `listen(options)` имеют приоритет. Относительные пути, переданные напрямую в `createServer()`, вычисляются от текущей рабочей директории; пути из `server.config.js` — от директории конфига. Пакет содержит сгенерированные TypeScript-декларации фасада и всех вариантов конфигурации.
547
+
548
+ ## Назначение и безопасность
549
+
550
+ Deep JSON Server предназначен для локальной разработки и автоматических тестов. В нём нет аутентификации и авторизации, CORS разрешён для любого источника, при дисковом хранилище принятые изменения сохраняются в настроенные файлы, а ссылочная целостность не проверяется. Оставляйте адрес loopback по умолчанию, если внешняя среда не предоставляет собственный контроль доступа; не открывайте сервер и файловые маршруты для недоверенной сети.
package/index.js CHANGED
@@ -1,2 +1,7 @@
1
- export { createOpenApiDocument, generateOpenApi } from './src/openapi/index.js';
2
- export { createServer, startServer } from './src/server.js';
1
+ /** @typedef {import('./src/config.js').DeepJsonServerConfig} DeepJsonServerConfig */
2
+ /** @typedef {import('./src/config.js').DatabaseConfig} DatabaseConfig */
3
+ /** @typedef {import('./src/config.js').FilesConfig} FilesConfig */
4
+ /** @typedef {import('./src/config.js').MemoryFile} MemoryFile */
5
+ /** @typedef {import('./src/server.js').OpenapiDocument} OpenapiDocument */
6
+
7
+ export { createServer } from './src/server.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kollors/deep-json-server",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "JSON mock server with deep filters and recursive relationship embedding",
5
5
  "type": "module",
6
6
  "types": "./types/index.d.ts",