@kollors/deep-json-server 0.9.0 → 1.0.0-alpha.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 (67) hide show
  1. package/README.md +286 -410
  2. package/README.ru.md +278 -404
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/src/cli.js +50 -63
  6. package/dist/src/cli.js.map +1 -1
  7. package/dist/src/config.d.ts +16 -2
  8. package/dist/src/config.js +24 -8
  9. package/dist/src/config.js.map +1 -1
  10. package/dist/src/constants.d.ts +1 -1
  11. package/dist/src/constants.js +1 -1
  12. package/dist/src/constants.js.map +1 -1
  13. package/dist/src/database.d.ts +4 -3
  14. package/dist/src/database.js +32 -19
  15. package/dist/src/database.js.map +1 -1
  16. package/dist/src/engine.d.ts +47 -0
  17. package/dist/src/engine.js +438 -0
  18. package/dist/src/engine.js.map +1 -0
  19. package/dist/src/files/disk-store.js +13 -2
  20. package/dist/src/files/disk-store.js.map +1 -1
  21. package/dist/src/files/openapi.d.ts +3 -0
  22. package/dist/src/files/openapi.js +86 -0
  23. package/dist/src/files/openapi.js.map +1 -0
  24. package/dist/src/graphql.d.ts +4 -0
  25. package/dist/src/graphql.js +223 -0
  26. package/dist/src/graphql.js.map +1 -0
  27. package/dist/src/model.d.ts +67 -0
  28. package/dist/src/model.js +319 -0
  29. package/dist/src/model.js.map +1 -0
  30. package/dist/src/openapi/document.d.ts +7 -7
  31. package/dist/src/openapi/document.js +234 -320
  32. package/dist/src/openapi/document.js.map +1 -1
  33. package/dist/src/openapi/index.d.ts +3 -6
  34. package/dist/src/openapi/index.js +2 -3
  35. package/dist/src/openapi/index.js.map +1 -1
  36. package/dist/src/query/filter.d.ts +0 -5
  37. package/dist/src/query/filter.js +6 -170
  38. package/dist/src/query/filter.js.map +1 -1
  39. package/dist/src/query/options.d.ts +31 -0
  40. package/dist/src/query/options.js +135 -0
  41. package/dist/src/query/options.js.map +1 -0
  42. package/dist/src/server.d.ts +4 -3
  43. package/dist/src/server.js +113 -135
  44. package/dist/src/server.js.map +1 -1
  45. package/dist/src/types.d.ts +1 -3
  46. package/dist/src/utils.d.ts +0 -1
  47. package/dist/src/utils.js +0 -1
  48. package/dist/src/utils.js.map +1 -1
  49. package/package.json +9 -4
  50. package/dist/src/openapi/config.d.ts +0 -11
  51. package/dist/src/openapi/config.js +0 -204
  52. package/dist/src/openapi/config.js.map +0 -1
  53. package/dist/src/openapi/inference.d.ts +0 -14
  54. package/dist/src/openapi/inference.js +0 -145
  55. package/dist/src/openapi/inference.js.map +0 -1
  56. package/dist/src/query/index.d.ts +0 -3
  57. package/dist/src/query/index.js +0 -4
  58. package/dist/src/query/index.js.map +0 -1
  59. package/dist/src/query/pagination.d.ts +0 -11
  60. package/dist/src/query/pagination.js +0 -28
  61. package/dist/src/query/pagination.js.map +0 -1
  62. package/dist/src/query/sort.d.ts +0 -1
  63. package/dist/src/query/sort.js +0 -54
  64. package/dist/src/query/sort.js.map +0 -1
  65. package/dist/src/relations.d.ts +0 -12
  66. package/dist/src/relations.js +0 -155
  67. package/dist/src/relations.js.map +0 -1
package/README.ru.md CHANGED
@@ -1,172 +1,315 @@
1
1
  # Deep JSON Server
2
2
 
3
- [Русский](README.ru.md) | [English](README.md)
3
+ [English](README.md)
4
4
 
