@kollors/deep-json-server 1.0.0-alpha.10 → 1.0.0-alpha.12
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 +2 -2
- package/README.ru.md +2 -2
- package/dist/src/cli/index.js +2 -11
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/core/constants.d.ts +1 -1
- package/dist/src/core/constants.js +1 -1
- package/dist/src/core/engine.d.ts +3 -15
- package/dist/src/core/engine.js +5 -68
- package/dist/src/core/engine.js.map +1 -1
- 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/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.js +21 -165
- package/dist/src/files/disk-store.js.map +1 -1
- package/dist/src/graphql/preflight.js +2 -0
- package/dist/src/graphql/preflight.js.map +1 -1
- package/dist/src/graphql/schema.js +4 -1
- package/dist/src/graphql/schema.js.map +1 -1
- package/dist/src/openapi/document.js +25 -15
- package/dist/src/openapi/document.js.map +1 -1
- 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/rest/options.d.ts +11 -3
- package/dist/src/rest/options.js +15 -0
- package/dist/src/rest/options.js.map +1 -1
- package/dist/src/rest/projection.d.ts +14 -3
- package/dist/src/rest/projection.js +39 -8
- package/dist/src/rest/projection.js.map +1 -1
- package/dist/src/rest/routes.js +14 -5
- package/dist/src/rest/routes.js.map +1 -1
- 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/create.d.ts +1 -1
- package/dist/src/server/create.js +11 -85
- package/dist/src/server/create.js.map +1 -1
- 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/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/package.json +3 -3
|
@@ -3,15 +3,23 @@ import { type ListOptions } from '../core/query/options.js';
|
|
|
3
3
|
export interface Fields {
|
|
4
4
|
[key: string]: Scope | true;
|
|
5
5
|
}
|
|
6
|
-
export type
|
|
6
|
+
export type TupleScope = [Fields, ListOptions?];
|
|
7
|
+
export interface UnionScope {
|
|
8
|
+
union: Scope[];
|
|
9
|
+
}
|
|
10
|
+
export type Scope = TupleScope | UnionScope;
|
|
7
11
|
export interface RestOptions {
|
|
8
12
|
scope: Scope;
|
|
9
13
|
}
|
|
10
|
-
export declare const ownScope:
|
|
14
|
+
export declare const ownScope: TupleScope;
|
|
15
|
+
/** Определяет специальный scope, объединяющий несколько обычных выборок списка.
|
|
16
|
+
* @example { union: [[{ id: true }], [{ name: true }]] } → true.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isUnionScope(scope: Scope): scope is UnionScope;
|
|
11
19
|
/** Выбирает вложенный набор полей; для отсутствующего поля или true возвращает выбор собственных полей.
|
|
12
20
|
* @example scopeFor([{ '*': true }], 'name') → [{ '*': true }].
|
|
13
21
|
*/
|
|
14
|
-
export declare function scopeFor(scope:
|
|
22
|
+
export declare function scopeFor(scope: TupleScope, key: string): Scope;
|
|
15
23
|
/** Читает JSON-массив выбора полей из единственного параметра URL; проверяет длину и синтаксис JSON.
|
|
16
24
|
* @example parseRestOptions({ scope: '[{"id":true}]' }) → { scope: [{ id: true }] }.
|
|
17
25
|
*/
|
package/dist/src/rest/options.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { badQuery, childrenOf } from '../core/query/options.js';
|
|
2
2
|
import { hasOnlyKeys, isObject, isSafeKey } from '../core/utils.js';
|
|
3
3
|
export const ownScope = [{ '*': true }];
|
|
4
|
+
/** Определяет специальный scope, объединяющий несколько обычных выборок списка.
|
|
5
|
+
* @example { union: [[{ id: true }], [{ name: true }]] } → true.
|
|
6
|
+
*/
|
|
7
|
+
export function isUnionScope(scope) {
|
|
8
|
+
return !Array.isArray(scope);
|
|
9
|
+
}
|
|
4
10
|
/** Выбирает вложенный набор полей; для отсутствующего поля или true возвращает выбор собственных полей.
|
|
5
11
|
* @example scopeFor([{ '*': true }], 'name') → [{ '*': true }].
|
|
6
12
|
*/
|
|
@@ -36,6 +42,15 @@ export function parseRestOptions(query) {
|
|
|
36
42
|
export function validateScope(node, scope, list = false, depth = 0) {
|
|
37
43
|
if (depth > 32)
|
|
38
44
|
badQuery('scope is too deep');
|
|
45
|
+
if (isObject(scope) && Object.hasOwn(scope, 'union')) {
|
|
46
|
+
if (!list || node.mixed)
|
|
47
|
+
badQuery('scope union requires a list with consistent object types');
|
|
48
|
+
if (!hasOnlyKeys(scope, ['union']) || !Array.isArray(scope.union) || scope.union.length < 1)
|
|
49
|
+
badQuery('scope union must contain scopes');
|
|
50
|
+
for (const item of scope.union)
|
|
51
|
+
validateScope(node, item, true, depth + 1);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
39
54
|
if (!Array.isArray(scope) || scope.length < 1 || scope.length > 2 || !isObject(scope[0]))
|
|
40
55
|
badQuery('scope must be [fields, arguments?]');
|
|
41
56
|
if (scope.length === 2) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../../src/rest/options.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAoB,MAAM,0BAA0B,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../../src/rest/options.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,12 +2,23 @@ import type { Engine, PreparedList } from '../core/engine.js';
|
|
|
2
2
|
import type { Entity, Node } from '../core/model.js';
|
|
3
3
|
import { type Ref } from '../core/records.js';
|
|
4
4
|
import type { JsonObject } from '../core/types.js';
|
|
5
|
-
import { type RestOptions, type Scope } from './options.js';
|
|
5
|
+
import { type RestOptions, type Scope, type TupleScope } from './options.js';
|
|
6
|
+
export interface ScopedPage {
|
|
7
|
+
data: Array<{
|
|
8
|
+
ref: Ref;
|
|
9
|
+
scope: TupleScope;
|
|
10
|
+
}>;
|
|
11
|
+
total: number;
|
|
12
|
+
}
|
|
6
13
|
/** Проверяет дерево выбора и заранее подготавливает фильтры, сортировку и пагинацию вложенных списков.
|
|
7
14
|
* @example Выбор без аргументов у одиночной записи → пустая Map; неверное поле → ошибка.
|
|
8
15
|
*/
|
|
9
|
-
export declare function validateRest(engine: Engine, entity: Entity, options: RestOptions, list?: boolean): Map<
|
|
16
|
+
export declare function validateRest(engine: Engine, entity: Entity, options: RestOptions, list?: boolean): Map<TupleScope | Node, PreparedList>;
|
|
17
|
+
/** Выполняет обычный или объединённый scope списка, сохраняя порядок частей и первую запись с каждым ключом.
|
|
18
|
+
* @example Две части с id 1, затем id 1 и 2 → данные [1, 2], total 2.
|
|
19
|
+
*/
|
|
20
|
+
export declare function listScope(engine: Engine, records: Ref[], node: Node, scope: Scope, plans: Map<TupleScope | Node, PreparedList>, planKey?: TupleScope | Node): ScopedPage;
|
|
10
21
|
/** Строит новый объект из выбранных полей, разворачивая связи и обрабатывая вложенные списки.
|
|
11
22
|
* @example Запись { id: '1', name: 'Анна' } и выбор [{ name: true }] → { name: 'Анна' }.
|
|
12
23
|
*/
|
|
13
|
-
export declare function project(engine: Engine, ref: Ref, scope?: Scope, plans?: Map<Node |
|
|
24
|
+
export declare function project(engine: Engine, ref: Ref, scope?: Scope, plans?: Map<Node | TupleScope, PreparedList>): JsonObject;
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import { childrenOf } from '../core/query/options.js';
|
|
2
2
|
import { isRef, resolveField } from '../core/records.js';
|
|
3
|
-
import { ownScope, scopeFor, validateScope } from './options.js';
|
|
3
|
+
import { isUnionScope, ownScope, scopeFor, validateScope } from './options.js';
|
|
4
4
|
/** Проверяет дерево выбора и заранее подготавливает фильтры, сортировку и пагинацию вложенных списков.
|
|
5
5
|
* @example Выбор без аргументов у одиночной записи → пустая Map; неверное поле → ошибка.
|
|
6
6
|
*/
|
|
7
7
|
export function validateRest(engine, entity, options, list = false) {
|
|
8
8
|
validateScope(entity.root, options.scope, list);
|
|
9
9
|
const plans = new Map();
|
|
10
|
-
if (list)
|
|
10
|
+
if (list && !isUnionScope(options.scope))
|
|
11
11
|
plans.set(options.scope, engine.prepareOptions(entity.root, options.scope[1]));
|
|
12
12
|
const visit = (node, scope) => {
|
|
13
|
+
if (isUnionScope(scope)) {
|
|
14
|
+
for (const item of scope.union) {
|
|
15
|
+
if (!isUnionScope(item) && item[1])
|
|
16
|
+
plans.set(item, engine.prepareOptions(node, item[1]));
|
|
17
|
+
visit(node, item);
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
13
21
|
for (const [key, selection] of Object.entries(scope[0])) {
|
|
14
22
|
if (selection === true)
|
|
15
23
|
continue;
|
|
16
24
|
const child = childrenOf(node)[key];
|
|
17
|
-
if (selection[1])
|
|
25
|
+
if (!isUnionScope(selection) && selection[1])
|
|
18
26
|
plans.set(selection, engine.prepareOptions(child, selection[1]));
|
|
19
27
|
visit(child, selection);
|
|
20
28
|
}
|
|
@@ -22,10 +30,36 @@ export function validateRest(engine, entity, options, list = false) {
|
|
|
22
30
|
visit(entity.root, options.scope);
|
|
23
31
|
return plans;
|
|
24
32
|
}
|
|
33
|
+
/** Выполняет обычный или объединённый scope списка, сохраняя порядок частей и первую запись с каждым ключом.
|
|
34
|
+
* @example Две части с id 1, затем id 1 и 2 → данные [1, 2], total 2.
|
|
35
|
+
*/
|
|
36
|
+
export function listScope(engine, records, node, scope, plans, planKey) {
|
|
37
|
+
if (!isUnionScope(scope)) {
|
|
38
|
+
const key = planKey ?? scope;
|
|
39
|
+
const prepared = plans.get(key) ?? engine.prepareOptions(node, scope[1]);
|
|
40
|
+
plans.set(key, prepared);
|
|
41
|
+
const page = engine.list(records, node, scope[1], prepared);
|
|
42
|
+
return { data: page.data.map((ref) => ({ ref, scope })), total: page.total };
|
|
43
|
+
}
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
const data = [];
|
|
46
|
+
for (const item of scope.union) {
|
|
47
|
+
for (const entry of listScope(engine, records, node, item, plans).data) {
|
|
48
|
+
const key = resolveField(entry.ref, entry.ref.entity.fields[entry.ref.entity.primary]);
|
|
49
|
+
if (seen.has(key))
|
|
50
|
+
continue;
|
|
51
|
+
seen.add(key);
|
|
52
|
+
data.push(entry);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { data, total: data.length };
|
|
56
|
+
}
|
|
25
57
|
/** Строит новый объект из выбранных полей, разворачивая связи и обрабатывая вложенные списки.
|
|
26
58
|
* @example Запись { id: '1', name: 'Анна' } и выбор [{ name: true }] → { name: 'Анна' }.
|
|
27
59
|
*/
|
|
28
60
|
export function project(engine, ref, scope = ownScope, plans = new Map()) {
|
|
61
|
+
if (isUnionScope(scope))
|
|
62
|
+
throw new Error('scope union cannot select a single record');
|
|
29
63
|
const output = Object.create(null);
|
|
30
64
|
const children = childrenOf(ref.node);
|
|
31
65
|
for (const [key, node] of Object.entries(children)) {
|
|
@@ -39,11 +73,8 @@ export function project(engine, ref, scope = ownScope, plans = new Map()) {
|
|
|
39
73
|
output[key] = project(engine, value, selection, plans);
|
|
40
74
|
else if (Array.isArray(value)) {
|
|
41
75
|
if (value.every(isRef) && (value.length > 0 || node.relation || node.base === 'object')) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
plans.set(planKey, prepared);
|
|
45
|
-
const page = engine.list(value, node, selection[1], prepared);
|
|
46
|
-
output[key] = { data: page.data.map((v) => project(engine, v, selection, plans)), total: page.total };
|
|
76
|
+
const page = listScope(engine, value, node, selection, plans, isUnionScope(selection) ? undefined : selection === ownScope ? node : selection);
|
|
77
|
+
output[key] = { data: page.data.map((entry) => project(engine, entry.ref, entry.scope, plans)), total: page.total };
|
|
47
78
|
}
|
|
48
79
|
else
|
|
49
80
|
output[key] = value.map((item) => (isRef(item) ? project(engine, item, selection, plans) : structuredClone(item)));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../../../src/rest/projection.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,KAAK,EAAY,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EAAE,QAAQ,EAAgC,QAAQ,
|
|
1
|
+
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../../../src/rest/projection.ts"],"names":[],"mappings":"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"}
|
package/dist/src/rest/routes.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { domainError } from '../core/errors.js';
|
|
2
2
|
import { MUTATIONS } from '../core/operations.js';
|
|
3
3
|
import { parseRestOptions } from './options.js';
|
|
4
|
-
import { project, validateRest } from './projection.js';
|
|
4
|
+
import { listScope, project, validateRest } from './projection.js';
|
|
5
|
+
/** Возвращает актуальную сущность из снимка модели или сообщает об исчезнувшем ресурсе.
|
|
6
|
+
* @example Коллекция users есть в модели → Entity; отсутствует → NOT_FOUND.
|
|
7
|
+
*/
|
|
8
|
+
function entityFor(engine, collection) {
|
|
9
|
+
const entity = engine.model.byCollection.get(collection);
|
|
10
|
+
if (!entity)
|
|
11
|
+
throw domainError('NOT_FOUND', 'Resource not found');
|
|
12
|
+
return entity;
|
|
13
|
+
}
|
|
5
14
|
/** Регистрирует чтение коллекций и операции создания, замены, изменения и удаления записей.
|
|
6
15
|
* @example После регистрации GET / → { resources: [...] }; функция возвращает undefined.
|
|
7
16
|
*/
|
|
@@ -12,15 +21,15 @@ export function registerRestRoutes(server, engine, authenticate) {
|
|
|
12
21
|
const itemPath = `${path}/:${initial.primary}`;
|
|
13
22
|
server.get(path, async (request) => {
|
|
14
23
|
const context = await engine.context();
|
|
15
|
-
const entity =
|
|
24
|
+
const entity = entityFor(engine, initial.collection);
|
|
16
25
|
const options = parseRestOptions(request.query);
|
|
17
26
|
const plans = validateRest(engine, entity, options, true);
|
|
18
|
-
const page =
|
|
19
|
-
return { data: page.data.map((
|
|
27
|
+
const page = listScope(engine, engine.records(context, entity), entity.root, options.scope, plans);
|
|
28
|
+
return { data: page.data.map((entry) => project(engine, entry.ref, entry.scope, plans)), total: page.total };
|
|
20
29
|
});
|
|
21
30
|
server.get(itemPath, async (request) => {
|
|
22
31
|
const context = await engine.context();
|
|
23
|
-
const entity =
|
|
32
|
+
const entity = entityFor(engine, initial.collection);
|
|
24
33
|
const options = parseRestOptions(request.query);
|
|
25
34
|
const plans = validateRest(engine, entity, options);
|
|
26
35
|
const ref = engine.find(context, entity, request.params[entity.primary]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/rest/routes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/rest/routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { Model } from '../core/model.js';
|
|
3
|
+
import { type NormalizedServerConfig } from './config.js';
|
|
4
|
+
/** Подключает к HTTP-серверу хранилище записей и включённые прикладные модули.
|
|
5
|
+
* @example Конфигурация только с базой → REST-маршруты без auth, files и API-экспортов.
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerConfiguredModules(app: FastifyInstance, config: NormalizedServerConfig, explicitModel: Model | undefined, openapi: () => Promise<import('../openapi/types.js').OpenapiDocument>): Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AUTH_PATHS } from '../auth/contract.js';
|
|
2
|
+
import { inputPaths } from '../core/paths.js';
|
|
3
|
+
import { configSourcePath } from './config.js';
|
|
4
|
+
import { validateEndpoints } from './features.js';
|
|
5
|
+
/** Подключает к HTTP-серверу хранилище записей и включённые прикладные модули.
|
|
6
|
+
* @example Конфигурация только с базой → REST-маршруты без auth, files и API-экспортов.
|
|
7
|
+
*/
|
|
8
|
+
export async function registerConfiguredModules(app, config, explicitModel, openapi) {
|
|
9
|
+
const enabled = { auth: config.auth !== undefined, files: config.files !== undefined };
|
|
10
|
+
const recordSettings = { auth: enabled.auth };
|
|
11
|
+
const keys = explicitModel ? new Map(explicitModel.entities.map((entity) => [entity.collection, entity.primary])) : undefined;
|
|
12
|
+
const [{ createDatabaseStore }, { Engine }, { inferModel }, { registerRestRoutes }] = await Promise.all([
|
|
13
|
+
import('../core/database.js'),
|
|
14
|
+
import('../core/engine.js'),
|
|
15
|
+
import('../core/model.js'),
|
|
16
|
+
import('../rest/routes.js'),
|
|
17
|
+
]);
|
|
18
|
+
const store = await createDatabaseStore(config.database, keys);
|
|
19
|
+
const model = explicitModel ?? inferModel(store.database.data, recordSettings);
|
|
20
|
+
const engine = new Engine(store, model, config.server.pageSize, config.server.maxPageSize);
|
|
21
|
+
engine.validateData(store.database.data);
|
|
22
|
+
const graphqlPath = config.graphql?.endpoint;
|
|
23
|
+
const openapiPath = config.openapi?.endpoint;
|
|
24
|
+
validateEndpoints(model.entities.map((entity) => entity.collection), [...(graphqlPath ? [graphqlPath] : []), ...(openapiPath ? [openapiPath] : []), ...(enabled.auth ? Object.values(AUTH_PATHS) : [])]);
|
|
25
|
+
const auth = enabled.auth && config.auth ? await (await import('../auth/service.js')).createAuthService(config.auth) : undefined;
|
|
26
|
+
if (auth) {
|
|
27
|
+
app.addHook('onClose', async () => auth.close());
|
|
28
|
+
const { registerAuthRoutes } = await import('../auth/routes.js');
|
|
29
|
+
registerAuthRoutes(app, auth);
|
|
30
|
+
}
|
|
31
|
+
const authenticate = auth ? (header) => auth.me(header) : undefined;
|
|
32
|
+
registerRestRoutes(app, engine, authenticate);
|
|
33
|
+
if (graphqlPath) {
|
|
34
|
+
const [{ registerGraphqlRoutes }, { buildGraphql }] = await Promise.all([import('../graphql/routes.js'), import('../graphql/schema.js')]);
|
|
35
|
+
registerGraphqlRoutes(app, buildGraphql(model), engine, graphqlPath, authenticate);
|
|
36
|
+
}
|
|
37
|
+
if (openapiPath) {
|
|
38
|
+
const document = await openapi();
|
|
39
|
+
document.servers = [{ url: '/' }];
|
|
40
|
+
app.get(openapiPath, async () => document);
|
|
41
|
+
}
|
|
42
|
+
if (enabled.files && config.files) {
|
|
43
|
+
const { createFileStore, registerFileRoutes } = await import('../files/index.js');
|
|
44
|
+
const protectedPaths = inputPaths({ database: config.database, auth: config.auth }, '.', configSourcePath(config));
|
|
45
|
+
registerFileRoutes(app, { getStore: () => createFileStore(config.files, protectedPaths), maxFileSize: config.server.maxFileSize });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../../src/server/bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ export interface ServerFacade {
|
|
|
9
9
|
/** Проверяет настройки и создаёт интерфейс запуска и генерации схем; сетевой порт ещё не открывает.
|
|
10
10
|
* @example await createServer({ storage: 'memory', database: { source: { notes: [] } } }) → объект с fastify(), openapi(), graphql().
|
|
11
11
|
*/
|
|
12
|
-
export declare function createServer(
|
|
12
|
+
export declare function createServer(...args: [DeepJsonServerConfig]): Promise<ServerFacade>;
|
|
13
13
|
/** Собирает сервер из нормализованных настроек; хранилища открываются при инициализации HTTP-экземпляра.
|
|
14
14
|
* @example Нормализованные настройки → Promise<ServerFacade>; listen() затем открывает сетевой порт.
|
|
15
15
|
*/
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { inputPaths } from '../core/paths.js';
|
|
5
|
-
import { errorMessage, isObject } from '../core/utils.js';
|
|
6
|
-
import { configSourcePath, normalizeServerConfig } from './config.js';
|
|
7
|
-
import { validateEndpoints } from './features.js';
|
|
1
|
+
import { registerConfiguredModules } from './bootstrap.js';
|
|
2
|
+
import { normalizeServerConfig } from './config.js';
|
|
3
|
+
import { createHttpServer } from './http.js';
|
|
8
4
|
import { configuredModel } from './model.js';
|
|
5
|
+
import { openapiOptions } from './openapi-options.js';
|
|
9
6
|
const CORS_HEADERS = {
|
|
10
7
|
'Access-Control-Allow-Headers': 'Content-Type',
|
|
11
8
|
'Access-Control-Allow-Methods': 'DELETE, GET, OPTIONS, PATCH, POST, PUT',
|
|
@@ -14,9 +11,10 @@ const CORS_HEADERS = {
|
|
|
14
11
|
/** Проверяет настройки и создаёт интерфейс запуска и генерации схем; сетевой порт ещё не открывает.
|
|
15
12
|
* @example await createServer({ storage: 'memory', database: { source: { notes: [] } } }) → объект с fastify(), openapi(), graphql().
|
|
16
13
|
*/
|
|
17
|
-
export async function createServer(
|
|
18
|
-
if (
|
|
14
|
+
export async function createServer(...args) {
|
|
15
|
+
if (args.length !== 1)
|
|
19
16
|
throw new Error('createServer accepts only a configuration object');
|
|
17
|
+
const [config] = args;
|
|
20
18
|
return createConfiguredServer(normalizeServerConfig(config));
|
|
21
19
|
}
|
|
22
20
|
/** Собирает сервер из нормализованных настроек; хранилища открываются при инициализации HTTP-экземпляра.
|
|
@@ -24,8 +22,6 @@ export async function createServer(config) {
|
|
|
24
22
|
*/
|
|
25
23
|
export async function createConfiguredServer(normalized) {
|
|
26
24
|
const enabled = { auth: normalized.auth !== undefined, files: normalized.files !== undefined };
|
|
27
|
-
const { inferModel } = await import('../core/model.js');
|
|
28
|
-
const recordSettings = { auth: enabled.auth };
|
|
29
25
|
const explicitModel = await configuredModel(normalized);
|
|
30
26
|
const { default: Fastify } = await import('fastify');
|
|
31
27
|
const corsHeaders = { ...CORS_HEADERS };
|
|
@@ -35,88 +31,18 @@ export async function createConfiguredServer(normalized) {
|
|
|
35
31
|
}
|
|
36
32
|
if (enabled.auth)
|
|
37
33
|
corsHeaders['Access-Control-Allow-Headers'] += ', Authorization';
|
|
38
|
-
const { cors, logger
|
|
34
|
+
const { cors, logger } = normalized.server;
|
|
39
35
|
const openapi = async () => {
|
|
40
36
|
if (!normalized.openapi)
|
|
41
37
|
throw new Error('OpenAPI is not configured');
|
|
42
|
-
return (await import('../openapi/generate.js')).openapiFromModel(explicitModel,
|
|
43
|
-
files: enabled.files,
|
|
44
|
-
...recordSettings,
|
|
45
|
-
pageSize,
|
|
46
|
-
maxPageSize,
|
|
47
|
-
info: normalized.openapi.info,
|
|
48
|
-
host: normalized.server.host,
|
|
49
|
-
port: normalized.server.port,
|
|
50
|
-
});
|
|
38
|
+
return (await import('../openapi/generate.js')).openapiFromModel(explicitModel, openapiOptions(normalized));
|
|
51
39
|
};
|
|
52
40
|
let instance;
|
|
53
41
|
const getFastify = () => {
|
|
54
42
|
if (instance)
|
|
55
43
|
return instance;
|
|
56
|
-
const server =
|
|
57
|
-
|
|
58
|
-
const defaults = { host: normalized.server.host, port: normalized.server.port };
|
|
59
|
-
const listen = (optionsOrCallback, callback) => {
|
|
60
|
-
if (typeof optionsOrCallback === 'function') {
|
|
61
|
-
originalListen(defaults, optionsOrCallback);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (callback) {
|
|
65
|
-
originalListen({ ...defaults, ...optionsOrCallback }, callback);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
return originalListen({ ...defaults, ...optionsOrCallback });
|
|
69
|
-
};
|
|
70
|
-
server.listen = listen;
|
|
71
|
-
server.setErrorHandler((error, request, reply) => {
|
|
72
|
-
const candidate = error instanceof DomainError ? domainStatus[error.code] : isObject(error) ? error.statusCode : undefined;
|
|
73
|
-
const status = typeof candidate === 'number' && Number.isInteger(candidate) && candidate >= 400 && candidate <= 599 ? candidate : 500;
|
|
74
|
-
if (status === 500)
|
|
75
|
-
request.log.error(error);
|
|
76
|
-
return reply.code(status).send({ error: status === 500 ? 'Внутренняя ошибка сервера' : errorMessage(error) });
|
|
77
|
-
});
|
|
78
|
-
if (cors) {
|
|
79
|
-
server.addHook('onRequest', async (_request, reply) => {
|
|
80
|
-
for (const [key, value] of Object.entries(corsHeaders))
|
|
81
|
-
reply.header(key, value);
|
|
82
|
-
});
|
|
83
|
-
server.options('/', async (_request, reply) => reply.code(204).send());
|
|
84
|
-
server.options('/*', async (_request, reply) => reply.code(204).send());
|
|
85
|
-
}
|
|
86
|
-
server.register(async (app) => {
|
|
87
|
-
const [{ createDatabaseStore }, { Engine }, { registerRestRoutes }] = await Promise.all([import('../core/database.js'), import('../core/engine.js'), import('../rest/routes.js')]);
|
|
88
|
-
const keys = explicitModel ? new Map(explicitModel.entities.map((e) => [e.collection, e.primary])) : undefined;
|
|
89
|
-
const store = await createDatabaseStore(normalized.database, keys);
|
|
90
|
-
const model = explicitModel ?? inferModel(store.database.data, recordSettings);
|
|
91
|
-
const engine = new Engine(store, model, pageSize, maxPageSize);
|
|
92
|
-
engine.validateData(store.database.data);
|
|
93
|
-
const graphqlPath = normalized.graphql?.endpoint;
|
|
94
|
-
const openapiPath = normalized.openapi?.endpoint;
|
|
95
|
-
validateEndpoints(model.entities.map((entity) => entity.collection), [...(graphqlPath ? [graphqlPath] : []), ...(openapiPath ? [openapiPath] : []), ...(enabled.auth ? Object.values(AUTH_PATHS) : [])]);
|
|
96
|
-
const auth = enabled.auth && normalized.auth ? await (await import('../auth/service.js')).createAuthService(normalized.auth) : undefined;
|
|
97
|
-
if (auth) {
|
|
98
|
-
app.addHook('onClose', async () => auth.close());
|
|
99
|
-
const { registerAuthRoutes } = await import('../auth/routes.js');
|
|
100
|
-
registerAuthRoutes(app, auth);
|
|
101
|
-
}
|
|
102
|
-
const authenticate = auth ? (header) => auth.me(header) : undefined;
|
|
103
|
-
registerRestRoutes(app, engine, authenticate);
|
|
104
|
-
if (graphqlPath) {
|
|
105
|
-
const [{ registerGraphqlRoutes }, { buildGraphql }] = await Promise.all([import('../graphql/routes.js'), import('../graphql/schema.js')]);
|
|
106
|
-
registerGraphqlRoutes(app, buildGraphql(model), engine, graphqlPath, authenticate);
|
|
107
|
-
}
|
|
108
|
-
if (openapiPath) {
|
|
109
|
-
const document = await openapi();
|
|
110
|
-
document.servers = [{ url: '/' }];
|
|
111
|
-
app.get(openapiPath, async () => document);
|
|
112
|
-
}
|
|
113
|
-
const files = normalized.files;
|
|
114
|
-
if (enabled.files && files) {
|
|
115
|
-
const { createFileStore, registerFileRoutes } = await import('../files/index.js');
|
|
116
|
-
const protectedPaths = inputPaths({ database: normalized.database, auth: normalized.auth }, '.', configSourcePath(normalized));
|
|
117
|
-
registerFileRoutes(app, { getStore: () => createFileStore(files, protectedPaths), maxFileSize });
|
|
118
|
-
}
|
|
119
|
-
});
|
|
44
|
+
const server = createHttpServer({ create: Fastify, host: normalized.server.host, port: normalized.server.port, logger, cors, corsHeaders });
|
|
45
|
+
server.register(async (app) => registerConfiguredModules(app, normalized, explicitModel, openapi));
|
|
120
46
|
instance = server;
|
|
121
47
|
return server;
|
|
122
48
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/server/create.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../src/server/create.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAA0D,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC5G,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAOtD,MAAM,YAAY,GAAG;IACnB,8BAA8B,EAAE,cAAc;IAC9C,8BAA8B,EAAE,wCAAwC;IACxE,6BAA6B,EAAE,GAAG;CACnC,CAAC;AACF;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAG,IAA4B;IAChE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IAC3F,MAAM,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACtB,OAAO,sBAAsB,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/D,CAAC;AACD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,UAAkC;IAC7E,MAAM,OAAO,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;IAC/F,MAAM,aAAa,GAAG,MAAM,eAAe,CAAC,UAAU,CAAC,CAAC;IACxD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,EAAE,GAAG,YAAY,EAAE,CAAC;IACxC,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAC1D,WAAW,CAAC,8BAA8B,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpI,CAAC;IACD,IAAI,OAAO,CAAC,IAAI;QAAE,WAAW,CAAC,8BAA8B,CAAC,IAAI,iBAAiB,CAAC;IACnF,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC;IAC3C,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,IAAI,CAAC,UAAU,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACtE,OAAO,CAAC,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC,CAAC,gBAAgB,CAAC,aAAa,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;IAC9G,CAAC,CAAC;IACF,IAAI,QAAqC,CAAC;IAC1C,MAAM,UAAU,GAAG,GAAoB,EAAE;QACvC,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,MAAM,GAAG,gBAAgB,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QAC5I,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,yBAAyB,CAAC,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;QACnG,QAAQ,GAAG,MAAM,CAAC;QAClB,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IACF,OAAO;QACL,OAAO,EAAE,UAAU;QACnB,OAAO;QACP,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,IAAI,CAAC,UAAU,CAAC,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;YACtE,OAAO,CAAC,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;QAClF,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyServerOptions } from 'fastify';
|
|
2
|
+
/** Создаёт HTTP-сервер с едиными ошибками, CORS и значениями listen по умолчанию.
|
|
3
|
+
* @example createHttpServer({ host: '127.0.0.1', port: 4001, cors: false }) → FastifyInstance.
|
|
4
|
+
*/
|
|
5
|
+
export declare function createHttpServer({ create, host, port, logger, cors, corsHeaders, }: {
|
|
6
|
+
create: typeof import('fastify').default;
|
|
7
|
+
host: string;
|
|
8
|
+
port: number;
|
|
9
|
+
logger: FastifyServerOptions['logger'];
|
|
10
|
+
cors: boolean;
|
|
11
|
+
corsHeaders: Record<string, string>;
|
|
12
|
+
}): FastifyInstance;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { DomainError } from '../core/errors.js';
|
|
2
|
+
import { domainStatus } from '../core/http-errors.js';
|
|
3
|
+
import { errorMessage, isObject } from '../core/utils.js';
|
|
4
|
+
/** Создаёт HTTP-сервер с едиными ошибками, CORS и значениями listen по умолчанию.
|
|
5
|
+
* @example createHttpServer({ host: '127.0.0.1', port: 4001, cors: false }) → FastifyInstance.
|
|
6
|
+
*/
|
|
7
|
+
export function createHttpServer({ create, host, port, logger, cors, corsHeaders, }) {
|
|
8
|
+
const server = create({ ajv: { customOptions: { coerceTypes: false, removeAdditional: false } }, logger });
|
|
9
|
+
const originalListen = server.listen.bind(server);
|
|
10
|
+
const defaults = { host, port };
|
|
11
|
+
const listen = (optionsOrCallback, callback) => {
|
|
12
|
+
if (typeof optionsOrCallback === 'function') {
|
|
13
|
+
originalListen(defaults, optionsOrCallback);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (callback) {
|
|
17
|
+
originalListen({ ...defaults, ...optionsOrCallback }, callback);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
return originalListen({ ...defaults, ...optionsOrCallback });
|
|
21
|
+
};
|
|
22
|
+
server.listen = listen;
|
|
23
|
+
server.setErrorHandler((error, request, reply) => {
|
|
24
|
+
const candidate = error instanceof DomainError ? domainStatus[error.code] : isObject(error) ? error.statusCode : undefined;
|
|
25
|
+
const status = typeof candidate === 'number' && Number.isInteger(candidate) && candidate >= 400 && candidate <= 599 ? candidate : 500;
|
|
26
|
+
if (status === 500)
|
|
27
|
+
request.log.error(error);
|
|
28
|
+
return reply.code(status).send({ error: status === 500 ? 'Внутренняя ошибка сервера' : errorMessage(error) });
|
|
29
|
+
});
|
|
30
|
+
if (cors) {
|
|
31
|
+
server.addHook('onRequest', async (_request, reply) => {
|
|
32
|
+
for (const [key, value] of Object.entries(corsHeaders))
|
|
33
|
+
reply.header(key, value);
|
|
34
|
+
});
|
|
35
|
+
server.options('/', async (_request, reply) => reply.code(204).send());
|
|
36
|
+
server.options('/*', async (_request, reply) => reply.code(204).send());
|
|
37
|
+
}
|
|
38
|
+
return server;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/server/http.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAI1D;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC/B,MAAM,EACN,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,IAAI,EACJ,WAAW,GAQZ;IACC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,EAAE,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3G,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,CAAC,iBAAyD,EAAE,QAAyB,EAA+B,EAAE;QACnI,IAAI,OAAO,iBAAiB,KAAK,UAAU,EAAE,CAAC;YAC5C,cAAc,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;YAC5C,OAAO;QACT,CAAC;QACD,IAAI,QAAQ,EAAE,CAAC;YACb,cAAc,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,iBAAiB,EAAE,EAAE,QAAQ,CAAC,CAAC;YAChE,OAAO;QACT,CAAC;QACD,OAAO,cAAc,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,iBAAiB,EAAE,CAAC,CAAC;IAC/D,CAAC,CAAC;IACF,MAAM,CAAC,MAAM,GAAG,MAAmC,CAAC;IACpD,MAAM,CAAC,eAAe,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QAC/C,MAAM,SAAS,GAAG,KAAK,YAAY,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3H,MAAM,MAAM,GAAG,OAAO,SAAS,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,GAAG,IAAI,SAAS,IAAI,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC;QACtI,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC7C,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChH,CAAC,CAAC,CAAC;IACH,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;YACpD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;gBAAE,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACnF,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACvE,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { OpenapiOptions } from '../openapi/options.js';
|
|
2
|
+
import type { NormalizedServerConfig } from './config.js';
|
|
3
|
+
/** Собирает параметры OpenAPI из настроек сервера без чтения базы или файлов.
|
|
4
|
+
* @example Конфигурация с files → { files: true, ... }.
|
|
5
|
+
*/
|
|
6
|
+
export declare function openapiOptions(config: NormalizedServerConfig): OpenapiOptions;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Собирает параметры OpenAPI из настроек сервера без чтения базы или файлов.
|
|
2
|
+
* @example Конфигурация с files → { files: true, ... }.
|
|
3
|
+
*/
|
|
4
|
+
export function openapiOptions(config) {
|
|
5
|
+
return {
|
|
6
|
+
files: config.files !== undefined,
|
|
7
|
+
auth: config.auth !== undefined,
|
|
8
|
+
host: config.server.host,
|
|
9
|
+
port: config.server.port,
|
|
10
|
+
pageSize: config.server.pageSize,
|
|
11
|
+
maxPageSize: config.server.maxPageSize,
|
|
12
|
+
info: config.openapi?.info,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=openapi-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi-options.js","sourceRoot":"","sources":["../../../src/server/openapi-options.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,MAA8B;IAC3D,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK,KAAK,SAAS;QACjC,IAAI,EAAE,MAAM,CAAC,IAAI,KAAK,SAAS;QAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI;QACxB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI;QACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ;QAChC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW;QACtC,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI;KAC3B,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kollors/deep-json-server",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
4
|
-
"description": "JSON mock server with schema-driven REST, GraphQL,
|
|
3
|
+
"version": "1.0.0-alpha.12",
|
|
4
|
+
"description": "JSON mock server with schema-driven REST, GraphQL, OpenAPI generation and file uploads",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
],
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "node scripts/clean-dist.js && tsc && node scripts/prepare-dist.js",
|
|
41
|
-
"lint": "biome check .",
|
|
41
|
+
"lint": "biome check . --error-on-warnings",
|
|
42
42
|
"lint:fix": "biome check --write .",
|
|
43
43
|
"pack:check": "node scripts/check-package.js",
|
|
44
44
|
"prepack": "npm run build",
|