@kollors/deep-json-server 1.0.0-alpha.1 → 1.0.0-alpha.11
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 +522 -179
- package/README.ru.md +525 -180
- package/dist/bin/deep-json-server.js +1 -1
- package/dist/bin/deep-json-server.js.map +1 -1
- package/dist/index.d.ts +13 -6
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/src/auth/contract.d.ts +31 -0
- package/dist/src/auth/contract.js +12 -0
- package/dist/src/auth/contract.js.map +1 -0
- package/dist/src/auth/input.d.ts +18 -0
- package/dist/src/auth/input.js +34 -0
- package/dist/src/auth/input.js.map +1 -0
- package/dist/src/auth/password.d.ts +17 -0
- package/dist/src/auth/password.js +41 -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 +18 -0
- package/dist/src/auth/routes.js.map +1 -0
- package/dist/src/auth/service.d.ts +78 -0
- package/dist/src/auth/service.js +218 -0
- package/dist/src/auth/service.js.map +1 -0
- package/dist/src/auth/store.d.ts +26 -0
- package/dist/src/auth/store.js +82 -0
- package/dist/src/auth/store.js.map +1 -0
- package/dist/src/cli/index.d.ts +7 -0
- package/dist/src/cli/index.js +113 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/core/config-values.d.ts +28 -0
- package/dist/src/core/config-values.js +53 -0
- package/dist/src/core/config-values.js.map +1 -0
- package/dist/src/{constants.d.ts → core/constants.d.ts} +1 -0
- package/dist/src/{constants.js → core/constants.js} +1 -0
- package/dist/src/core/constants.js.map +1 -0
- package/dist/src/core/database.d.ts +48 -0
- package/dist/src/{database.js → core/database.js} +36 -14
- package/dist/src/core/database.js.map +1 -0
- package/dist/src/core/engine.d.ts +53 -0
- package/dist/src/core/engine.js +249 -0
- 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 +21 -0
- package/dist/src/core/lifecycle/options.js +19 -0
- package/dist/src/core/lifecycle/options.js.map +1 -0
- package/dist/src/core/model.d.ts +145 -0
- package/dist/src/{model.js → core/model.js} +224 -33
- package/dist/src/core/model.js.map +1 -0
- package/dist/src/core/mutations/write.d.ts +26 -0
- package/dist/src/core/mutations/write.js +284 -0
- package/dist/src/core/mutations/write.js.map +1 -0
- package/dist/src/core/operations.d.ts +33 -0
- package/dist/src/core/operations.js +10 -0
- package/dist/src/core/operations.js.map +1 -0
- package/dist/src/core/pagination.d.ts +10 -0
- package/dist/src/core/pagination.js +13 -0
- package/dist/src/core/pagination.js.map +1 -0
- package/dist/src/core/paths.d.ts +16 -0
- package/dist/src/core/paths.js +64 -0
- package/dist/src/core/paths.js.map +1 -0
- package/dist/src/core/query/contract.d.ts +6 -0
- package/dist/src/core/query/contract.js +22 -0
- package/dist/src/core/query/contract.js.map +1 -0
- package/dist/src/core/query/execute.d.ts +25 -0
- package/dist/src/core/query/execute.js +82 -0
- package/dist/src/core/query/execute.js.map +1 -0
- package/dist/src/core/query/filter.d.ts +6 -0
- package/dist/src/core/query/filter.js +100 -0
- package/dist/src/core/query/filter.js.map +1 -0
- package/dist/src/core/query/options.d.ts +30 -0
- package/dist/src/core/query/options.js +44 -0
- package/dist/src/core/query/options.js.map +1 -0
- package/dist/src/core/records.d.ts +46 -0
- package/dist/src/core/records.js +69 -0
- 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 -2
- package/dist/src/core/relation-metadata.js.map +1 -0
- package/dist/src/core/storage.d.ts +2 -0
- package/dist/src/core/storage.js +2 -0
- package/dist/src/core/storage.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 +32 -30
- package/dist/src/files/contract.js +29 -48
- package/dist/src/files/contract.js.map +1 -1
- package/dist/src/files/disk-metadata.d.ts +9 -0
- package/dist/src/files/disk-metadata.js +44 -0
- package/dist/src/files/disk-metadata.js.map +1 -0
- package/dist/src/files/disk-paths.d.ts +15 -0
- package/dist/src/files/disk-paths.js +100 -0
- package/dist/src/files/disk-paths.js.map +1 -0
- package/dist/src/files/disk-store.d.ts +5 -1
- package/dist/src/files/disk-store.js +46 -173
- package/dist/src/files/disk-store.js.map +1 -1
- package/dist/src/files/http.d.ts +36 -0
- package/dist/src/files/http.js +53 -0
- package/dist/src/files/http.js.map +1 -0
- package/dist/src/files/index.d.ts +5 -4
- package/dist/src/files/index.js +7 -2
- 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 +29 -22
- 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 +27 -2
- package/dist/src/files/routes.js.map +1 -1
- package/dist/src/files/streams.d.ts +5 -0
- package/dist/src/files/streams.js +20 -0
- package/dist/src/files/streams.js.map +1 -0
- package/dist/src/graphql/entry.d.ts +12 -0
- package/dist/src/graphql/entry.js +13 -0
- package/dist/src/graphql/entry.js.map +1 -0
- package/dist/src/graphql/generate.d.ts +12 -0
- package/dist/src/graphql/generate.js +19 -0
- package/dist/src/graphql/generate.js.map +1 -0
- package/dist/src/graphql/preflight.d.ts +6 -0
- package/dist/src/graphql/preflight.js +44 -0
- package/dist/src/graphql/preflight.js.map +1 -0
- package/dist/src/graphql/resolvers.d.ts +17 -0
- package/dist/src/graphql/resolvers.js +44 -0
- package/dist/src/graphql/resolvers.js.map +1 -0
- package/dist/src/graphql/routes.d.ts +8 -0
- package/dist/src/graphql/routes.js +30 -0
- package/dist/src/graphql/routes.js.map +1 -0
- package/dist/src/graphql/schema.d.ts +6 -0
- package/dist/src/{graphql.js → graphql/schema.js} +59 -49
- package/dist/src/graphql/schema.js.map +1 -0
- package/dist/src/graphql/write.d.ts +4 -0
- package/dist/src/graphql/write.js +10 -0
- package/dist/src/graphql/write.js.map +1 -0
- package/dist/src/openapi/auth.d.ts +13 -0
- package/dist/src/openapi/auth.js +139 -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 +154 -103
- package/dist/src/openapi/document.js.map +1 -1
- package/dist/src/openapi/entry.d.ts +14 -0
- package/dist/src/openapi/entry.js +13 -0
- package/dist/src/openapi/entry.js.map +1 -0
- package/dist/src/openapi/files.d.ts +6 -0
- package/dist/src/{files/openapi.js → openapi/files.js} +6 -5
- package/dist/src/openapi/files.js.map +1 -0
- package/dist/src/openapi/generate.d.ts +19 -0
- package/dist/src/openapi/generate.js +41 -0
- package/dist/src/openapi/generate.js.map +1 -0
- package/dist/src/openapi/helpers.d.ts +26 -0
- package/dist/src/openapi/helpers.js +13 -0
- package/dist/src/openapi/helpers.js.map +1 -0
- package/dist/src/openapi/options.d.ts +18 -0
- package/dist/src/openapi/options.js +13 -0
- package/dist/src/openapi/options.js.map +1 -0
- package/dist/src/openapi/registry.d.ts +11 -0
- package/dist/src/openapi/registry.js +20 -0
- package/dist/src/openapi/registry.js.map +1 -0
- 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/openapi/write.d.ts +5 -0
- package/dist/src/openapi/write.js +12 -0
- package/dist/src/openapi/write.js.map +1 -0
- package/dist/src/rest/options.d.ts +22 -0
- package/dist/src/rest/options.js +65 -0
- package/dist/src/rest/options.js.map +1 -0
- package/dist/src/rest/projection.d.ts +13 -0
- package/dist/src/rest/projection.js +61 -0
- package/dist/src/rest/projection.js.map +1 -0
- package/dist/src/rest/routes.d.ts +7 -0
- package/dist/src/rest/routes.js +58 -0
- package/dist/src/rest/routes.js.map +1 -0
- package/dist/src/server/bootstrap.d.ts +7 -0
- package/dist/src/server/bootstrap.js +48 -0
- package/dist/src/server/bootstrap.js.map +1 -0
- package/dist/src/server/config.d.ts +74 -0
- package/dist/src/server/config.js +142 -0
- package/dist/src/server/config.js.map +1 -0
- package/dist/src/server/create.d.ts +16 -0
- package/dist/src/server/create.js +59 -0
- package/dist/src/server/create.js.map +1 -0
- package/dist/src/server/features.d.ts +4 -0
- package/dist/src/server/features.js +11 -0
- package/dist/src/server/features.js.map +1 -0
- package/dist/src/server/http.d.ts +12 -0
- package/dist/src/server/http.js +40 -0
- package/dist/src/server/http.js.map +1 -0
- package/dist/src/server/model.d.ts +6 -0
- package/dist/src/server/model.js +14 -0
- package/dist/src/server/model.js.map +1 -0
- package/dist/src/server/openapi-options.d.ts +6 -0
- package/dist/src/server/openapi-options.js +15 -0
- package/dist/src/server/openapi-options.js.map +1 -0
- package/dist/src/server/public.d.ts +2 -0
- package/dist/src/server/public.js +2 -0
- package/dist/src/server/public.js.map +1 -0
- package/package.json +19 -3
- package/dist/src/cli.d.ts +0 -4
- package/dist/src/cli.js +0 -67
- package/dist/src/cli.js.map +0 -1
- package/dist/src/config.d.ts +0 -68
- package/dist/src/config.js +0 -161
- package/dist/src/config.js.map +0 -1
- package/dist/src/constants.js.map +0 -1
- package/dist/src/database.d.ts +0 -21
- package/dist/src/database.js.map +0 -1
- package/dist/src/engine.d.ts +0 -47
- package/dist/src/engine.js +0 -438
- package/dist/src/engine.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 -4
- package/dist/src/graphql.js.map +0 -1
- package/dist/src/model.d.ts +0 -67
- package/dist/src/model.js.map +0 -1
- package/dist/src/openapi/index.d.ts +0 -7
- package/dist/src/openapi/index.js +0 -26
- package/dist/src/openapi/index.js.map +0 -1
- package/dist/src/query/filter.d.ts +0 -1
- package/dist/src/query/filter.js +0 -84
- package/dist/src/query/filter.js.map +0 -1
- package/dist/src/query/options.d.ts +0 -31
- package/dist/src/query/options.js +0 -135
- package/dist/src/query/options.js.map +0 -1
- package/dist/src/relation-metadata.d.ts +0 -10
- package/dist/src/relation-metadata.js.map +0 -1
- package/dist/src/server.d.ts +0 -14
- package/dist/src/server.js +0 -166
- package/dist/src/server.js.map +0 -1
- package/dist/src/utils.d.ts +0 -19
- package/dist/src/utils.js +0 -62
- 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
|
+
**Breaking changes: 1.0.0-alpha.10.** Изменились форматы конфигурации и схемы. Актуальные примеры — в разделах «Конфигурация» и «Схема моделей».
|
|
8
8
|
|
|
9
9
|
## Установка
|
|
10
10
|
|
|
@@ -12,88 +12,138 @@ JSON-сервер для имитации API: REST, GraphQL, вложенные
|
|
|
12
12
|
npm install @kollors/deep-json-server@alpha
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Для установки конкретной версии укажите `@1.0.0-alpha.11`.
|
|
16
16
|
|
|
17
17
|
## Быстрый старт
|
|
18
18
|
|
|
19
|
+
Создайте два файла в одном каталоге.
|
|
20
|
+
|
|
21
|
+
`database.json`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"users": [
|
|
26
|
+
{ "id": "1", "fullName": "Мира Волкова" }
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
19
31
|
`server.config.js`:
|
|
20
32
|
|
|
21
33
|
```js
|
|
22
|
-
export default {
|
|
23
|
-
database: { path: './database.json', schema: './schema.json' },
|
|
24
|
-
graphql: { enabled: true, path: './generated/schema.graphql' },
|
|
25
|
-
openapi: { path: './generated/openapi.yaml' },
|
|
26
|
-
server: { host: '127.0.0.1', port: 4001, pageSize: 10, maxPageSize: 100 },
|
|
27
|
-
};
|
|
34
|
+
export default { storage: 'file', database: { source: './database.json' } };
|
|
28
35
|
```
|
|
29
36
|
|
|
30
37
|
```sh
|
|
31
38
|
npx deep-json-server server.config.js
|
|
32
|
-
npx deep-json-server --openapi-only --graphql-only server.config.js
|
|
33
39
|
```
|
|
34
40
|
|
|
35
|
-
|
|
41
|
+
Список пользователей доступен по адресу `http://127.0.0.1:4001/users`.
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
Для связей и валидации добавьте [схему моделей](#схема-моделей). Далее описаны [запросы](#запросы-и-ответы), [аутентификация](#аутентификация), [мягкое удаление](#даты-записей-удаление-и-владельцы), [файлы](#файлы) и [программный API](#программный-api).
|
|
38
44
|
|
|
39
45
|
## Конфигурация
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
При `storage: 'file'` все источники и схема задаются путями, при `'memory'` — данными в памяти. Режимы нельзя смешивать. Наличие секций `auth`, `files`, `graphql` и `openapi` включает соответствующие модули. `graphql: {}` и `openapi: {}` включают только HTTP-маршруты со стандартными адресами; для экспорта добавьте `target`.
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
export default {
|
|
51
|
+
storage: 'file',
|
|
52
|
+
database: { source: './database.json', schema: './schema.json' },
|
|
53
|
+
auth: { source: './users.json', expiresIn: 3600 },
|
|
54
|
+
files: { source: './uploads' },
|
|
55
|
+
graphql: { target: './generated/schema.graphql' },
|
|
56
|
+
openapi: { target: './generated/openapi.yaml' },
|
|
57
|
+
server: { host: '127.0.0.1', port: 4001 },
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| Настройка | Назначение |
|
|
42
62
|
|---|---|
|
|
43
|
-
| `
|
|
44
|
-
| `database.
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
63
|
+
| `storage` | Обязательный режим: `file` или `memory`; общий для всех источников и схемы |
|
|
64
|
+
| `database.source` | Путь к JSON базы или объект коллекций |
|
|
65
|
+
| `database.schema` | Путь к JSON схемы или объект схемы; необязателен для REST |
|
|
66
|
+
| `auth.source` | Путь к JSON пользователей или массив пользователей |
|
|
67
|
+
| `auth.expiresIn` | Срок сессии в секундах; по умолчанию 3600 |
|
|
68
|
+
| `files.source` | Каталог файлов или массив начальных файлов |
|
|
69
|
+
| `files.metadata` | Только для `file`: путь JSON метаданных; по умолчанию `.files.json` внутри `files.source` |
|
|
70
|
+
| `graphql.endpoint` | HTTP-маршрут; по умолчанию `/graphql` |
|
|
71
|
+
| `graphql.target` | Файл для экспорта GraphQL SDL |
|
|
72
|
+
| `openapi.endpoint` | HTTP-маршрут; по умолчанию `/openapi.json` |
|
|
73
|
+
| `openapi.target` | Файл для экспорта OpenAPI |
|
|
74
|
+
| `openapi.info` | Метаданные: обязательные `title`, `version`, необязательный `description` |
|
|
50
75
|
| `server.host`, `server.port` | По умолчанию `127.0.0.1`, `4001`; CLI также читает `HOST`/`PORT` |
|
|
51
76
|
| `server.pageSize`, `server.maxPageSize` | По умолчанию 10 и 100; размер по умолчанию ограничен максимумом |
|
|
52
|
-
| `server.cors`, `server.logger` | По умолчанию `true`; logger также
|
|
77
|
+
| `server.cors`, `server.logger` | По умолчанию `true`; logger принимает также настройки Fastify |
|
|
53
78
|
| `server.maxFileSize` | По умолчанию 100 МиБ |
|
|
54
|
-
| `files.data` | Бинарные файлы в памяти |
|
|
55
|
-
| `files.directory`, `files.metadata` | Каталог файлов и JSON метаданных; необходимы оба |
|
|
56
79
|
|
|
57
|
-
|
|
80
|
+
Относительные пути разрешаются от каталога файла конфигурации; при вызове `createServer(config)` — от рабочего каталога. Данные в памяти, включая схему, копируются. Порт `0` позволяет системе выбрать свободный порт.
|
|
81
|
+
|
|
82
|
+
### CLI
|
|
58
83
|
|
|
59
|
-
| Флаг
|
|
84
|
+
| Флаг | Действие |
|
|
60
85
|
|---|---|
|
|
61
|
-
| `--
|
|
62
|
-
| `--
|
|
63
|
-
| `--
|
|
64
|
-
| `--
|
|
65
|
-
| `--
|
|
66
|
-
| `--
|
|
67
|
-
| `--help` | Показать справку |
|
|
86
|
+
| `--generate` | Экспортировать схемы и запустить сервер |
|
|
87
|
+
| `--generate-only` | Экспортировать схемы и завершить работу |
|
|
88
|
+
| `--host <host>` | Адрес сервера |
|
|
89
|
+
| `--port <port>` | Порт сервера |
|
|
90
|
+
| `--help, -h` | Справка |
|
|
91
|
+
| `--version, -v` | Версия пакета |
|
|
68
92
|
|
|
69
|
-
|
|
93
|
+
Приоритет адреса и порта: CLI → конфигурация → `HOST`/`PORT` → значения по умолчанию. Без флагов генерации запускается только сервер. `--generate` и `--generate-only` нельзя передавать вместе.
|
|
70
94
|
|
|
71
95
|
## Схема моделей
|
|
72
96
|
|
|
97
|
+
Примеры: [база данных](examples/database.json), [схема моделей](examples/schema.json), [конфигурация](examples/server.config.js).
|
|
98
|
+
|
|
73
99
|
```json
|
|
74
100
|
{
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
"api": [
|
|
102
|
+
"openapi",
|
|
103
|
+
"graphql"
|
|
104
|
+
],
|
|
105
|
+
"models": {
|
|
106
|
+
"Country": {
|
|
107
|
+
"collection": "countries",
|
|
108
|
+
"fields": {
|
|
109
|
+
"id": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"primary": true,
|
|
112
|
+
"generated": "uuid"
|
|
113
|
+
},
|
|
114
|
+
"name": {
|
|
115
|
+
"type": "string",
|
|
116
|
+
"required": true
|
|
117
|
+
},
|
|
118
|
+
"users": {
|
|
119
|
+
"type": "User[]",
|
|
120
|
+
"target": "countryId"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"User": {
|
|
125
|
+
"collection": "users",
|
|
126
|
+
"fields": {
|
|
127
|
+
"id": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"primary": true,
|
|
130
|
+
"generated": "uuid"
|
|
131
|
+
},
|
|
132
|
+
"fullName": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"required": true
|
|
135
|
+
},
|
|
136
|
+
"country": {
|
|
137
|
+
"type": "Country",
|
|
138
|
+
"source": "countryId"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
91
141
|
}
|
|
92
142
|
}
|
|
93
143
|
}
|
|
94
144
|
```
|
|
95
145
|
|
|
96
|
-
|
|
146
|
+
Модели находятся в `models`. В корне схемы можно задать `api`, `timestamps` и `softDelete`; у модели эти параметры переопределяют общие значения. Для `api` приоритет такой: модель → корень схемы → секции конфигурации. Массив заменяется целиком; `[]` исключает модель из GraphQL и OpenAPI, но REST продолжает работать. Явный список не включает отсутствующий модуль. Связанные модели должны разрешать тот же формат. Имена моделей должны быть допустимыми идентификаторами; конфликты типов и операций вызывают ошибку. Имена `and`, `or`, `not` зарезервированы фильтрами.
|
|
97
147
|
|
|
98
148
|
| Возможность | Со схемой | Без схемы |
|
|
99
149
|
|---|---|---|
|
|
@@ -102,11 +152,15 @@ npx deep-json-server --openapi-only --graphql-only server.config.js
|
|
|
102
152
|
| Экспорт OpenAPI 3.0.3 | Доступен | Ошибка при запросе экспорта |
|
|
103
153
|
| GraphQL SDL / API | Доступен | Ошибка при запросе |
|
|
104
154
|
|
|
105
|
-
При явной схеме неизвестные поля и коллекции запрещены. Исключение — хранимые ключи, выведенные из связей. Исходная база проверяется при загрузке.
|
|
155
|
+
При явной схеме неизвестные поля и коллекции запрещены. Исключение — хранимые ключи, выведенные из связей. Исходная база проверяется при загрузке. Генерация использует описание моделей.
|
|
156
|
+
|
|
157
|
+
REST без схемы создаёт ключ `id` и сохраняет произвольные JSON-поля. Для фильтров и выбора отдельных полей используются имена в формате идентификаторов; остальные поля возвращаются через `scope=[{"*":true}]`. Поля с разными типами значений можно читать, но фильтрация, сортировка и пагинация неоднородных списков требуют явной схемы.
|
|
158
|
+
|
|
159
|
+
У модели обязательны `collection` — имя коллекции в базе и REST-пути — и `fields` — описание полей. Имя модели (`User`) задаёт имена типов и операций GraphQL. `api` управляет доступностью форматов, а `timestamps` и `softDelete` переопределяют глобальные настройки для этой модели.
|
|
106
160
|
|
|
107
161
|
### Поля
|
|
108
162
|
|
|
109
|
-
|
|
163
|
+
Поле `type` принимает `string`, `number`, `boolean`, `object` или имя модели. Для массива добавьте суффикс `[]`: `string[]`, `object[]`, `Genre[]`. Вложенные поля описываются через точку, например `actors.fullName`.
|
|
110
164
|
|
|
111
165
|
| Свойства | Назначение |
|
|
112
166
|
|---|---|
|
|
@@ -114,7 +168,7 @@ npx deep-json-server --openapi-only --graphql-only server.config.js
|
|
|
114
168
|
| `description`, `example` | Документация и пример |
|
|
115
169
|
| `required`, `nullable` | По умолчанию `false`; наличие поля и разрешение `null` независимы |
|
|
116
170
|
| `default` | Значение при пропуске в create/replace; PATCH не вставляет значения по умолчанию |
|
|
117
|
-
| `enum` | Допустимые значения; у массива — значения каждого элемента |
|
|
171
|
+
| `enum` | Допустимые строки, числа или логические значения; у массива — значения каждого элемента |
|
|
118
172
|
| `primary` | Корневой первичный ключ: обязательный, уникальный, неизменяемый, без `null` |
|
|
119
173
|
| `generated` | `uuid` для строк, `increment` для чисел; значение создаёт сервер |
|
|
120
174
|
| `readOnly`, `writeOnly` | Только ответ или только входные данные; взаимно исключаются |
|
|
@@ -123,137 +177,40 @@ npx deep-json-server --openapi-only --graphql-only server.config.js
|
|
|
123
177
|
| `minimum`, `maximum` | Включительные числовые границы |
|
|
124
178
|
| `source`, `target`, `onDelete` | Описание связи |
|
|
125
179
|
|
|
126
|
-
Ограничения строк и чисел применяются к каждому элементу `string[]`/`number[]`. `required
|
|
180
|
+
Ограничения строк и чисел применяются к каждому элементу `string[]`/`number[]`. `required` и `nullable` относятся ко всему массиву; `default` и `example` содержат массив целиком. Элементы массива должны соответствовать его типу и быть отличны от `null`. `required` требует наличия поля; обычный массив при этом может быть пустым.
|
|
127
181
|
|
|
128
|
-
У модели ровно один
|
|
182
|
+
У модели ровно один первичный ключ типа `string` или `number`, объявленный на верхнем уровне. Имя произвольное: `id`, `username`, `code`. Если `generated` не задан, значение передаёт клиент при создании. Генерируемые поля объявляются на верхнем уровне, исключаются из входных типов и несовместимы с `default`. При замене записи сохраняются генерируемые значения и поля `readOnly`, включая вложенные объекты. Для защиты полей внутри массива задайте `readOnly` всему массиву или содержащему его объекту. Объекты, состоящие из серверных полей, доступны только в ответах.
|
|
129
183
|
|
|
130
|
-
Например, `LocalUser` с первичным `username` и полем `password: {"type":"string","required":true,"writeOnly":true}` получает запрос `localUser(username: ...)` и маршрут `/localUsers/{username}`.
|
|
184
|
+
Например, `LocalUser` с первичным ключом `username` и полем `password: {"type":"string","required":true,"writeOnly":true}` получает запрос `localUser(username: ...)` и маршрут `/localUsers/{username}`. Поле `writeOnly` доступно для записи и исключено из ответов, `scope`, фильтров и сортировки.
|
|
185
|
+
|
|
186
|
+
Объекты в GraphQL должны содержать хотя бы одно поле, доступное в ответе; REST допускает и пустые объекты.
|
|
131
187
|
|
|
132
188
|
### Связи
|
|
133
189
|
|
|
134
190
|
```json
|
|
135
|
-
|
|
136
|
-
"
|
|
137
|
-
|
|
138
|
-
|
|
191
|
+
{
|
|
192
|
+
"actors.genres": {
|
|
193
|
+
"type": "Genre[]",
|
|
194
|
+
"source": "actors.genreIds",
|
|
195
|
+
"required": true
|
|
196
|
+
}
|
|
139
197
|
}
|
|
140
198
|
```
|
|
141
199
|
|
|
142
|
-
`Genre` возвращает объект, `Genre[]` — список.
|
|
200
|
+
`Genre` возвращает объект, `Genre[]` — список. По умолчанию `source` указывает на первичный ключ текущей модели, `target` — целевой. Это правило действует и для вложенных связей. Пути задаются от корня соответствующей записи: в примере `actors.genreIds` содержит ключи жанров текущего актёра.
|
|
143
201
|
|
|
144
|
-
|
|
202
|
+
Ключи связей хранятся в базе и входят в собственные поля записи. Их типы выводятся из сопоставляемых ключей. Если поле `source` ссылается на первичный ключ целевой модели, его можно не объявлять отдельно: для множественной связи создаётся описание массива ключей, для одиночной — одного значения. При неоднозначном сопоставлении опишите хранимое поле явно.
|
|
145
203
|
|
|
146
204
|
Обратная связь: `User.movies = {"type":"Movie[]","target":"actors.userId"}`. Фильм возвращается один раз, даже если совпало несколько актёров. Несколько совпадений для одиночной связи — ошибка.
|
|
147
205
|
|
|
148
|
-
|
|
206
|
+
Каждый переданный ключ прямой связи должен указывать на существующую запись. `required: true` у связи требует хотя бы одну связанную запись до фильтрации и пагинации ответа. Обратная связь с первичным ключом в `source` может быть пустой, если не объявлена обязательной. Отсутствующая одиночная связь возвращает `null`.
|
|
149
207
|
|
|
150
208
|
`onDelete` срабатывает **при удалении целевой записи**:
|
|
151
209
|
|
|
152
210
|
- `restrict` — по умолчанию: запретить удаление, пока на цель ссылается сохраняемая запись.
|
|
153
|
-
- `cascade` — удалить ссылающуюся запись. Для `User.country` удаление страны удаляет пользователей. Для `Movie.actors.user` удаление пользователя удаляет соответствующие элементы actors
|
|
154
|
-
|
|
155
|
-
Сначала вычисляется весь каскад, обрабатываются циклы и проверяются ограничения, затем сохраняется результат. Ошибка отменяет всю операцию. Политики действуют и на явно объявленные обратные связи; при описании обоих направлений учитывайте оба правила.
|
|
156
|
-
|
|
157
|
-
## Запросы и ответы
|
|
158
|
-
|
|
159
|
-
Коллекции и списки объектов, включая вложенные `object[]`, возвращают:
|
|
160
|
-
|
|
161
|
-
```json
|
|
162
|
-
{ "data": [], "total": 0 }
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
Массивы примитивов остаются обычными массивами. У каждого списка объектов есть необязательные `where`, `order`, `pager`. Порядок обработки: фильтрация → сортировка → пагинация. `total` считается после фильтрации, до пагинации. Страницы начинаются с 1; отсутствие pager включает пагинацию по умолчанию. Превышение максимума, дробные и неположительные значения — ошибка. За пределами списка возвращается пустой data с правильным total.
|
|
166
|
-
|
|
167
|
-
Фильтры: `eq`, `ne`, `in`; для строк `contains`, `startsWith`, `endsWith`; сравнения `gt`, `gte`, `lt`, `lte`. Логика — `and`, `or`, `not`. У массивов есть `some`, `every`, `none`; у примитивных массивов также `contains`, `in`. Поиск строк не учитывает регистр. Условия полей записываются объектами операторов, без сокращения до скалярного значения.
|
|
168
|
-
|
|
169
|
-
```json
|
|
170
|
-
{
|
|
171
|
-
"movies": {
|
|
172
|
-
"some": {
|
|
173
|
-
"actors": {
|
|
174
|
-
"some": {
|
|
175
|
-
"genres": { "some": { "id": { "in": ["2", "3"] } } }
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
Корневой where выбирает родителей. Where внутри связи фильтрует её элементы, сохраняя родителя. Каждый вложенный список обрабатывается отдельно. Для фильтра по связи не нужно раскрывать её в ответе.
|
|
184
|
-
|
|
185
|
-
`order` — массив правил `{ "field": "fullName", "direction": "ASC" }`. Первое правило приоритетнее; полные совпадения сохраняют порядок хранения. `null` и отсутствующее значение сравниваются одинаково. REST использует путь `profile.name`, GraphQL — enum `profile_name`. Неоднозначные имена enum вызывают ошибку генерации. Сортировка родителей по связям и массивам пока не поддерживается; внутри связи сортировка доступна.
|
|
186
|
-
|
|
187
|
-
### REST
|
|
188
|
-
|
|
189
|
-
| Метод | Путь | Операция |
|
|
190
|
-
|---|---|---|
|
|
191
|
-
| GET | `/users` | `userList` |
|
|
192
|
-
| GET | `/users/{id}` | `user` |
|
|
193
|
-
| POST | `/users` | `userCreate` |
|
|
194
|
-
| PUT | `/users/{id}` | `userReplace` |
|
|
195
|
-
| PATCH | `/users/{id}` | `userUpdate` |
|
|
196
|
-
| DELETE | `/users/{id}` | `userDelete` |
|
|
197
|
-
|
|
198
|
-
Имя параметра пути соответствует первичному ключу. POST/PUT/PATCH принимают обычный объект записи. PUT заменяет запись с сохранением ключа и корневых серверных полей. PATCH поверхностно объединяет поля; переданные вложенные объекты заменяются целиком. Create/replace проверяют обязательность. Update проверяет переданные значения и итоговую запись. Отсутствующая запись — 404, конфликт — 409. DELETE возвращает удалённую запись.
|
|
199
|
-
|
|
200
|
-
Параметры `where`, `order`, `pager`, `nested` содержат JSON; `scope` — строку выбора. Пример до URL-кодирования:
|
|
201
|
-
|
|
202
|
-
```text
|
|
203
|
-
GET /users?where={"fullName":{"contains":"Мира"}}&order=[{"field":"fullName","direction":"ASC"}]&pager={"page":1,"pageSize":20}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Кодирование через `URLSearchParams`:
|
|
207
|
-
|
|
208
|
-
```js
|
|
209
|
-
const params = new URLSearchParams({
|
|
210
|
-
scope: 'id,fullName,movies(id,title)',
|
|
211
|
-
nested: JSON.stringify({ movies: { order: [{ field: 'title', direction: 'ASC' }], pager: { page: 1, pageSize: 5 } } }),
|
|
212
|
-
});
|
|
213
|
-
const response = await fetch(`/users?${params}`);
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
`scope=*,actors(user(id,fullName),genres(*))` выбирает собственные поля и явные связи. `*` включает собственные поля текущего объекта и хранимые ключи, исключает writeOnly и не раскрывает связи. Без scope выбираются собственные поля. Оболочки data/total сохраняются.
|
|
217
|
-
|
|
218
|
-
`nested` сопоставляет полные пути ответа и настройки списков:
|
|
219
|
-
|
|
220
|
-
```json
|
|
221
|
-
{
|
|
222
|
-
"actors": { "pager": { "pageSize": 5 } },
|
|
223
|
-
"actors.genres": {
|
|
224
|
-
"where": { "id": { "in": ["2", "3"] } },
|
|
225
|
-
"order": [{ "field": "name", "direction": "ASC" }]
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Путь nested должен быть выбран через scope и вести к списку объектов. Одиночные маршруты и мутации принимают scope/nested; корневые where/order/pager применяются только к GET коллекции. Неизвестные имена и небезопасные пути возвращают 400.
|
|
231
|
-
|
|
232
|
-
### GraphQL
|
|
233
|
-
|
|
234
|
-
```graphql
|
|
235
|
-
query {
|
|
236
|
-
userList(
|
|
237
|
-
order: [{ field: fullName, direction: ASC }]
|
|
238
|
-
pager: { page: 1, pageSize: 20 }
|
|
239
|
-
) {
|
|
240
|
-
total
|
|
241
|
-
data {
|
|
242
|
-
id
|
|
243
|
-
fullName
|
|
244
|
-
movies(order: [{ field: title, direction: ASC }], pager: { pageSize: 5 }) {
|
|
245
|
-
total
|
|
246
|
-
data { id title }
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
Одиночный запрос — `user(id: ...)`, без ById; отсутствующая запись даёт null. Мутации: `userCreate(data: ...)`, `userReplace(id: ..., data: ...)`, `userUpdate(id: ..., data: ...)`, `userDelete(id: ...)`. Если модель содержит только генерируемые поля, создание не имеет аргумента data. Используются те же операции хранения и проверки, что в REST. Выбор связей в результате мутации управляет только ответом.
|
|
254
|
-
|
|
255
|
-
Строковый первичный ключ представлен GraphQL ID, обычные строки — String, числа — Float, пагинация — Int. Enum сохраняет допустимые строковые имена; остальные значения получают имена VALUE_0, VALUE_1 и т. д. Длины строк и форматы проверяет общий валидатор во время выполнения: SDL не выражает все ограничения. Доступны интроспекция, обычные query и mutation; подписки и массовые мутации не добавлены.
|
|
211
|
+
- `cascade` — удалить ссылающуюся запись. Для `User.country` удаление страны удаляет пользователей. Для `Movie.actors.user` удаление пользователя удаляет соответствующие элементы `actors`, сохраняя фильм.
|
|
256
212
|
|
|
213
|
+
Каскадное удаление выполняется целиком, включая циклические связи. Ошибка проверки отменяет всю операцию. Правила `onDelete` действуют и на явно объявленные обратные связи; при описании обоих направлений учитывайте оба правила.
|
|
257
214
|
|
|
258
215
|
## Пример базы данных
|
|
259
216
|
|
|
@@ -359,15 +316,381 @@ query {
|
|
|
359
316
|
}
|
|
360
317
|
```
|
|
361
318
|
|
|
319
|
+
## Запросы и ответы
|
|
320
|
+
|
|
321
|
+
Примеры с фильмами, актёрами и жанрами используют полную [схему из examples](examples/schema.json). Для их запуска используйте [конфигурацию примера](examples/server.config.js):
|
|
322
|
+
|
|
323
|
+
```sh
|
|
324
|
+
npx deep-json-server examples/server.config.js
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Коллекции и списки объектов, включая вложенные `object[]`, возвращают:
|
|
328
|
+
|
|
329
|
+
```json
|
|
330
|
+
{ "data": [], "total": 0 }
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Массивы примитивов возвращаются обычными массивами. Каждый список объектов принимает необязательные `where`, `order` и `pager`. Порядок обработки: фильтрация → сортировка → пагинация. `total` — число записей после фильтрации, до пагинации.
|
|
334
|
+
|
|
335
|
+
В `pager` задаются `page` и `pageSize`. По умолчанию возвращается первая страница с размером из `server.pageSize`. Оба значения должны быть положительными целыми числами; `pageSize` ограничен `server.maxPageSize`. За пределами списка возвращается пустой `data` с общим числом найденных записей в `total`.
|
|
336
|
+
|
|
337
|
+
Фильтры: `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" } }`.
|
|
338
|
+
|
|
339
|
+
```json
|
|
340
|
+
{
|
|
341
|
+
"movies": {
|
|
342
|
+
"some": {
|
|
343
|
+
"actors": {
|
|
344
|
+
"some": {
|
|
345
|
+
"genres": { "some": { "id": { "in": ["2", "3"] } } }
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Корневой `where` выбирает записи основной коллекции. `where` внутри связи фильтрует её элементы, сохраняя родительскую запись. Каждый вложенный список обрабатывается отдельно. Фильтрация по связи работает независимо от её включения в ответ.
|
|
354
|
+
|
|
355
|
+
`order` — массив правил `{ "field": "fullName", "direction": "ASC" }`. `ASC` сортирует по возрастанию, `DESC` — по убыванию. Первое правило приоритетнее; при равных значениях сохраняется порядок хранения. `null` и отсутствующее значение сравниваются одинаково. REST использует путь `profile.name`, GraphQL — enum `profile_name`. Неоднозначные имена enum вызывают ошибку генерации. Сортировка доступна по скалярным полям текущего объекта, включая вложенные поля. Для связанных списков задаётся собственный `order`.
|
|
356
|
+
|
|
357
|
+
### REST
|
|
358
|
+
|
|
359
|
+
`GET /` возвращает имена коллекций: `{ "resources": ["users", "movies"] }`. Для каждой коллекции доступны следующие маршруты:
|
|
360
|
+
|
|
361
|
+
| Метод | Путь | Операция |
|
|
362
|
+
|---|---|---|
|
|
363
|
+
| GET | `/users` | `userList` |
|
|
364
|
+
| GET | `/users/{id}` | `user` |
|
|
365
|
+
| POST | `/users` | `userCreate` |
|
|
366
|
+
| PUT | `/users/{id}` | `userReplace` |
|
|
367
|
+
| PATCH | `/users/{id}` | `userUpdate` |
|
|
368
|
+
| DELETE | `/users/{id}` | `userDelete` |
|
|
369
|
+
|
|
370
|
+
Имя параметра пути соответствует первичному ключу. POST, PUT и PATCH принимают JSON-объект записи. PUT заменяет запись с сохранением ключа и серверных полей. PATCH объединяет поля на верхнем уровне; переданные вложенные объекты заменяются с сохранением их полей `readOnly`. Создание и замена требуют всех обязательных полей. При обновлении проверяются переданные значения и итоговая запись. Отсутствующая запись — `404`, конфликт — `409`.
|
|
371
|
+
|
|
372
|
+
POST возвращает созданную запись со статусом `201`; PUT, PATCH и DELETE — результат со статусом `200`. Ошибки REST имеют вид `{ "error": "Описание ошибки" }`.
|
|
373
|
+
|
|
374
|
+
### Параметры REST-запросов
|
|
375
|
+
|
|
376
|
+
Для запросов к записям REST принимает один параметр URL `scope` с JSON-массивом `[поля, аргументы?]`. Первый объект выбирает поля, второй задаёт `where`, `order` и `pager` для списка. Этот формат одинаков для корневого запроса, вложенных объектов и связей.
|
|
377
|
+
|
|
378
|
+
Пример выбора пользователей и их фильмов с отдельной сортировкой и пагинацией:
|
|
379
|
+
|
|
380
|
+
```js
|
|
381
|
+
const scope = [
|
|
382
|
+
{
|
|
383
|
+
id: true,
|
|
384
|
+
fullName: true,
|
|
385
|
+
movies: [
|
|
386
|
+
{ id: true, title: true },
|
|
387
|
+
{ order: [{ field: 'title', direction: 'ASC' }], pager: { page: 1, pageSize: 5 } },
|
|
388
|
+
],
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
where: { fullName: { contains: 'Мира' } },
|
|
392
|
+
order: [{ field: 'fullName', direction: 'ASC' }],
|
|
393
|
+
pager: { page: 1, pageSize: 20 },
|
|
394
|
+
},
|
|
395
|
+
];
|
|
396
|
+
const params = new URLSearchParams({ scope: JSON.stringify(scope) });
|
|
397
|
+
const response = await fetch(`/users?${params}`);
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Обычные поля выбираются через `true`, объекты и связи — через свой массив `scope`. Если аргументы не нужны, в массиве остаётся только объект полей. `"*": true` включает собственные поля и хранимые ключи, кроме `writeOnly`; связи выбираются явно.
|
|
401
|
+
|
|
402
|
+
Например, собственные поля фильма, пользователи актёров и отсортированные жанры:
|
|
403
|
+
|
|
404
|
+
```json
|
|
405
|
+
[
|
|
406
|
+
{
|
|
407
|
+
"*": true,
|
|
408
|
+
"actors": [
|
|
409
|
+
{
|
|
410
|
+
"user": [{ "id": true, "fullName": true }],
|
|
411
|
+
"genres": [
|
|
412
|
+
{ "*": true },
|
|
413
|
+
{ "order": [{ "field": "name", "direction": "ASC" }] }
|
|
414
|
+
]
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
}
|
|
418
|
+
]
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Без `scope` возвращаются собственные поля, как при `[{"*":true}]`. Пустой выбор `[{}]` возвращает объект без полей. Списки сохраняют структуру `{ data, total }`.
|
|
422
|
+
|
|
423
|
+
Аргументы доступны только у списков. В запросе отдельной записи и в ответе мутации их можно задать для вложенных списков. Параметры проверяются даже на пустых данных; ошибка в выборе ответа отменяет изменения записи. Некорректный `scope` возвращает `400`. Максимальная длина JSON — 10 000 символов, глубина выбора — 32 уровня.
|
|
424
|
+
|
|
425
|
+
### Вложенная запись
|
|
426
|
+
|
|
427
|
+
Поля ключей, например `genreIds: ["1"]`, только задают связь. В поля связей можно передавать записи для создания или обновления:
|
|
428
|
+
|
|
429
|
+
```http
|
|
430
|
+
PATCH /movies/1
|
|
431
|
+
Content-Type: application/json
|
|
432
|
+
|
|
433
|
+
{
|
|
434
|
+
"actors": [
|
|
435
|
+
{
|
|
436
|
+
"userId": "1",
|
|
437
|
+
"genres": [
|
|
438
|
+
"1",
|
|
439
|
+
{ "id": "2", "name": "Обновлённый жанр" },
|
|
440
|
+
{ "name": "Новый жанр" }
|
|
441
|
+
]
|
|
442
|
+
}
|
|
443
|
+
]
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
| Значение в связи | Действие |
|
|
448
|
+
|---|---|
|
|
449
|
+
| Ключ, например `"1"` | Связать существующую запись без её изменения |
|
|
450
|
+
| Объект с первичным ключом | PATCH обновляет переданные поля, PUT заменяет связанную запись |
|
|
451
|
+
| Объект без первичного ключа | Создать запись со значениями по умолчанию и сгенерированным ключом |
|
|
452
|
+
|
|
453
|
+
Имя и тип ключа берутся из целевой модели. Объект только с ключом тоже считается обновлением: в PUT он должен содержать обязательные поля модели. При замене сохраняются первичный ключ, генерируемые значения и поля `readOnly`. В POST вложенные объекты с существующими ключами обновляются частично. Если запись по ключу не найдена, операция завершится ошибкой. Для создания вложенной записи без ключа нужна его автоматическая генерация.
|
|
454
|
+
|
|
455
|
+
Переданный список заменяет состав связи. PATCH сохраняет пропущенные связи, PUT очищает пропущенные связи, ключи которых доступны для записи. `[]` очищает список, `null` — одиночную связь с разрешённым `nullable`. Разрыв связи не удаляет связанную запись. Обязательные связи должны оставаться заполненными.
|
|
456
|
+
|
|
457
|
+
В одном объекте указывайте либо связь, либо её хранимый ключ: например, `genres` или `genreIds`. Для обратной связи сервер меняет целевой ключ. Если путь проходит через массив и нельзя однозначно выбрать элемент для связи, передайте массив с нужными ключами явно. Защищённые ключи изменять нельзя.
|
|
458
|
+
|
|
459
|
+
Все вложенные изменения входят в транзакцию основной записи. Ошибка проверки, отсутствующая запись или неверный выбор полей ответа отменяет всю операцию. Изменения общей записи видны всем, кто с ней связан.
|
|
460
|
+
|
|
461
|
+
GraphQL принимает в полях связей типизированные объекты. Чтобы при замене изменить только связи, используйте поля ключей, например `genreIds`. Пример:
|
|
462
|
+
|
|
463
|
+
```graphql
|
|
464
|
+
mutation {
|
|
465
|
+
movieUpdate(id: "1", data: {
|
|
466
|
+
actors: [{
|
|
467
|
+
userId: "1"
|
|
468
|
+
genres: [{ id: "2", name: "Обновлённый жанр" }, { name: "Новый жанр" }]
|
|
469
|
+
}]
|
|
470
|
+
}) {
|
|
471
|
+
actors { data { genres { data { id name } } } }
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### GraphQL
|
|
477
|
+
|
|
478
|
+
Укажите `database.schema` и добавьте секцию `graphql: {}` в конфигурацию:
|
|
479
|
+
|
|
480
|
+
```sh
|
|
481
|
+
npx deep-json-server server.config.js
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Отправляйте запросы на `/graphql` методом POST с `Content-Type: application/json` и телом `{ "query": "…", "variables": {} }`. Путь можно изменить через `graphql.endpoint`.
|
|
485
|
+
|
|
486
|
+
```graphql
|
|
487
|
+
query {
|
|
488
|
+
userList(
|
|
489
|
+
where: { fullName: { contains: "Мира" } }
|
|
490
|
+
order: [{ field: fullName, direction: ASC }]
|
|
491
|
+
pager: { page: 1, pageSize: 20 }
|
|
492
|
+
) {
|
|
493
|
+
total
|
|
494
|
+
data {
|
|
495
|
+
id
|
|
496
|
+
fullName
|
|
497
|
+
movies(
|
|
498
|
+
where: { title: { contains: "Тени" } }
|
|
499
|
+
order: [{ field: title, direction: ASC }]
|
|
500
|
+
pager: { pageSize: 5 }
|
|
501
|
+
) {
|
|
502
|
+
total
|
|
503
|
+
data { id title }
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Запрос `user(id: ...)` возвращает одну запись или `null`, если она отсутствует. Мутации: `userCreate(data: ...)`, `userReplace(id: ..., data: ...)`, `userUpdate(id: ..., data: ...)`, `userDelete(id: ...)`. Для модели, состоящей из генерируемых полей, мутация создания вызывается без аргумента `data`. Правила записи и валидации совпадают с REST. Связи, выбранные в результате мутации, определяют содержимое ответа.
|
|
511
|
+
|
|
512
|
+
Строковый первичный ключ представлен типом GraphQL `ID`, обычные строки — `String`, числа — `Float`, параметры пагинации — `Int`. Enum сохраняет допустимые строковые имена; остальные значения получают имена `VALUE_0`, `VALUE_1` и т. д. Длины строк, форматы и другие ограничения модели проверяются сервером при выполнении запроса. Для просмотра схемы доступна интроспекция. Параметры выбранных списков проверяются до выполнения мутаций.
|
|
513
|
+
|
|
514
|
+
Ошибки содержат `extensions.code`: `INVALID_INPUT`, `INVALID_QUERY`, `NOT_FOUND`, `CONFLICT`, `UNAUTHENTICATED`, `FORBIDDEN` или `INTERNAL_ERROR`. Ошибки синтаксиса и типов GraphQL возвращаются в стандартном массиве `errors`. Максимальная глубина запроса — 32 уровня.
|
|
515
|
+
|
|
516
|
+
## OpenAPI и экспорт схем
|
|
517
|
+
|
|
518
|
+
Экспорт использует OpenAPI 3.0.3. Добавьте `openapi: {}` и `database.schema`, чтобы получать спецификацию по HTTP на `/openapi.json`. Путь меняется через `openapi.endpoint`. Спецификацию можно открыть в Swagger UI или импортировать в API-клиент.
|
|
519
|
+
|
|
520
|
+
Для сохранения схем задайте пути экспорта:
|
|
521
|
+
|
|
522
|
+
```js
|
|
523
|
+
export default {
|
|
524
|
+
storage: 'file',
|
|
525
|
+
database: { source: './database.json', schema: './schema.json' },
|
|
526
|
+
openapi: { target: './generated/openapi.yaml' },
|
|
527
|
+
graphql: { target: './generated/schema.graphql' },
|
|
528
|
+
};
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
npx deep-json-server server.config.js --generate-only
|
|
533
|
+
npx deep-json-server server.config.js --generate
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
`--generate-only` экспортирует и завершает работу, `--generate` после экспорта запускает сервер. Форматы определяются наличием секций. Для каждого выбранного формата обязателен свой `target`. Если секций нет, отсутствует `target` или генерация завершилась ошибкой, команда возвращает ошибку и сервер не запускается.
|
|
537
|
+
|
|
538
|
+
Экспорт не открывает базу, учётные записи и файлы. Перед записью проверяются все выбранные `target`; они не могут совпадать с конфигурацией, базой, схемой, пользователями, счётчиками или метаданными файлов.
|
|
539
|
+
|
|
540
|
+
## Аутентификация
|
|
541
|
+
|
|
542
|
+
Секция `auth` включает регистрацию, вход и проверку прав на изменение записей в REST и GraphQL. Чтение и все операции с файлами остаются открытыми.
|
|
543
|
+
|
|
544
|
+
Первого администратора задайте в исходных данных. Например, создайте `auth.json` с помощью `setup-auth.mjs`:
|
|
545
|
+
|
|
546
|
+
```js
|
|
547
|
+
import { writeFile } from 'node:fs/promises';
|
|
548
|
+
import { hashPassword } from '@kollors/deep-json-server/auth';
|
|
549
|
+
|
|
550
|
+
const password = process.env.DJS_PASSWORD;
|
|
551
|
+
if (!password) throw new Error('Set DJS_PASSWORD');
|
|
552
|
+
await writeFile(
|
|
553
|
+
'./auth.json',
|
|
554
|
+
JSON.stringify(
|
|
555
|
+
[{ id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true }],
|
|
556
|
+
null,
|
|
557
|
+
2,
|
|
558
|
+
),
|
|
559
|
+
{ flag: 'wx', mode: 0o600 },
|
|
560
|
+
);
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Задайте `DJS_PASSWORD` и выполните `node setup-auth.mjs`. Добавьте файл в конфигурацию сервера:
|
|
564
|
+
|
|
565
|
+
```js
|
|
566
|
+
export default {
|
|
567
|
+
storage: 'file',
|
|
568
|
+
database: { source: './database.json' },
|
|
569
|
+
auth: { source: './auth.json', expiresIn: 3600 },
|
|
570
|
+
};
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Запустите `npx deep-json-server server.config.js`. Каждой исходной учётной записи нужны уникальный строковый `id`, уникальный `username` и `passwordHash`, созданный функцией выше. `isAdmin` по умолчанию равен `false`. Пароли хешируются через scrypt со случайной солью.
|
|
574
|
+
|
|
575
|
+
При `storage: 'memory'` передайте массив в `auth.source`:
|
|
576
|
+
|
|
577
|
+
```js
|
|
578
|
+
import { hashPassword } from '@kollors/deep-json-server/auth';
|
|
579
|
+
|
|
580
|
+
const password = process.env.DJS_PASSWORD;
|
|
581
|
+
if (!password) throw new Error('Set DJS_PASSWORD');
|
|
582
|
+
|
|
583
|
+
export default {
|
|
584
|
+
storage: 'memory',
|
|
585
|
+
database: { source: { items: [] } },
|
|
586
|
+
auth: {
|
|
587
|
+
source: [
|
|
588
|
+
{ id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true },
|
|
589
|
+
],
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
Учётные записи auth хранятся отдельно от базы. В файловом режиме изменения сохраняются в `auth.source`; в режиме памяти они исчезают после перезапуска. Переданный массив пользователей не изменяется. После ручного изменения файла перезапустите сервер.
|
|
595
|
+
|
|
596
|
+
| REST-запрос | JSON тела | Ответ |
|
|
597
|
+
|---|---|---|
|
|
598
|
+
| `POST /auth/register` | `{ "username": "anna", "password": "…" }` | `201`: `{ id, username, isAdmin: false }` |
|
|
599
|
+
| `POST /auth/login` | `{ "username": "anna", "password": "…" }` | `{ accessToken, expiresIn, user: { id, username, isAdmin } }` |
|
|
600
|
+
| `GET /auth/me` | — | `{ id, username, isAdmin }` |
|
|
601
|
+
| `POST /auth/logout` | — | `{ success: true }` |
|
|
602
|
+
| `PATCH /auth/users/:id/password` | `{ "currentPassword": "…", "newPassword": "…" }` | `{ success: true }` |
|
|
603
|
+
| `PATCH /auth/users/:id/admin` | `{ "isAdmin": true }` | `{ id, username, isAdmin }` |
|
|
604
|
+
|
|
605
|
+
Регистрация и вход доступны без токена. Для остальных методов передавайте `Authorization: Bearer <accessToken>`. Регистрация создаёт обычного пользователя с новым `id`; поля `id`, `passwordHash` и `isAdmin` в запросе запрещены. `username` уникален с учётом регистра, повтор возвращает `409`. Имя должно содержать хотя бы один непробельный символ и не более 256 символов, пароль — от 1 до 1024 символов. Значения не обрезаются. Регистрация не открывает сессию: после неё выполните вход.
|
|
606
|
+
|
|
607
|
+
Пользователь меняет только свой пароль, передавая `currentPassword` и `newPassword`. Для администратора при смене своего пароля действует то же правило. Администратор может сменить пароль обычному пользователю, передав только `newPassword`. Изменение пароля другого администратора возвращает `403`. После успешной смены завершаются все сессии этого пользователя, включая текущую при смене своего пароля; требуется новый вход. Неверный текущий пароль возвращает `401` без изменения сессий.
|
|
608
|
+
|
|
609
|
+
Статус `isAdmin` меняет только администратор. Можно назначать других администраторов и снимать с них статус. Снять статус с себя можно, только если остаётся другой администратор; иначе возвращается `409`. Проверка учитывает параллельные запросы. Новые права действуют с прежними токенами сразу после сохранения, в том числе для мутаций GraphQL. Администратор может снять статус с другого администратора, а затем сменить ему пароль как обычному пользователю.
|
|
610
|
+
|
|
611
|
+
Недействительный или истёкший токен возвращает `401`, недостаток прав — `403`, отсутствующий пользователь при разрешённой операции — `404`. Ошибки тела запроса возвращают `400`. Вход, регистрация и смена пароля могут вернуть `429` при превышении числа одновременных вычислений паролей; вход также ограничивает число активных сессий. Сессии хранятся в памяти и исчезают после перезапуска. Выход завершает только сессию переданного токена.
|
|
612
|
+
|
|
613
|
+
OpenAPI описывает все маршруты auth и требования Bearer-токена. В Swagger UI токен из ответа на вход можно вставить в **Authorize**. Для экспорта схем включите auth в конфигурации и выполните `npx deep-json-server server.config.js --generate-only`; файл учётных записей при генерации не читается. Методы auth доступны через REST. В GraphQL тот же токен проверяется при изменении записей. Для GraphQL и OpenAPI нужна `database.schema`.
|
|
614
|
+
|
|
615
|
+
## Даты записей, удаление и владельцы
|
|
616
|
+
|
|
617
|
+
Общие настройки задаются в корне схемы. В этом примере модель `Note` наследует мягкое удаление и отключает даты:
|
|
618
|
+
|
|
619
|
+
```json
|
|
620
|
+
{
|
|
621
|
+
"timestamps": true,
|
|
622
|
+
"softDelete": true,
|
|
623
|
+
"models": {
|
|
624
|
+
"Note": {
|
|
625
|
+
"collection": "notes",
|
|
626
|
+
"timestamps": false,
|
|
627
|
+
"fields": {
|
|
628
|
+
"id": {
|
|
629
|
+
"type": "string",
|
|
630
|
+
"primary": true,
|
|
631
|
+
"generated": "uuid"
|
|
632
|
+
},
|
|
633
|
+
"text": {
|
|
634
|
+
"type": "string",
|
|
635
|
+
"required": true
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
Приоритет: модель → корень схемы → `false`. Явное `false` отключает унаследованную настройку. Без схемы даты и мягкое удаление выключены.
|
|
644
|
+
|
|
645
|
+
| Поле | Когда включено | Значение |
|
|
646
|
+
|---|---|---|
|
|
647
|
+
| `createdAt`, `updatedAt` | `timestamps` | Даты создания и последнего изменения |
|
|
648
|
+
| `deletedAt` | `softDelete` | Дата удаления или `null` для активной записи |
|
|
649
|
+
| `createdById`, `updatedById` | `auth` | Создатель/владелец и последний редактор |
|
|
650
|
+
| `deletedById` | `auth` + `softDelete` | Пользователь, удаливший запись, или `null` |
|
|
651
|
+
|
|
652
|
+
Даты — строки ISO 8601 в UTC. При создании даты создания и изменения совпадают, а при включённом auth создателем и редактором становится текущий пользователь. PUT/PATCH сохраняют создателя и дату создания. DELETE обновляет поля удаления и включённые поля последнего изменения. Для старых записей с неизвестными датами или авторами возвращается `null`. Эти поля доступны только для чтения в REST и GraphQL; обычные вложенные объекты собственных полей авторства и дат не получают. При отключении функции уже сохранённые значения её полей сохраняются.
|
|
653
|
+
|
|
654
|
+
При мягком удалении DELETE оставляет запись в базе. Повторный DELETE уже удалённой записи сохраняет прежние сведения об удалении. Получение по первичному ключу возвращает и удалённые записи. Успешный PUT/PATCH восстанавливает запись, обнуляя `deletedAt` и `deletedById`. Пустой PATCH восстанавливает без замены остальных полей; для PUT нужно передать все обязательные поля.
|
|
655
|
+
|
|
656
|
+
Списки по умолчанию возвращают активные записи. Для получения удалённых задайте условие по `deletedAt`:
|
|
657
|
+
|
|
658
|
+
```json
|
|
659
|
+
[
|
|
660
|
+
{ "id": true, "deletedAt": true },
|
|
661
|
+
{ "where": { "deletedAt": { "ne": null } } }
|
|
662
|
+
]
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
`eq: null` выбирает активные записи, `ne: null` — удалённые, объединение обоих условий через `or` — все записи. Те же фильтры работают в GraphQL. Явное условие по `deletedAt`, в том числе внутри `and`, `or` или `not`, заменяет автоматическую фильтрацию на этом уровне. Фильтры связей и списки связей применяют правило независимо. Одиночные связи скрывают удалённые цели; запрос по первичному ключу позволяет получить их напрямую.
|
|
666
|
+
|
|
667
|
+
Каскадное удаление учитывает `onDelete` и настройку `softDelete` каждой затронутой модели. Восстановление записи, начавшей каскад, возвращает записи, удалённые той же операцией, не затрагивая удалённые ранее. Физически удалённые записи восстановить нельзя. Если каскад удалил вложенный объект, восстановление возможно, пока соответствующее поле объекта не менялось после удаления. Конфликт или отсутствие обязательной связи отменяет всю операцию.
|
|
668
|
+
|
|
669
|
+
Служебные сведения восстановления хранятся вместе с записями и переживают перезапуск; сохраняйте их при резервном копировании базы. Внутреннее поле `djsDeletion` зарезервировано и не выдаётся через API.
|
|
670
|
+
|
|
671
|
+
При включённом auth создавать записи может любой вошедший пользователь. Изменять, удалять и восстанавливать — владелец по `createdById` или пользователь с `isAdmin: true`. Записи без владельца изменяет только администратор. Его правки не меняют владельца. Правила действуют на вложенные записи, изменение ключей связей, каскады и восстановление. Ссылка на существующую запись без её изменения не требует владения ею. Каждая мутация атомарна: отказ сохраняет прежнее состояние всех затронутых записей.
|
|
672
|
+
|
|
673
|
+
REST возвращает 401 при отсутствии действительного токена и 403 при недостатке прав. GraphQL проверяет мутации по тем же правилам и возвращает коды `UNAUTHENTICATED` или `FORBIDDEN`; токен получают через REST-вход и передают в `Authorization: Bearer <токен>`. GraphQL-запросы чтения, OPTIONS и все операции с файлами остаются открытыми.
|
|
674
|
+
|
|
362
675
|
## Файлы
|
|
363
676
|
|
|
364
|
-
|
|
677
|
+
Файлы доступны через REST и описываются в OpenAPI. Добавьте хранилище в конфигурацию:
|
|
678
|
+
|
|
679
|
+
```js
|
|
680
|
+
export default {
|
|
681
|
+
storage: 'file',
|
|
682
|
+
database: { source: './database.json' },
|
|
683
|
+
files: { source: './uploads' },
|
|
684
|
+
};
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
Запустите сервер:
|
|
365
688
|
|
|
366
689
|
```bash
|
|
367
|
-
deep-json-server
|
|
690
|
+
npx deep-json-server server.config.js
|
|
368
691
|
```
|
|
369
692
|
|
|
370
|
-
Для временных тестов
|
|
693
|
+
Для временных тестов выберите `storage: 'memory'` и передайте массив в `files.source`. Каждая начальная запись содержит `name`, `mimeType`, бинарное `content` в виде `Uint8Array` и необязательный `directory`. Загруженные файлы в таком режиме остаются в памяти до завершения процесса.
|
|
371
694
|
|
|
372
695
|
Один файл отправляется непосредственно в теле запроса. `Content-Name` содержит URI-кодированное имя файла, `Content-Type` — его MIME-тип, а необязательный `Content-Directory` — URI-кодированный относительный путь к директории:
|
|
373
696
|
|
|
@@ -418,39 +741,61 @@ Content-Type: application/json
|
|
|
418
741
|
}
|
|
419
742
|
```
|
|
420
743
|
|
|
421
|
-
`PATCH` возвращает обновлённые метаданные со статусом `200`; если по новому пути уже существует файл, сервер возвращает `409`. `DELETE` отвечает статусом `204` без тела. Если файл не найден, любая операция по пути возвращает `404`. Пути в URL задаются относительно `files.
|
|
744
|
+
`PATCH` возвращает обновлённые метаданные со статусом `200`; если по новому пути уже существует файл, сервер возвращает `409`. `DELETE` отвечает статусом `204` без тела. Если файл не найден, любая операция по пути возвращает `404`. Пути в URL задаются относительно `files.source`, а все возвращаемые URL — относительно адреса сервера.
|
|
745
|
+
|
|
746
|
+
При хранении на диске бинарный файл находится по пути `<files.source>/<directory>/<name>`. Метаданные по умолчанию хранятся в `<files.source>/.files.json`; другой путь можно задать через `files.metadata`. Директории и файл метаданных создаются по мере необходимости.
|
|
422
747
|
|
|
423
|
-
|
|
748
|
+
Используйте один процесс сервера для дисковой базы и файлового хранилища. Перед ручным изменением файлов или метаданных остановите его. Пути в хранилище не могут содержать символические ссылки. Загрузка и переименование не могут перезаписать базу, счётчики, схему, учётные записи auth, загруженную конфигурацию или файл метаданных.
|
|
424
749
|
|
|
425
|
-
|
|
750
|
+
Отправляйте файл как бинарное тело запроса. В браузере для этого можно использовать `xhr.send(file)`, а прогресс отслеживать через `XMLHttpRequest.upload.onprogress`. Максимальный размер по умолчанию равен 100 МиБ и настраивается через `server.maxFileSize`. Отсутствующие или небезопасные заголовки и пути возвращают `400`, превышение лимита — `413`, а отсутствующий, некорректный или не поддерживаемый Fastify `Content-Type` — `400` либо `415` в зависимости от этапа проверки.
|
|
426
751
|
|
|
427
752
|
## Программный API
|
|
428
753
|
|
|
429
754
|
```js
|
|
430
|
-
import { createServer } from '@kollors/deep-json-server';
|
|
755
|
+
import { createServer } from '@kollors/deep-json-server/server';
|
|
431
756
|
import config from './server.config.js';
|
|
432
757
|
|
|
433
758
|
const facade = await createServer(config);
|
|
434
|
-
const openapi = await facade.openapi();
|
|
435
|
-
const sdl = await facade.graphql();
|
|
436
759
|
const server = facade.fastify();
|
|
437
760
|
await server.listen();
|
|
438
761
|
// await server.close();
|
|
439
762
|
```
|
|
440
763
|
|
|
441
|
-
|
|
764
|
+
Методы `openapi()` и `graphql()` возвращают схемы и требуют `database.schema`. `fastify()` возвращает экземпляр сервера для настройки и запуска. База и включённые сервисы инициализируются при `ready()`, `listen()` или первом `inject()`; ошибка инициализации останавливает запуск.
|
|
442
765
|
|
|
443
|
-
|
|
766
|
+
`createServer(config)` принимает один аргумент. Наличие секций управляет модулями так же, как при запуске через CLI. Для методов `openapi()` и `graphql()` нужна соответствующая секция. Методы возвращают схему и не записывают файлы.
|
|
444
767
|
|
|
445
|
-
|
|
768
|
+
Эти функции доступны и через общий импорт `@kollors/deep-json-server`. Адаптеры сервера загружаются при включении. Генераторы можно использовать отдельно:
|
|
446
769
|
|
|
447
|
-
|
|
770
|
+
```js
|
|
771
|
+
import { generateOpenapi, writeOpenapi } from '@kollors/deep-json-server/openapi';
|
|
772
|
+
import { generateGraphql, writeGraphql } from '@kollors/deep-json-server/graphql';
|
|
773
|
+
|
|
774
|
+
const document = await generateOpenapi('./schema.json', { files: true });
|
|
775
|
+
const sdl = await generateGraphql('./schema.json');
|
|
776
|
+
await writeOpenapi(document, './generated/openapi.yaml');
|
|
777
|
+
await writeGraphql(sdl, './generated/schema.graphql');
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
Отдельные генераторы принимают путь или объект схемы и не требуют конфигурации сервера. Выбранная функция задаёт формат по умолчанию; `api` в схеме и моделях может его ограничить. `timestamps` и `softDelete` берутся из схемы. Опция `{ auth: true }` добавляет поля владельца; OpenAPI также описывает маршруты auth и требования токена. `hashPassword()` доступна и через общий импорт пакета.
|
|
781
|
+
|
|
782
|
+
`generateOpenapi()` также принимает `host`, `port`, `pageSize`, `maxPageSize` и `info`. Вместо пути можно передать объект схемы. Сервер и генераторы работают с собственной копией модели. Размеры страниц должны быть положительными целыми числами; `pageSize` не может превышать `maxPageSize`.
|
|
783
|
+
|
|
784
|
+
## Хранение данных
|
|
785
|
+
|
|
786
|
+
Изменения выполняются последовательно внутри экземпляра сервера и проверяются на копии данных до сохранения. Для одного файла базы используйте один процесс сервера. Счётчики `increment` хранятся рядом с базой в `<путь базы>.counters.json`; сохраняйте этот файл вместе с базой. Номера резервируются до записи данных: сбой может оставить пропуск, но не приводит к повторной выдаче номера.
|
|
787
|
+
|
|
788
|
+
## Разработка
|
|
789
|
+
|
|
790
|
+
Исходники разделены на `rest`, `graphql`, `openapi`, `auth`, `files`, `cli` и `server`. Общая модель, хранение, запросы и правила изменения записей находятся в `core`. Модуль `server` подключает остальные; генераторы схем загружаются независимо от HTTP-сервера.
|
|
448
791
|
|
|
449
792
|
```sh
|
|
450
793
|
npm ci
|
|
451
794
|
npm run verify
|
|
452
795
|
```
|
|
453
796
|
|
|
454
|
-
|
|
797
|
+
Команда проверяет типы, стиль кода, покрытие тестами и установку пакета из архива.
|
|
798
|
+
|
|
799
|
+
Для публикации новой альфы обновите версию в `package.json`, `package-lock.json` и `src/core/constants.ts`, затем отправьте изменения в `main`. GitHub Actions создаст тег версии и опубликует пакет в канал npm `alpha` через trusted publishing. Уже опубликованная версия пропускается. Если тег создан, а публикация не завершилась, повторный запуск использует этот тег и проверяет соответствие ему файлов пакета. Отправка тега версии также запускает публикацию; стабильные версии публикуются в `latest`.
|
|
455
800
|
|
|
456
801
|
Лицензия: MIT.
|