5
- [GitHub](https://github.com/kollors/deep-json-server) | [npm](https://www.npmjs.com/package/@kollors/deep-json-server)
5
+ JSON-сервер для имитации API: REST, GraphQL, вложенные запросы, бинарные файлы и экспорт схем. Требуется Node.js 22 или новее.
6
6
 
7
- Небольшой сервер для имитации REST API с CRUD, пагинацией, фильтрацией вложенных данных, подстановкой связанных записей через `_embed`, бинарными файлами и генерацией OpenAPI. Данные могут храниться в JSON-файлах или памяти, а связи определяются по именам полей: `countryId`, `genreIds`, `publisherIds` и так далее.
7
+ **1.0.0-alpha.1 предварительная версия с несовместимыми изменениями.** Старые `$schema`/`$info` и параметры `_where`, `_sort`, `_embed`, `_page`, `_perPage` больше не поддерживаются.
8
8
 
9
9
  ## Установка
10
10
 
11
- Требуется Node.js 22 или новее.
12
-
13
- ```bash
14
- npm install --save-dev @kollors/deep-json-server
11
+ ```sh
12
+ npm install @kollors/deep-json-server@alpha
15
13
  ```
16
14
 
15
+ Канал npm `alpha` отделён от `latest`. Для точной версии используйте `@1.0.0-alpha.1`.
16
+
17
17
  ## Быстрый старт
18
18
 
19
- До запуска создайте файл базы `mock/database.json`:
19
+ `server.config.js`:
20
+
21
+ ```js
22
+ export default {
23
+ database: { path: './database.json', schema: './schema.json' },
24
+ graphql: { enabled: true, path: './generated/schema.graphql' },
25
+ openapi: { path: './generated/openapi.yaml' },
26
+ server: { host: '127.0.0.1', port: 4001, pageSize: 10, maxPageSize: 100 },
27
+ };
28
+ ```
29
+
30
+ ```sh
31
+ npx deep-json-server server.config.js
32
+ npx deep-json-server --openapi-only --graphql-only server.config.js
33
+ ```
34
+
35
+ Вторая команда экспортирует обе схемы без запуска сервера. Обычный запуск не записывает файлы схем.
36
+
37
+ Полные примеры каталога: [база](examples/database.json), [схема моделей](examples/schema.json), [конфигурация](examples/server.config.js).
38
+
39
+ ## Конфигурация
40
+
41
+ | Ключ | Назначение |
42
+ |---|---|
43
+ | `database.path` / `database.data` | Ровно один: JSON-файл или объект коллекций в памяти |
44
+ | `database.schema` | Объект моделей или путь к JSON-файлу; необязателен для REST |
45
+ | `openapi.path` | Путь экспорта YAML |
46
+ | `openapi.info` | Необязательные метаданные: `title`, `version`, `description` |
47
+ | `graphql.enabled` | Включить GraphQL HTTP API; по умолчанию `false` |
48
+ | `graphql.endpoint` | Путь GraphQL; по умолчанию `/graphql` |
49
+ | `graphql.path` | Путь экспорта GraphQL SDL |
50
+ | `server.host`, `server.port` | По умолчанию `127.0.0.1`, `4001`; CLI также читает `HOST`/`PORT` |
51
+ | `server.pageSize`, `server.maxPageSize` | По умолчанию 10 и 100; размер по умолчанию ограничен максимумом |
52
+ | `server.cors`, `server.logger` | По умолчанию `true`; logger также принимает настройки Fastify |
53
+ | `server.maxFileSize` | По умолчанию 100 МиБ |
54
+ | `files.data` | Бинарные файлы в памяти |
55
+ | `files.directory`, `files.metadata` | Каталог файлов и JSON метаданных; необходимы оба |
56
+
57
+ Пути конфигурационного файла разрешаются относительно него. При прямом `createServer()` — относительно рабочего каталога. Переданные данные в памяти копируются.
58
+
59
+ | Флаг CLI | Действие |
60
+ |---|---|
61
+ | `--files` | Включить файловые маршруты |
62
+ | `--graphql` | Включить GraphQL API |
63
+ | `--openapi` | Экспортировать OpenAPI и запустить сервер |
64
+ | `--openapi-only` | Только экспортировать OpenAPI |
65
+ | `--graphql-schema` | Экспортировать GraphQL SDL и запустить сервер |
66
+ | `--graphql-only` | Только экспортировать GraphQL SDL |
67
+ | `--help` | Показать справку |
68
+
69
+ Экспортеры можно объединять. Любой флаг `--*-only` исключает запуск сервера. В CLI для файлов нужен `--files`; программный API включает их при наличии секции `files`, если второй аргумент `createServer()` не переопределяет это поведение.
70
+
71
+ ## Схема моделей
20
72
 
21
73
  ```json
22
74
  {
23
- "movies": [
24
- { "id": "1", "title": "Тени Ардении" }
25
- ]
75
+ "Country": {
76
+ "collection": "countries",
77
+ "api": ["openapi", "graphql"],
78
+ "fields": {
79
+ "id": { "type": "string", "primary": true, "generated": "uuid" },
80
+ "name": { "type": "string", "required": true },
81
+ "users": { "type": "User[]", "target": "countryId" }
82
+ }
83
+ },
84
+ "User": {
85
+ "collection": "users",
86
+ "api": ["openapi", "graphql"],
87
+ "fields": {
88
+ "id": { "type": "string", "primary": true, "generated": "uuid" },
89
+ "fullName": { "type": "string", "required": true },
90
+ "country": { "type": "Country", "source": "countryId" }
91
+ }
92
+ }
26
93
  }
27
94
  ```
28
95
 
29
- Рядом с `package.json` создайте ESM-модуль `server.config.js`:
96
+ По умолчанию `api` содержит `["openapi", "graphql"]`. Пустой массив исключает модель из экспорта и GraphQL, но REST продолжает работать. Связанные модели должны разрешать тот же формат экспорта. Имена должны быть допустимыми идентификаторами; конфликты генерируемых типов и операций вызывают ошибку. Имена `and`, `or`, `not` зарезервированы фильтрами.
30
97
 
31
- ```js
32
- export default {
33
- database: {
34
- path: 'mock/database.json',
35
- },
36
- };
98
+ | Возможность | Со схемой | Без схемы |
99
+ |---|---|---|
100
+ | REST CRUD | Валидация модели | Только проверки JSON, тела запроса и идентификаторов |
101
+ | Связи | Поля схемы | По ключам `countryId`, `genreIds` и аналогичным |
102
+ | Экспорт OpenAPI 3.0.3 | Доступен | Ошибка при запросе экспорта |
103
+ | GraphQL SDL / API | Доступен | Ошибка при запросе |
104
+
105
+ При явной схеме неизвестные поля и коллекции запрещены. Исключение — хранимые ключи, выведенные из связей. Исходная база проверяется при загрузке. Схемы можно экспортировать для пустых коллекций и пустого объекта базы. REST без схемы сохраняет обычный генерируемый ключ `id`.
106
+
107
+ ### Поля
108
+
109
+ Типы: `string`, `number`, `boolean`, `object` или имя модели. Суффикс `[]` обозначает массив. Нет `integer`, `relation`, `items` и многомерных строк типов. Вложенные поля описываются через точку: `actors.fullName`. Объект внутри массива может содержать собственные массивы.
110
+
111
+ | Свойства | Назначение |
112
+ |---|---|
113
+ | `type` | Обязательный тип |
114
+ | `description`, `example` | Документация и пример |
115
+ | `required`, `nullable` | По умолчанию `false`; наличие поля и разрешение `null` независимы |
116
+ | `default` | Значение при пропуске в create/replace; PATCH не вставляет значения по умолчанию |
117
+ | `enum` | Допустимые значения; у массива — значения каждого элемента |
118
+ | `primary` | Корневой первичный ключ: обязательный, уникальный, неизменяемый, без `null` |
119
+ | `generated` | `uuid` для строк, `increment` для чисел; значение создаёт сервер |
120
+ | `readOnly`, `writeOnly` | Только ответ или только входные данные; взаимно исключаются |
121
+ | `minLength`, `maxLength`, `pattern` | Ограничения строк |
122
+ | `format` | `date`, `date-time`, `email`, `uri`, `uuid` |
123
+ | `minimum`, `maximum` | Включительные числовые границы |
124
+ | `source`, `target`, `onDelete` | Описание связи |
125
+
126
+ Ограничения строк и чисел применяются к каждому элементу `string[]`/`number[]`. `required`/`nullable` относятся ко всему массиву; `default`/`example` содержат массив целиком. Элементы массива не допускают `null`. Ограничений количества и уникальности элементов нет. Обязательный обычный массив может быть пустым.
127
+
128
+ У модели ровно один корневой первичный ключ типа string/number. Имя произвольное: `id`, `username`, `code`. Без `generated` значение передаёт клиент при создании. Генерируемые поля могут быть только корневыми, исключаются из входных типов и несовместимы с `default`. Replace сохраняет корневые генерируемые поля и `readOnly`.
129
+
130
+ Например, `LocalUser` с первичным `username` и полем `password: {"type":"string","required":true,"writeOnly":true}` получает запрос `localUser(username: ...)` и маршрут `/localUsers/{username}`. Пароль исключён из ответов, scope, фильтров и сортировки. Это не реализует хеширование и авторизацию.
131
+
132
+ ### Связи
133
+
134
+ ```json
135
+ "actors.genres": {
136
+ "type": "Genre[]",
137
+ "source": "actors.genreIds",
138
+ "required": true
139
+ }
37
140
  ```
38
141
 
39
- Запустите сервер:
142
+ `Genre` возвращает объект, `Genre[]` — список. Без `source` используем первичный ключ текущей модели, без `target` — первичный ключ целевой. Пути полные, от корня соответствующей записи. Внутри `actors` путь `actors.genreIds` читает ключи текущего актёра. Пропущенный `source` означает ключ корневой модели, а не `actors.id`.
40
143
 
41
- ```bash
42
- npx deep-json-server server.config.js
144
+ Хранимые ключи остаются в базе и входят в собственные поля. Их типы выводятся из сопоставляемых ключей. Для необъявленного source, ссылающегося на первичный ключ цели, множественная связь означает массив ключей, одиночная — одно значение. Если сопоставление неоднозначно, нужное хранимое поле следует описать явно. Генерация не зависит от первой записи базы.
145
+
146
+ Обратная связь: `User.movies = {"type":"Movie[]","target":"actors.userId"}`. Фильм возвращается один раз, даже если совпало несколько актёров. Несколько совпадений для одиночной связи — ошибка.
147
+
148
+ Каждая переданная прямая ссылка должна существовать. `required: true` у связи требует хотя бы одну связанную запись до фильтрации и пагинации ответа. Обратная связь с первичным source может быть пустой, если не объявлена обязательной. Отсутствующая одиночная связь возвращает `null`.
149
+
150
+ `onDelete` срабатывает **при удалении целевой записи**:
151
+
152
+ - `restrict` — по умолчанию: запретить удаление, пока на цель ссылается сохраняемая запись.
153
+ - `cascade` — удалить ссылающуюся запись. Для `User.country` удаление страны удаляет пользователей. Для `Movie.actors.user` удаление пользователя удаляет соответствующие элементы actors, сохраняя фильм.
154
+
155
+ Сначала вычисляется весь каскад, обрабатываются циклы и проверяются ограничения, затем сохраняется результат. Ошибка отменяет всю операцию. Политики действуют и на явно объявленные обратные связи; при описании обоих направлений учитывайте оба правила.
156
+
157
+ ## Запросы и ответы
158
+
159
+ Коллекции и списки объектов, включая вложенные `object[]`, возвращают:
160
+
161
+ ```json
162
+ { "data": [], "total": 0 }
43
163
  ```
44
164
 
45
- По умолчанию API доступен по адресу `http://127.0.0.1:4001`. Например, `GET http://127.0.0.1:4001/movies` вернёт страницу:
165
+ Массивы примитивов остаются обычными массивами. У каждого списка объектов есть необязательные `where`, `order`, `pager`. Порядок обработки: фильтрация → сортировка → пагинация. `total` считается после фильтрации, до пагинации. Страницы начинаются с 1; отсутствие pager включает пагинацию по умолчанию. Превышение максимума, дробные и неположительные значения — ошибка. За пределами списка возвращается пустой data с правильным total.
166
+
167
+ Фильтры: `eq`, `ne`, `in`; для строк `contains`, `startsWith`, `endsWith`; сравнения `gt`, `gte`, `lt`, `lte`. Логика — `and`, `or`, `not`. У массивов есть `some`, `every`, `none`; у примитивных массивов также `contains`, `in`. Поиск строк не учитывает регистр. Условия полей записываются объектами операторов, без сокращения до скалярного значения.
46
168
 
47
169
  ```json
48
170
  {
49
- "data": [{ "id": "1", "title": "Тени Ардении" }],
50
- "total": 1
171
+ "movies": {
172
+ "some": {
173
+ "actors": {
174
+ "some": {
175
+ "genres": { "some": { "id": { "in": ["2", "3"] } } }
176
+ }
177
+ }
178
+ }
179
+ }
51
180
  }
52
181
  ```
53
182
 
54
- ## Конфигурация и запуск
183
+ Корневой where выбирает родителей. Where внутри связи фильтрует её элементы, сохраняя родителя. Каждый вложенный список обрабатывается отдельно. Для фильтра по связи не нужно раскрывать её в ответе.
55
184
 
56
- Создайте ESM-модуль `server.config.js`. В примере ниже показаны настройки всех возможностей:
57
-
58
- ```js
59
- import process from 'node:process';
185
+ `order` массив правил `{ "field": "fullName", "direction": "ASC" }`. Первое правило приоритетнее; полные совпадения сохраняют порядок хранения. `null` и отсутствующее значение сравниваются одинаково. REST использует путь `profile.name`, GraphQL — enum `profile_name`. Неоднозначные имена enum вызывают ошибку генерации. Сортировка родителей по связям и массивам пока не поддерживается; внутри связи сортировка доступна.
60
186
 
61
- export default {
62
- database: {
63
- path: process.env.DATABASE_PATH ?? 'mock/database.json',
64
- schema: 'mock/database-schema.json',
65
- },
66
- files: {
67
- directory: 'mock/files',
68
- metadata: 'mock/files/_database.json',
69
- },
70
- openapi: {
71
- path: 'mock/openapi-schema.yaml',
72
- },
73
- server: {
74
- cors: true,
75
- host: '127.0.0.1',
76
- logger: true,
77
- maxFileSize: 100 * 1024 * 1024,
78
- maxPageSize: 1000,
79
- port: 4001,
80
- },
81
- };
82
- ```
187
+ ### REST
83
188
 
84
- Ключи конфигурации:
189
+ | Метод | Путь | Операция |
190
+ |---|---|---|
191
+ | GET | `/users` | `userList` |
192
+ | GET | `/users/{id}` | `user` |
193
+ | POST | `/users` | `userCreate` |
194
+ | PUT | `/users/{id}` | `userReplace` |
195
+ | PATCH | `/users/{id}` | `userUpdate` |
196
+ | DELETE | `/users/{id}` | `userDelete` |
85
197
 
86
- | Ключ | Условие | Назначение |
87
- | --- | --- | --- |
88
- | `database.path` | Требуется ровно один из `path` или `data` | Существующий JSON-файл базы данных |
89
- | `database.data` | Требуется ровно один из `path` или `data` | Объект базы данных, хранящийся в памяти |
90
- | `database.schema` | Необязателен | Путь к JSON-настройкам либо объект с настройками проверки запросов и OpenAPI |
91
- | `files.directory` | Вместе с `files.metadata` | Директория для бинарного содержимого на диске |
92
- | `files.metadata` | Вместе с `files.directory` | JSON-файл с метаданными файлов на диске |
93
- | `files.data` | Вместо пары `directory` и `metadata` | Файлы в памяти с содержимым в `Uint8Array` |
94
- | `openapi.path` | Обязателен для CLI-флагов `--openapi` и `--openapi-only` | Генерируемый YAML-файл OpenAPI; программный API может вернуть документ без этого пути |
95
- | `server.cors` | Необязателен | Включает разрешающие CORS-заголовки и маршруты `OPTIONS`; по умолчанию `true` |
96
- | `server.host` | Необязателен | Адрес для CLI, `server.openapi()` и вызова `server.fastify().listen()` без аргументов; по умолчанию `127.0.0.1` |
97
- | `server.logger` | Необязателен | Настройки логгера Fastify; по умолчанию `true` |
98
- | `server.maxFileSize` | Необязателен | Максимальный размер загружаемого файла в байтах при включённых файловых маршрутах; по умолчанию 100 МиБ |
99
- | `server.maxPageSize` | Необязателен | Максимальное значение `_perPage` в API и OpenAPI; по умолчанию `1000` |
100
- | `server.port` | Необязателен | Порт для CLI, `server.openapi()` и вызова `server.fastify().listen()` без аргументов; по умолчанию `4001` |
198
+ Имя параметра пути соответствует первичному ключу. POST/PUT/PATCH принимают обычный объект записи. PUT заменяет запись с сохранением ключа и корневых серверных полей. PATCH поверхностно объединяет поля; переданные вложенные объекты заменяются целиком. Create/replace проверяют обязательность. Update проверяет переданные значения и итоговую запись. Отсутствующая запись — 404, конфликт — 409. DELETE возвращает удалённую запись.
101
199
 
102
- `server.port` должен быть целым числом от `0` до `65535`. Значение `0` позволяет Fastify выбрать свободный порт при запуске, но не подходит для генерации URL в OpenAPI, где допустимы порты от `1` до `65535`. `server.maxFileSize` и `server.maxPageSize` должны быть положительными целыми числами.
200
+ Параметры `where`, `order`, `pager`, `nested` содержат JSON; `scope` строку выбора. Пример до URL-кодирования:
103
201
 
104
- Все относительные пути вычисляются от директории с `server.config.js`, а не от текущей рабочей директории. Неизвестные ключи, пустые пути и значения некорректных типов отклоняются до запуска. Конфиг является исполняемым JavaScript: в нём можно читать переменные окружения, импортировать другие модули и вычислять значения перед экспортом объекта. Для `.js`-конфига с `export default` проект должен быть ESM (`"type": "module"`); в CommonJS-проекте сохраните тот же конфиг как `server.config.mjs`.
202
+ ```text
203
+ GET /users?where={"fullName":{"contains":"Мира"}}&order=[{"field":"fullName","direction":"ASC"}]&pager={"page":1,"pageSize":20}
204
+ ```
105
205
 
106
- В том же конфиге все данные можно разместить в памяти. `database.path` и `database.data` взаимоисключающие, а `database.schema` принимает путь или объект. Аналогично, `files.data` нельзя сочетать с `files.directory` или `files.metadata`:
206
+ Кодирование через `URLSearchParams`:
107
207
 
108
208
  ```js
109
- export default {
110
- database: {
111
- data: { movies: [{ id: '1', title: 'Тени Ардении' }] },
112
- schema: { $info: { title: 'API фильмов', version: '1.0.0' } },
113
- },
114
- files: {
115
- data: [{ content: new Uint8Array([1, 2, 3]), directory: 'examples', mimeType: 'application/octet-stream', name: 'example.bin' }],
116
- },
117
- };
209
+ const params = new URLSearchParams({
210
+ scope: 'id,fullName,movies(id,title)',
211
+ nested: JSON.stringify({ movies: { order: [{ field: 'title', direction: 'ASC' }], pager: { page: 1, pageSize: 5 } } }),
212
+ });
213
+ const response = await fetch(`/users?${params}`);
118
214
  ```
119
215
 
120
- Значения в памяти клонируются при инициализации. Поэтому операции с CRUD и файлами не изменяют экспортированный объект конфига, а их результаты исчезают после завершения процесса.
216
+ `scope=*,actors(user(id,fullName),genres(*))` выбирает собственные поля и явные связи. `*` включает собственные поля текущего объекта и хранимые ключи, исключает writeOnly и не раскрывает связи. Без scope выбираются собственные поля. Оболочки data/total сохраняются.
121
217
 
122
- Добавьте нужные команды в `package.json`. Здесь `mock:openapi:files` сначала обновляет OpenAPI, а затем оставляет сервер запущенным с файловыми маршрутами:
218
+ `nested` сопоставляет полные пути ответа и настройки списков:
123
219
 
124
220
  ```json
125
221
  {
126
- "scripts": {
127
- "mock": "deep-json-server server.config.js",
128
- "mock:files": "deep-json-server --files server.config.js",
129
- "mock:openapi:files": "deep-json-server --files --openapi server.config.js",
130
- "openapi": "deep-json-server --openapi-only server.config.js",
131
- "openapi:files": "deep-json-server --files --openapi-only server.config.js"
222
+ "actors": { "pager": { "pageSize": 5 } },
223
+ "actors.genres": {
224
+ "where": { "id": { "in": ["2", "3"] } },
225
+ "order": [{ "field": "name", "direction": "ASC" }]
132
226
  }
133
227
  }
134
228
  ```
135
229
 
136
- Режимы CLI:
230
+ Путь nested должен быть выбран через scope и вести к списку объектов. Одиночные маршруты и мутации принимают scope/nested; корневые where/order/pager применяются только к GET коллекции. Неизвестные имена и небезопасные пути возвращают 400.
231
+
232
+ ### GraphQL
233
+
234
+ ```graphql
235
+ query {
236
+ userList(
237
+ order: [{ field: fullName, direction: ASC }]
238
+ pager: { page: 1, pageSize: 20 }
239
+ ) {
240
+ total
241
+ data {
242
+ id
243
+ fullName
244
+ movies(order: [{ field: title, direction: ASC }], pager: { pageSize: 5 }) {
245
+ total
246
+ data { id title }
247
+ }
248
+ }
249
+ }
250
+ }
251
+ ```
137
252
 
138
- | Команда | Поведение |
139
- | --- | --- |
140
- | `deep-json-server server.config.js` | Запускает CRUD-сервер без файловых маршрутов |
141
- | `deep-json-server --files server.config.js` | Запускает CRUD-сервер с файловыми маршрутами |
142
- | `deep-json-server --openapi server.config.js` | Генерирует OpenAPI и запускает CRUD-сервер |
143
- | `deep-json-server --files --openapi server.config.js` | Генерирует OpenAPI с файловыми маршрутами и запускает сервер с ними |
144
- | `deep-json-server --openapi-only server.config.js` | Генерирует OpenAPI и завершает работу |
145
- | `deep-json-server --files --openapi-only server.config.js` | Генерирует OpenAPI с файловыми маршрутами и завершает работу |
253
+ Одиночный запрос `user(id: ...)`, без ById; отсутствующая запись даёт null. Мутации: `userCreate(data: ...)`, `userReplace(id: ..., data: ...)`, `userUpdate(id: ..., data: ...)`, `userDelete(id: ...)`. Если модель содержит только генерируемые поля, создание не имеет аргумента data. Используются те же операции хранения и проверки, что в REST. Выбор связей в результате мутации управляет только ответом.
146
254
 
147
- Флаг `--files` независим: без него файловые маршруты не регистрируются и не добавляются в OpenAPI, даже если секция `files` присутствует в конфиге. Параметры `--openapi` и `--openapi-only` взаимоисключающие. Команда `deep-json-server --help` выводит краткую справку по CLI.
255
+ Строковый первичный ключ представлен GraphQL ID, обычные строки String, числа Float, пагинация Int. Enum сохраняет допустимые строковые имена; остальные значения получают имена VALUE_0, VALUE_1 и т. д. Длины строк и форматы проверяет общий валидатор во время выполнения: SDL не выражает все ограничения. Доступны интроспекция, обычные query и mutation; подписки и массовые мутации не добавлены.
148
256
 
149
- ## Пример базы данных
150
257
 
151
- Ниже приведён пример каталога фильмов с тестовыми данными. `Гангстер` связан с родительским жанром `Криминал`.
258
+ ## Пример базы данных
152
259
 
153
260
  ```json
154
261
  {
155
262
  "countries": [
156
- { "id": "1", "isArchived": false, "name": "Ардения" },
157
- { "id": "2", "isArchived": false, "name": "Велория" }
263
+ {
264
+ "id": "1",
265
+ "isArchived": false,
266
+ "name": "Ардения"
267
+ },
268
+ {
269
+ "id": "2",
270
+ "isArchived": false,
271
+ "name": "Велория"
272
+ }
158
273
  ],
159
274
  "genres": [
160
- { "id": "1", "isArchived": false, "name": "Криминал", "parentIds": [] },
161
- { "id": "2", "isArchived": false, "name": "Гангстер", "parentIds": ["1"] },
162
- { "id": "3", "isArchived": false, "name": "Драма", "parentIds": [] },
163
- { "id": "4", "isArchived": false, "name": "Комедия", "parentIds": [] }
275
+ {
276
+ "id": "1",
277
+ "isArchived": false,
278
+ "name": "Криминал",
279
+ "parentIds": []
280
+ },
281
+ {
282
+ "id": "2",
283
+ "isArchived": false,
284
+ "name": "Гангстер",
285
+ "parentIds": ["1"]
286
+ },
287
+ {
288
+ "id": "3",
289
+ "isArchived": false,
290
+ "name": "Драма",
291
+ "parentIds": []
292
+ },
293
+ {
294
+ "id": "4",
295
+ "isArchived": false,
296
+ "name": "Комедия",
297
+ "parentIds": []
298
+ }
164
299
  ],
165
300
  "movies": [
166
301
  {
167
302
  "actors": [
168
- { "genreIds": ["2", "3"], "id": "movie-1-actor-1", "userId": "1" },
169
- { "genreIds": ["3"], "id": "movie-1-actor-2", "userId": "2" }
303
+ {
304
+ "genreIds": ["2", "3"],
305
+ "id": "movie-1-actor-1",
306
+ "userId": "1"
307
+ },
308
+ {
309
+ "genreIds": ["3"],
310
+ "id": "movie-1-actor-2",
311
+ "userId": "2"
312
+ }
170
313
  ],
171
314
  "coverSrc": "https://example.com/covers/shadows-of-ardenia.jpg",
172
315
  "description": "Наследница портового города раскрывает заговор двух соперничающих семей.",
@@ -186,8 +329,16 @@ export default {
186
329
  }
187
330
  ],
188
331
  "publishers": [
189
- { "id": "1", "isArchived": false, "name": "Northlight Studio" },
190
- { "id": "2", "isArchived": false, "name": "Aurora Pictures" }
332
+ {
333
+ "id": "1",
334
+ "isArchived": false,
335
+ "name": "Northlight Studio"
336
+ },
337
+ {
338
+ "id": "2",
339
+ "isArchived": false,
340
+ "name": "Aurora Pictures"
341
+ }
191
342
  ],
192
343
  "users": [
193
344
  {
@@ -208,144 +359,6 @@ export default {
208
359
  }
209
360
  ```
210
361
 
211
- Каждый массив верхнего уровня становится REST-ресурсом:
212
-
213
- ```text
214
- GET /movies
215
- GET /movies/:id
216
- POST /movies
217
- PUT /movies/:id
218
- PATCH /movies/:id
219
- DELETE /movies/:id
220
- ```
221
-
222
- `POST` генерирует строковый ID. `PUT` полностью заменяет выбранную запись, а `PATCH` изменяет только переданные поля; обе операции сохраняют существующий ID и его тип. Поле `id` в теле любого запроса не может переопределить ID, которым управляет сервер. Все операции записи — `POST`, `PUT`, `PATCH` и `DELETE` — выполняются последовательно; дисковое хранилище записывает их в JSON, а хранилище в памяти сохраняет до завершения процесса.
223
-
224
- Файл базы должен существовать до запуска. Имена ресурсов могут содержать латинские буквы, цифры, `_` и `-` и должны начинаться с буквы. Каждый ресурс является массивом JSON-объектов. У каждой записи должен быть непустой строковый или конечный числовой `id`; ID должны быть уникальны внутри ресурса при сравнении как строки, поэтому `1` и `"1"` не могут существовать одновременно. Все вложенные значения должны быть совместимы с JSON: конечные числа, строки, логические значения, `null`, массивы и обычные объекты. Перед каждым GET-запросом к ресурсу и изменением сервер заново читает файл, поэтому корректные правки существующих ресурсов становятся видны сразу. Имена ресурсов и маршруты определяются при запуске; после добавления, удаления или переименования массива верхнего уровня перезапустите сервер.
225
-
226
- Успешная операция записи возвращает созданную, заменённую, обновлённую или удалённую запись. Ошибки используют подходящий HTTP-статус и следующий JSON-формат:
227
-
228
- ```json
229
- { "error": "..." }
230
- ```
231
-
232
- ## Пагинация и сортировка
233
-
234
- ```http
235
- GET /movies?_page=1&_perPage=10&_sort=-id,title
236
- ```
237
-
238
- GET-запрос к коллекции всегда возвращает объект с массивом текущей страницы и общим количеством записей после фильтрации. По умолчанию `_page` равен `1`, а `_perPage` — `10`:
239
-
240
- ```json
241
- {
242
- "data": [],
243
- "total": 0
244
- }
245
- ```
246
-
247
- `data` содержит записи только запрошенной страницы. `total` содержит количество всех записей, соответствующих фильтру, до применения пагинации. Номер последней страницы при необходимости вычисляется на клиенте как `Math.max(1, Math.ceil(total / pageSize))`.
248
-
249
- Оба параметра пагинации должны быть положительными целыми числами. По умолчанию `_perPage` не может превышать `1000`; лимит меняется через `server.maxPageSize` в конфиге, переданном CLI или `createServer()`. При некорректном значении сервер возвращает `400`, а не исправляет его автоматически. Страница после последней возвращает пустой массив `data`, но сохраняет фактическое значение `total`.
250
-
251
- `_sort` принимает пути полей через запятую. Правила применяются слева направо; префикс `-` включает сортировку по убыванию. Через точку можно обратиться к полю вложенного объекта, включая поля, добавленные через `_embed`, например `GET /users?_embed=country&_sort=country.name,-id`. Неизвестные и небезопасные поля сортировки возвращают `400`.
252
-
253
- ## Фильтры
254
-
255
- Передайте JSON-объект через `_where`:
256
-
257
- ```http
258
- GET /movies?_where={"title":{"contains":"тени"}}
259
- ```
260
-
261
- Можно фильтровать вложенные объекты и массивы на любой глубине. Условия внутри одного объекта по умолчанию объединяются через `AND`:
262
-
263
- ```json
264
- {
265
- "actors": { "some": { "userId": { "eq": "1" } } },
266
- "title": { "contains": "тени" }
267
- }
268
- ```
269
-
270
- Для явных логических групп используйте `and`, `or` и `not`:
271
-
272
- ```json
273
- {
274
- "and": [
275
- {
276
- "or": [
277
- { "title": { "contains": "тени" } },
278
- { "actors": { "some": { "userId": { "eq": "2" } } } }
279
- ]
280
- },
281
- { "not": { "isArchived": { "eq": true } } }
282
- ]
283
- }
284
- ```
285
-
286
- Операторы полей:
287
-
288
- | Оператор | Поведение |
289
- | --- | --- |
290
- | `eq`, `ne` | Равенство или неравенство |
291
- | `contains` | Подстрока без учёта регистра для строк или совпадающий элемент массива |
292
- | `startsWith`, `endsWith` | Начало или окончание строки без учёта регистра |
293
- | `gt`, `gte`, `lt`, `lte` | Сравнение значений; строки дат ISO можно сравнивать лексикографически |
294
- | `in` | Совпадение скалярного значения или элемента массива с одним из переданных значений |
295
- | `some`, `every`, `none` | Применение вложенного условия к элементам массива |
296
- | `not` | Отрицание вложенного условия поля |
297
-
298
- Также можно использовать простые query-параметры:
299
-
300
- ```http
301
- GET /movies?title:contains=тени
302
- ```
303
-
304
- В простых фильтрах распознаются JSON-примитивы: числа, `true`, `false` и `null`. Значения с ведущими нулями, например `001`, остаются строками. Неизвестные операторы, некорректные логические условия и отсутствующие в непустом ресурсе пути фильтра возвращают `400`.
305
-
306
- Разные простые query-фильтры объединяются через `AND`. Повтор одного фильтра равенства выбирает любое из его значений, поэтому `GET /movies?id=1&id=2` равнозначен `GET /movies?id:in=1,2`. Для оператора `in` значения перечисляются через запятую. Для поля-массива `in` означает, что хотя бы один элемент поля совпадает хотя бы с одним переданным значением. `every` для пустого массива возвращает `true`, а `some` — `false`.
307
-
308
- Если передан `_where`, он становится полным фильтром, а остальные простые параметры фильтрации игнорируются. В примерах JSON оставлен читаемым; при ручном формировании URL HTTP-клиент должен закодировать значение `_where`, например через `encodeURIComponent(JSON.stringify(where))`.
309
-
310
- Фильтрация выполняется после `_embed`. Поэтому фильтр может обращаться к полям добавленной связи, если в том же запросе указан соответствующий `_embed`; сохранённые поля `...Id` и `...Ids` всегда можно фильтровать напрямую.
311
-
312
- ## Связи
313
-
314
- Используйте `_embed`, чтобы добавить связанные записи в ответ:
315
-
316
- ```http
317
- GET /movies/1?_embed=actors.user.country&_embed=actors.genres&_embed=publishers
318
- ```
319
-
320
- Ответ будет содержать актёров, пользователя и жанры каждого актёра, страну пользователя и издателей. Исходные поля с ID остаются в ответе, а файл базы не изменяется. Сервер не устанавливает фиксированный лимит глубины, но каждый требуемый уровень должен быть явно указан в конечном пути `_embed`:
321
-
322
- ```http
323
- GET /movies/1?_embed=actors.user.country
324
- GET /genres/2?_embed=parents.parents
325
- ```
326
-
327
- Неизвестные и некорректные пути `_embed` возвращают `400`.
328
-
329
- Параметр `_embed` можно передать несколько раз, как в примере выше, либо перечислить пути через запятую в одном параметре. Пагинация применяется только к запрошенной корневой коллекции; вложенные связанные записи возвращаются полностью.
330
-
331
- Поддерживаются и обратные связи:
332
-
333
- ```http
334
- GET /countries/1?_embed=users
335
- ```
336
-
337
- Связи определяются по именам полей. Поле `<relation>Id` создаёт одиночную связь, а `<relation>Ids` — связь с коллекцией. Имя связи сопоставляется с ресурсом верхнего уровня напрямую или через его форму в единственном числе. Например:
338
-
339
- - `countryId` ссылается на `countries`;
340
- - `userId` ссылается на `users`, если запрошена связь `user`;
341
- - `genreIds` ссылается на `genres`;
342
- - `publisherIds` ссылается на `publishers`;
343
- - `parentIds` ссылается на тот же ресурс, если запрошена связь `_embed=parents`; `_embed=children` загружает обратную связь с дочерними записями.
344
-
345
- Имя обратной связи совпадает с именем исходного ресурса. Например, `_embed=users` у страны находит пользователей, во вложенных данных которых указан соответствующий `countryId`. Сервер получает связи по запросу, но не проверяет ссылочную целостность при записи данных.
346
-
347
- Явные поля `...Id` и `...Ids` определяют связи. Если в записи также сохранено устаревшее вложенное значение, `_embed` заменяет это свойство ответа актуальной связанной записью. Если связанная запись для одиночной связи не найдена, результатом будет `null`; отсутствующие связанные записи коллекции не попадут в итоговый массив. ID-индексы создаются только для ресурсов, используемых при поиске связей в текущем запросе.
348
-
349
362
  ## Файлы
350
363
 
351
364
  Добавьте `files.directory` и `files.metadata` в конфиг сервера, затем передайте `--files`, чтобы включить загрузку бинарных файлов:
@@ -411,172 +424,33 @@ Content-Type: application/json
411
424
 
412
425
  Используется бинарное тело запроса, а не `multipart/form-data`, поэтому `XMLHttpRequest.upload.onprogress` может показывать прогресс при непосредственной отправке `File` через `xhr.send(file)`. Максимальный размер по умолчанию равен 100 МиБ и настраивается через `server.maxFileSize`. Отсутствующие или небезопасные заголовки и пути возвращают `400`, превышение лимита — `413`, а отсутствующий, некорректный или не поддерживаемый Fastify `Content-Type` — `400` либо `415` в зависимости от этапа проверки.
413
426
 
414
- ## Схема базы данных и генерация OpenAPI
415
-
416
- Необязательный путь или объект в `database.schema` настраивает автоматически выведенные схемы. Это собственный формат настроек Deep JSON Server, а не стандартный документ JSON Schema: `$schema` здесь является объектом с настройками ресурсов. Например, файл `mock/database-schema.json` может содержать:
417
-
418
- ```json
419
- {
420
- "$info": {
421
- "title": "API каталога фильмов",
422
- "version": "1.0.0"
423
- },
424
- "$schema": {
425
- "movies": {
426
- "required": ["actors", "actors.genreIds", "actors.userId", "publisherIds", "title"],
427
- "formats": {
428
- "coverSrc": "uri"
429
- }
430
- },
431
- "users": {
432
- "required": ["bornAt", "fullName"],
433
- "formats": {
434
- "avatarSrc": "uri",
435
- "bornAt": "date"
436
- }
437
- }
438
- }
439
- }
440
- ```
441
-
442
- Настройки схемы:
443
-
444
- | Ключ | Назначение |
445
- | --- | --- |
446
- | `$info` | Объект `info` в OpenAPI; если он указан, обязательны непустые `title` и `version` |
447
- | `$schema.<resource>.name` | Явное имя компонента, если автоматическое образование единственного числа не подходит или создаёт коллизию |
448
- | `$schema.<resource>.required` | Пути обязательных полей; вложенные пути записываются через точку, например `actors.userId` |
449
- | `$schema.<resource>.formats` | Форматы OpenAPI для автоматически найденных или явно описанных строковых полей, например `date`, `date-time` или `uri` |
450
- | `$schema.<resource>.properties` | Рекурсивные OpenAPI-совместимые схемы полей, объединяемые с автоматически найденными |
451
-
452
- `formats` — сокращённая запись для назначения `format` уже существующему строковому полю. `properties` позволяет полностью описать поле, в том числе его `type`, `format`, ограничения и вложенные свойства, либо добавить поле, которого нет в данных. Если одно поле получает `format` через оба механизма, значение из `formats` применяется последним.
453
-
454
- Укажите `openapi.path` в конфиге сервера, затем сгенерируйте OpenAPI 3.0.3 и завершите работу:
455
-
456
- ```bash
457
- deep-json-server --openapi-only server.config.js
458
- ```
459
-
460
- Чтобы включить в документ файловые маршруты, настройте секцию `files` и добавьте флаг `--files`: `deep-json-server --files --openapi-only server.config.js`.
461
-
462
- Генератор определяет ресурсы и типы полей по всем записям базы. По умолчанию все найденные поля необязательные, а поле `id` верхнего уровня всегда обязательное в схемах ответа и исключается из схем создания и обновления. Остальные обязательные поля перечисляются в `required`. Вложенный обязательный путь делает обязательным само вложенное свойство, но не все его родительские пути; при необходимости родителя нужно перечислить отдельно.
463
-
464
- Разные непересекающиеся типы значений определяются независимо и объединяются через `oneOf`; сочетание целых и дробных чисел описывается одной схемой `number`. Перед генерацией проверяются `$info`, имена ресурсов и компонентов, поддерживаемые ключи `properties` и типы их значений; пути из `required` и `formats` должны существовать в итоговой схеме. Явные имена компонентов могут содержать латинские буквы, цифры, точки, подчёркивания и дефисы.
465
-
466
- Используйте `properties`, чтобы описать поля, которые невозможно определить автоматически, особенно у пустого ресурса. Явно заданные свойства объединяются с найденными автоматически:
467
-
468
- ```json
469
- {
470
- "$schema": {
471
- "reviews": {
472
- "properties": {
473
- "rating": { "type": "integer", "minimum": 1, "maximum": 5 },
474
- "text": { "type": "string" }
475
- },
476
- "required": ["rating"]
477
- }
478
- }
479
- }
480
- ```
481
-
482
- Пустой ресурс всё равно получает обязательное строковое поле `id`, поскольку сервер создаёт строковые ID. При совпадении имён схем или `operationId` генерация завершается понятной ошибкой; коллизию имён схем можно устранить с помощью явного `name`. Директория результата создаётся автоматически, а настроенный YAML-файл заменяется при каждой генерации.
483
-
484
- `$info` становится объектом `info` в OpenAPI, а настройки ресурсов находятся внутри `$schema`. В CLI поле `servers` формируется из `server.host` и `server.port`, затем из резервных переменных окружения `HOST` и `PORT`, а при их отсутствии используется `http://127.0.0.1:4001`. При прямом вызове `createServer()` переменные окружения автоматически не читаются: `server.openapi()` использует значения конфига или тот же адрес по умолчанию.
485
-
486
- Используйте `name`, если ресурсу нужно явно задать имя схемы вместо автоматически полученного имени в единственном числе:
487
-
488
- ```json
489
- {
490
- "$schema": {
491
- "equipment": {
492
- "name": "Equipment"
493
- }
494
- }
495
- }
496
- ```
497
-
498
- В сгенерированном документе описаны CRUD-маршруты, пагинация, сортировка, фильтрация вложенных данных, `_embed`, а также прямые и обратные связи в ответах, определённые по полям `...Id` и `...Ids`. При наличии `--files` в него также добавляются маршруты бинарной загрузки, получения и удаления файлов с поддержкой произвольных MIME-типов. Числовой ID базы описывается как `integer | string`, поскольку последующий `POST` создаст строковый ID в том же ресурсе. До создания документа генератор отклоняет повторяющиеся имена схем и `operationId`, а также некорректные переопределения схем. Файл можно передать, например, в RTK Query OpenAPI Codegen. OpenAPI создаётся только с параметром `--openapi` или `--openapi-only`; обычный запуск сервера файл не перезаписывает.
499
-
500
- При обычном запуске тела запросов проверяются по тем же автоматически выведенным и настроенным схемам. Для `POST` и `PUT` проверяются обязательные поля из `required`; `PATCH` проверяет только фактически переданные поля. Настройки `formats` и `properties` применяются ко всем трём методам. Не перечисленные дополнительные поля объекта остаются разрешёнными. Некорректные тела возвращают `400`.
501
-
502
427
  ## Программный API
503
428
 
504
429
  ```js
505
430
  import { createServer } from '@kollors/deep-json-server';
431
+ import config from './server.config.js';
432
+
433
+ const facade = await createServer(config);
434
+ const openapi = await facade.openapi();
435
+ const sdl = await facade.graphql();
436
+ const server = facade.fastify();
437
+ await server.listen();
438
+ // await server.close();
439
+ ```
506
440
 
507
- const config = {
508
- database: {
509
- path: 'mock/database.json',
510
- schema: 'mock/database-schema.json',
511
- },
512
- files: {
513
- directory: 'mock/files',
514
- metadata: 'mock/files/_database.json',
515
- },
516
- openapi: {
517
- path: 'mock/openapi-schema.yaml',
518
- },
519
- server: {
520
- cors: true,
521
- host: '127.0.0.1',
522
- logger: false,
523
- maxFileSize: 100 * 1024 * 1024,
524
- maxPageSize: 1000,
525
- port: 4001,
526
- },
527
- };
528
-
529
- // Запрос без открытия сетевого порта — удобно для автоматических тестов.
530
- const server = await createServer(config);
531
- const fastify = server.fastify();
532
- const response = await fastify.inject({ method: 'GET', url: '/movies' });
533
-
534
- console.log(response.json());
535
-
536
- // Возвращает документ и записывает его в config.openapi.path.
537
- const document = await server.openapi();
538
-
539
- await fastify.close();
441
+ Аксессоры ленивые: экспорт не открывает порт и не инициализирует дисковое файловое хранилище. Возможности сервера можно переопределить через `createServer(config, { files: false, graphql: true })`.
540
442
 
541
- // Запуск сетевого сервера. Вызов без аргументов использует server.host и server.port.
542
- const runningServer = await createServer(config);
543
- const runningFastify = runningServer.fastify();
443
+ ## Хранение и разработка
544
444
 
545
- await runningFastify.listen();
445
+ Изменения выполняются последовательно внутри экземпляра сервера и проверяются на копии данных до сохранения. Для одного файла базы используйте один пишущий сервер. Счётчики increment хранятся рядом с базой в `<путь базы>.counters.json`; сохраняйте этот файл вместе с базой. Номера резервируются до записи данных: сбой может оставить пропуск, но не приводит к повторной выдаче номера. UUID создаётся встроенным crypto Node.js.
546
446
 
547
- // Позже, при завершении приложения:
548
- await runningFastify.close();
447
+ Это сервер для имитации API: авторизации и хеширования паролей нет. Файловые маршруты сохраняют отдельное хранилище и собственную валидацию.
549
448
 
550
- // База, схема и файлы полностью в памяти.
551
- const memoryServer = await createServer({
552
- database: {
553
- data: { movies: [{ id: '1', title: 'Тени Ардении' }] },
554
- schema: { $info: { title: 'API фильмов', version: '1.0.0' } },
555
- },
556
- files: {
557
- data: [{ content: new Uint8Array([1, 2, 3]), directory: 'examples', mimeType: 'application/octet-stream', name: 'example.bin' }],
558
- },
559
- });
560
-
561
- const memoryFastify = memoryServer.fastify();
562
- const memoryResponse = await memoryFastify.inject({ method: 'GET', url: '/movies/1' });
563
-
564
- console.log(memoryResponse.json());
565
-
566
- await memoryFastify.close();
449
+ ```sh
450
+ npm ci
451
+ npm run verify
567
452
  ```
568
453
 
569
- `createServer()` принимает точно ту же структуру конфига, что и `server.config.js`. Функция загружает и клонирует базу данных, схему и файловое хранилище, а затем возвращает объект с двумя методами:
570
-
571
- | Метод | Назначение |
572
- | --- | --- |
573
- | `server.fastify()` | Лениво создаёт и кэширует настоящий экземпляр Fastify; все нативные методы доступны, а `listen()` без аргументов использует `server.host` и `server.port` |
574
- | `server.openapi()` | Возвращает документ OpenAPI и дополнительно записывает его, если настроен `openapi.path` |
575
-
576
- При программном использовании файловые маршруты включаются при наличии секции `files`. Сигнатура второго аргумента — `{ files?: boolean }`: передайте `{ files: false }`, чтобы оставить настроенное хранилище выключенным, или `{ files: true }`, чтобы потребовать секцию `files` и включить маршруты. `server.openapi()` использует ту же настройку файловых маршрутов, что и `server.fastify()`.
577
-
578
- Вызов `server.fastify().listen()` без аргументов использует `server.host` и `server.port`, а при их отсутствии — `127.0.0.1:4001`. Явные параметры `listen(options)` имеют приоритет. Относительные пути, переданные напрямую в `createServer()`, вычисляются от текущей рабочей директории; пути из `server.config.js` — от директории конфига. Пакет содержит сгенерированные TypeScript-декларации возвращаемого объекта и всех вариантов конфигурации.
579
-
580
- ## Назначение и безопасность
454
+ Проверка включает типы, линтер, пороги покрытия и установку упакованного пакета. Новая alpha-версия package.json в main создаёт свой тег и публикуется через GitHub Actions trusted publishing в канал npm alpha. Если тег уже существует, автоматическая публикация пропускается. Явная отправка тега версии также запускает публикацию; стабильные версии используют latest. Скрипт проверяет совпадение Git-тега и версии пакета.
581
455
 
582
- Deep JSON Server предназначен для локальной разработки и автоматических тестов. В нём нет аутентификации и авторизации, CORS по умолчанию разрешён для любого источника, при дисковом хранилище принятые изменения сохраняются в настроенные файлы, а ссылочная целостность не проверяется. Установите `server.cors: false`, чтобы отключить встроенные CORS-заголовки и маршруты `OPTIONS`. Оставляйте адрес loopback по умолчанию, если внешняя среда не предоставляет собственный контроль доступа; не открывайте сервер и файловые маршруты для недоверенной сети.
456
+ Лицензия: MIT.