@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.md +196 -109
- package/README.ru.md +196 -109
- package/index.js +7 -2
- package/package.json +1 -1
- package/src/cli.js +45 -44
- package/src/config.js +132 -35
- package/src/database.js +31 -2
- package/src/files.js +98 -8
- package/src/openapi/config.js +15 -1
- package/src/openapi/document.js +19 -33
- package/src/openapi/index.js +29 -18
- package/src/query/pagination.js +1 -7
- package/src/server.js +87 -77
- package/types/index.d.ts +11 -2
- package/types/src/config.d.ts +86 -0
- package/types/src/database.d.ts +5 -2
- package/types/src/files.d.ts +23 -4
- package/types/src/openapi/config.d.ts +1 -1
- package/types/src/openapi/document.d.ts +6 -7
- package/types/src/openapi/index.d.ts +9 -14
- package/types/src/query/pagination.d.ts +1 -6
- package/types/src/server.d.ts +11 -28
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
|
-
Небольшой
|
|
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` |
|
|
48
|
-
| `database.
|
|
49
|
-
| `
|
|
50
|
-
| `files.
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
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
|
-
|
|
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
|
|
63
|
-
"
|
|
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 --
|
|
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
|
-
|
|
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": "
|
|
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://
|
|
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://
|
|
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": "
|
|
120
|
-
{ "id": "2", "isArchived": false, "name": "
|
|
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": "
|
|
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": "
|
|
132
|
-
"countryId": "
|
|
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` — выполняются
|
|
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-запрос к коллекции всегда возвращает объект
|
|
236
|
+
GET-запрос к коллекции всегда возвращает объект с массивом текущей страницы и общим количеством записей после фильтрации. По умолчанию `_page` равен `1`, а `_perPage` — `10`:
|
|
169
237
|
|
|
170
238
|
```json
|
|
171
239
|
{
|
|
172
240
|
"data": [],
|
|
173
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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%
|
|
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
|
-
|
|
367
|
+
Успешная загрузка возвращает статус `201`, метаданные и постоянный URL:
|
|
299
368
|
|
|
300
369
|
```json
|
|
301
370
|
{
|
|
302
371
|
"id": "generated-id",
|
|
303
372
|
"mimeType": "image/jpeg",
|
|
304
|
-
"name": "posters/
|
|
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` возвращает исходные
|
|
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 МиБ и настраивается
|
|
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
|
-
Необязательный
|
|
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
|
|
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`.
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
|
507
|
+
await fastify.close();
|
|
431
508
|
|
|
432
|
-
//
|
|
433
|
-
await
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
537
|
+
`createServer()` принимает точно ту же структуру конфига, что и `server.config.js`. Функция загружает и клонирует настроенные источники, а затем возвращает фасад с двумя операциями:
|
|
451
538
|
|
|
452
|
-
|
|
|
539
|
+
| Член | Назначение |
|
|
453
540
|
| --- | --- | --- |
|
|
454
|
-
| `
|
|
455
|
-
| `
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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';
|