@kollors/deep-json-server 0.9.0 → 1.0.0-alpha.10
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 +566 -347
- package/README.ru.md +560 -341
- 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 -5
- 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 +122 -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} +2 -1
- package/dist/src/{constants.js → core/constants.js} +2 -1
- 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} +64 -29
- package/dist/src/core/database.js.map +1 -0
- package/dist/src/core/engine.d.ts +65 -0
- package/dist/src/core/engine.js +312 -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/core/model.js +510 -0
- 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/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-store.d.ts +5 -1
- package/dist/src/files/disk-store.js +61 -33
- 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 +42 -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/schema.js +230 -0
- 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 +11 -7
- package/dist/src/openapi/document.js +287 -318
- 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/openapi/files.js +87 -0
- 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/{types.d.ts → openapi/types.d.ts} +2 -12
- 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 +49 -0
- package/dist/src/rest/routes.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 +133 -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/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/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 +25 -4
- package/dist/src/cli.d.ts +0 -4
- package/dist/src/cli.js +0 -80
- package/dist/src/cli.js.map +0 -1
- package/dist/src/config.d.ts +0 -54
- package/dist/src/config.js +0 -145
- package/dist/src/config.js.map +0 -1
- package/dist/src/constants.js.map +0 -1
- package/dist/src/database.d.ts +0 -20
- package/dist/src/database.js.map +0 -1
- package/dist/src/openapi/config.d.ts +0 -11
- package/dist/src/openapi/config.js +0 -204
- package/dist/src/openapi/config.js.map +0 -1
- package/dist/src/openapi/index.d.ts +0 -10
- package/dist/src/openapi/index.js +0 -27
- package/dist/src/openapi/index.js.map +0 -1
- package/dist/src/openapi/inference.d.ts +0 -14
- package/dist/src/openapi/inference.js +0 -145
- package/dist/src/openapi/inference.js.map +0 -1
- package/dist/src/query/filter.d.ts +0 -6
- package/dist/src/query/filter.js +0 -248
- package/dist/src/query/filter.js.map +0 -1
- package/dist/src/query/index.d.ts +0 -3
- package/dist/src/query/index.js +0 -4
- package/dist/src/query/index.js.map +0 -1
- package/dist/src/query/pagination.d.ts +0 -11
- package/dist/src/query/pagination.js +0 -28
- package/dist/src/query/pagination.js.map +0 -1
- package/dist/src/query/sort.d.ts +0 -1
- package/dist/src/query/sort.js +0 -54
- package/dist/src/query/sort.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/relations.d.ts +0 -12
- package/dist/src/relations.js +0 -155
- package/dist/src/relations.js.map +0 -1
- package/dist/src/server.d.ts +0 -13
- package/dist/src/server.js +0 -188
- package/dist/src/server.js.map +0 -1
- package/dist/src/utils.d.ts +0 -20
- package/dist/src/utils.js +0 -63
- package/dist/src/utils.js.map +0 -1
- /package/dist/src/{types.js → core/types.js} +0 -0
|
@@ -1,338 +1,307 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { VERSION } from '../core/constants.js';
|
|
2
|
+
import { assertApi, canonicalNode, nodeName, objectSchema, operationName, relationInputSchema, valueSchema } from '../core/model.js';
|
|
3
|
+
import { MUTATIONS } from '../core/operations.js';
|
|
4
|
+
import { normalizePagination } from '../core/pagination.js';
|
|
5
|
+
import { operatorsFor } from '../core/query/contract.js';
|
|
6
|
+
import { sortableFields } from '../core/query/options.js';
|
|
7
|
+
import { capitalize, isObject } from '../core/utils.js';
|
|
8
|
+
import { FILE_HEADERS, FILE_METADATA_SCHEMA, FILE_UPDATE_SCHEMA } from '../files/http.js';
|
|
9
|
+
import { AUTH_SCHEMAS, AUTH_SECURITY_SCHEMES, authOpenapiPaths } from './auth.js';
|
|
10
|
+
import { createFilePaths } from './files.js';
|
|
11
|
+
import { json, ref, response } from './helpers.js';
|
|
12
|
+
/** Преобразует nullable-значения JSON Schema в представление OpenAPI 3.0, рекурсивно обрабатывая поля и элементы.
|
|
13
|
+
* @example { anyOf: [{ type: 'string' }, { type: 'null' }] } → { type: 'string', nullable: true }.
|
|
14
|
+
*/
|
|
15
|
+
function toOpenapi(schema) {
|
|
16
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.some((v) => isObject(v) && v.type === 'null')) {
|
|
17
|
+
const nonNull = schema.anyOf.find((v) => isObject(v) && v.type !== 'null');
|
|
18
|
+
const converted = toOpenapi(nonNull);
|
|
19
|
+
if (!converted.type)
|
|
20
|
+
return { anyOf: [converted, { type: 'object', nullable: true, enum: [null] }] };
|
|
21
|
+
return { ...converted, nullable: true, ...(converted.enum ? { enum: [...converted.enum.filter((value) => value !== null), null] } : {}) };
|
|
12
22
|
}
|
|
13
|
-
|
|
14
|
-
|
|
23
|
+
const result = { ...schema };
|
|
24
|
+
if (isObject(schema.properties))
|
|
25
|
+
result.properties = Object.fromEntries(Object.entries(schema.properties).map(([k, v]) => [k, toOpenapi(v)]));
|
|
26
|
+
if (isObject(schema.items))
|
|
27
|
+
result.items = toOpenapi(schema.items);
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
/** Строит документ с маршрутами и схемами записей, включая выбранные дополнительные маршруты.
|
|
31
|
+
* @example { model, files: true } → документ с paths['/_files/storage'].
|
|
32
|
+
*/
|
|
33
|
+
export function buildOpenapiDocument({ model, files = false, auth = false, pageSize, maxPageSize, info = { title: 'Deep JSON Server API', version: VERSION }, }) {
|
|
34
|
+
assertApi(model, 'openapi');
|
|
35
|
+
({ pageSize, maxPageSize } = normalizePagination({ pageSize, maxPageSize }));
|
|
36
|
+
const schemas = {
|
|
37
|
+
Error: { type: 'object', properties: { error: { type: 'string' } }, required: ['error'] },
|
|
38
|
+
Pager: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
additionalProperties: false,
|
|
41
|
+
properties: { page: { type: 'integer', minimum: 1, default: 1 }, pageSize: { type: 'integer', minimum: 1, maximum: maxPageSize, default: pageSize } },
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const owners = new Map();
|
|
45
|
+
function reserve(name, node) {
|
|
46
|
+
if (owners.get(name) === node)
|
|
47
|
+
return false;
|
|
48
|
+
if (schemas[name])
|
|
49
|
+
throw new Error(`OpenAPI schema name collision: ${name}`);
|
|
50
|
+
owners.set(name, node);
|
|
51
|
+
schemas[name] = {};
|
|
52
|
+
return true;
|
|
15
53
|
}
|
|
16
|
-
|
|
54
|
+
function annotate(schema, node) {
|
|
55
|
+
for (const key of ['description', 'example', 'default', 'readOnly', 'writeOnly'])
|
|
56
|
+
if (node[key] !== undefined)
|
|
57
|
+
schema[key] = node[key];
|
|
58
|
+
if (node.generated)
|
|
59
|
+
schema.readOnly = true;
|
|
17
60
|
return schema;
|
|
18
61
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
62
|
+
function baseField(node) {
|
|
63
|
+
return annotate(toOpenapi(valueSchema(node)), node);
|
|
64
|
+
}
|
|
65
|
+
function annotateInput(schema, node, defaults) {
|
|
66
|
+
for (const [key, property] of Object.entries(schema.properties ?? {})) {
|
|
67
|
+
const child = node.children[key];
|
|
68
|
+
for (const attribute of ['description', 'example', 'writeOnly'])
|
|
69
|
+
if (child[attribute] !== undefined)
|
|
70
|
+
property[attribute] = child[attribute];
|
|
71
|
+
if (defaults && child.default !== undefined)
|
|
72
|
+
property.default = child.default;
|
|
73
|
+
if (!child.relation && child.base === 'object')
|
|
74
|
+
annotateInput(child.many ? property.items : property, child, defaults);
|
|
25
75
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (Array.isArray(schema.oneOf)) {
|
|
31
|
-
schema.oneOf.forEach((nestedSchema) => {
|
|
32
|
-
collectRelations(nestedSchema, resources, sourceResource, relations);
|
|
33
|
-
});
|
|
34
|
-
return relations;
|
|
76
|
+
return schema;
|
|
77
|
+
}
|
|
78
|
+
function writeInput(node, mode, root = false, nestedMode = mode) {
|
|
79
|
+
return annotateInput(toOpenapi(objectSchema(node, mode, root, (child) => relationInputSchema(child, nestedInput(child.relation, nestedMode)))), node, mode !== 'update');
|
|
35
80
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
81
|
+
function nestedInput(entity, mode) {
|
|
82
|
+
const name = `${entity.name}Nested${capitalize(mode)}`;
|
|
83
|
+
if (!reserve(name, entity.root))
|
|
84
|
+
return ref(name);
|
|
85
|
+
const existing = writeInput(entity.root, mode === 'replace' ? 'replace' : 'update', true, mode);
|
|
86
|
+
existing.properties ??= {};
|
|
87
|
+
existing.properties[entity.primary] = toOpenapi(valueSchema(entity.fields[entity.primary]));
|
|
88
|
+
existing.required = [...(existing.required ?? []), entity.primary];
|
|
89
|
+
const variants = [existing];
|
|
90
|
+
if (entity.fields[entity.primary].generated)
|
|
91
|
+
variants.push(writeInput(entity.root, 'create', true, mode));
|
|
92
|
+
schemas[name] = { anyOf: variants, description: 'An object with a primary key updates an existing record; an object without a key creates one. PUT replaces, PATCH updates supplied fields.' };
|
|
93
|
+
return ref(name);
|
|
39
94
|
}
|
|
40
|
-
|
|
41
|
-
|
|
95
|
+
function output(entity, node) {
|
|
96
|
+
[entity, node] = canonicalNode(entity, node);
|
|
97
|
+
const name = nodeName(entity, node);
|
|
98
|
+
if (!reserve(name, node))
|
|
99
|
+
return ref(name);
|
|
100
|
+
const properties = {};
|
|
101
|
+
for (const [key, child] of Object.entries(node.children)) {
|
|
102
|
+
if (child.writeOnly)
|
|
103
|
+
continue;
|
|
104
|
+
if (child.relation || child.base === 'object') {
|
|
105
|
+
const value = child.many ? page(entity, child) : output(entity, child);
|
|
106
|
+
const shape = child.nullable || (child.relation && !child.many && !child.required) ? { anyOf: [value, { type: 'object', nullable: true, enum: [null] }] } : value;
|
|
107
|
+
const attributes = annotate({}, child);
|
|
108
|
+
properties[key] = Object.keys(attributes).length ? { allOf: [shape], ...attributes } : shape;
|
|
109
|
+
}
|
|
110
|
+
else
|
|
111
|
+
properties[key] = baseField(child);
|
|
112
|
+
}
|
|
113
|
+
// Выбор может содержать любую часть полей, поэтому поля ответа необязательны.
|
|
114
|
+
schemas[name] = { type: 'object', additionalProperties: false, properties };
|
|
115
|
+
return ref(name);
|
|
42
116
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
117
|
+
function scope(entity, node, list = node.many) {
|
|
118
|
+
[entity, node] = canonicalNode(entity, node);
|
|
119
|
+
const name = `${nodeName(entity, node)}${list ? 'List' : ''}Scope`;
|
|
120
|
+
if (!reserve(name, node))
|
|
121
|
+
return ref(name);
|
|
122
|
+
const fieldsName = `${nodeName(entity, node)}ScopeFields`;
|
|
123
|
+
if (reserve(fieldsName, node)) {
|
|
124
|
+
const included = { type: 'boolean', enum: [true] };
|
|
125
|
+
const properties = { '*': included };
|
|
126
|
+
for (const [key, child] of Object.entries(node.children)) {
|
|
127
|
+
if (child.writeOnly)
|
|
128
|
+
continue;
|
|
129
|
+
properties[key] = child.relation || child.base === 'object' ? scope(entity, child) : included;
|
|
130
|
+
}
|
|
131
|
+
schemas[fieldsName] = { type: 'object', additionalProperties: false, properties };
|
|
47
132
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
133
|
+
schemas[name] = {
|
|
134
|
+
type: 'array',
|
|
135
|
+
minItems: 1,
|
|
136
|
+
maxItems: list ? 2 : 1,
|
|
137
|
+
items: list ? { anyOf: [ref(fieldsName), options(entity, node)] } : ref(fieldsName),
|
|
138
|
+
description: list
|
|
139
|
+
? '[fields, arguments?]. The first object selects fields; the optional second object contains where, order and pager. OpenAPI 3.0 cannot express positional item schemas; the server validates their order.'
|
|
140
|
+
: '[fields]. * selects own fields without relations or writeOnly fields.',
|
|
141
|
+
};
|
|
142
|
+
return ref(name);
|
|
143
|
+
}
|
|
144
|
+
function page(entity, node) {
|
|
145
|
+
[entity, node] = canonicalNode(entity, node);
|
|
146
|
+
const name = `${nodeName(entity, node)}Page`;
|
|
147
|
+
if (reserve(name, node))
|
|
148
|
+
schemas[name] = { type: 'object', required: ['data', 'total'], properties: { data: { type: 'array', items: output(entity, node) }, total: { type: 'integer', minimum: 0 } } };
|
|
149
|
+
return ref(name);
|
|
150
|
+
}
|
|
151
|
+
function where(entity, node) {
|
|
152
|
+
[entity, node] = canonicalNode(entity, node);
|
|
153
|
+
const name = `${nodeName(entity, node)}Where`;
|
|
154
|
+
if (!reserve(name, node))
|
|
155
|
+
return ref(name);
|
|
156
|
+
const properties = { and: { type: 'array', items: ref(name) }, or: { type: 'array', minItems: 1, items: ref(name) }, not: ref(name) };
|
|
157
|
+
for (const [key, child] of Object.entries(node.children))
|
|
158
|
+
if (!child.writeOnly) {
|
|
159
|
+
if (Object.hasOwn(properties, key))
|
|
160
|
+
throw new Error(`Reserved filter field: ${entity.name}.${child.path}`);
|
|
161
|
+
properties[key] = !child.many && (child.relation || child.base === 'object') ? where(entity, child) : filter(entity, child);
|
|
65
162
|
}
|
|
163
|
+
schemas[name] = { type: 'object', additionalProperties: false, properties };
|
|
164
|
+
return ref(name);
|
|
165
|
+
}
|
|
166
|
+
function filter(entity, node) {
|
|
167
|
+
const name = `${nodeName(entity, node)}Filter`;
|
|
168
|
+
if (!reserve(name, node))
|
|
169
|
+
return ref(name);
|
|
170
|
+
const properties = {};
|
|
171
|
+
const element = node.many ? (node.relation || node.base === 'object' ? where(entity, node) : filter(entity, { ...node, many: false, path: `${node.path}_element` })) : undefined;
|
|
172
|
+
for (const [key, operand] of Object.entries(operatorsFor(node))) {
|
|
173
|
+
const nullable = ['eq', 'ne', 'in'].includes(key);
|
|
174
|
+
const scalar = {
|
|
175
|
+
type: node.base,
|
|
176
|
+
...(nullable ? { nullable: true } : {}),
|
|
177
|
+
...(node.enum ? { enum: [...node.enum, ...(nullable && !node.enum.includes(null) ? [null] : [])] } : {}),
|
|
178
|
+
};
|
|
179
|
+
if (operand === 'condition')
|
|
180
|
+
properties[key] = ref(name);
|
|
181
|
+
else if (operand === 'element')
|
|
182
|
+
properties[key] = element;
|
|
183
|
+
else
|
|
184
|
+
properties[key] =
|
|
185
|
+
operand === 'values' ? { type: 'array', items: scalar } : operand === 'text' ? { type: 'string' } : operand === 'comparison' ? { type: node.base } : scalar;
|
|
186
|
+
}
|
|
187
|
+
schemas[name] = { type: 'object', additionalProperties: false, properties };
|
|
188
|
+
return ref(name);
|
|
189
|
+
}
|
|
190
|
+
function order(entity, node) {
|
|
191
|
+
[entity, node] = canonicalNode(entity, node);
|
|
192
|
+
const name = `${nodeName(entity, node)}Order`;
|
|
193
|
+
if (reserve(name, node)) {
|
|
194
|
+
const paths = sortableFields(node);
|
|
195
|
+
schemas[name] = {
|
|
196
|
+
type: 'array',
|
|
197
|
+
...(paths.length
|
|
198
|
+
? {
|
|
199
|
+
items: {
|
|
200
|
+
type: 'object',
|
|
201
|
+
additionalProperties: false,
|
|
202
|
+
required: ['field', 'direction'],
|
|
203
|
+
properties: { field: { type: 'string', enum: paths }, direction: { type: 'string', enum: ['ASC', 'DESC'] } },
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
: { maxItems: 0, items: { type: 'object' } }),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return ref(name);
|
|
210
|
+
}
|
|
211
|
+
function options(entity, node) {
|
|
212
|
+
return { type: 'object', additionalProperties: false, properties: { where: where(entity, node), order: order(entity, node), pager: ref('Pager') } };
|
|
213
|
+
}
|
|
214
|
+
const paths = {};
|
|
215
|
+
const operations = new Set();
|
|
216
|
+
for (const entity of model.entities.filter((e) => e.api.includes('openapi'))) {
|
|
217
|
+
const name = entity.name;
|
|
218
|
+
const op = operationName(entity);
|
|
219
|
+
for (const { mode, hasBody } of MUTATIONS) {
|
|
220
|
+
if (!hasBody)
|
|
221
|
+
continue;
|
|
222
|
+
const key = `${name}${capitalize(mode)}`;
|
|
223
|
+
reserve(key, entity.root);
|
|
224
|
+
schemas[key] = writeInput(entity.root, mode, true);
|
|
225
|
+
}
|
|
226
|
+
const selectionParameter = (list) => ({
|
|
227
|
+
in: 'query',
|
|
228
|
+
name: 'scope',
|
|
229
|
+
...json(scope(entity, entity.root, list)),
|
|
230
|
+
description: 'JSON [fields, arguments?]. Scalars use true; objects and relations use their own scope arrays. * includes own fields without relations or writeOnly fields. Arguments are available only on lists.',
|
|
66
231
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Page: { in: 'query', name: '_page', required: false, schema: { default: 1, minimum: 1, type: 'integer' } },
|
|
82
|
-
PerPage: { in: 'query', name: '_perPage', required: false, schema: { default: DEFAULT_PAGE_SIZE, maximum: maxPageSize, minimum: 1, type: 'integer' } },
|
|
83
|
-
Sort: { description: 'Comma-separated fields; prefix with - for descending order', in: 'query', name: '_sort', schema: { type: 'string' } },
|
|
84
|
-
Where: { description: 'JSON-encoded filter for nested data', in: 'query', name: '_where', schema: { type: 'string' } },
|
|
85
|
-
});
|
|
86
|
-
const createJsonContent = (schema) => ({ content: { 'application/json': { schema } } });
|
|
87
|
-
const createResponse = (description, schema) => ({ description, ...(schema != null && createJsonContent(schema)) });
|
|
88
|
-
const createErrorResponse = (description) => createResponse(description, createSchemaReference('Error'));
|
|
89
|
-
const createParameterReference = (name) => ({ $ref: `#/components/parameters/${name}` });
|
|
90
|
-
const createRequestBody = (name) => ({ required: true, ...createJsonContent(createSchemaReference(name)) });
|
|
91
|
-
const createFilePaths = () => ({
|
|
92
|
-
[`${FILE_ROUTES.download}/{path}`]: {
|
|
93
|
-
get: {
|
|
94
|
-
operationId: 'downloadFile',
|
|
95
|
-
parameters: [createParameterReference('FilePath')],
|
|
96
|
-
responses: {
|
|
97
|
-
200: { content: { '*/*': { schema: { format: 'binary', type: 'string' } } }, description: 'File download' },
|
|
98
|
-
400: createErrorResponse('Invalid path'),
|
|
99
|
-
404: createErrorResponse('Not found'),
|
|
100
|
-
},
|
|
101
|
-
tags: ['files'],
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
[`${FILE_ROUTES.metadata}/{path}`]: {
|
|
105
|
-
get: {
|
|
106
|
-
operationId: 'getFileMetadata',
|
|
107
|
-
parameters: [createParameterReference('FilePath')],
|
|
108
|
-
responses: {
|
|
109
|
-
200: createResponse('File metadata', createSchemaReference('FileMetadata')),
|
|
110
|
-
400: createErrorResponse('Invalid path'),
|
|
111
|
-
404: createErrorResponse('Not found'),
|
|
112
|
-
},
|
|
113
|
-
tags: ['files'],
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
[FILE_ROUTES.storage]: {
|
|
117
|
-
post: {
|
|
118
|
-
operationId: 'uploadFile',
|
|
119
|
-
parameters: ['ContentName', 'ContentDirectory', 'ContentOverride'].map(createParameterReference),
|
|
120
|
-
requestBody: { content: { '*/*': { schema: { format: 'binary', type: 'string' } } }, required: true },
|
|
121
|
-
responses: {
|
|
122
|
-
200: createResponse('Overwritten', createSchemaReference('FileMetadata')),
|
|
123
|
-
201: createResponse('Created', createSchemaReference('FileMetadata')),
|
|
124
|
-
400: createErrorResponse('Invalid request'),
|
|
125
|
-
409: createErrorResponse('Already exists'),
|
|
126
|
-
413: createErrorResponse('File is too large'),
|
|
127
|
-
415: createErrorResponse('Unsupported media type'),
|
|
128
|
-
},
|
|
129
|
-
tags: ['files'],
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
[`${FILE_ROUTES.storage}/{path}`]: {
|
|
133
|
-
delete: {
|
|
134
|
-
operationId: 'deleteFile',
|
|
135
|
-
parameters: [createParameterReference('FilePath')],
|
|
136
|
-
responses: {
|
|
137
|
-
204: { description: 'Deleted' },
|
|
138
|
-
400: createErrorResponse('Invalid path'),
|
|
139
|
-
404: createErrorResponse('Not found'),
|
|
140
|
-
},
|
|
141
|
-
tags: ['files'],
|
|
142
|
-
},
|
|
143
|
-
get: {
|
|
144
|
-
operationId: 'getFileContent',
|
|
145
|
-
parameters: [createParameterReference('FilePath')],
|
|
146
|
-
responses: {
|
|
147
|
-
200: { content: { '*/*': { schema: { format: 'binary', type: 'string' } } }, description: 'File contents' },
|
|
148
|
-
400: createErrorResponse('Invalid path'),
|
|
149
|
-
404: createErrorResponse('Not found'),
|
|
150
|
-
},
|
|
151
|
-
tags: ['files'],
|
|
152
|
-
},
|
|
153
|
-
patch: {
|
|
154
|
-
operationId: 'updateFile',
|
|
155
|
-
parameters: [createParameterReference('FilePath')],
|
|
156
|
-
requestBody: createRequestBody('FileUpdate'),
|
|
157
|
-
responses: {
|
|
158
|
-
200: createResponse('Updated', createSchemaReference('FileMetadata')),
|
|
159
|
-
400: createErrorResponse('Invalid request'),
|
|
160
|
-
404: createErrorResponse('Not found'),
|
|
161
|
-
409: createErrorResponse('Already exists'),
|
|
162
|
-
413: createErrorResponse('Request is too large'),
|
|
163
|
-
415: createErrorResponse('Unsupported media type'),
|
|
164
|
-
},
|
|
165
|
-
tags: ['files'],
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
});
|
|
169
|
-
const createResourceOperationIds = (resource) => {
|
|
170
|
-
const resourceName = toPascalCase(resource);
|
|
171
|
-
return {
|
|
172
|
-
create: `post${resourceName}`,
|
|
173
|
-
get: `get${resourceName}ById`,
|
|
174
|
-
list: `get${resourceName}`,
|
|
175
|
-
remove: `delete${resourceName}ById`,
|
|
176
|
-
replace: `put${resourceName}ById`,
|
|
177
|
-
update: `patch${resourceName}ById`,
|
|
178
|
-
};
|
|
179
|
-
};
|
|
180
|
-
const createResourcePaths = (resource, componentName) => {
|
|
181
|
-
const operationIds = createResourceOperationIds(resource);
|
|
182
|
-
return {
|
|
183
|
-
[`/${resource}`]: {
|
|
184
|
-
get: {
|
|
185
|
-
operationId: operationIds.list,
|
|
186
|
-
parameters: ['Page', 'PerPage', 'Sort', 'Where', 'Embed'].map(createParameterReference),
|
|
187
|
-
responses: { 200: createResponse('Successful response', createSchemaReference(`${componentName}Page`)), 400: createErrorResponse('Invalid query') },
|
|
188
|
-
tags: [resource],
|
|
189
|
-
},
|
|
190
|
-
post: {
|
|
191
|
-
operationId: operationIds.create,
|
|
192
|
-
requestBody: createRequestBody(`${componentName}Create`),
|
|
193
|
-
responses: { 201: createResponse('Created', createSchemaReference(componentName)), 400: createErrorResponse('Invalid request') },
|
|
194
|
-
tags: [resource],
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
[`/${resource}/{id}`]: {
|
|
198
|
-
delete: {
|
|
199
|
-
operationId: operationIds.remove,
|
|
200
|
-
parameters: [createParameterReference('Id')],
|
|
201
|
-
responses: { 200: createResponse('Deleted', createSchemaReference(componentName)), 404: createErrorResponse('Not found') },
|
|
202
|
-
tags: [resource],
|
|
203
|
-
},
|
|
204
|
-
get: {
|
|
205
|
-
operationId: operationIds.get,
|
|
206
|
-
parameters: [createParameterReference('Id'), createParameterReference('Embed')],
|
|
232
|
+
const shape = [selectionParameter(false)];
|
|
233
|
+
const list = [selectionParameter(true)];
|
|
234
|
+
const key = { in: 'path', name: entity.primary, required: true, schema: baseField({ ...entity.fields[entity.primary], generated: undefined }) };
|
|
235
|
+
const errors = { 400: response('Invalid request', ref('Error')), 404: response('Not found', ref('Error')), 409: response('Conflict', ref('Error')) };
|
|
236
|
+
const make = (operationId, parameters, schema, mutation) => {
|
|
237
|
+
if (operations.has(operationId))
|
|
238
|
+
throw new Error(`OpenAPI operation collision: ${operationId}`);
|
|
239
|
+
operations.add(operationId);
|
|
240
|
+
return {
|
|
241
|
+
operationId,
|
|
242
|
+
tags: [entity.collection],
|
|
243
|
+
parameters,
|
|
244
|
+
...(auth && mutation ? { security: [{ AuthBearer: [] }] } : {}),
|
|
245
|
+
...(mutation?.hasBody ? { requestBody: { required: true, ...json(ref(`${name}${capitalize(mutation.mode)}`)) } } : {}),
|
|
207
246
|
responses: {
|
|
208
|
-
200:
|
|
209
|
-
|
|
210
|
-
|
|
247
|
+
[mutation?.status ?? 200]: response('Success', schema),
|
|
248
|
+
...errors,
|
|
249
|
+
...(auth && mutation ? { 401: response('Authentication required', ref('Error')), 403: response('Forbidden', ref('Error')) } : {}),
|
|
211
250
|
},
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
400: createErrorResponse('Invalid request'),
|
|
221
|
-
404: createErrorResponse('Not found'),
|
|
222
|
-
},
|
|
223
|
-
tags: [resource],
|
|
224
|
-
},
|
|
225
|
-
put: {
|
|
226
|
-
operationId: operationIds.replace,
|
|
227
|
-
parameters: [createParameterReference('Id')],
|
|
228
|
-
requestBody: createRequestBody(`${componentName}Create`),
|
|
229
|
-
responses: {
|
|
230
|
-
200: createResponse('Replaced', createSchemaReference(componentName)),
|
|
231
|
-
400: createErrorResponse('Invalid request'),
|
|
232
|
-
404: createErrorResponse('Not found'),
|
|
233
|
-
},
|
|
234
|
-
tags: [resource],
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
};
|
|
238
|
-
};
|
|
239
|
-
const validateGeneratedNames = (resources, componentNames, files) => {
|
|
240
|
-
const schemaOwners = new Map([
|
|
241
|
-
['Error', 'встроенная схема ошибки'],
|
|
242
|
-
...(files
|
|
243
|
-
? [
|
|
244
|
-
['FileMetadata', 'встроенная схема метаданных файла'],
|
|
245
|
-
['FileUpdate', 'встроенная схема изменения файла'],
|
|
246
|
-
]
|
|
247
|
-
: []),
|
|
248
|
-
]);
|
|
249
|
-
resources.forEach((resource) => {
|
|
250
|
-
const componentName = componentNames[resource];
|
|
251
|
-
if (componentName === '') {
|
|
252
|
-
throw new Error(`Не удалось сформировать имя OpenAPI-схемы для ресурса «${resource}». Укажите $schema.${resource}.name`);
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
const collectionPath = `/${entity.collection}`;
|
|
254
|
+
const itemPath = `${collectionPath}/{${entity.primary}}`;
|
|
255
|
+
paths[collectionPath] = { get: make(`${op}List`, list, page(entity, entity.root)) };
|
|
256
|
+
paths[itemPath] = { get: make(op, [key, ...shape], output(entity, entity.root)) };
|
|
257
|
+
for (const mutation of MUTATIONS) {
|
|
258
|
+
paths[mutation.hasKey ? itemPath : collectionPath][mutation.method.toLowerCase()] = make(`${op}${capitalize(mutation.mode)}`, mutation.hasKey ? [key, ...shape] : shape, output(entity, entity.root), mutation);
|
|
253
259
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
+
}
|
|
261
|
+
const parameters = {};
|
|
262
|
+
if (files) {
|
|
263
|
+
for (const [name, schema] of Object.entries({ FileMetadata: FILE_METADATA_SCHEMA, FileUpdate: FILE_UPDATE_SCHEMA })) {
|
|
264
|
+
if (schemas[name])
|
|
265
|
+
throw new Error(`OpenAPI schema collision: ${name}`);
|
|
266
|
+
schemas[name] = schema;
|
|
267
|
+
}
|
|
268
|
+
Object.assign(parameters, {
|
|
269
|
+
FilePath: { in: 'path', name: 'path', required: true, schema: { type: 'string' } },
|
|
270
|
+
ContentDirectory: { in: 'header', name: FILE_HEADERS.directory.name, schema: { type: 'string' } },
|
|
271
|
+
ContentName: { in: 'header', name: FILE_HEADERS.name.name, required: true, schema: { type: 'string' } },
|
|
272
|
+
ContentOverride: { in: 'header', name: FILE_HEADERS.override.name, schema: { type: 'string', enum: ['false', 'true'], default: 'false' } },
|
|
260
273
|
});
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const owner = operationOwners.get(operation.operationId);
|
|
271
|
-
if (owner != null) {
|
|
272
|
-
throw new Error(`operationId «${operation.operationId}» используется операциями «${owner}» и «${method.toUpperCase()} ${path}»`);
|
|
274
|
+
for (const [path, item] of Object.entries(createFilePaths())) {
|
|
275
|
+
if (paths[path])
|
|
276
|
+
throw new Error(`File path collision: ${path}`);
|
|
277
|
+
for (const operation of Object.values(item)) {
|
|
278
|
+
if (isObject(operation) && typeof operation.operationId === 'string') {
|
|
279
|
+
if (operations.has(operation.operationId))
|
|
280
|
+
throw new Error(`OpenAPI operation collision: ${operation.operationId}`);
|
|
281
|
+
operations.add(operation.operationId);
|
|
282
|
+
}
|
|
273
283
|
}
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
};
|
|
278
|
-
/** Builds an OpenAPI document without runtime server addresses. */
|
|
279
|
-
export function buildOpenapiDocument(options) {
|
|
280
|
-
const { database, files = false, maxPageSize = DEFAULT_MAX_PAGE_SIZE, schema: schemaConfig = {} } = options ?? {};
|
|
281
|
-
if (typeof files !== 'boolean') {
|
|
282
|
-
throw new Error('Ключ files должен содержать boolean');
|
|
283
|
-
}
|
|
284
|
-
validateDatabase(database);
|
|
285
|
-
if (!Number.isInteger(maxPageSize) || maxPageSize < 1) {
|
|
286
|
-
throw new Error('Максимальный размер страницы должен быть положительным целым числом');
|
|
284
|
+
paths[path] = item;
|
|
285
|
+
}
|
|
287
286
|
}
|
|
288
|
-
if (
|
|
289
|
-
|
|
287
|
+
if (auth) {
|
|
288
|
+
for (const [name, schema] of Object.entries(AUTH_SCHEMAS)) {
|
|
289
|
+
if (schemas[name])
|
|
290
|
+
throw new Error(`OpenAPI schema collision: ${name}`);
|
|
291
|
+
schemas[name] = structuredClone(schema);
|
|
292
|
+
}
|
|
293
|
+
for (const [path, item] of Object.entries(authOpenapiPaths())) {
|
|
294
|
+
if (paths[path] || model.entities.some((entity) => path.startsWith(`/${entity.collection}/`)))
|
|
295
|
+
throw new Error(`Auth path collision: ${path}`);
|
|
296
|
+
for (const operation of Object.values(item)) {
|
|
297
|
+
const id = operation.operationId;
|
|
298
|
+
if (operations.has(id))
|
|
299
|
+
throw new Error(`OpenAPI operation collision: ${id}`);
|
|
300
|
+
operations.add(id);
|
|
301
|
+
}
|
|
302
|
+
paths[path] = item;
|
|
303
|
+
}
|
|
290
304
|
}
|
|
291
|
-
|
|
292
|
-
const resourceConfigs = normalizeSchemaConfig(schemaConfig, resources);
|
|
293
|
-
const componentNames = Object.fromEntries(resources.map((resource) => {
|
|
294
|
-
const resourceConfig = resourceConfigs[resource];
|
|
295
|
-
const componentName = resourceConfig.name ?? toPascalCase(singularize(resource));
|
|
296
|
-
return [resource, componentName];
|
|
297
|
-
}));
|
|
298
|
-
validateGeneratedNames(resources, componentNames, files);
|
|
299
|
-
const rawSchemas = Object.fromEntries(resources.map((resource) => {
|
|
300
|
-
const resourceConfig = resourceConfigs[resource];
|
|
301
|
-
const inferredSchema = database[resource].length === 0 ? { properties: { id: { type: 'string' } }, type: 'object' } : inferObjectSchema(database[resource]);
|
|
302
|
-
const configuredSchema = mergeSchemaOverrides(inferredSchema, { properties: resourceConfig.properties });
|
|
303
|
-
return [resource, applyConfiguredFields(ensureGeneratedIdSchema(configuredSchema), resource, resourceConfig)];
|
|
304
|
-
}));
|
|
305
|
-
const schemas = {
|
|
306
|
-
Error: { properties: { error: { type: 'string' } }, required: ['error'], type: 'object' },
|
|
307
|
-
...(files && {
|
|
308
|
-
FileMetadata: FILE_METADATA_SCHEMA,
|
|
309
|
-
FileUpdate: FILE_UPDATE_SCHEMA,
|
|
310
|
-
}),
|
|
311
|
-
};
|
|
312
|
-
resources.forEach((resource) => {
|
|
313
|
-
const componentName = componentNames[resource];
|
|
314
|
-
const rawSchema = rawSchemas[resource];
|
|
315
|
-
schemas[componentName] = addForwardRelations(rawSchema, resources, componentNames, resource);
|
|
316
|
-
schemas[`${componentName}Create`] = omitId(rawSchema, true);
|
|
317
|
-
schemas[`${componentName}Update`] = omitId(rawSchema, false);
|
|
318
|
-
schemas[`${componentName}Page`] = {
|
|
319
|
-
properties: {
|
|
320
|
-
data: { items: createSchemaReference(componentName), type: 'array' },
|
|
321
|
-
total: { minimum: 0, type: 'integer' },
|
|
322
|
-
},
|
|
323
|
-
required: ['data', 'total'],
|
|
324
|
-
type: 'object',
|
|
325
|
-
};
|
|
326
|
-
});
|
|
327
|
-
addReverseRelations(schemas, rawSchemas, resources, componentNames);
|
|
328
|
-
const paths = Object.assign({}, ...resources.map((resource) => createResourcePaths(resource, componentNames[resource])), files ? createFilePaths() : {});
|
|
329
|
-
validateOperationIds(paths);
|
|
330
|
-
return {
|
|
331
|
-
components: { parameters: createParameters(maxPageSize), schemas },
|
|
332
|
-
info: isObject(schemaConfig.$info) ? schemaConfig.$info : { title: 'Deep JSON Server API', version: '1.0.0' },
|
|
333
|
-
openapi: '3.0.3',
|
|
334
|
-
paths,
|
|
335
|
-
tags: [...new Set([...resources, ...(files ? ['files'] : [])])].map((name) => ({ name })),
|
|
336
|
-
};
|
|
305
|
+
return { openapi: '3.0.3', info, components: { schemas, parameters, ...(auth ? { securitySchemes: structuredClone(AUTH_SECURITY_SCHEMES) } : {}) }, paths };
|
|
337
306
|
}
|
|
338
307
|
//# sourceMappingURL=document.js.map
|