@kollors/deep-json-server 1.0.0-rc.6 → 1.0.0-rc.8
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/CHANGELOG.md +20 -0
- package/MIGRATION.md +187 -25
- package/MIGRATION.ru.md +211 -0
- package/README.md +48 -33
- package/README.ru.md +50 -35
- package/dist/index.d.ts +1 -1
- package/dist/package.json +104 -0
- package/dist/src/auth/contract.d.ts +8 -0
- package/dist/src/auth/contract.js.map +1 -1
- package/dist/src/auth/public.d.ts +1 -1
- package/dist/src/auth/service.d.ts +7 -16
- package/dist/src/auth/service.js +40 -48
- package/dist/src/auth/service.js.map +1 -1
- package/dist/src/auth/store.d.ts +11 -5
- package/dist/src/auth/store.js +37 -19
- package/dist/src/auth/store.js.map +1 -1
- package/dist/src/cli/index.js +2 -2
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/core/constants.d.ts +1 -1
- package/dist/src/core/constants.js +2 -1
- package/dist/src/core/constants.js.map +1 -1
- package/dist/src/core/engine.d.ts +2 -1
- package/dist/src/core/engine.js +7 -3
- package/dist/src/core/engine.js.map +1 -1
- package/dist/src/core/model/infer.js +8 -5
- package/dist/src/core/model/infer.js.map +1 -1
- package/dist/src/core/model/load.js +1 -1
- package/dist/src/core/model/load.js.map +1 -1
- package/dist/src/core/mutations/write.d.ts +3 -3
- package/dist/src/core/mutations/write.js +10 -5
- package/dist/src/core/mutations/write.js.map +1 -1
- package/dist/src/core/paths.d.ts +4 -0
- package/dist/src/core/paths.js +7 -0
- package/dist/src/core/paths.js.map +1 -1
- package/dist/src/core/query/budget.d.ts +24 -0
- package/dist/src/core/query/budget.js +69 -0
- package/dist/src/core/query/budget.js.map +1 -0
- package/dist/src/core/query/execute.d.ts +2 -1
- package/dist/src/core/query/execute.js +5 -2
- package/dist/src/core/query/execute.js.map +1 -1
- package/dist/src/core/query/filter.d.ts +2 -1
- package/dist/src/core/query/filter.js +27 -9
- package/dist/src/core/query/filter.js.map +1 -1
- package/dist/src/core/records.d.ts +2 -0
- package/dist/src/core/records.js +15 -4
- package/dist/src/core/records.js.map +1 -1
- package/dist/src/files/contract.js +1 -0
- package/dist/src/files/contract.js.map +1 -1
- package/dist/src/files/disk-store.js +7 -5
- package/dist/src/files/disk-store.js.map +1 -1
- package/dist/src/graphql/preflight.d.ts +11 -2
- package/dist/src/graphql/preflight.js +48 -2
- package/dist/src/graphql/preflight.js.map +1 -1
- package/dist/src/graphql/resolvers.d.ts +3 -0
- package/dist/src/graphql/resolvers.js +47 -8
- package/dist/src/graphql/resolvers.js.map +1 -1
- package/dist/src/graphql/routes.d.ts +1 -1
- package/dist/src/graphql/routes.js +16 -0
- package/dist/src/graphql/routes.js.map +1 -1
- package/dist/src/graphql/schema.js +6 -4
- package/dist/src/graphql/schema.js.map +1 -1
- package/dist/src/openapi/document.js +8 -2
- package/dist/src/openapi/document.js.map +1 -1
- package/dist/src/rest/options.d.ts +2 -1
- package/dist/src/rest/options.js +5 -3
- package/dist/src/rest/options.js.map +1 -1
- package/dist/src/rest/projection.d.ts +4 -3
- package/dist/src/rest/projection.js +31 -12
- package/dist/src/rest/projection.js.map +1 -1
- package/dist/src/rest/routes.js +13 -7
- package/dist/src/rest/routes.js.map +1 -1
- package/dist/src/server/bootstrap.js +15 -7
- package/dist/src/server/bootstrap.js.map +1 -1
- package/dist/src/server/input-paths.d.ts +14 -1
- package/dist/src/server/input-paths.js +18 -2
- package/dist/src/server/input-paths.js.map +1 -1
- package/dist/src/server/storage-lock.d.ts +7 -1
- package/dist/src/server/storage-lock.js +103 -99
- package/dist/src/server/storage-lock.js.map +1 -1
- package/examples/database.json +2 -14
- package/examples/schema.json +0 -15
- package/package.json +3 -1
package/README.ru.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md)
|
|
4
4
|
|
|
5
|
-
[История выпусков](CHANGELOG.md) · [Переход с 0.9.0](MIGRATION.md)
|
|
5
|
+
[История выпусков](CHANGELOG.md) · [Переход с 0.9.0](MIGRATION.ru.md)
|
|
6
6
|
|
|
7
7
|
JSON-сервер для создания тестового API с REST, GraphQL, связями между записями, загрузкой файлов и экспортом схем. Поддерживает вход пользователей, права владельца и администратора, даты записей и мягкое удаление. Требуется Node.js 22 или новее.
|
|
8
8
|
|
|
9
|
-
**Изменения в 1.0.0, нарушающие совместимость.** При переходе с 0.9.0 воспользуйтесь [инструкцией по миграции](MIGRATION.md). `*` в REST `scope` выбирает только скалярные поля, которые не хранят ключи связей. Массивы, объекты, связи и явно объявленные ключи связей нужно указывать в выборе полей. При включённой аутентификации права на запись доступны через виртуальное поле `actions`.
|
|
9
|
+
**Изменения в 1.0.0, нарушающие совместимость.** При переходе с 0.9.0 воспользуйтесь [инструкцией по миграции](MIGRATION.ru.md). `*` в REST `scope` выбирает только скалярные поля, которые не хранят ключи связей. Массивы, объекты, связи и явно объявленные ключи связей нужно указывать в выборе полей. При включённой аутентификации права на запись доступны через виртуальное поле `actions`.
|
|
10
10
|
|
|
11
11
|
## Установка
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ JSON-сервер для создания тестового API с REST, GraphQ
|
|
|
14
14
|
npm install @kollors/deep-json-server@rc
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Для установки этого релиз-кандидата укажите `@1.0.0-rc.
|
|
17
|
+
Для установки этого релиз-кандидата укажите `@1.0.0-rc.8`.
|
|
18
18
|
|
|
19
19
|
## Быстрый старт
|
|
20
20
|
|
|
@@ -177,7 +177,20 @@ export default {
|
|
|
177
177
|
}
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
-
Модели задаются в `models`. Корень схемы принимает `models`, `api`, `timestamps` и `softDelete`. Корневой `api` выбирает маршруты базы. Если
|
|
180
|
+
Модели задаются в `models`. Корень схемы принимает `models`, `api`, `timestamps` и `softDelete`. Корневой `api` выбирает маршруты базы. Если он не задан, значение по умолчанию зависит от способа использования схемы:
|
|
181
|
+
|
|
182
|
+
| Контекст | Корневой `api` по умолчанию |
|
|
183
|
+
|---|---|
|
|
184
|
+
| Сервер без секции `graphql` | `['rest']` |
|
|
185
|
+
| Сервер с секцией `graphql` | `['rest', 'graphql']` |
|
|
186
|
+
| Отдельный вызов `generateOpenapi()` | `['rest']` |
|
|
187
|
+
| Отдельный вызов `generateGraphql()` | `['graphql']` |
|
|
188
|
+
|
|
189
|
+
Явный `['rest']` включает REST-маршруты базы; `['graphql']` — операции GraphQL; `['rest', 'graphql']` — оба API. OpenAPI описывает REST-маршруты при наличии секции `openapi`. Корневой `api: []` недопустим; `[]` разрешён только на уровне модели.
|
|
190
|
+
|
|
191
|
+
Модель без собственного `api` наследует значение из корня. Указанный у модели массив может содержать `['rest']`, `['graphql']`, оба варианта или `[]`. Модель не может включить API, отсутствующий в корневом `api`. OpenAPI включает модели, доступные через REST. Для генерации GraphQL нужна хотя бы одна GraphQL-модель; для генерации OpenAPI с маршрутами базы — хотя бы одна REST-модель. Если база доступна только через GraphQL, OpenAPI всё равно может описывать настроенные маршруты auth и файлов. Связанные модели должны разрешать тот же API. Имена моделей должны быть допустимыми идентификаторами; конфликты типов и операций вызывают ошибку. Имена `and`, `or`, `not` зарезервированы фильтрами.
|
|
192
|
+
|
|
193
|
+
Таким образом, явный корневой `"api": ["rest", "graphql"]` включает оба API у моделей, наследующих настройку из корня. Порядок элементов массива не важен.
|
|
181
194
|
|
|
182
195
|
Например, при корневом `api: ['rest', 'graphql']` модель с `api: ['rest']` получает REST-маршруты и пути в OpenAPI без операций GraphQL, а модель с `api: ['graphql']` — операции GraphQL без REST-маршрутов и путей в OpenAPI.
|
|
183
196
|
|
|
@@ -219,7 +232,7 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
219
232
|
|
|
220
233
|
Каждое значение `enum` должно соответствовать типу поля и его ограничениям. Чтобы разрешить `null` для поля, укажите `nullable: true`; добавлять `null` в `enum` не нужно. Некорректные значения отклоняются при загрузке схемы.
|
|
221
234
|
|
|
222
|
-
У модели ровно один первичный ключ типа `string` или `number`, объявленный на верхнем уровне. Имя произвольное: `id`, `username`, `code`. Если `generated` не задан, значение передаёт клиент при создании. Генерируемые поля объявляются на верхнем уровне, исключаются из входных типов и несовместимы с `default`. При замене записи сохраняются генерируемые значения и поля `readOnly`, включая вложенные объекты. Для защиты полей внутри массива задайте `readOnly` всему массиву или содержащему его объекту. Объекты, состоящие из серверных полей, доступны только в ответах.
|
|
235
|
+
У модели ровно один первичный ключ типа `string` или `number`, объявленный на верхнем уровне. Имя произвольное: `id`, `username`, `code`. Если `generated` не задан, значение передаёт клиент при создании. Генерируемые поля объявляются на верхнем уровне, исключаются из входных типов и несовместимы с `default`. При замене записи сохраняются генерируемые значения и поля `readOnly`, включая вложенные объекты. Обнуление nullable-родителя отклоняется, если оно удалит сохранённые поля `readOnly` внутри него. Для защиты полей внутри массива задайте `readOnly` всему массиву или содержащему его объекту. Объекты, состоящие из серверных полей, доступны только в ответах.
|
|
223
236
|
|
|
224
237
|
Например, `LocalUser` с первичным ключом `username` и полем `password: {"type":"string","required":true,"writeOnly":true}` получает запрос `localUser(username: ...)` и маршрут `/localUsers/{username}`. Поле `writeOnly` доступно для записи и исключено из ответов, `scope`, фильтров и сортировки.
|
|
225
238
|
|
|
@@ -244,7 +257,9 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
244
257
|
|
|
245
258
|
`source` всегда указывает на поле текущей модели, а `target` — на поле модели из `type`. Для обратной связи пути меняются местами: у `Country.users` это `source: "id"` и `target: "countryId"`. Если ключ хранится под нестандартным именем, задайте путь явно. Когда между двумя моделями несколько связей, уточните обратные поля: например, `Country.birthUsers = {"type":"User[]","keyOn":"related","target":"birthCountryId"}` и `Country.residenceUsers = {"type":"User[]","keyOn":"related","target":"residenceCountryId"}`. Отсутствующая, противоречивая или неоднозначная пара вызывает ошибку при загрузке схемы.
|
|
246
259
|
|
|
247
|
-
Ключи связей остаются в базе, даже если их нет в `fields`.
|
|
260
|
+
Ключи связей остаются в базе, даже если их нет в `fields`. На стороне `current` поле `source` можно не объявлять отдельно, если известен тип целевого ключа: сервер выведет массив для множественной связи или одно значение для одиночной. Это действует и для первичного, и для непервичного целевого поля. Такой ключ остаётся внутренним: REST и GraphQL не позволяют читать, выбирать, фильтровать, сортировать или записывать его напрямую, а OpenAPI его не описывает. Сама связь при этом доступна. Чтобы открыть ключ в API, объявите его в `fields`, например `"countryId": { "type": "string" }`. Неоднозначное сопоставление тоже требует явного объявления.
|
|
261
|
+
|
|
262
|
+
Например, `publishers: { "type": "Publisher[]", "keyOn": "current", "target": "code" }` выводит `publisherCodes: { "type": "string[]" }`, если `Publisher.code` — строка, даже при первичном ключе `Publisher.id`. Типы явно объявленных хранимых ключей сохраняются.
|
|
248
263
|
|
|
249
264
|
Обратная связь: `User.movies = {"type":"Movie[]","keyOn":"related","target":"actors.userId"}` соответствует `Movie.actors.user = {"type":"User","keyOn":"current","source":"actors.userId"}`. Фильм возвращается один раз, даже если совпало несколько актёров. Несколько совпадений для одиночной связи — ошибка.
|
|
250
265
|
|
|
@@ -266,37 +281,31 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
266
281
|
"countries": [
|
|
267
282
|
{
|
|
268
283
|
"id": "1",
|
|
269
|
-
"isArchived": false,
|
|
270
284
|
"name": "Ardenia"
|
|
271
285
|
},
|
|
272
286
|
{
|
|
273
287
|
"id": "2",
|
|
274
|
-
"isArchived": false,
|
|
275
288
|
"name": "Veloria"
|
|
276
289
|
}
|
|
277
290
|
],
|
|
278
291
|
"genres": [
|
|
279
292
|
{
|
|
280
293
|
"id": "1",
|
|
281
|
-
"isArchived": false,
|
|
282
294
|
"name": "Crime",
|
|
283
295
|
"parentIds": []
|
|
284
296
|
},
|
|
285
297
|
{
|
|
286
298
|
"id": "2",
|
|
287
|
-
"isArchived": false,
|
|
288
299
|
"name": "Gangster",
|
|
289
300
|
"parentIds": ["1"]
|
|
290
301
|
},
|
|
291
302
|
{
|
|
292
303
|
"id": "3",
|
|
293
|
-
"isArchived": false,
|
|
294
304
|
"name": "Drama",
|
|
295
305
|
"parentIds": []
|
|
296
306
|
},
|
|
297
307
|
{
|
|
298
308
|
"id": "4",
|
|
299
|
-
"isArchived": false,
|
|
300
309
|
"name": "Comedy",
|
|
301
310
|
"parentIds": []
|
|
302
311
|
}
|
|
@@ -318,7 +327,6 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
318
327
|
"coverSrc": "https://example.com/covers/shadows-of-ardenia.jpg",
|
|
319
328
|
"description": "An heiress in a port city uncovers a plot involving two rival families.",
|
|
320
329
|
"id": "1",
|
|
321
|
-
"isArchived": false,
|
|
322
330
|
"publisherIds": ["2"],
|
|
323
331
|
"title": "Shadows of Ardenia"
|
|
324
332
|
},
|
|
@@ -327,7 +335,6 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
327
335
|
"coverSrc": "https://example.com/covers/northern-star.jpg",
|
|
328
336
|
"description": "A night clerk at an old hotel gets drawn into the search for a missing painting.",
|
|
329
337
|
"id": "2",
|
|
330
|
-
"isArchived": false,
|
|
331
338
|
"publisherIds": ["1"],
|
|
332
339
|
"title": "Midnight at the Northern Star"
|
|
333
340
|
}
|
|
@@ -335,12 +342,10 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
335
342
|
"publishers": [
|
|
336
343
|
{
|
|
337
344
|
"id": "1",
|
|
338
|
-
"isArchived": false,
|
|
339
345
|
"name": "Northlight Studio"
|
|
340
346
|
},
|
|
341
347
|
{
|
|
342
348
|
"id": "2",
|
|
343
|
-
"isArchived": false,
|
|
344
349
|
"name": "Aurora Pictures"
|
|
345
350
|
}
|
|
346
351
|
],
|
|
@@ -349,15 +354,13 @@ REST без схемы создаёт ключ `id` и сохраняет про
|
|
|
349
354
|
"bornAt": "1988-03-14",
|
|
350
355
|
"countryId": "1",
|
|
351
356
|
"fullName": "Mira Volkova",
|
|
352
|
-
"id": "1"
|
|
353
|
-
"isArchived": false
|
|
357
|
+
"id": "1"
|
|
354
358
|
},
|
|
355
359
|
{
|
|
356
360
|
"bornAt": "1991-11-02",
|
|
357
361
|
"countryId": "2",
|
|
358
362
|
"fullName": "Leon Vetrov",
|
|
359
|
-
"id": "2"
|
|
360
|
-
"isArchived": false
|
|
363
|
+
"id": "2"
|
|
361
364
|
}
|
|
362
365
|
]
|
|
363
366
|
}
|
|
@@ -471,6 +474,8 @@ const response = await fetch(`/users?${params}`);
|
|
|
471
474
|
|
|
472
475
|
Аргументы доступны только у списков. Вместо них список может использовать `{ "union": [scope, ...] }`: каждая часть — обычный scope списка, части выполняются в порядке массива, а для каждого первичного ключа остаётся первая запись. Во вложенных массивах объектов без первичных ключей каждый исходный элемент включается один раз, даже если его выбрали несколько частей. Разные элементы с одинаковым содержимым сохраняются отдельно. В запросе отдельной записи и в ответе мутации аргументы можно задать для вложенных списков. Параметры проверяются даже на пустых данных; ошибка в выборе ответа отменяет изменения записи. Некорректный `scope` возвращает `400`. Максимальная длина JSON — 10 000 символов, глубина выбора — 32 уровня.
|
|
473
476
|
|
|
477
|
+
REST и GraphQL также ограничивают общий объём работы каждого запроса: до 1 000 узлов выбора, 10 000 развёрнутых записей, 100 000 учитываемых шагов выполнения и консервативной оценки размера JSON в 8 МиБ. Вложенные списки, части `union` и GraphQL aliases расходуют общий бюджет. При превышении сервер возвращает REST `400` или GraphQL `INVALID_QUERY`; уменьшите выборку полей, вложенность или размер страниц. Мутация REST откатывается, если построение её ответа превышает бюджет.
|
|
478
|
+
|
|
474
479
|
### Вложенная запись
|
|
475
480
|
|
|
476
481
|
Явно объявленные поля ключей, например `genreIds: ["1"]`, только задают связь. В поля связей можно передавать записи для создания или обновления:
|
|
@@ -495,20 +500,20 @@ Content-Type: application/json
|
|
|
495
500
|
|
|
496
501
|
| Значение в связи | Действие |
|
|
497
502
|
|---|---|
|
|
498
|
-
| Объект только с первичным ключом | Связать существующую запись без её
|
|
503
|
+
| Объект только с первичным ключом | Связать существующую запись без обновления её полей, дат и авторства |
|
|
499
504
|
| Объект с первичным ключом и другими полями | PATCH обновляет переданные поля, PUT заменяет связанную запись |
|
|
500
505
|
| Объект без первичного ключа | Создать запись со значениями по умолчанию и сгенерированным ключом |
|
|
501
506
|
|
|
502
|
-
Имя и тип ключа берутся из целевой модели. Объект только с ключом
|
|
507
|
+
Имя и тип ключа берутся из целевой модели. Объект только с этим ключом считается ссылкой в POST, PUT и PATCH; остальные обязательные поля целевой модели ему не нужны. Если добавить другие поля, это уже обновление: PUT требует все обязательные поля, удаляет пропущенные необязательные или задаёт им значения по умолчанию; POST/PATCH обновляют переданные поля. При замене сохраняются первичный ключ, генерируемые значения и поля `readOnly`. Если запись по ключу не найдена, операция завершится ошибкой. Для создания вложенной записи без ключа нужна его автоматическая генерация. Объявленные хранимые ключи, например `countryId`, тоже позволяют менять ссылки напрямую.
|
|
503
508
|
|
|
504
|
-
Переданный список заменяет состав связи. PATCH сохраняет пропущенные
|
|
509
|
+
Переданный список заменяет состав связи. PATCH сохраняет пропущенные связи. PUT заменяет доступные для записи поля текущей записи, включая её собственные ключи связей, но сохраняет пропущенные связи `keyOn: "related"`, чьи ключи хранятся в других записях. `[]` очищает список, `null` — одиночную связь с разрешённым `nullable`. Разрыв связи не удаляет связанную запись. Обязательная одиночная связь должна сохраняться; обязательная связь-список может быть пустой.
|
|
505
510
|
|
|
506
511
|
Если хранимый ключ объявлен в `fields`, в одном объекте указывайте либо связь, либо её ключ: например, `genres` или `genreIds`. Одновременная передача обоих полей возвращает `400 INVALID_INPUT`, даже если наборы ключей совпадают:
|
|
507
512
|
|
|
508
513
|
```json
|
|
509
514
|
{
|
|
510
515
|
"genreIds": ["1", "2"],
|
|
511
|
-
"genres": ["1", "2"]
|
|
516
|
+
"genres": [{ "id": "1" }, { "id": "2" }]
|
|
512
517
|
}
|
|
513
518
|
```
|
|
514
519
|
|
|
@@ -516,7 +521,7 @@ Content-Type: application/json
|
|
|
516
521
|
|
|
517
522
|
Все вложенные изменения входят в транзакцию основной записи. Ошибка проверки, отсутствующая запись или неверный выбор полей ответа отменяет всю операцию. Изменения общей записи видны всем, кто с ней связан.
|
|
518
523
|
|
|
519
|
-
GraphQL принимает в полях связей типизированные объекты. Чтобы при
|
|
524
|
+
GraphQL принимает в полях связей типизированные объекты. Чтобы изменить только ссылки, в том числе при замене, передайте объекты только с первичными ключами либо объявленные поля ключей, например `genreIds`. Обязательные поля вложенного создания или замены с дополнительными полями проверяются при выполнении. Пример:
|
|
520
525
|
|
|
521
526
|
```graphql
|
|
522
527
|
mutation {
|
|
@@ -568,9 +573,13 @@ query {
|
|
|
568
573
|
|
|
569
574
|
Запрос `user(id: ...)` возвращает одну запись или `null`, если она отсутствует. Мутации: `userCreate(data: ...)`, `userReplace(id: ..., data: ...)`, `userUpdate(id: ..., data: ...)`, `userDelete(id: ...)`. Для модели, состоящей из генерируемых полей, мутация создания вызывается без аргумента `data`. Правила записи и валидации совпадают с REST. Связи, выбранные в результате мутации, определяют содержимое ответа.
|
|
570
575
|
|
|
576
|
+
Если первичный ключ называется `data`, мутации Update/Replace принимают его значение в аргументе `key`, а объект изменений — в `data`, например `entryUpdate(key: "one", data: { name: "Updated" })`. В Find/Delete аргумент ключа по-прежнему называется `data`.
|
|
577
|
+
|
|
571
578
|
Строковый первичный ключ представлен типом GraphQL `ID`, обычные строки — `String`, числа — `Float`, параметры пагинации — `Int`. Enum сохраняет допустимые строковые имена; остальные значения получают имена `VALUE_0`, `VALUE_1` и т. д. Длины строк, форматы и другие ограничения модели проверяются сервером при выполнении запроса. Для просмотра схемы доступна интроспекция. Параметры выбранных списков проверяются до выполнения мутаций.
|
|
572
579
|
|
|
573
|
-
Ошибки содержат `extensions.code`: `INVALID_INPUT`, `INVALID_QUERY`, `NOT_FOUND`, `CONFLICT`, `UNAUTHENTICATED`, `FORBIDDEN` или `INTERNAL_ERROR`. Ошибки синтаксиса и типов GraphQL возвращаются в стандартном массиве `errors`. Максимальная глубина запроса — 32 уровня.
|
|
580
|
+
Ошибки содержат `extensions.code`: `INVALID_INPUT`, `INVALID_QUERY`, `NOT_FOUND`, `CONFLICT`, `UNAUTHENTICATED`, `FORBIDDEN` или `INTERNAL_ERROR`. Ошибки синтаксиса и типов GraphQL возвращаются в стандартном массиве `errors`. Максимальная глубина запроса — 32 уровня. Ограничения общего объёма работы, описанные для REST, действуют и в GraphQL. Лимит узлов выбора и аргументы списков проверяются до выполнения мутаций. Ошибки при построении результата мутации, включая превышение бюджета во время выполнения, не отменяют уже сохранённую мутацию; перед повтором запроса проверьте состояние записи.
|
|
581
|
+
|
|
582
|
+
Операция GraphQL с несколькими полями мутаций не является одной транзакцией: поля выполняются по порядку, а ранее сохранённые изменения остаются при ошибке последующего результата. Превышение бюджета во время выполнения возвращает `data: null` и останавливает следующие поля мутаций, но `data: null` не означает откат записей. Прочитайте сохранённое состояние менее объёмным запросом. Автоматический повтор создания с генерируемым ключом может создать вторую запись.
|
|
574
583
|
|
|
575
584
|
## OpenAPI и экспорт схем
|
|
576
585
|
|
|
@@ -612,7 +621,7 @@ if (!password) throw new Error('Set DJS_PASSWORD');
|
|
|
612
621
|
await writeFile(
|
|
613
622
|
'./auth.json',
|
|
614
623
|
JSON.stringify(
|
|
615
|
-
[{ id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true }],
|
|
624
|
+
[{ id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true, sessions: [] }],
|
|
616
625
|
null,
|
|
617
626
|
2,
|
|
618
627
|
),
|
|
@@ -630,7 +639,7 @@ export default {
|
|
|
630
639
|
};
|
|
631
640
|
```
|
|
632
641
|
|
|
633
|
-
Запустите `npx deep-json-server server.config.js`. Каждой исходной учётной записи нужны уникальный строковый `id`, уникальный `username` и `passwordHash`, созданный функцией выше. `isAdmin` по умолчанию равен `false
|
|
642
|
+
Запустите `npx deep-json-server server.config.js`. Каждой исходной учётной записи нужны уникальный строковый `id`, уникальный `username` и `passwordHash`, созданный функцией выше. `isAdmin` по умолчанию равен `false`, а `sessions` — `[]`, поэтому существующие файлы пользователей остаются допустимыми. Пароли хешируются через scrypt со случайной солью.
|
|
634
643
|
|
|
635
644
|
При `storage: 'memory'` передайте массив в `auth.source`:
|
|
636
645
|
|
|
@@ -645,13 +654,19 @@ export default {
|
|
|
645
654
|
database: { source: { items: [] } },
|
|
646
655
|
auth: {
|
|
647
656
|
source: [
|
|
648
|
-
{ id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true },
|
|
657
|
+
{ id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true, sessions: [] },
|
|
649
658
|
],
|
|
650
659
|
},
|
|
651
660
|
};
|
|
652
661
|
```
|
|
653
662
|
|
|
654
|
-
Учётные записи auth хранятся отдельно от базы.
|
|
663
|
+
Учётные записи auth и их сессии хранятся вместе в `auth.source`, отдельно от базы. Каждый элемент массива `sessions` пользователя содержит `{ tokenHash, expiresAt }`: `tokenHash` — SHA-256 хеш токена в виде 64 шестнадцатеричных символов нижнего регистра, а `expiresAt` — абсолютное время истечения сессии в миллисекундах Unix. Исходные токены не сохраняются, а ответы auth не раскрывают ни хеши паролей, ни записи сессий. Хеши токенов должны быть уникальны среди всех пользователей.
|
|
664
|
+
|
|
665
|
+
При `storage: 'file'` вход, выход и изменения пользователей сохраняются в один JSON-файл до успешного ответа. Действующие сессии переживают перезапуск сервера: клиент может продолжать использовать прежний токен. Новый пароль и удаление всех сессий пользователя сохраняются вместе. При ошибке записи прежние пользователи и сессии сохраняются без изменений. После ручного редактирования файла перезапустите сервер; если меняете хеш пароля вручную, также очистите `sessions` этого пользователя.
|
|
666
|
+
|
|
667
|
+
При `storage: 'memory'` переданный массив пользователей не изменяется. Каждый запуск заново загружает исходных пользователей и сессии. Сессии, созданные во время работы, после перезапуска исчезают; заданные в исходных данных неистёкшие сессии восстанавливаются, включая завершённые через logout во время предыдущего запуска. Клиенту по-прежнему нужен соответствующий исходный токен: восстановить его из `tokenHash` нельзя.
|
|
668
|
+
|
|
669
|
+
`auth.expiresIn` задаёт срок новых сессий и по умолчанию равен 3600 секундам. У существующих сессий сохраняется записанное время истечения, в том числе после перезапуска. Истёкшие сессии игнорируются при загрузке, отклоняются при каждом запросе и удаляются у всех пользователей при следующем успешном изменении auth, например входе или выходе. Срок сессии автоматически не продлевается.
|
|
655
670
|
|
|
656
671
|
| REST-запрос | JSON тела | Ответ |
|
|
657
672
|
|---|---|---|
|
|
@@ -662,13 +677,13 @@ export default {
|
|
|
662
677
|
| `PATCH /auth/users/:id/password` | `{ "currentPassword": "…", "newPassword": "…" }` | `{ success: true }` |
|
|
663
678
|
| `PATCH /auth/users/:id/admin` | `{ "isAdmin": true }` | `{ id, username, isAdmin }` |
|
|
664
679
|
|
|
665
|
-
Регистрация и вход доступны без токена. Для остальных методов передавайте `Authorization: Bearer <accessToken>`. Регистрация создаёт обычного пользователя с новым `id`; поля `id`, `passwordHash` и `
|
|
680
|
+
Регистрация и вход доступны без токена. Для остальных методов передавайте `Authorization: Bearer <accessToken>`. Регистрация создаёт обычного пользователя с новым `id`; поля `id`, `passwordHash`, `isAdmin` и `sessions` в запросе запрещены. `username` уникален с учётом регистра, повтор возвращает `409`. Имя должно содержать хотя бы один непробельный символ и не более 256 символов, пароль — от 1 до 1024 символов. Значения не обрезаются. Регистрация не открывает сессию: после неё выполните вход.
|
|
666
681
|
|
|
667
682
|
Пользователь меняет только свой пароль, передавая `currentPassword` и `newPassword`. Для администратора при смене своего пароля действует то же правило. Администратор может сменить пароль обычному пользователю, передав только `newPassword`. Изменение пароля другого администратора возвращает `403`. После успешной смены завершаются все сессии этого пользователя, включая текущую при смене своего пароля; требуется новый вход. Неверный текущий пароль возвращает `401` без изменения сессий.
|
|
668
683
|
|
|
669
684
|
Статус `isAdmin` меняет только администратор. Можно назначать других администраторов и снимать с них статус. Снять статус с себя можно, только если остаётся другой администратор; иначе возвращается `409`. Проверка учитывает параллельные запросы. Новые права действуют с прежними токенами сразу после сохранения, в том числе для мутаций GraphQL. Администратор может снять статус с другого администратора, а затем сменить ему пароль как обычному пользователю.
|
|
670
685
|
|
|
671
|
-
Недействительный или истёкший токен возвращает `401`, недостаток прав — `403`, отсутствующий пользователь при разрешённой операции — `404`. Ошибки тела запроса возвращают `400`. Вход, регистрация и смена пароля могут вернуть `429` при превышении числа одновременных вычислений паролей; вход также
|
|
686
|
+
Недействительный или истёкший токен возвращает `401`, недостаток прав — `403`, отсутствующий пользователь при разрешённой операции — `404`. Ошибки тела запроса возвращают `400`. Вход, регистрация и смена пароля могут вернуть `429` при превышении числа одновременных вычислений паролей; вход также возвращает `429`, если уже есть 10 000 активных сессий среди всех пользователей, включая загруженные из источника. Выход завершает только сессию переданного токена.
|
|
672
687
|
|
|
673
688
|
При включённом auth у каждой записи модели доступен виртуальный объект `actions`. Он вычисляется для текущего пользователя и не сохраняется в базе. В REST его нужно явно запросить через `scope`:
|
|
674
689
|
|
|
@@ -760,7 +775,7 @@ query {
|
|
|
760
775
|
|
|
761
776
|
Служебные сведения восстановления хранятся вместе с записями и переживают перезапуск; сохраняйте их при резервном копировании базы. Внутреннее поле `djsDeletion` зарезервировано и не выдаётся через API.
|
|
762
777
|
|
|
763
|
-
При включённом auth создавать записи может любой вошедший пользователь. Изменять, удалять и восстанавливать — владелец по `createdById` или пользователь с `isAdmin: true`. Записи без владельца изменяет только администратор. Его правки не меняют владельца. Правила действуют на вложенные записи, изменение ключей связей, каскады и восстановление. Ссылка
|
|
778
|
+
При включённом auth создавать записи может любой вошедший пользователь. Изменять, удалять и восстанавливать — владелец по `createdById` или пользователь с `isAdmin: true`. Записи без владельца изменяет только администратор. Его правки не меняют владельца. Правила действуют на вложенные записи, изменение ключей связей, каскады и восстановление. Ссылка через объект только с первичным ключом или объявленный хранимый ключ не требует владения неизменяемой целевой записью. Вложенный объект с дополнительными полями требует права изменять её. При изменении обратных связей нужны права на каждую запись, чей хранимый ключ меняется. Каждая мутация атомарна: отказ сохраняет прежнее состояние всех затронутых записей.
|
|
764
779
|
|
|
765
780
|
REST возвращает 401 при отсутствии действительного токена и 403 при недостатке прав. GraphQL проверяет мутации по тем же правилам и возвращает коды `UNAUTHENTICATED` или `FORBIDDEN`; токен получают через REST-вход и передают в `Authorization: Bearer <token>`. GraphQL-запросы чтения, OPTIONS и все операции с файлами остаются открытыми.
|
|
766
781
|
|
|
@@ -839,7 +854,7 @@ Content-Type: application/json
|
|
|
839
854
|
|
|
840
855
|
При хранении на диске бинарный файл находится по пути `<files.source>/<directory>/<name>`. Метаданные по умолчанию хранятся в `<files.source>/.files.json`; другой путь можно задать через `files.metadata`. Директории и файл метаданных создаются по мере необходимости.
|
|
841
856
|
|
|
842
|
-
Используйте один процесс сервера для
|
|
857
|
+
Используйте один процесс сервера для каждого записываемого дискового источника. Второй сервер с общей базой, файлом пользователей auth, каталогом файлового хранилища или метаданными не запустится. Блокировки — отдельные JSON-файлы рядом с источниками: для `database.json` создаётся `database-lock.json`; последнее расширение имени из конфигурации убирается. Файл содержит тип блокировки, PID процесса и токен владельца. При штатном закрытии файлы блокировок удаляются; после аварии ОС освобождает блокировки, а следующий сервер повторно использует оставшиеся файлы. Для дисковых блокировок используется нативная зависимость `fs-native-extensions`; хранение в памяти и генерация схем её не загружают. Для источника с символической ссылкой блокируются исходная запись и конечная цель. Перед ручным изменением файлов или метаданных остановите сервер. Пути внутри каталога файлового хранилища не могут содержать символические ссылки. Загрузка и переименование не могут перезаписать базу, её блокировку, счётчики, схему, учётные записи auth, загруженную конфигурацию или файл метаданных.
|
|
843
858
|
|
|
844
859
|
Отправляйте файл как бинарное тело запроса. В браузере для этого можно использовать `xhr.send(file)`, а прогресс отслеживать через `XMLHttpRequest.upload.onprogress`. Максимальный размер по умолчанию равен 100 МиБ и настраивается через `server.maxFileSize`. Отсутствующие или небезопасные заголовки и пути возвращают `400`, превышение лимита — `413`, а отсутствующий, некорректный или не поддерживаемый Fastify `Content-Type` — `400` либо `415` в зависимости от этапа проверки.
|
|
845
860
|
|
|
@@ -892,6 +907,6 @@ npm run verify
|
|
|
892
907
|
|
|
893
908
|
`npm run verify` проверяет типы, стиль кода, покрытие тестами и установку пакета из архива.
|
|
894
909
|
|
|
895
|
-
Для публикации предварительной версии обновите номер в `package.json
|
|
910
|
+
Для публикации предварительной версии обновите номер в `package.json` и `package-lock.json` (сервер и CLI читают его из метаданных пакета), затем отправьте коммит в `main`. GitHub Actions создаст тег `v<version>` и опубликует пакет через trusted publishing в канал `alpha`, `beta` или `rc`. Стабильные версии публикуются в `latest` из явно отправленного тега версии.
|
|
896
911
|
|
|
897
912
|
Лицензия: MIT.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { AuthConfig, AuthSession, AuthUser, AuthUserRecord } from './src/auth/contract.js';
|
|
1
|
+
export type { AuthConfig, AuthSession, AuthSessionRecord, AuthUser, AuthUserRecord } from './src/auth/contract.js';
|
|
2
2
|
export { hashPassword } from './src/auth/public.js';
|
|
3
3
|
export type { EntityDefinition, Field, ModelSchema } from './src/core/model.js';
|
|
4
4
|
export type { FileUpdate } from './src/files/contract.js';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kollors/deep-json-server",
|
|
3
|
+
"version": "1.0.0-rc.8",
|
|
4
|
+
"description": "JSON mock server with schema-driven REST, GraphQL, OpenAPI generation and file uploads",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"deep-json-server": "dist/bin/deep-json-server.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./server": {
|
|
17
|
+
"types": "./dist/src/server/public.d.ts",
|
|
18
|
+
"import": "./dist/src/server/public.js"
|
|
19
|
+
},
|
|
20
|
+
"./openapi": {
|
|
21
|
+
"types": "./dist/src/openapi/entry.d.ts",
|
|
22
|
+
"import": "./dist/src/openapi/entry.js"
|
|
23
|
+
},
|
|
24
|
+
"./graphql": {
|
|
25
|
+
"types": "./dist/src/graphql/entry.d.ts",
|
|
26
|
+
"import": "./dist/src/graphql/entry.js"
|
|
27
|
+
},
|
|
28
|
+
"./auth": {
|
|
29
|
+
"types": "./dist/src/auth/public.d.ts",
|
|
30
|
+
"import": "./dist/src/auth/public.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"README.md",
|
|
37
|
+
"README.ru.md",
|
|
38
|
+
"CHANGELOG.md",
|
|
39
|
+
"MIGRATION.md",
|
|
40
|
+
"MIGRATION.ru.md",
|
|
41
|
+
"examples"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "node scripts/clean-dist.js && tsc && node scripts/prepare-dist.js",
|
|
45
|
+
"lint": "biome check . --error-on-warnings",
|
|
46
|
+
"lint:fix": "biome check --write .",
|
|
47
|
+
"pack:check": "node scripts/check-package.js",
|
|
48
|
+
"prepack": "npm run build",
|
|
49
|
+
"prepublishOnly": "npm run verify",
|
|
50
|
+
"test": "npm run build && node --test",
|
|
51
|
+
"test:coverage": "npm run build && node scripts/run-coverage.js",
|
|
52
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.type-tests.json",
|
|
53
|
+
"verify": "npm run typecheck && npm run lint && npm run test:coverage && npm run pack:check"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=22"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"ajv": "^8.20.0",
|
|
60
|
+
"ajv-formats": "^3.0.1",
|
|
61
|
+
"fastify": "^5.12.3",
|
|
62
|
+
"fs-native-extensions": "^1.5.1",
|
|
63
|
+
"graphql": "^16.14.2",
|
|
64
|
+
"lowdb": "^7.0.1",
|
|
65
|
+
"mercurius": "^16.10.0",
|
|
66
|
+
"pluralize": "^8.0.0",
|
|
67
|
+
"yaml": "^2.8.1"
|
|
68
|
+
},
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/kollors/deep-json-server.git"
|
|
72
|
+
},
|
|
73
|
+
"homepage": "https://github.com/kollors/deep-json-server",
|
|
74
|
+
"bugs": {
|
|
75
|
+
"url": "https://github.com/kollors/deep-json-server/issues"
|
|
76
|
+
},
|
|
77
|
+
"keywords": [
|
|
78
|
+
"api",
|
|
79
|
+
"crud",
|
|
80
|
+
"deep-filter",
|
|
81
|
+
"file-upload",
|
|
82
|
+
"json",
|
|
83
|
+
"mock",
|
|
84
|
+
"mock-api",
|
|
85
|
+
"mock-server",
|
|
86
|
+
"openapi",
|
|
87
|
+
"relations",
|
|
88
|
+
"rest",
|
|
89
|
+
"rest-api",
|
|
90
|
+
"graphql",
|
|
91
|
+
"openapi-generator"
|
|
92
|
+
],
|
|
93
|
+
"author": "kollors",
|
|
94
|
+
"license": "MIT",
|
|
95
|
+
"publishConfig": {
|
|
96
|
+
"access": "public"
|
|
97
|
+
},
|
|
98
|
+
"devDependencies": {
|
|
99
|
+
"@biomejs/biome": "^2.5.12",
|
|
100
|
+
"@types/node": "^22.0.0",
|
|
101
|
+
"@types/pluralize": "^0.0.33",
|
|
102
|
+
"typescript": "^7.0.2"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -4,9 +4,17 @@ export interface AuthUser {
|
|
|
4
4
|
username: string;
|
|
5
5
|
isAdmin: boolean;
|
|
6
6
|
}
|
|
7
|
+
export interface AuthSessionRecord {
|
|
8
|
+
/** SHA-256 of the access token, encoded as 64 lowercase hexadecimal characters. */
|
|
9
|
+
tokenHash: string;
|
|
10
|
+
/** Absolute expiration time in milliseconds since the Unix epoch. */
|
|
11
|
+
expiresAt: number;
|
|
12
|
+
}
|
|
7
13
|
export interface AuthUserRecord extends Omit<AuthUser, 'isAdmin'> {
|
|
8
14
|
isAdmin?: boolean;
|
|
9
15
|
passwordHash: string;
|
|
16
|
+
/** Initial or persisted sessions. Defaults to an empty array. */
|
|
17
|
+
sessions?: AuthSessionRecord[];
|
|
10
18
|
}
|
|
11
19
|
export interface AuthConfig<S extends Storage = Storage> {
|
|
12
20
|
source: Source<S, AuthUserRecord[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../src/auth/contract.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../src/auth/contract.ts"],"names":[],"mappings":"AA4BA,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,KAAK,EAAE,aAAa;IACpB,QAAQ,EAAE,gBAAgB;IAC1B,EAAE,EAAE,UAAU;IACd,MAAM,EAAE,cAAc;IACtB,QAAQ,EAAE,0BAA0B;IACpC,KAAK,EAAE,uBAAuB;CACtB,CAAC;AACX,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAC5C,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AACvC,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { AuthConfig, AuthSession, AuthUser, AuthUserRecord } from './contract.js';
|
|
1
|
+
export type { AuthConfig, AuthSession, AuthSessionRecord, AuthUser, AuthUserRecord } from './contract.js';
|
|
2
2
|
/** Creates a salted password hash for an auth user fixture. */
|
|
3
3
|
export declare function hashPassword(password: string): Promise<string>;
|
|
@@ -3,7 +3,6 @@ import { type AuthStore } from './store.js';
|
|
|
3
3
|
export declare class AuthService {
|
|
4
4
|
private store;
|
|
5
5
|
private expiresIn;
|
|
6
|
-
private sessions;
|
|
7
6
|
private pendingPasswords;
|
|
8
7
|
private closed;
|
|
9
8
|
constructor(store: AuthStore, expiresIn: number);
|
|
@@ -29,13 +28,13 @@ export declare class AuthService {
|
|
|
29
28
|
* @example После снятия статуса прежний токен возвращает isAdmin: false без повторного входа.
|
|
30
29
|
*/
|
|
31
30
|
me(authorization: unknown): AuthUser;
|
|
32
|
-
/** Проверяет токен и
|
|
31
|
+
/** Проверяет токен и сохраняет удаление соответствующей сессии.
|
|
33
32
|
* @example Действительный токен → { success: true }; повторный выход с ним → ошибка.
|
|
34
33
|
*/
|
|
35
|
-
logout(authorization: unknown): {
|
|
34
|
+
logout(authorization: unknown): Promise<{
|
|
36
35
|
success: boolean;
|
|
37
|
-
}
|
|
38
|
-
/** Запрещает новые
|
|
36
|
+
}>;
|
|
37
|
+
/** Запрещает новые обращения, сохраняя записанные сессии для следующего запуска.
|
|
39
38
|
* @example close() → undefined; последующий login() → отказ.
|
|
40
39
|
*/
|
|
41
40
|
close(): void;
|
|
@@ -47,8 +46,8 @@ export declare class AuthService {
|
|
|
47
46
|
* @example closed = true → UNAUTHENTICATED; false → undefined.
|
|
48
47
|
*/
|
|
49
48
|
private assertOpen;
|
|
50
|
-
/** Находит сессию по хешу токена и проверяет
|
|
51
|
-
* @example Действительный Bearer-токен → [ключ,
|
|
49
|
+
/** Находит сессию по хешу токена и проверяет срок, не изменяя сохранённое состояние.
|
|
50
|
+
* @example Действительный Bearer-токен → [ключ, пользователь]; отсутствующий или истёкший → ошибка.
|
|
52
51
|
*/
|
|
53
52
|
private session;
|
|
54
53
|
/** Получает текущую запись по идентификатору из проверенной сессии.
|
|
@@ -63,16 +62,8 @@ export declare class AuthService {
|
|
|
63
62
|
* @example Администратор и обычная чужая запись → { actor, target }; два разных администратора → FORBIDDEN.
|
|
64
63
|
*/
|
|
65
64
|
private passwordTarget;
|
|
66
|
-
/** Завершает все сессии одного пользователя после сохранения нового пароля.
|
|
67
|
-
* @example Две сессии id = '1' и одна id = '2' → остаётся сессия id = '2'.
|
|
68
|
-
*/
|
|
69
|
-
private revoke;
|
|
70
|
-
/** Удаляет из памяти сессии, срок которых истёк к текущему моменту.
|
|
71
|
-
* @example Одна истёкшая и одна действующая сессия → остаётся одна действующая; результат undefined.
|
|
72
|
-
*/
|
|
73
|
-
private prune;
|
|
74
65
|
}
|
|
75
|
-
/** Открывает источник учётных записей
|
|
66
|
+
/** Открывает источник учётных записей вместе с сохранёнными или исходными сессиями.
|
|
76
67
|
* @example createAuthService({ source: [] }) → Promise<AuthService>; повторяющиеся id → ошибка.
|
|
77
68
|
*/
|
|
78
69
|
export declare function createAuthService(config: AuthConfig): Promise<AuthService>;
|