@kollors/deep-json-server 1.0.0-alpha.6 → 1.0.0-alpha.7
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 +344 -230
- package/README.ru.md +341 -227
- package/dist/bin/deep-json-server.js +1 -1
- package/dist/bin/deep-json-server.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/src/auth/contract.d.ts +3 -2
- package/dist/src/auth/contract.js.map +1 -1
- package/dist/src/auth/password.d.ts +9 -0
- package/dist/src/auth/password.js +12 -0
- package/dist/src/auth/password.js.map +1 -1
- package/dist/src/auth/routes.d.ts +3 -0
- package/dist/src/auth/routes.js +3 -0
- package/dist/src/auth/routes.js.map +1 -1
- package/dist/src/auth/service.d.ts +21 -0
- package/dist/src/auth/service.js +38 -7
- package/dist/src/auth/service.js.map +1 -1
- package/dist/src/cli/index.d.ts +7 -0
- package/dist/src/{cli.js → cli/index.js} +32 -14
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/{constants.d.ts → core/constants.d.ts} +1 -1
- package/dist/src/{constants.js → core/constants.js} +1 -1
- package/dist/src/core/constants.js.map +1 -0
- package/dist/src/core/database.d.ts +50 -0
- package/dist/src/{database.js → core/database.js} +21 -1
- package/dist/src/core/database.js.map +1 -0
- package/dist/src/core/engine.d.ts +64 -0
- package/dist/src/{engine.js → core/engine.js} +64 -18
- package/dist/src/core/engine.js.map +1 -0
- package/dist/src/{errors.d.ts → core/errors.d.ts} +4 -1
- package/dist/src/core/errors.js +12 -0
- package/dist/src/core/errors.js.map +1 -0
- package/dist/src/core/http-errors.d.ts +9 -0
- package/dist/src/core/http-errors.js +15 -0
- package/dist/src/core/http-errors.js.map +1 -0
- package/dist/src/core/lifecycle/mutation.d.ts +49 -0
- package/dist/src/core/lifecycle/mutation.js +170 -0
- package/dist/src/core/lifecycle/mutation.js.map +1 -0
- package/dist/src/core/lifecycle/options.d.ts +20 -0
- package/dist/src/core/lifecycle/options.js +21 -0
- package/dist/src/core/lifecycle/options.js.map +1 -0
- package/dist/src/core/model.d.ts +130 -0
- package/dist/src/{model.js → core/model.js} +106 -10
- package/dist/src/core/model.js.map +1 -0
- package/dist/src/{mutations → core/mutations}/write.d.ts +3 -2
- package/dist/src/{mutations → core/mutations}/write.js +14 -4
- package/dist/src/core/mutations/write.js.map +1 -0
- package/dist/src/core/pagination.d.ts +10 -0
- package/dist/src/{pagination.js → core/pagination.js} +3 -0
- package/dist/src/core/pagination.js.map +1 -0
- package/dist/src/core/paths.d.ts +16 -0
- package/dist/src/{paths.js → core/paths.js} +12 -0
- package/dist/src/core/paths.js.map +1 -0
- package/dist/src/core/query/contract.d.ts +6 -0
- package/dist/src/{query → core/query}/contract.js +3 -0
- package/dist/src/core/query/contract.js.map +1 -0
- package/dist/src/core/query/filter.d.ts +6 -0
- package/dist/src/{query → core/query}/filter.js +18 -4
- package/dist/src/core/query/filter.js.map +1 -0
- package/dist/src/core/query/options.d.ts +30 -0
- package/dist/src/{query → core/query}/options.js +12 -0
- package/dist/src/core/query/options.js.map +1 -0
- package/dist/src/core/records.d.ts +46 -0
- package/dist/src/{records.js → core/records.js} +24 -3
- package/dist/src/core/records.js.map +1 -0
- package/dist/src/core/relation-metadata.d.ts +15 -0
- package/dist/src/{relation-metadata.js → core/relation-metadata.js} +7 -1
- package/dist/src/core/relation-metadata.js.map +1 -0
- package/dist/src/core/types.d.ts +10 -0
- package/dist/src/{types.js.map → core/types.js.map} +1 -1
- package/dist/src/core/utils.d.ts +56 -0
- package/dist/src/core/utils.js +102 -0
- package/dist/src/core/utils.js.map +1 -0
- package/dist/src/files/contract.d.ts +33 -0
- package/dist/src/files/contract.js +25 -3
- package/dist/src/files/contract.js.map +1 -1
- package/dist/src/files/disk-store.d.ts +3 -0
- package/dist/src/files/disk-store.js +27 -3
- package/dist/src/files/disk-store.js.map +1 -1
- package/dist/src/files/http.d.ts +8 -3
- package/dist/src/files/http.js +9 -0
- package/dist/src/files/http.js.map +1 -1
- package/dist/src/files/index.d.ts +4 -3
- package/dist/src/files/index.js +3 -1
- package/dist/src/files/index.js.map +1 -1
- package/dist/src/files/memory-store.d.ts +4 -1
- package/dist/src/files/memory-store.js +8 -2
- package/dist/src/files/memory-store.js.map +1 -1
- package/dist/src/files/routes.d.ts +3 -0
- package/dist/src/files/routes.js +25 -1
- package/dist/src/files/routes.js.map +1 -1
- package/dist/src/graphql/entry.d.ts +2 -2
- package/dist/src/graphql/entry.js.map +1 -1
- package/dist/src/graphql/lazy.d.ts +10 -0
- package/dist/src/graphql/lazy.js +13 -0
- package/dist/src/graphql/lazy.js.map +1 -0
- package/dist/src/graphql/preflight.d.ts +4 -1
- package/dist/src/graphql/preflight.js +3 -0
- package/dist/src/graphql/preflight.js.map +1 -1
- package/dist/src/graphql/public.d.ts +10 -2
- package/dist/src/graphql/public.js +10 -4
- package/dist/src/graphql/public.js.map +1 -1
- package/dist/src/graphql/resolvers.d.ts +10 -2
- package/dist/src/graphql/resolvers.js +8 -2
- package/dist/src/graphql/resolvers.js.map +1 -1
- package/dist/src/graphql/routes.d.ts +6 -2
- package/dist/src/graphql/routes.js +6 -3
- package/dist/src/graphql/routes.js.map +1 -1
- package/dist/src/graphql/schema.d.ts +6 -0
- package/dist/src/{graphql.js → graphql/schema.js} +14 -11
- package/dist/src/graphql/schema.js.map +1 -0
- package/dist/src/graphql/write.d.ts +3 -0
- package/dist/src/graphql/write.js +3 -0
- package/dist/src/graphql/write.js.map +1 -1
- package/dist/src/openapi/auth.d.ts +13 -0
- package/dist/src/{auth/openapi.js → openapi/auth.js} +15 -5
- package/dist/src/openapi/auth.js.map +1 -0
- package/dist/src/openapi/document.d.ts +5 -2
- package/dist/src/openapi/document.js +29 -33
- package/dist/src/openapi/document.js.map +1 -1
- package/dist/src/openapi/entry.d.ts +2 -2
- package/dist/src/openapi/entry.js.map +1 -1
- package/dist/src/openapi/files.d.ts +6 -0
- package/dist/src/{files/openapi.js → openapi/files.js} +6 -3
- package/dist/src/openapi/files.js.map +1 -0
- package/dist/src/openapi/helpers.d.ts +10 -1
- package/dist/src/openapi/helpers.js +9 -0
- package/dist/src/openapi/helpers.js.map +1 -1
- package/dist/src/openapi/index.d.ts +7 -1
- package/dist/src/openapi/index.js +14 -5
- package/dist/src/openapi/index.js.map +1 -1
- package/dist/src/openapi/lazy.d.ts +11 -0
- package/dist/src/openapi/lazy.js +13 -0
- package/dist/src/openapi/lazy.js.map +1 -0
- package/dist/src/openapi/public.d.ts +10 -2
- package/dist/src/openapi/public.js +9 -3
- package/dist/src/openapi/public.js.map +1 -1
- package/dist/src/{types.d.ts → openapi/types.d.ts} +1 -10
- package/dist/src/openapi/types.js +2 -0
- package/dist/src/openapi/types.js.map +1 -0
- package/dist/src/rest/options.d.ts +11 -2
- package/dist/src/rest/options.js +12 -3
- package/dist/src/rest/options.js.map +1 -1
- package/dist/src/rest/projection.d.ts +10 -4
- package/dist/src/rest/projection.js +10 -4
- package/dist/src/rest/projection.js.map +1 -1
- package/dist/src/rest/routes.d.ts +6 -2
- package/dist/src/rest/routes.js +7 -3
- package/dist/src/rest/routes.js.map +1 -1
- package/dist/src/server/config.d.ts +79 -0
- package/dist/src/{config.js → server/config.js} +57 -19
- package/dist/src/server/config.js.map +1 -0
- package/dist/src/server/create.d.ts +18 -0
- package/dist/src/{server.js → server/create.js} +31 -23
- package/dist/src/server/create.js.map +1 -0
- package/dist/src/server/features.d.ts +15 -0
- package/dist/src/{features.js → server/features.js} +8 -2
- package/dist/src/server/features.js.map +1 -0
- package/dist/src/server/public.d.ts +3 -3
- package/dist/src/server/public.js +1 -1
- package/dist/src/server/public.js.map +1 -1
- package/package.json +1 -1
- package/dist/src/auth/openapi.d.ts +0 -10
- package/dist/src/auth/openapi.js.map +0 -1
- package/dist/src/cli.d.ts +0 -4
- package/dist/src/cli.js.map +0 -1
- package/dist/src/config.d.ts +0 -86
- package/dist/src/config.js.map +0 -1
- package/dist/src/constants.js.map +0 -1
- package/dist/src/database.d.ts +0 -20
- package/dist/src/database.js.map +0 -1
- package/dist/src/engine.d.ts +0 -36
- package/dist/src/engine.js.map +0 -1
- package/dist/src/errors.js +0 -9
- package/dist/src/errors.js.map +0 -1
- package/dist/src/features.d.ts +0 -9
- package/dist/src/features.js.map +0 -1
- package/dist/src/files/openapi.d.ts +0 -3
- package/dist/src/files/openapi.js.map +0 -1
- package/dist/src/graphql.d.ts +0 -3
- package/dist/src/graphql.js.map +0 -1
- package/dist/src/http/errors.d.ts +0 -6
- package/dist/src/http/errors.js +0 -3
- package/dist/src/http/errors.js.map +0 -1
- package/dist/src/model.d.ts +0 -72
- package/dist/src/model.js.map +0 -1
- package/dist/src/mutations/write.js.map +0 -1
- package/dist/src/pagination.d.ts +0 -7
- package/dist/src/pagination.js.map +0 -1
- package/dist/src/paths.d.ts +0 -7
- package/dist/src/paths.js.map +0 -1
- package/dist/src/query/contract.d.ts +0 -3
- package/dist/src/query/contract.js.map +0 -1
- package/dist/src/query/filter.d.ts +0 -3
- package/dist/src/query/filter.js.map +0 -1
- package/dist/src/query/options.d.ts +0 -18
- package/dist/src/query/options.js.map +0 -1
- package/dist/src/records.d.ts +0 -25
- package/dist/src/records.js.map +0 -1
- package/dist/src/relation-metadata.d.ts +0 -9
- package/dist/src/relation-metadata.js.map +0 -1
- package/dist/src/schema.d.ts +0 -8
- package/dist/src/schema.js +0 -13
- package/dist/src/schema.js.map +0 -1
- package/dist/src/server.d.ts +0 -12
- package/dist/src/server.js.map +0 -1
- package/dist/src/utils.d.ts +0 -12
- package/dist/src/utils.js +0 -58
- package/dist/src/utils.js.map +0 -1
- /package/dist/src/{types.js → core/types.js} +0 -0
package/README.ru.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md)
|
|
4
4
|
|
|
5
|
-
JSON-сервер для имитации API: REST, GraphQL,
|
|
5
|
+
JSON-сервер для имитации API: REST, GraphQL, связанные записи, загрузка файлов и экспорт схем. Поддерживает вход пользователей, права владельца и администратора, даты записей и мягкое удаление. Требуется Node.js 22 или новее.
|
|
6
6
|
|
|
7
|
-
**1.0.0-alpha.
|
|
7
|
+
**1.0.0-alpha.7 — предварительная версия.** REST-запросы используют `scope=[поля, аргументы?]` на всех уровнях. При обновлении измените параметры запросов по примерам ниже; для перехода с 0.x также нужна новая схема моделей.
|
|
8
8
|
|
|
9
9
|
## Установка
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@ JSON-сервер для имитации API: REST, GraphQL, вложенные
|
|
|
12
12
|
npm install @kollors/deep-json-server@alpha
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Для установки конкретной версии укажите `@1.0.0-alpha.
|
|
15
|
+
Для установки конкретной версии укажите `@1.0.0-alpha.7`.
|
|
16
16
|
|
|
17
17
|
## Быстрый старт
|
|
18
18
|
|
|
@@ -42,12 +42,16 @@ npx deep-json-server server.config.js
|
|
|
42
42
|
|
|
43
43
|
Список пользователей доступен по адресу `http://127.0.0.1:4001/users`.
|
|
44
44
|
|
|
45
|
+
Для связей и валидации добавьте [схему моделей](#схема-моделей). Далее описаны [запросы](#запросы-и-ответы), [аутентификация](#аутентификация), [мягкое удаление](#даты-записей-удаление-и-владельцы), [файлы](#файлы) и [программный API](#программный-api).
|
|
46
|
+
|
|
45
47
|
## Конфигурация
|
|
46
48
|
|
|
47
49
|
| Ключ | Назначение |
|
|
48
50
|
|---|---|
|
|
49
|
-
| `database.path` / `database.data` |
|
|
51
|
+
| `database.path` / `database.data` | Для запуска укажите один вариант: JSON-файл или объект коллекций в памяти |
|
|
50
52
|
| `database.schema` | Объект моделей или путь к JSON-файлу; необязателен для REST |
|
|
53
|
+
| `database.timestamps` | Добавить даты создания и изменения; по умолчанию `false` |
|
|
54
|
+
| `database.softDelete` | Сохранять удалённые записи с возможностью восстановления; по умолчанию `false` |
|
|
51
55
|
| `openapi.enabled` | Включить HTTP-маршрут спецификации; по умолчанию `false` |
|
|
52
56
|
| `openapi.endpoint` | Путь спецификации; по умолчанию `/openapi.json` |
|
|
53
57
|
| `openapi.path` | Путь экспорта YAML |
|
|
@@ -55,7 +59,6 @@ npx deep-json-server server.config.js
|
|
|
55
59
|
| `graphql.enabled` | Включить GraphQL HTTP API; по умолчанию `false` |
|
|
56
60
|
| `graphql.endpoint` | Путь GraphQL; по умолчанию `/graphql` |
|
|
57
61
|
| `graphql.path` | Путь экспорта GraphQL SDL |
|
|
58
|
-
| `auth.enabled` | Включить аутентификацию; по умолчанию `false` |
|
|
59
62
|
| `auth.users` | Путь к JSON-массиву учётных записей или массив в памяти |
|
|
60
63
|
| `auth.expiresIn` | Срок действия сессии в секундах; по умолчанию 3600 |
|
|
61
64
|
| `server.host`, `server.port` | По умолчанию `127.0.0.1`, `4001`; CLI также читает `HOST`/`PORT` |
|
|
@@ -69,38 +72,21 @@ npx deep-json-server server.config.js
|
|
|
69
72
|
|
|
70
73
|
Значение `server.port: 0` позволяет системе выбрать свободный порт. OpenAPI на HTTP-эндпоинте использует относительный адрес сервера.
|
|
71
74
|
|
|
75
|
+
### CLI
|
|
76
|
+
|
|
72
77
|
| Флаг CLI | Действие |
|
|
73
78
|
|---|---|
|
|
74
|
-
| `--files` | Включить файловые
|
|
79
|
+
| `--files` | Включить файловые маршруты; нужна секция `files` |
|
|
80
|
+
| `--timestamps` | Включить даты записей глобально |
|
|
81
|
+
| `--soft-delete` | Включить мягкое удаление глобально |
|
|
75
82
|
| `--graphql` | Включить GraphQL API |
|
|
76
83
|
| `--openapi` | Включить HTTP-маршрут OpenAPI |
|
|
77
|
-
| `--auth` | Включить аутентификацию с учётными записями из `auth.users` |
|
|
78
84
|
| `--host <host>` | Адрес сервера |
|
|
79
85
|
| `--port <port>` | Порт сервера |
|
|
80
86
|
| `--help`, `-h` | Справка |
|
|
81
87
|
| `--version`, `-v` | Версия пакета |
|
|
82
88
|
|
|
83
|
-
Приоритет
|
|
84
|
-
|
|
85
|
-
Для генерации укажите формат и файл конфигурации:
|
|
86
|
-
|
|
87
|
-
```sh
|
|
88
|
-
npx deep-json-server generate openapi server.config.js
|
|
89
|
-
npx deep-json-server generate graphql server.config.js
|
|
90
|
-
npx deep-json-server generate openapi,graphql server.config.js
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Команда читает `database.schema` и сохраняет схемы в `openapi.path` и `graphql.path`. Для генерации достаточно такой конфигурации:
|
|
94
|
-
|
|
95
|
-
```js
|
|
96
|
-
export default {
|
|
97
|
-
database: { schema: './schema.json' },
|
|
98
|
-
openapi: { path: './generated/openapi.yaml' },
|
|
99
|
-
graphql: { path: './generated/schema.graphql' },
|
|
100
|
-
};
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Для каждого формата нужен отдельный файл. Команда отклонит путь, который перезапишет конфигурацию, базу, схему, учётные записи auth или метаданные файлов.
|
|
89
|
+
Приоритет адреса и порта: CLI → конфигурация → `HOST`/`PORT` → значения по умолчанию. Файловые маршруты включаются при наличии секции `files`, а вход и проверка прав — при наличии `auth`. Настройки модели могут переопределять глобальные `timestamps` и `softDelete`.
|
|
104
90
|
|
|
105
91
|
## Схема моделей
|
|
106
92
|
|
|
@@ -142,6 +128,8 @@ export default {
|
|
|
142
128
|
|
|
143
129
|
REST без схемы создаёт ключ `id` и сохраняет произвольные JSON-поля. Для фильтров и выбора отдельных полей используются имена в формате идентификаторов; остальные поля возвращаются через `scope=[{"*":true}]`. Поля с разными типами значений можно читать, но фильтрация, сортировка и пагинация неоднородных списков требуют явной схемы.
|
|
144
130
|
|
|
131
|
+
У модели обязательны `collection` — имя коллекции в базе и REST-пути — и `fields` — описание полей. Имя модели (`User`) задаёт имена типов и операций GraphQL. `api` управляет доступностью форматов, а `timestamps` и `softDelete` переопределяют глобальные настройки для этой модели.
|
|
132
|
+
|
|
145
133
|
### Поля
|
|
146
134
|
|
|
147
135
|
Поле `type` принимает `string`, `number`, `boolean`, `object` или имя модели. Для массива добавьте суффикс `[]`: `string[]`, `object[]`, `Genre[]`. Вложенные поля описываются через точку, например `actors.fullName`.
|
|
@@ -172,10 +160,12 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
172
160
|
### Связи
|
|
173
161
|
|
|
174
162
|
```json
|
|
175
|
-
|
|
176
|
-
"
|
|
177
|
-
|
|
178
|
-
|
|
163
|
+
{
|
|
164
|
+
"actors.genres": {
|
|
165
|
+
"type": "Genre[]",
|
|
166
|
+
"source": "actors.genreIds",
|
|
167
|
+
"required": true
|
|
168
|
+
}
|
|
179
169
|
}
|
|
180
170
|
```
|
|
181
171
|
|
|
@@ -196,6 +186,12 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
196
186
|
|
|
197
187
|
## Запросы и ответы
|
|
198
188
|
|
|
189
|
+
Примеры с фильмами, актёрами и жанрами используют полную [схему из examples](examples/schema.json). Для их запуска используйте [конфигурацию примера](examples/server.config.js):
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
npx deep-json-server examples/server.config.js
|
|
193
|
+
```
|
|
194
|
+
|
|
199
195
|
Коллекции и списки объектов, включая вложенные `object[]`, возвращают:
|
|
200
196
|
|
|
201
197
|
```json
|
|
@@ -206,7 +202,7 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
206
202
|
|
|
207
203
|
В `pager` задаются `page` и `pageSize`. По умолчанию возвращается первая страница с размером из `server.pageSize`. Оба значения должны быть положительными целыми числами; `pageSize` ограничен `server.maxPageSize`. За пределами списка возвращается пустой `data` с общим числом найденных записей в `total`.
|
|
208
204
|
|
|
209
|
-
Фильтры: `eq`, `ne`, `in`; для строк `contains`, `startsWith`, `endsWith`; сравнения `gt`, `gte`, `lt`, `lte`.
|
|
205
|
+
Фильтры: `eq`, `ne`, `in`; для строк `contains`, `startsWith`, `endsWith`; сравнения `gt`, `gte`, `lt`, `lte`. Несколько условий в одном объекте должны выполняться одновременно. `and` и `or` принимают массив условий, `not` — одно условие; `not` также можно использовать внутри фильтра поля. У массивов есть `some`, `every`, `none`; у примитивных массивов также `contains`, `in`. `contains`, `startsWith` и `endsWith` для строк не учитывают регистр; `eq`, `ne` и `in` сравнивают точные значения. Условие поля задаётся объектом с оператором, например `{ "id": { "eq": "1" } }`.
|
|
210
206
|
|
|
211
207
|
```json
|
|
212
208
|
{
|
|
@@ -224,10 +220,12 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
224
220
|
|
|
225
221
|
Корневой `where` выбирает записи основной коллекции. `where` внутри связи фильтрует её элементы, сохраняя родительскую запись. Каждый вложенный список обрабатывается отдельно. Фильтрация по связи работает независимо от её включения в ответ.
|
|
226
222
|
|
|
227
|
-
`order` — массив правил `{ "field": "fullName", "direction": "ASC" }`. Первое правило приоритетнее; при равных значениях сохраняется порядок хранения. `null` и отсутствующее значение сравниваются одинаково. REST использует путь `profile.name`, GraphQL — enum `profile_name`. Неоднозначные имена enum вызывают ошибку генерации. Сортировка доступна по скалярным полям текущего объекта, включая вложенные поля. Для связанных списков задаётся собственный `order`.
|
|
223
|
+
`order` — массив правил `{ "field": "fullName", "direction": "ASC" }`. `ASC` сортирует по возрастанию, `DESC` — по убыванию. Первое правило приоритетнее; при равных значениях сохраняется порядок хранения. `null` и отсутствующее значение сравниваются одинаково. REST использует путь `profile.name`, GraphQL — enum `profile_name`. Неоднозначные имена enum вызывают ошибку генерации. Сортировка доступна по скалярным полям текущего объекта, включая вложенные поля. Для связанных списков задаётся собственный `order`.
|
|
228
224
|
|
|
229
225
|
### REST
|
|
230
226
|
|
|
227
|
+
`GET /` возвращает имена коллекций: `{ "resources": ["users", "movies"] }`. Для каждой коллекции доступны следующие маршруты:
|
|
228
|
+
|
|
231
229
|
| Метод | Путь | Операция |
|
|
232
230
|
|---|---|---|
|
|
233
231
|
| GET | `/users` | `userList` |
|
|
@@ -237,62 +235,13 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
237
235
|
| PATCH | `/users/{id}` | `userUpdate` |
|
|
238
236
|
| DELETE | `/users/{id}` | `userDelete` |
|
|
239
237
|
|
|
240
|
-
Имя параметра пути соответствует первичному ключу. POST, PUT и PATCH принимают JSON-объект записи. PUT заменяет запись с сохранением ключа и серверных полей. PATCH объединяет поля на верхнем уровне; переданные вложенные объекты заменяются с сохранением их полей `readOnly`. Создание и замена требуют всех обязательных полей. При обновлении проверяются переданные значения и итоговая запись. Отсутствующая запись — `404`, конфликт — `409`.
|
|
238
|
+
Имя параметра пути соответствует первичному ключу. POST, PUT и PATCH принимают JSON-объект записи. PUT заменяет запись с сохранением ключа и серверных полей. PATCH объединяет поля на верхнем уровне; переданные вложенные объекты заменяются с сохранением их полей `readOnly`. Создание и замена требуют всех обязательных полей. При обновлении проверяются переданные значения и итоговая запись. Отсутствующая запись — `404`, конфликт — `409`.
|
|
241
239
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
Поля ключей, например `genreIds: ["1"]`, только задают связь. В поля связей можно передавать записи для создания или обновления:
|
|
245
|
-
|
|
246
|
-
```http
|
|
247
|
-
PATCH /movies/1
|
|
248
|
-
Content-Type: application/json
|
|
249
|
-
|
|
250
|
-
{
|
|
251
|
-
"actors": [
|
|
252
|
-
{
|
|
253
|
-
"userId": "1",
|
|
254
|
-
"genres": [
|
|
255
|
-
"1",
|
|
256
|
-
{ "id": "2", "name": "Обновлённый жанр" },
|
|
257
|
-
{ "name": "Новый жанр" }
|
|
258
|
-
]
|
|
259
|
-
}
|
|
260
|
-
]
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
| Значение в связи | Действие |
|
|
265
|
-
|---|---|
|
|
266
|
-
| Ключ, например `"1"` | Связать существующую запись без её изменения |
|
|
267
|
-
| Объект с первичным ключом | PATCH обновляет переданные поля, PUT заменяет связанную запись |
|
|
268
|
-
| Объект без первичного ключа | Создать запись со значениями по умолчанию и сгенерированным ключом |
|
|
269
|
-
|
|
270
|
-
Имя и тип ключа берутся из целевой модели. Объект только с ключом тоже считается обновлением: в PUT он должен содержать обязательные поля модели. При замене сохраняются первичный ключ, генерируемые значения и поля `readOnly`. В POST вложенные объекты с существующими ключами обновляются частично. Если запись по ключу не найдена, операция завершится ошибкой. Для создания вложенной записи без ключа нужна его автоматическая генерация.
|
|
271
|
-
|
|
272
|
-
Переданный список заменяет состав связи. PATCH сохраняет пропущенные связи, PUT очищает пропущенные связи, ключи которых доступны для записи. `[]` очищает список, `null` — одиночную связь с разрешённым `nullable`. Разрыв связи не удаляет связанную запись. Обязательные связи должны оставаться заполненными.
|
|
273
|
-
|
|
274
|
-
В одном объекте указывайте либо связь, либо её хранимый ключ: например, `genres` или `genreIds`. Для обратной связи сервер меняет целевой ключ. Если путь проходит через массив и нельзя однозначно выбрать элемент для связи, передайте массив с нужными ключами явно. Защищённые ключи изменять нельзя.
|
|
275
|
-
|
|
276
|
-
Все вложенные изменения входят в транзакцию основной записи. Ошибка проверки, отсутствующая запись или неверный выбор полей ответа отменяет всю операцию. Изменения общей записи видны всем, кто с ней связан.
|
|
277
|
-
|
|
278
|
-
GraphQL принимает в полях связей типизированные объекты. Чтобы при замене изменить только связи, используйте поля ключей, например `genreIds`. Пример:
|
|
279
|
-
|
|
280
|
-
```graphql
|
|
281
|
-
mutation {
|
|
282
|
-
movieUpdate(id: "1", data: {
|
|
283
|
-
actors: [{
|
|
284
|
-
userId: "1"
|
|
285
|
-
genres: [{ id: "2", name: "Обновлённый жанр" }, { name: "Новый жанр" }]
|
|
286
|
-
}]
|
|
287
|
-
}) {
|
|
288
|
-
actors { data { genres { data { id name } } } }
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
```
|
|
240
|
+
POST возвращает созданную запись со статусом `201`; PUT, PATCH и DELETE — результат со статусом `200`. Ошибки REST имеют вид `{ "error": "Описание ошибки" }`.
|
|
292
241
|
|
|
293
242
|
### Параметры REST-запросов
|
|
294
243
|
|
|
295
|
-
REST принимает один
|
|
244
|
+
Для запросов к записям REST принимает один параметр URL `scope` с JSON-массивом `[поля, аргументы?]`. Первый объект выбирает поля, второй задаёт `where`, `order` и `pager` для списка. Этот формат одинаков для корневого запроса, вложенных объектов и связей.
|
|
296
245
|
|
|
297
246
|
Пример выбора пользователей и их фильмов с отдельной сортировкой и пагинацией:
|
|
298
247
|
|
|
@@ -344,13 +293,71 @@ const response = await fetch(`/users?${params}`);
|
|
|
344
293
|
|
|
345
294
|
Аргументы доступны только у списков. В запросе отдельной записи и в ответе мутации их можно задать для вложенных списков. Параметры проверяются даже на пустых данных; ошибка в выборе ответа отменяет изменения записи. Некорректный `scope` возвращает `400`. Максимальная длина JSON — 10 000 символов, глубина выбора — 32 уровня.
|
|
346
295
|
|
|
347
|
-
|
|
296
|
+
### Вложенная запись
|
|
297
|
+
|
|
298
|
+
Поля ключей, например `genreIds: ["1"]`, только задают связь. В поля связей можно передавать записи для создания или обновления:
|
|
299
|
+
|
|
300
|
+
```http
|
|
301
|
+
PATCH /movies/1
|
|
302
|
+
Content-Type: application/json
|
|
303
|
+
|
|
304
|
+
{
|
|
305
|
+
"actors": [
|
|
306
|
+
{
|
|
307
|
+
"userId": "1",
|
|
308
|
+
"genres": [
|
|
309
|
+
"1",
|
|
310
|
+
{ "id": "2", "name": "Обновлённый жанр" },
|
|
311
|
+
{ "name": "Новый жанр" }
|
|
312
|
+
]
|
|
313
|
+
}
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
| Значение в связи | Действие |
|
|
319
|
+
|---|---|
|
|
320
|
+
| Ключ, например `"1"` | Связать существующую запись без её изменения |
|
|
321
|
+
| Объект с первичным ключом | PATCH обновляет переданные поля, PUT заменяет связанную запись |
|
|
322
|
+
| Объект без первичного ключа | Создать запись со значениями по умолчанию и сгенерированным ключом |
|
|
323
|
+
|
|
324
|
+
Имя и тип ключа берутся из целевой модели. Объект только с ключом тоже считается обновлением: в PUT он должен содержать обязательные поля модели. При замене сохраняются первичный ключ, генерируемые значения и поля `readOnly`. В POST вложенные объекты с существующими ключами обновляются частично. Если запись по ключу не найдена, операция завершится ошибкой. Для создания вложенной записи без ключа нужна его автоматическая генерация.
|
|
325
|
+
|
|
326
|
+
Переданный список заменяет состав связи. PATCH сохраняет пропущенные связи, PUT очищает пропущенные связи, ключи которых доступны для записи. `[]` очищает список, `null` — одиночную связь с разрешённым `nullable`. Разрыв связи не удаляет связанную запись. Обязательные связи должны оставаться заполненными.
|
|
327
|
+
|
|
328
|
+
В одном объекте указывайте либо связь, либо её хранимый ключ: например, `genres` или `genreIds`. Для обратной связи сервер меняет целевой ключ. Если путь проходит через массив и нельзя однозначно выбрать элемент для связи, передайте массив с нужными ключами явно. Защищённые ключи изменять нельзя.
|
|
329
|
+
|
|
330
|
+
Все вложенные изменения входят в транзакцию основной записи. Ошибка проверки, отсутствующая запись или неверный выбор полей ответа отменяет всю операцию. Изменения общей записи видны всем, кто с ней связан.
|
|
331
|
+
|
|
332
|
+
GraphQL принимает в полях связей типизированные объекты. Чтобы при замене изменить только связи, используйте поля ключей, например `genreIds`. Пример:
|
|
333
|
+
|
|
334
|
+
```graphql
|
|
335
|
+
mutation {
|
|
336
|
+
movieUpdate(id: "1", data: {
|
|
337
|
+
actors: [{
|
|
338
|
+
userId: "1"
|
|
339
|
+
genres: [{ id: "2", name: "Обновлённый жанр" }, { name: "Новый жанр" }]
|
|
340
|
+
}]
|
|
341
|
+
}) {
|
|
342
|
+
actors { data { genres { data { id name } } } }
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
```
|
|
348
346
|
|
|
349
347
|
### GraphQL
|
|
350
348
|
|
|
349
|
+
Укажите `database.schema` и включите `graphql.enabled: true` в конфигурации или запустите сервер с `--graphql`:
|
|
350
|
+
|
|
351
|
+
```sh
|
|
352
|
+
npx deep-json-server --graphql server.config.js
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Отправляйте запросы на `/graphql` методом POST с `Content-Type: application/json` и телом `{ "query": "…", "variables": {} }`. Путь можно изменить через `graphql.endpoint`.
|
|
356
|
+
|
|
351
357
|
```graphql
|
|
352
358
|
query {
|
|
353
359
|
userList(
|
|
360
|
+
where: { fullName: { contains: "Мира" } }
|
|
354
361
|
order: [{ field: fullName, direction: ASC }]
|
|
355
362
|
pager: { page: 1, pageSize: 20 }
|
|
356
363
|
) {
|
|
@@ -358,7 +365,11 @@ query {
|
|
|
358
365
|
data {
|
|
359
366
|
id
|
|
360
367
|
fullName
|
|
361
|
-
movies(
|
|
368
|
+
movies(
|
|
369
|
+
where: { title: { contains: "Тени" } }
|
|
370
|
+
order: [{ field: title, direction: ASC }]
|
|
371
|
+
pager: { pageSize: 5 }
|
|
372
|
+
) {
|
|
362
373
|
total
|
|
363
374
|
data { id title }
|
|
364
375
|
}
|
|
@@ -369,7 +380,243 @@ query {
|
|
|
369
380
|
|
|
370
381
|
Запрос `user(id: ...)` возвращает одну запись или `null`, если она отсутствует. Мутации: `userCreate(data: ...)`, `userReplace(id: ..., data: ...)`, `userUpdate(id: ..., data: ...)`, `userDelete(id: ...)`. Для модели, состоящей из генерируемых полей, мутация создания вызывается без аргумента `data`. Правила записи и валидации совпадают с REST. Связи, выбранные в результате мутации, определяют содержимое ответа.
|
|
371
382
|
|
|
372
|
-
Строковый первичный ключ представлен типом GraphQL `ID`, обычные строки — `String`, числа — `Float`, параметры пагинации — `Int`. Enum сохраняет допустимые строковые имена; остальные значения получают имена `VALUE_0`, `VALUE_1` и т. д. Длины строк, форматы и другие ограничения модели проверяются сервером при выполнении запроса. Для просмотра схемы доступна интроспекция. Параметры выбранных списков проверяются до выполнения мутаций.
|
|
383
|
+
Строковый первичный ключ представлен типом GraphQL `ID`, обычные строки — `String`, числа — `Float`, параметры пагинации — `Int`. Enum сохраняет допустимые строковые имена; остальные значения получают имена `VALUE_0`, `VALUE_1` и т. д. Длины строк, форматы и другие ограничения модели проверяются сервером при выполнении запроса. Для просмотра схемы доступна интроспекция. Параметры выбранных списков проверяются до выполнения мутаций.
|
|
384
|
+
|
|
385
|
+
Ошибки содержат `extensions.code`: `INVALID_INPUT`, `INVALID_QUERY`, `NOT_FOUND`, `CONFLICT`, `UNAUTHENTICATED`, `FORBIDDEN` или `INTERNAL_ERROR`. Ошибки синтаксиса и типов GraphQL возвращаются в стандартном массиве `errors`. Максимальная глубина запроса — 32 уровня.
|
|
386
|
+
|
|
387
|
+
## OpenAPI и экспорт схем
|
|
388
|
+
|
|
389
|
+
Экспорт использует OpenAPI 3.0.3. Для `scope` назначение позиций массива описано текстом; их порядок проверяет сервер.
|
|
390
|
+
|
|
391
|
+
Для получения спецификации по HTTP укажите `database.schema` и включите `openapi.enabled: true` или запустите сервер с `--openapi`. По умолчанию JSON доступен по адресу `/openapi.json`; путь задаётся в `openapi.endpoint`. Спецификацию можно открыть в отдельно установленном Swagger UI или импортировать в API-клиент.
|
|
392
|
+
|
|
393
|
+
Для генерации укажите формат и файл конфигурации:
|
|
394
|
+
|
|
395
|
+
```sh
|
|
396
|
+
npx deep-json-server generate openapi server.config.js
|
|
397
|
+
npx deep-json-server generate graphql server.config.js
|
|
398
|
+
npx deep-json-server generate openapi,graphql server.config.js
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Генерация выполняется без запуска сервера и чтения записей базы. Команда читает `database.schema` и сохраняет схемы в `openapi.path` и `graphql.path`. Флаги `enabled` управляют HTTP-маршрутами и для экспорта не требуются. Для генерации достаточно такой конфигурации:
|
|
402
|
+
|
|
403
|
+
```js
|
|
404
|
+
export default {
|
|
405
|
+
database: { schema: './schema.json' },
|
|
406
|
+
openapi: { path: './generated/openapi.yaml' },
|
|
407
|
+
graphql: { path: './generated/schema.graphql' },
|
|
408
|
+
};
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Для каждого формата нужен отдельный файл. Команда отклонит путь, который перезапишет конфигурацию, базу, схему, учётные записи auth или метаданные файлов.
|
|
412
|
+
|
|
413
|
+
## Аутентификация
|
|
414
|
+
|
|
415
|
+
Секция `auth` включает REST-вход и проверку прав на изменение записей в REST и GraphQL. Чтение и все операции с файлами остаются открытыми.
|
|
416
|
+
|
|
417
|
+
Создайте учётную запись в отдельном файле с помощью `setup-auth.mjs`:
|
|
418
|
+
|
|
419
|
+
```js
|
|
420
|
+
import { writeFile } from 'node:fs/promises';
|
|
421
|
+
import { hashPassword } from '@kollors/deep-json-server/auth';
|
|
422
|
+
|
|
423
|
+
const password = process.env.DJS_PASSWORD;
|
|
424
|
+
if (!password) throw new Error('Set DJS_PASSWORD');
|
|
425
|
+
await writeFile('./auth.json', JSON.stringify([
|
|
426
|
+
{ id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true },
|
|
427
|
+
], null, 2), { flag: 'wx', mode: 0o600 });
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Задайте `DJS_PASSWORD` и выполните `node setup-auth.mjs`. Добавьте файл в конфигурацию сервера:
|
|
431
|
+
|
|
432
|
+
```js
|
|
433
|
+
export default {
|
|
434
|
+
database: { path: './database.json' },
|
|
435
|
+
auth: { users: './auth.json', expiresIn: 3600 },
|
|
436
|
+
};
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Запустите `npx deep-json-server server.config.js`. Наличие секции `auth` включает модуль; без неё изменения записей доступны без токена. Каждой учётной записи нужны уникальный строковый `id`, уникальный `username` и `passwordHash`, созданный функцией выше. `isAdmin` по умолчанию равен `false`. Пароли хешируются через scrypt со случайной солью. Учётные записи auth хранятся отдельно от коллекций базы. Файл читается при запуске; после его изменения перезапустите сервер.
|
|
440
|
+
|
|
441
|
+
| REST-запрос | Входные данные | Ответ |
|
|
442
|
+
|---|---|---|
|
|
443
|
+
| `POST /auth/login` | JSON `{ "username": "admin", "password": "…" }` | `{ accessToken, expiresIn, user: { id, username, isAdmin } }` |
|
|
444
|
+
| `GET /auth/me` | Bearer-токен | `{ id, username, isAdmin }` |
|
|
445
|
+
| `POST /auth/logout` | Bearer-токен | `{ success: true }` |
|
|
446
|
+
|
|
447
|
+
Передавайте токен в заголовке `Authorization: Bearer <accessToken>`. Неверные учётные данные, недействительный или истёкший токен возвращают HTTP 401. Сессии хранятся в памяти и исчезают при перезапуске; выход отзывает переданный токен. Вход может вернуть HTTP 429 при слишком большом числе одновременных попыток или активных сессий.
|
|
448
|
+
|
|
449
|
+
В OpenAPI описывается Bearer-аутентификация для изменения записей, `/auth/me` и `/auth/logout`. В Swagger UI токен из ответа на вход можно вставить в **Authorize**. Для экспорта схем включите auth в конфигурации и выполните `generate openapi server.config.js`; файл учётных записей при генерации не читается. Для GraphQL и OpenAPI нужна `database.schema`.
|
|
450
|
+
|
|
451
|
+
## Даты записей, удаление и владельцы
|
|
452
|
+
|
|
453
|
+
Глобальные настройки задаются в `database`:
|
|
454
|
+
|
|
455
|
+
```js
|
|
456
|
+
export default {
|
|
457
|
+
database: {
|
|
458
|
+
path: './database.json',
|
|
459
|
+
schema: './schema.json',
|
|
460
|
+
timestamps: true,
|
|
461
|
+
softDelete: true,
|
|
462
|
+
},
|
|
463
|
+
auth: { users: './auth.json' },
|
|
464
|
+
};
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
В модели можно переопределить `timestamps` и `softDelete` рядом с `collection` и `fields`. Отсутствующее значение наследуется из глобальных настроек; `true` или `false` переопределяет его. CLI имеет приоритет над глобальным конфигом, а настройки модели — над обоими. Без схемы глобальные значения действуют на все коллекции. Например, эта модель отключает даты и сохраняет удалённые записи независимо от глобальных настроек:
|
|
468
|
+
|
|
469
|
+
```json
|
|
470
|
+
{
|
|
471
|
+
"Note": {
|
|
472
|
+
"collection": "notes",
|
|
473
|
+
"timestamps": false,
|
|
474
|
+
"softDelete": true,
|
|
475
|
+
"fields": {
|
|
476
|
+
"id": { "type": "string", "primary": true, "generated": "uuid" },
|
|
477
|
+
"text": { "type": "string", "required": true }
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
| Поле | Когда включено | Значение |
|
|
484
|
+
|---|---|---|
|
|
485
|
+
| `createdAt`, `updatedAt` | `timestamps` | Даты создания и последнего изменения |
|
|
486
|
+
| `deletedAt` | `softDelete` | Дата удаления или `null` для активной записи |
|
|
487
|
+
| `createdById`, `updatedById` | `auth` | Создатель/владелец и последний редактор |
|
|
488
|
+
| `deletedById` | `auth` + `softDelete` | Пользователь, удаливший запись, или `null` |
|
|
489
|
+
|
|
490
|
+
Даты — строки ISO 8601 в UTC. При создании даты создания и изменения совпадают, а при включённом auth создателем и редактором становится текущий пользователь. PUT/PATCH сохраняют создателя и дату создания. DELETE обновляет поля удаления и включённые поля последнего изменения. Для старых записей с неизвестными датами или авторами возвращается `null`. Эти поля доступны только для чтения в REST и GraphQL; обычные вложенные объекты собственных полей авторства и дат не получают. При отключении функции уже сохранённые значения её полей сохраняются.
|
|
491
|
+
|
|
492
|
+
При мягком удалении DELETE оставляет запись в базе. Повторный DELETE уже удалённой записи сохраняет прежние сведения об удалении. Получение по первичному ключу возвращает и удалённые записи. Успешный PUT/PATCH восстанавливает запись, обнуляя `deletedAt` и `deletedById`. Пустой PATCH восстанавливает без замены остальных полей; для PUT нужно передать все обязательные поля.
|
|
493
|
+
|
|
494
|
+
Списки по умолчанию возвращают активные записи. Для получения удалённых задайте условие по `deletedAt`:
|
|
495
|
+
|
|
496
|
+
```json
|
|
497
|
+
[
|
|
498
|
+
{ "id": true, "deletedAt": true },
|
|
499
|
+
{ "where": { "deletedAt": { "ne": null } } }
|
|
500
|
+
]
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
`eq: null` выбирает активные записи, `ne: null` — удалённые, объединение обоих условий через `or` — все записи. Те же фильтры работают в GraphQL. Явное условие по `deletedAt`, в том числе внутри `and`, `or` или `not`, заменяет автоматическую фильтрацию на этом уровне. Фильтры связей и списки связей применяют правило независимо. Одиночные связи скрывают удалённые цели; запрос по первичному ключу позволяет получить их напрямую.
|
|
504
|
+
|
|
505
|
+
Каскадное удаление учитывает `onDelete` и настройку `softDelete` каждой затронутой модели. Восстановление записи, начавшей каскад, возвращает записи, удалённые той же операцией, не затрагивая удалённые ранее. Физически удалённые записи восстановить нельзя. Если каскад удалил вложенный объект, восстановление возможно, пока соответствующее поле объекта не менялось после удаления. Конфликт или отсутствие обязательной связи отменяет всю операцию.
|
|
506
|
+
|
|
507
|
+
Служебные сведения восстановления хранятся вместе с записями и переживают перезапуск; сохраняйте их при резервном копировании базы. Внутреннее поле `djsDeletion` зарезервировано и не выдаётся через API.
|
|
508
|
+
|
|
509
|
+
При включённом auth создавать записи может любой вошедший пользователь. Изменять, удалять и восстанавливать — владелец по `createdById` или пользователь с `isAdmin: true`. Записи без владельца изменяет только администратор. Его правки не меняют владельца. Правила действуют на вложенные записи, изменение ключей связей, каскады и восстановление. Ссылка на существующую запись без её изменения не требует владения ею. Каждая мутация атомарна: отказ сохраняет прежнее состояние всех затронутых записей.
|
|
510
|
+
|
|
511
|
+
REST возвращает 401 при отсутствии действительного токена и 403 при недостатке прав. GraphQL проверяет мутации по тем же правилам и возвращает коды `UNAUTHENTICATED` или `FORBIDDEN`; токен получают через REST-вход и передают в `Authorization: Bearer <токен>`. GraphQL-запросы чтения, OPTIONS и все операции с файлами остаются открытыми.
|
|
512
|
+
|
|
513
|
+
## Файлы
|
|
514
|
+
|
|
515
|
+
Файлы доступны через REST и описываются в OpenAPI. Добавьте хранилище в конфигурацию:
|
|
516
|
+
|
|
517
|
+
```js
|
|
518
|
+
export default {
|
|
519
|
+
database: { path: './database.json' },
|
|
520
|
+
files: { directory: './uploads', metadata: './files.json' },
|
|
521
|
+
};
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Запустите сервер:
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
npx deep-json-server server.config.js
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Для временных тестов вместо этого используйте `files.data`. Каждая начальная запись содержит `name`, `mimeType`, бинарное `content` в виде `Uint8Array` и необязательный `directory`. Загруженные файлы в таком режиме остаются в памяти до завершения процесса.
|
|
531
|
+
|
|
532
|
+
Один файл отправляется непосредственно в теле запроса. `Content-Name` содержит URI-кодированное имя файла, `Content-Type` — его MIME-тип, а необязательный `Content-Directory` — URI-кодированный относительный путь к директории:
|
|
533
|
+
|
|
534
|
+
```http
|
|
535
|
+
POST /_files/storage
|
|
536
|
+
Content-Name: shadows-of-ardenia.jpg
|
|
537
|
+
Content-Directory: posters
|
|
538
|
+
Content-Type: image/jpeg
|
|
539
|
+
|
|
540
|
+
<binary body>
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Новый файл возвращает статус `201` и вычисленные метаданные:
|
|
544
|
+
|
|
545
|
+
```json
|
|
546
|
+
{
|
|
547
|
+
"directory": "posters",
|
|
548
|
+
"downloadUrl": "/_files/download/posters/shadows-of-ardenia.jpg",
|
|
549
|
+
"metadataUrl": "/_files/metadata/posters/shadows-of-ardenia.jpg",
|
|
550
|
+
"mimeType": "image/jpeg",
|
|
551
|
+
"name": "shadows-of-ardenia.jpg",
|
|
552
|
+
"size": 182340,
|
|
553
|
+
"url": "/_files/storage/posters/shadows-of-ardenia.jpg"
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
Сочетание `directory` и `name` идентифицирует файл. Повторная загрузка по существующему пути возвращает `409`. Чтобы заменить файл, передайте `Content-Override: true`; успешная перезапись возвращает `200`. Сервер поддерживает следующие файловые маршруты:
|
|
558
|
+
|
|
559
|
+
```text
|
|
560
|
+
POST /_files/storage Загрузка или перезапись файла
|
|
561
|
+
GET /_files/storage/* Просмотр содержимого файла
|
|
562
|
+
PATCH /_files/storage/* Переименование или перемещение файла
|
|
563
|
+
DELETE /_files/storage/* Удаление файла
|
|
564
|
+
|
|
565
|
+
GET /_files/metadata/* Получение метаданных в JSON
|
|
566
|
+
GET /_files/download/* Скачивание файла
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Для переименования, перемещения либо обеих операций отправьте JSON-объект. Нужно указать хотя бы одно поле:
|
|
570
|
+
|
|
571
|
+
```http
|
|
572
|
+
PATCH /_files/storage/posters/shadows-of-ardenia.jpg
|
|
573
|
+
Content-Type: application/json
|
|
574
|
+
|
|
575
|
+
{
|
|
576
|
+
"directory": "archive/posters",
|
|
577
|
+
"name": "ardenia-shadows.jpg"
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
`PATCH` возвращает обновлённые метаданные со статусом `200`; если по новому пути уже существует файл, сервер возвращает `409`. `DELETE` отвечает статусом `204` без тела. Если файл не найден, любая операция по пути возвращает `404`. Пути в URL задаются относительно `files.directory`, а все возвращаемые URL — относительно адреса сервера.
|
|
582
|
+
|
|
583
|
+
При хранении на диске бинарный файл находится по пути `<files.directory>/<directory>/<name>`. Метаданные содержат `directory`, `mimeType` и `name`; размер сервер читает из файла, а URL формирует сам. Директории и файл метаданных создаются по мере необходимости.
|
|
584
|
+
|
|
585
|
+
Используйте один процесс сервера для дисковой базы и файлового хранилища. Перед ручным изменением файлов или метаданных остановите его. Пути в хранилище не могут содержать символические ссылки. Загрузка и переименование не могут перезаписать базу, счётчики, схему, учётные записи auth, загруженную конфигурацию или файл метаданных.
|
|
586
|
+
|
|
587
|
+
Отправляйте файл как бинарное тело запроса. В браузере для этого можно использовать `xhr.send(file)`, а прогресс отслеживать через `XMLHttpRequest.upload.onprogress`. Максимальный размер по умолчанию равен 100 МиБ и настраивается через `server.maxFileSize`. Отсутствующие или небезопасные заголовки и пути возвращают `400`, превышение лимита — `413`, а отсутствующий, некорректный или не поддерживаемый Fastify `Content-Type` — `400` либо `415` в зависимости от этапа проверки.
|
|
588
|
+
|
|
589
|
+
## Программный API
|
|
590
|
+
|
|
591
|
+
```js
|
|
592
|
+
import { createServer } from '@kollors/deep-json-server/server';
|
|
593
|
+
import config from './server.config.js';
|
|
594
|
+
|
|
595
|
+
const facade = await createServer(config);
|
|
596
|
+
const server = facade.fastify();
|
|
597
|
+
await server.listen();
|
|
598
|
+
// await server.close();
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Методы `openapi()` и `graphql()` возвращают схемы и требуют `database.schema`. `fastify()` возвращает экземпляр сервера для настройки и запуска. База и включённые сервисы инициализируются при `ready()`, `listen()` или первом `inject()`; ошибка инициализации останавливает запуск.
|
|
602
|
+
|
|
603
|
+
Второй аргумент переопределяет подключение модулей, например `createServer(config, { files: false, graphql: true })`. Допустимы флаги `files`, `graphql`, `openapi` и `auth`. Для включения auth или файлов нужна соответствующая секция конфигурации; для GraphQL и OpenAPI — схема моделей.
|
|
604
|
+
|
|
605
|
+
Эти функции доступны и через общий импорт `@kollors/deep-json-server`. Адаптеры сервера загружаются при включении. Генераторы можно использовать отдельно:
|
|
606
|
+
|
|
607
|
+
```js
|
|
608
|
+
import { generateOpenapi, writeOpenapi } from '@kollors/deep-json-server/openapi';
|
|
609
|
+
import { generateGraphql, writeGraphql } from '@kollors/deep-json-server/graphql';
|
|
610
|
+
|
|
611
|
+
const document = await generateOpenapi('./schema.json', { files: true });
|
|
612
|
+
const sdl = await generateGraphql('./schema.json');
|
|
613
|
+
await writeOpenapi(document, './generated/openapi.yaml');
|
|
614
|
+
await writeGraphql(sdl, './generated/schema.graphql');
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
Оба генератора принимают `timestamps`, `softDelete` и `auth` для описания полей записей. При `{ auth: true }` OpenAPI также добавляет REST-маршруты auth и требования токена для изменения записей. Функция `hashPassword()` доступна и через общий импорт пакета.
|
|
618
|
+
|
|
619
|
+
`generateOpenapi()` также принимает `host`, `port`, `pageSize`, `maxPageSize` и `info`. Вместо пути можно передать объект схемы. Сервер и генераторы работают с собственной копией модели. Размеры страниц должны быть положительными целыми числами; `pageSize` не может превышать `maxPageSize`.
|
|
373
620
|
|
|
374
621
|
## Пример базы данных
|
|
375
622
|
|
|
@@ -475,146 +722,13 @@ query {
|
|
|
475
722
|
}
|
|
476
723
|
```
|
|
477
724
|
|
|
478
|
-
##
|
|
479
|
-
|
|
480
|
-
Добавьте `files.directory` и `files.metadata` в конфигурацию и запустите сервер:
|
|
481
|
-
|
|
482
|
-
```bash
|
|
483
|
-
deep-json-server server.config.js
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
Для временных тестов вместо этого используйте `files.data`. Каждая начальная запись содержит `name`, `mimeType`, бинарное `content` в виде `Uint8Array` и необязательный `directory`. Загруженные файлы в таком режиме остаются в памяти до завершения процесса.
|
|
487
|
-
|
|
488
|
-
Один файл отправляется непосредственно в теле запроса. `Content-Name` содержит URI-кодированное имя файла, `Content-Type` — его MIME-тип, а необязательный `Content-Directory` — URI-кодированный относительный путь к директории:
|
|
489
|
-
|
|
490
|
-
```http
|
|
491
|
-
POST /_files/storage
|
|
492
|
-
Content-Name: shadows-of-ardenia.jpg
|
|
493
|
-
Content-Directory: posters
|
|
494
|
-
Content-Type: image/jpeg
|
|
495
|
-
|
|
496
|
-
<binary body>
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
Новый файл возвращает статус `201` и вычисленные метаданные:
|
|
500
|
-
|
|
501
|
-
```json
|
|
502
|
-
{
|
|
503
|
-
"directory": "posters",
|
|
504
|
-
"downloadUrl": "/_files/download/posters/shadows-of-ardenia.jpg",
|
|
505
|
-
"metadataUrl": "/_files/metadata/posters/shadows-of-ardenia.jpg",
|
|
506
|
-
"mimeType": "image/jpeg",
|
|
507
|
-
"name": "shadows-of-ardenia.jpg",
|
|
508
|
-
"size": 182340,
|
|
509
|
-
"url": "/_files/storage/posters/shadows-of-ardenia.jpg"
|
|
510
|
-
}
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
Сочетание `directory` и `name` идентифицирует файл. Повторная загрузка по существующему пути возвращает `409`. Чтобы заменить файл, передайте `Content-Override: true`; успешная перезапись возвращает `200`. Сервер поддерживает следующие файловые маршруты:
|
|
514
|
-
|
|
515
|
-
```text
|
|
516
|
-
POST /_files/storage Загрузка или перезапись файла
|
|
517
|
-
GET /_files/storage/* Просмотр содержимого файла
|
|
518
|
-
PATCH /_files/storage/* Переименование или перемещение файла
|
|
519
|
-
DELETE /_files/storage/* Удаление файла
|
|
520
|
-
|
|
521
|
-
GET /_files/metadata/* Получение метаданных в JSON
|
|
522
|
-
GET /_files/download/* Скачивание файла
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
Для переименования, перемещения либо обеих операций отправьте JSON-объект. Нужно указать хотя бы одно поле:
|
|
526
|
-
|
|
527
|
-
```http
|
|
528
|
-
PATCH /_files/storage/posters/shadows-of-ardenia.jpg
|
|
529
|
-
Content-Type: application/json
|
|
530
|
-
|
|
531
|
-
{
|
|
532
|
-
"directory": "archive/posters",
|
|
533
|
-
"name": "ardenia-shadows.jpg"
|
|
534
|
-
}
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
`PATCH` возвращает обновлённые метаданные со статусом `200`; если по новому пути уже существует файл, сервер возвращает `409`. `DELETE` отвечает статусом `204` без тела. Если файл не найден, любая операция по пути возвращает `404`. Пути в URL задаются относительно `files.directory`, а все возвращаемые URL — относительно адреса сервера.
|
|
538
|
-
|
|
539
|
-
При хранении на диске бинарный файл находится по пути `<files.directory>/<directory>/<name>`. Метаданные содержат `directory`, `mimeType` и `name`; размер сервер читает из файла, а URL формирует сам. Директории и файл метаданных создаются по мере необходимости.
|
|
540
|
-
|
|
541
|
-
Используйте один процесс сервера для дисковой базы и файлового хранилища. Перед ручным изменением файлов или метаданных остановите его. Пути в хранилище не могут содержать символические ссылки. Загрузка и переименование не могут перезаписать базу, счётчики, схему, учётные записи auth, загруженную конфигурацию или файл метаданных.
|
|
542
|
-
|
|
543
|
-
Отправляйте файл как бинарное тело запроса. В браузере для этого можно использовать `xhr.send(file)`, а прогресс отслеживать через `XMLHttpRequest.upload.onprogress`. Максимальный размер по умолчанию равен 100 МиБ и настраивается через `server.maxFileSize`. Отсутствующие или небезопасные заголовки и пути возвращают `400`, превышение лимита — `413`, а отсутствующий, некорректный или не поддерживаемый Fastify `Content-Type` — `400` либо `415` в зависимости от этапа проверки.
|
|
544
|
-
|
|
545
|
-
## Аутентификация
|
|
546
|
-
|
|
547
|
-
Модуль auth добавляет REST-маршруты входа, получения текущего пользователя и выхода. **Он не ограничивает доступ к REST-записям, файлам и GraphQL.**
|
|
548
|
-
|
|
549
|
-
Создайте учётную запись в отдельном файле с помощью `setup-auth.mjs`:
|
|
550
|
-
|
|
551
|
-
```js
|
|
552
|
-
import { writeFile } from 'node:fs/promises';
|
|
553
|
-
import { hashPassword } from '@kollors/deep-json-server/auth';
|
|
554
|
-
|
|
555
|
-
const password = process.env.DJS_PASSWORD;
|
|
556
|
-
if (!password) throw new Error('Set DJS_PASSWORD');
|
|
557
|
-
await writeFile('./auth.json', JSON.stringify([
|
|
558
|
-
{ id: '1', username: 'admin', passwordHash: await hashPassword(password) },
|
|
559
|
-
], null, 2), { flag: 'wx', mode: 0o600 });
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
Задайте `DJS_PASSWORD` и выполните `node setup-auth.mjs`. Добавьте файл в конфигурацию сервера:
|
|
563
|
-
|
|
564
|
-
```js
|
|
565
|
-
export default {
|
|
566
|
-
database: { path: './database.json' },
|
|
567
|
-
auth: { users: './auth.json', expiresIn: 3600 },
|
|
568
|
-
};
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
Запустите `npx deep-json-server --auth server.config.js` или задайте `auth.enabled: true`. Каждой записи нужны уникальный строковый `id`, уникальный `username` и `passwordHash`, созданный функцией выше. Пароли хешируются через scrypt со случайной солью. Файл читается при запуске; после его изменения перезапустите сервер.
|
|
572
|
-
|
|
573
|
-
| REST-запрос | Входные данные | Ответ |
|
|
574
|
-
|---|---|---|
|
|
575
|
-
| `POST /auth/login` | JSON `{ "username": "admin", "password": "…" }` | `{ accessToken, expiresIn, user: { id, username } }` |
|
|
576
|
-
| `GET /auth/me` | Bearer-токен | `{ id, username }` |
|
|
577
|
-
| `POST /auth/logout` | Bearer-токен | `{ success: true }` |
|
|
578
|
-
|
|
579
|
-
Передавайте токен в заголовке `Authorization: Bearer <accessToken>`. Неверные учётные данные, недействительный или истёкший токен возвращают HTTP 401. Сессии хранятся в памяти и исчезают при перезапуске; выход отзывает переданный токен. Вход может вернуть HTTP 429 при слишком большом числе одновременных попыток или активных сессий.
|
|
580
|
-
|
|
581
|
-
В OpenAPI добавляются эти REST-операции и Bearer-схема для `/auth/me` и `/auth/logout`. В Swagger UI токен из ответа на вход можно вставить в **Authorize**. Для экспорта схем включите auth в конфигурации или используйте `generate openapi --auth server.config.js`; файл учётных записей при генерации не читается. Для GraphQL и OpenAPI по-прежнему нужна `database.schema`.
|
|
582
|
-
|
|
583
|
-
## Программный API
|
|
584
|
-
|
|
585
|
-
```js
|
|
586
|
-
import { createServer } from '@kollors/deep-json-server/server';
|
|
587
|
-
import config from './server.config.js';
|
|
588
|
-
|
|
589
|
-
const facade = await createServer(config);
|
|
590
|
-
const server = facade.fastify();
|
|
591
|
-
await server.listen();
|
|
592
|
-
// await server.close();
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
Методы `openapi()` и `graphql()` возвращают схемы и требуют `database.schema`. `fastify()` возвращает экземпляр сервера для настройки и запуска. База и включённые сервисы инициализируются при `ready()`, `listen()` или первом `inject()`; ошибка инициализации останавливает запуск. Возможности сервера можно переопределить через `createServer(config, { files: false, graphql: true, openapi: true, auth: true })`.
|
|
596
|
-
|
|
597
|
-
Эти функции доступны и через общий импорт `@kollors/deep-json-server`. Адаптеры сервера загружаются при включении. Генераторы можно использовать отдельно:
|
|
598
|
-
|
|
599
|
-
```js
|
|
600
|
-
import { generateOpenapi, writeOpenapi } from '@kollors/deep-json-server/openapi';
|
|
601
|
-
import { generateGraphql, writeGraphql } from '@kollors/deep-json-server/graphql';
|
|
602
|
-
|
|
603
|
-
const document = await generateOpenapi('./schema.json', { files: true });
|
|
604
|
-
const sdl = await generateGraphql('./schema.json');
|
|
605
|
-
await writeOpenapi(document, './generated/openapi.yaml');
|
|
606
|
-
await writeGraphql(sdl, './generated/schema.graphql');
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
`generateOpenapi()` принимает `{ auth: true }`, чтобы добавить операции auth. Функция `hashPassword()` доступна и через общий импорт пакета.
|
|
610
|
-
|
|
611
|
-
`generateOpenapi()` также принимает `host`, `port`, `pageSize`, `maxPageSize` и `info`. Вместо пути можно передать объект схемы. Сервер и генераторы работают с собственной копией модели. Размеры страниц должны быть положительными целыми числами; `pageSize` не может превышать `maxPageSize`.
|
|
612
|
-
|
|
613
|
-
## Хранение и разработка
|
|
725
|
+
## Хранение данных
|
|
614
726
|
|
|
615
727
|
Изменения выполняются последовательно внутри экземпляра сервера и проверяются на копии данных до сохранения. Для одного файла базы используйте один процесс сервера. Счётчики `increment` хранятся рядом с базой в `<путь базы>.counters.json`; сохраняйте этот файл вместе с базой. Номера резервируются до записи данных: сбой может оставить пропуск, но не приводит к повторной выдаче номера.
|
|
616
728
|
|
|
617
|
-
|
|
729
|
+
## Разработка
|
|
730
|
+
|
|
731
|
+
Исходники разделены на `rest`, `graphql`, `openapi`, `auth`, `files`, `cli` и `server`. Общая модель, хранение, запросы и правила изменения записей находятся в `core`. Модуль `server` подключает остальные; генераторы схем загружаются независимо от HTTP-сервера.
|
|
618
732
|
|
|
619
733
|
```sh
|
|
620
734
|
npm ci
|
|
@@ -623,6 +737,6 @@ npm run verify
|
|
|
623
737
|
|
|
624
738
|
Команда проверяет типы, стиль кода, покрытие тестами и установку пакета из архива.
|
|
625
739
|
|
|
626
|
-
Для публикации новой альфы обновите версию в `package.json`, `package-lock.json` и `src/constants.ts`, затем отправьте изменения в `main`. GitHub Actions создаст тег версии и опубликует пакет в канал npm `alpha` через trusted publishing. Уже опубликованная версия пропускается. Если тег создан, а публикация не завершилась, повторный запуск использует этот тег и проверяет соответствие ему файлов пакета. Отправка тега версии также запускает публикацию; стабильные версии публикуются в `latest`.
|
|
740
|
+
Для публикации новой альфы обновите версию в `package.json`, `package-lock.json` и `src/core/constants.ts`, затем отправьте изменения в `main`. GitHub Actions создаст тег версии и опубликует пакет в канал npm `alpha` через trusted publishing. Уже опубликованная версия пропускается. Если тег создан, а публикация не завершилась, повторный запуск использует этот тег и проверяет соответствие ему файлов пакета. Отправка тега версии также запускает публикацию; стабильные версии публикуются в `latest`.
|
|
627
741
|
|
|
628
742
|
Лицензия: MIT.
|