@kollors/deep-json-server 1.0.0-alpha.5 → 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 +346 -188
- package/README.ru.md +343 -185
- package/dist/bin/deep-json-server.js +1 -1
- package/dist/bin/deep-json-server.js.map +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/src/auth/contract.d.ts +27 -0
- package/dist/src/auth/contract.js +5 -0
- package/dist/src/auth/contract.js.map +1 -0
- package/dist/src/auth/password.d.ts +13 -0
- package/dist/src/auth/password.js +37 -0
- package/dist/src/auth/password.js.map +1 -0
- package/dist/src/auth/public.d.ts +3 -0
- package/dist/src/auth/public.js +5 -0
- package/dist/src/auth/public.js.map +1 -0
- package/dist/src/auth/routes.d.ts +6 -0
- package/dist/src/auth/routes.js +15 -0
- package/dist/src/auth/routes.js.map +1 -0
- package/dist/src/auth/service.d.ts +39 -0
- package/dist/src/auth/service.js +143 -0
- package/dist/src/auth/service.js.map +1 -0
- package/dist/src/cli/index.d.ts +7 -0
- package/dist/src/{cli.js → cli/index.js} +32 -12
- 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/core/errors.d.ts +9 -0
- 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} +14 -1
- 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/openapi/auth.js +72 -0
- package/dist/src/openapi/auth.js.map +1 -0
- package/dist/src/openapi/document.d.ts +7 -3
- package/dist/src/openapi/document.js +49 -34
- 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 +11 -2
- package/dist/src/openapi/public.js +12 -4
- package/dist/src/openapi/public.js.map +1 -1
- package/dist/src/{types.d.ts → openapi/types.d.ts} +2 -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} +73 -12
- 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} +39 -21
- package/dist/src/server/create.js.map +1 -0
- package/dist/src/server/features.d.ts +15 -0
- package/dist/src/server/features.js +31 -0
- 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 +5 -1
- package/dist/src/cli.d.ts +0 -4
- package/dist/src/cli.js.map +0 -1
- package/dist/src/config.d.ts +0 -81
- 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.d.ts +0 -6
- package/dist/src/errors.js +0 -9
- package/dist/src/errors.js.map +0 -1
- package/dist/src/features.d.ts +0 -8
- package/dist/src/features.js +0 -18
- 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 -6
- 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,6 +59,8 @@ 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 |
|
|
62
|
+
| `auth.users` | Путь к JSON-массиву учётных записей или массив в памяти |
|
|
63
|
+
| `auth.expiresIn` | Срок действия сессии в секундах; по умолчанию 3600 |
|
|
58
64
|
| `server.host`, `server.port` | По умолчанию `127.0.0.1`, `4001`; CLI также читает `HOST`/`PORT` |
|
|
59
65
|
| `server.pageSize`, `server.maxPageSize` | По умолчанию 10 и 100; размер по умолчанию ограничен максимумом |
|
|
60
66
|
| `server.cors`, `server.logger` | По умолчанию `true`; logger также принимает настройки Fastify |
|
|
@@ -66,9 +72,13 @@ npx deep-json-server server.config.js
|
|
|
66
72
|
|
|
67
73
|
Значение `server.port: 0` позволяет системе выбрать свободный порт. OpenAPI на HTTP-эндпоинте использует относительный адрес сервера.
|
|
68
74
|
|
|
75
|
+
### CLI
|
|
76
|
+
|
|
69
77
|
| Флаг CLI | Действие |
|
|
70
78
|
|---|---|
|
|
71
|
-
| `--files` | Включить файловые
|
|
79
|
+
| `--files` | Включить файловые маршруты; нужна секция `files` |
|
|
80
|
+
| `--timestamps` | Включить даты записей глобально |
|
|
81
|
+
| `--soft-delete` | Включить мягкое удаление глобально |
|
|
72
82
|
| `--graphql` | Включить GraphQL API |
|
|
73
83
|
| `--openapi` | Включить HTTP-маршрут OpenAPI |
|
|
74
84
|
| `--host <host>` | Адрес сервера |
|
|
@@ -76,27 +86,7 @@ npx deep-json-server server.config.js
|
|
|
76
86
|
| `--help`, `-h` | Справка |
|
|
77
87
|
| `--version`, `-v` | Версия пакета |
|
|
78
88
|
|
|
79
|
-
Приоритет
|
|
80
|
-
|
|
81
|
-
Для генерации укажите формат и файл конфигурации:
|
|
82
|
-
|
|
83
|
-
```sh
|
|
84
|
-
npx deep-json-server generate openapi server.config.js
|
|
85
|
-
npx deep-json-server generate graphql server.config.js
|
|
86
|
-
npx deep-json-server generate openapi,graphql server.config.js
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Команда читает `database.schema` и сохраняет схемы в `openapi.path` и `graphql.path`. Для генерации достаточно такой конфигурации:
|
|
90
|
-
|
|
91
|
-
```js
|
|
92
|
-
export default {
|
|
93
|
-
database: { schema: './schema.json' },
|
|
94
|
-
openapi: { path: './generated/openapi.yaml' },
|
|
95
|
-
graphql: { path: './generated/schema.graphql' },
|
|
96
|
-
};
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Для каждого формата нужен отдельный файл. Команда отклонит путь, который перезапишет конфигурацию, базу, схему или метаданные файлов.
|
|
89
|
+
Приоритет адреса и порта: CLI → конфигурация → `HOST`/`PORT` → значения по умолчанию. Файловые маршруты включаются при наличии секции `files`, а вход и проверка прав — при наличии `auth`. Настройки модели могут переопределять глобальные `timestamps` и `softDelete`.
|
|
100
90
|
|
|
101
91
|
## Схема моделей
|
|
102
92
|
|
|
@@ -138,6 +128,8 @@ export default {
|
|
|
138
128
|
|
|
139
129
|
REST без схемы создаёт ключ `id` и сохраняет произвольные JSON-поля. Для фильтров и выбора отдельных полей используются имена в формате идентификаторов; остальные поля возвращаются через `scope=[{"*":true}]`. Поля с разными типами значений можно читать, но фильтрация, сортировка и пагинация неоднородных списков требуют явной схемы.
|
|
140
130
|
|
|
131
|
+
У модели обязательны `collection` — имя коллекции в базе и REST-пути — и `fields` — описание полей. Имя модели (`User`) задаёт имена типов и операций GraphQL. `api` управляет доступностью форматов, а `timestamps` и `softDelete` переопределяют глобальные настройки для этой модели.
|
|
132
|
+
|
|
141
133
|
### Поля
|
|
142
134
|
|
|
143
135
|
Поле `type` принимает `string`, `number`, `boolean`, `object` или имя модели. Для массива добавьте суффикс `[]`: `string[]`, `object[]`, `Genre[]`. Вложенные поля описываются через точку, например `actors.fullName`.
|
|
@@ -168,10 +160,12 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
168
160
|
### Связи
|
|
169
161
|
|
|
170
162
|
```json
|
|
171
|
-
|
|
172
|
-
"
|
|
173
|
-
|
|
174
|
-
|
|
163
|
+
{
|
|
164
|
+
"actors.genres": {
|
|
165
|
+
"type": "Genre[]",
|
|
166
|
+
"source": "actors.genreIds",
|
|
167
|
+
"required": true
|
|
168
|
+
}
|
|
175
169
|
}
|
|
176
170
|
```
|
|
177
171
|
|
|
@@ -192,6 +186,12 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
192
186
|
|
|
193
187
|
## Запросы и ответы
|
|
194
188
|
|
|
189
|
+
Примеры с фильмами, актёрами и жанрами используют полную [схему из examples](examples/schema.json). Для их запуска используйте [конфигурацию примера](examples/server.config.js):
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
npx deep-json-server examples/server.config.js
|
|
193
|
+
```
|
|
194
|
+
|
|
195
195
|
Коллекции и списки объектов, включая вложенные `object[]`, возвращают:
|
|
196
196
|
|
|
197
197
|
```json
|
|
@@ -202,7 +202,7 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
202
202
|
|
|
203
203
|
В `pager` задаются `page` и `pageSize`. По умолчанию возвращается первая страница с размером из `server.pageSize`. Оба значения должны быть положительными целыми числами; `pageSize` ограничен `server.maxPageSize`. За пределами списка возвращается пустой `data` с общим числом найденных записей в `total`.
|
|
204
204
|
|
|
205
|
-
Фильтры: `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" } }`.
|
|
206
206
|
|
|
207
207
|
```json
|
|
208
208
|
{
|
|
@@ -220,10 +220,12 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
220
220
|
|
|
221
221
|
Корневой `where` выбирает записи основной коллекции. `where` внутри связи фильтрует её элементы, сохраняя родительскую запись. Каждый вложенный список обрабатывается отдельно. Фильтрация по связи работает независимо от её включения в ответ.
|
|
222
222
|
|
|
223
|
-
`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`.
|
|
224
224
|
|
|
225
225
|
### REST
|
|
226
226
|
|
|
227
|
+
`GET /` возвращает имена коллекций: `{ "resources": ["users", "movies"] }`. Для каждой коллекции доступны следующие маршруты:
|
|
228
|
+
|
|
227
229
|
| Метод | Путь | Операция |
|
|
228
230
|
|---|---|---|
|
|
229
231
|
| GET | `/users` | `userList` |
|
|
@@ -233,62 +235,13 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
233
235
|
| PATCH | `/users/{id}` | `userUpdate` |
|
|
234
236
|
| DELETE | `/users/{id}` | `userDelete` |
|
|
235
237
|
|
|
236
|
-
Имя параметра пути соответствует первичному ключу. POST, PUT и PATCH принимают JSON-объект записи. PUT заменяет запись с сохранением ключа и серверных полей. PATCH объединяет поля на верхнем уровне; переданные вложенные объекты заменяются с сохранением их полей `readOnly`. Создание и замена требуют всех обязательных полей. При обновлении проверяются переданные значения и итоговая запись. Отсутствующая запись — `404`, конфликт — `409`.
|
|
237
|
-
|
|
238
|
-
### Вложенная запись
|
|
239
|
-
|
|
240
|
-
Поля ключей, например `genreIds: ["1"]`, только задают связь. В поля связей можно передавать записи для создания или обновления:
|
|
241
|
-
|
|
242
|
-
```http
|
|
243
|
-
PATCH /movies/1
|
|
244
|
-
Content-Type: application/json
|
|
245
|
-
|
|
246
|
-
{
|
|
247
|
-
"actors": [
|
|
248
|
-
{
|
|
249
|
-
"userId": "1",
|
|
250
|
-
"genres": [
|
|
251
|
-
"1",
|
|
252
|
-
{ "id": "2", "name": "Обновлённый жанр" },
|
|
253
|
-
{ "name": "Новый жанр" }
|
|
254
|
-
]
|
|
255
|
-
}
|
|
256
|
-
]
|
|
257
|
-
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
| Значение в связи | Действие |
|
|
261
|
-
|---|---|
|
|
262
|
-
| Ключ, например `"1"` | Связать существующую запись без её изменения |
|
|
263
|
-
| Объект с первичным ключом | PATCH обновляет переданные поля, PUT заменяет связанную запись |
|
|
264
|
-
| Объект без первичного ключа | Создать запись со значениями по умолчанию и сгенерированным ключом |
|
|
238
|
+
Имя параметра пути соответствует первичному ключу. POST, PUT и PATCH принимают JSON-объект записи. PUT заменяет запись с сохранением ключа и серверных полей. PATCH объединяет поля на верхнем уровне; переданные вложенные объекты заменяются с сохранением их полей `readOnly`. Создание и замена требуют всех обязательных полей. При обновлении проверяются переданные значения и итоговая запись. Отсутствующая запись — `404`, конфликт — `409`.
|
|
265
239
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
Переданный список заменяет состав связи. PATCH сохраняет пропущенные связи, PUT очищает пропущенные связи, ключи которых доступны для записи. `[]` очищает список, `null` — одиночную связь с разрешённым `nullable`. Разрыв связи не удаляет связанную запись. Обязательные связи должны оставаться заполненными.
|
|
269
|
-
|
|
270
|
-
В одном объекте указывайте либо связь, либо её хранимый ключ: например, `genres` или `genreIds`. Для обратной связи сервер меняет целевой ключ. Если путь проходит через массив и нельзя однозначно выбрать элемент для связи, передайте массив с нужными ключами явно. Защищённые ключи изменять нельзя.
|
|
271
|
-
|
|
272
|
-
Все вложенные изменения входят в транзакцию основной записи. Ошибка проверки, отсутствующая запись или неверный выбор полей ответа отменяет всю операцию. Изменения общей записи видны всем, кто с ней связан.
|
|
273
|
-
|
|
274
|
-
GraphQL принимает в полях связей типизированные объекты. Чтобы при замене изменить только связи, используйте поля ключей, например `genreIds`. Пример:
|
|
275
|
-
|
|
276
|
-
```graphql
|
|
277
|
-
mutation {
|
|
278
|
-
movieUpdate(id: "1", data: {
|
|
279
|
-
actors: [{
|
|
280
|
-
userId: "1"
|
|
281
|
-
genres: [{ id: "2", name: "Обновлённый жанр" }, { name: "Новый жанр" }]
|
|
282
|
-
}]
|
|
283
|
-
}) {
|
|
284
|
-
actors { data { genres { data { id name } } } }
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
```
|
|
240
|
+
POST возвращает созданную запись со статусом `201`; PUT, PATCH и DELETE — результат со статусом `200`. Ошибки REST имеют вид `{ "error": "Описание ошибки" }`.
|
|
288
241
|
|
|
289
242
|
### Параметры REST-запросов
|
|
290
243
|
|
|
291
|
-
REST принимает один
|
|
244
|
+
Для запросов к записям REST принимает один параметр URL `scope` с JSON-массивом `[поля, аргументы?]`. Первый объект выбирает поля, второй задаёт `where`, `order` и `pager` для списка. Этот формат одинаков для корневого запроса, вложенных объектов и связей.
|
|
292
245
|
|
|
293
246
|
Пример выбора пользователей и их фильмов с отдельной сортировкой и пагинацией:
|
|
294
247
|
|
|
@@ -340,13 +293,71 @@ const response = await fetch(`/users?${params}`);
|
|
|
340
293
|
|
|
341
294
|
Аргументы доступны только у списков. В запросе отдельной записи и в ответе мутации их можно задать для вложенных списков. Параметры проверяются даже на пустых данных; ошибка в выборе ответа отменяет изменения записи. Некорректный `scope` возвращает `400`. Максимальная длина JSON — 10 000 символов, глубина выбора — 32 уровня.
|
|
342
295
|
|
|
343
|
-
|
|
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
|
+
```
|
|
344
346
|
|
|
345
347
|
### GraphQL
|
|
346
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
|
+
|
|
347
357
|
```graphql
|
|
348
358
|
query {
|
|
349
359
|
userList(
|
|
360
|
+
where: { fullName: { contains: "Мира" } }
|
|
350
361
|
order: [{ field: fullName, direction: ASC }]
|
|
351
362
|
pager: { page: 1, pageSize: 20 }
|
|
352
363
|
) {
|
|
@@ -354,7 +365,11 @@ query {
|
|
|
354
365
|
data {
|
|
355
366
|
id
|
|
356
367
|
fullName
|
|
357
|
-
movies(
|
|
368
|
+
movies(
|
|
369
|
+
where: { title: { contains: "Тени" } }
|
|
370
|
+
order: [{ field: title, direction: ASC }]
|
|
371
|
+
pager: { pageSize: 5 }
|
|
372
|
+
) {
|
|
358
373
|
total
|
|
359
374
|
data { id title }
|
|
360
375
|
}
|
|
@@ -365,7 +380,243 @@ query {
|
|
|
365
380
|
|
|
366
381
|
Запрос `user(id: ...)` возвращает одну запись или `null`, если она отсутствует. Мутации: `userCreate(data: ...)`, `userReplace(id: ..., data: ...)`, `userUpdate(id: ..., data: ...)`, `userDelete(id: ...)`. Для модели, состоящей из генерируемых полей, мутация создания вызывается без аргумента `data`. Правила записи и валидации совпадают с REST. Связи, выбранные в результате мутации, определяют содержимое ответа.
|
|
367
382
|
|
|
368
|
-
Строковый первичный ключ представлен типом 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`.
|
|
369
620
|
|
|
370
621
|
## Пример базы данных
|
|
371
622
|
|
|
@@ -471,106 +722,13 @@ query {
|
|
|
471
722
|
}
|
|
472
723
|
```
|
|
473
724
|
|
|
474
|
-
##
|
|
475
|
-
|
|
476
|
-
Добавьте `files.directory` и `files.metadata` в конфигурацию и запустите сервер:
|
|
477
|
-
|
|
478
|
-
```bash
|
|
479
|
-
deep-json-server server.config.js
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
Для временных тестов вместо этого используйте `files.data`. Каждая начальная запись содержит `name`, `mimeType`, бинарное `content` в виде `Uint8Array` и необязательный `directory`. Загруженные файлы в таком режиме остаются в памяти до завершения процесса.
|
|
483
|
-
|
|
484
|
-
Один файл отправляется непосредственно в теле запроса. `Content-Name` содержит URI-кодированное имя файла, `Content-Type` — его MIME-тип, а необязательный `Content-Directory` — URI-кодированный относительный путь к директории:
|
|
485
|
-
|
|
486
|
-
```http
|
|
487
|
-
POST /_files/storage
|
|
488
|
-
Content-Name: shadows-of-ardenia.jpg
|
|
489
|
-
Content-Directory: posters
|
|
490
|
-
Content-Type: image/jpeg
|
|
491
|
-
|
|
492
|
-
<binary body>
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
Новый файл возвращает статус `201` и вычисленные метаданные:
|
|
496
|
-
|
|
497
|
-
```json
|
|
498
|
-
{
|
|
499
|
-
"directory": "posters",
|
|
500
|
-
"downloadUrl": "/_files/download/posters/shadows-of-ardenia.jpg",
|
|
501
|
-
"metadataUrl": "/_files/metadata/posters/shadows-of-ardenia.jpg",
|
|
502
|
-
"mimeType": "image/jpeg",
|
|
503
|
-
"name": "shadows-of-ardenia.jpg",
|
|
504
|
-
"size": 182340,
|
|
505
|
-
"url": "/_files/storage/posters/shadows-of-ardenia.jpg"
|
|
506
|
-
}
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
Сочетание `directory` и `name` идентифицирует файл. Повторная загрузка по существующему пути возвращает `409`. Чтобы заменить файл, передайте `Content-Override: true`; успешная перезапись возвращает `200`. Сервер поддерживает следующие файловые маршруты:
|
|
510
|
-
|
|
511
|
-
```text
|
|
512
|
-
POST /_files/storage Загрузка или перезапись файла
|
|
513
|
-
GET /_files/storage/* Просмотр содержимого файла
|
|
514
|
-
PATCH /_files/storage/* Переименование или перемещение файла
|
|
515
|
-
DELETE /_files/storage/* Удаление файла
|
|
516
|
-
|
|
517
|
-
GET /_files/metadata/* Получение метаданных в JSON
|
|
518
|
-
GET /_files/download/* Скачивание файла
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
Для переименования, перемещения либо обеих операций отправьте JSON-объект. Нужно указать хотя бы одно поле:
|
|
522
|
-
|
|
523
|
-
```http
|
|
524
|
-
PATCH /_files/storage/posters/shadows-of-ardenia.jpg
|
|
525
|
-
Content-Type: application/json
|
|
526
|
-
|
|
527
|
-
{
|
|
528
|
-
"directory": "archive/posters",
|
|
529
|
-
"name": "ardenia-shadows.jpg"
|
|
530
|
-
}
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
`PATCH` возвращает обновлённые метаданные со статусом `200`; если по новому пути уже существует файл, сервер возвращает `409`. `DELETE` отвечает статусом `204` без тела. Если файл не найден, любая операция по пути возвращает `404`. Пути в URL задаются относительно `files.directory`, а все возвращаемые URL — относительно адреса сервера.
|
|
534
|
-
|
|
535
|
-
При хранении на диске бинарный файл находится по пути `<files.directory>/<directory>/<name>`. Метаданные содержат `directory`, `mimeType` и `name`; размер сервер читает из файла, а URL формирует сам. Директории и файл метаданных создаются по мере необходимости.
|
|
536
|
-
|
|
537
|
-
Используйте один процесс сервера для дисковой базы и файлового хранилища. Перед ручным изменением файлов или метаданных остановите его. Пути в хранилище не могут содержать символические ссылки. Загрузка и переименование не могут перезаписать базу, счётчики, схему, загруженную конфигурацию или файл метаданных.
|
|
538
|
-
|
|
539
|
-
Отправляйте файл как бинарное тело запроса. В браузере для этого можно использовать `xhr.send(file)`, а прогресс отслеживать через `XMLHttpRequest.upload.onprogress`. Максимальный размер по умолчанию равен 100 МиБ и настраивается через `server.maxFileSize`. Отсутствующие или небезопасные заголовки и пути возвращают `400`, превышение лимита — `413`, а отсутствующий, некорректный или не поддерживаемый Fastify `Content-Type` — `400` либо `415` в зависимости от этапа проверки.
|
|
540
|
-
|
|
541
|
-
## Программный API
|
|
542
|
-
|
|
543
|
-
```js
|
|
544
|
-
import { createServer } from '@kollors/deep-json-server/server';
|
|
545
|
-
import config from './server.config.js';
|
|
546
|
-
|
|
547
|
-
const facade = await createServer(config);
|
|
548
|
-
const server = facade.fastify();
|
|
549
|
-
await server.listen();
|
|
550
|
-
// await server.close();
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
Методы `openapi()` и `graphql()` возвращают схемы и требуют `database.schema`. `fastify()` возвращает экземпляр сервера для настройки и запуска. База и включённые сервисы инициализируются при `ready()`, `listen()` или первом `inject()`; ошибка инициализации останавливает запуск. Возможности сервера можно переопределить через `createServer(config, { files: false, graphql: true, openapi: true })`.
|
|
554
|
-
|
|
555
|
-
Эти функции доступны и через общий импорт `@kollors/deep-json-server`. Адаптеры сервера загружаются при включении. Генераторы можно использовать отдельно:
|
|
556
|
-
|
|
557
|
-
```js
|
|
558
|
-
import { generateOpenapi, writeOpenapi } from '@kollors/deep-json-server/openapi';
|
|
559
|
-
import { generateGraphql, writeGraphql } from '@kollors/deep-json-server/graphql';
|
|
560
|
-
|
|
561
|
-
const document = await generateOpenapi('./schema.json', { files: true });
|
|
562
|
-
const sdl = await generateGraphql('./schema.json');
|
|
563
|
-
await writeOpenapi(document, './generated/openapi.yaml');
|
|
564
|
-
await writeGraphql(sdl, './generated/schema.graphql');
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
`generateOpenapi()` также принимает `host`, `port`, `pageSize`, `maxPageSize` и `info`. Вместо пути можно передать объект схемы. Сервер и генераторы работают с собственной копией модели. Размеры страниц должны быть положительными целыми числами; `pageSize` не может превышать `maxPageSize`.
|
|
568
|
-
|
|
569
|
-
## Хранение и разработка
|
|
725
|
+
## Хранение данных
|
|
570
726
|
|
|
571
727
|
Изменения выполняются последовательно внутри экземпляра сервера и проверяются на копии данных до сохранения. Для одного файла базы используйте один процесс сервера. Счётчики `increment` хранятся рядом с базой в `<путь базы>.counters.json`; сохраняйте этот файл вместе с базой. Номера резервируются до записи данных: сбой может оставить пропуск, но не приводит к повторной выдаче номера.
|
|
572
728
|
|
|
573
|
-
|
|
729
|
+
## Разработка
|
|
730
|
+
|
|
731
|
+
Исходники разделены на `rest`, `graphql`, `openapi`, `auth`, `files`, `cli` и `server`. Общая модель, хранение, запросы и правила изменения записей находятся в `core`. Модуль `server` подключает остальные; генераторы схем загружаются независимо от HTTP-сервера.
|
|
574
732
|
|
|
575
733
|
```sh
|
|
576
734
|
npm ci
|
|
@@ -579,6 +737,6 @@ npm run verify
|
|
|
579
737
|
|
|
580
738
|
Команда проверяет типы, стиль кода, покрытие тестами и установку пакета из архива.
|
|
581
739
|
|
|
582
|
-
Для публикации новой альфы обновите версию в `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`.
|
|
583
741
|
|
|
584
742
|
Лицензия: MIT.
|