@kollors/deep-json-server 0.2.6 → 0.3.0
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 +25 -14
- package/README.ru.md +25 -14
- package/package.json +1 -1
- package/src/cli.js +8 -4
- package/src/openapi.js +25 -8
package/README.md
CHANGED
|
@@ -201,16 +201,23 @@ Create a small configuration file next to the database, for example `mock/databa
|
|
|
201
201
|
|
|
202
202
|
```json
|
|
203
203
|
{
|
|
204
|
-
"
|
|
205
|
-
"
|
|
206
|
-
"
|
|
207
|
-
"coverSrc": "uri"
|
|
208
|
-
}
|
|
204
|
+
"$info": {
|
|
205
|
+
"title": "Movie Catalog API",
|
|
206
|
+
"version": "1.0.0"
|
|
209
207
|
},
|
|
210
|
-
"
|
|
211
|
-
"
|
|
212
|
-
"
|
|
213
|
-
"
|
|
208
|
+
"$schema": {
|
|
209
|
+
"movies": {
|
|
210
|
+
"required": ["actors", "actors.genreIds", "actors.userId", "publisherIds", "title"],
|
|
211
|
+
"formats": {
|
|
212
|
+
"coverSrc": "uri"
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
"users": {
|
|
216
|
+
"required": ["bornAt", "fullName"],
|
|
217
|
+
"formats": {
|
|
218
|
+
"avatarSrc": "uri",
|
|
219
|
+
"bornAt": "date"
|
|
220
|
+
}
|
|
214
221
|
}
|
|
215
222
|
}
|
|
216
223
|
}
|
|
@@ -219,17 +226,21 @@ Create a small configuration file next to the database, for example `mock/databa
|
|
|
219
226
|
Generate an OpenAPI 3.0.3 file and exit:
|
|
220
227
|
|
|
221
228
|
```bash
|
|
222
|
-
deep-json-server mock/database.json --generate mock/database-schema.json mock/openapi-schema.yaml
|
|
229
|
+
deep-json-server mock/database.json --generate mock/database-schema.json mock/openapi-schema.yaml --host 127.0.0.1 --port 4001
|
|
223
230
|
```
|
|
224
231
|
|
|
225
|
-
The generator infers resources and field types from all database records.
|
|
232
|
+
The generator infers resources and field types from all database records. Every inferred field is optional by default, while the top-level resource record's primary key `id` is always required. Add other required fields to `required`; nested fields use dot paths such as `actors.userId`. The `formats` object adds OpenAPI formats such as `date` and `uri`.
|
|
233
|
+
|
|
234
|
+
`$info` becomes the OpenAPI `info` object, while resource settings live under `$schema`. The OpenAPI `servers` entry is generated automatically from `--host` and `--port`, their `HOST` and `PORT` environment variable equivalents, or the default `http://127.0.0.1:4001`.
|
|
226
235
|
|
|
227
236
|
Use `name` when a resource needs an explicit schema name instead of the automatically singularized name:
|
|
228
237
|
|
|
229
238
|
```json
|
|
230
239
|
{
|
|
231
|
-
"
|
|
232
|
-
"
|
|
240
|
+
"$schema": {
|
|
241
|
+
"equipment": {
|
|
242
|
+
"name": "Equipment"
|
|
243
|
+
}
|
|
233
244
|
}
|
|
234
245
|
}
|
|
235
246
|
```
|
|
@@ -249,7 +260,7 @@ await server.close();
|
|
|
249
260
|
|
|
250
261
|
await startServer({ databasePath: 'mock/database.json', host: '127.0.0.1', port: 4001 });
|
|
251
262
|
|
|
252
|
-
await generateOpenApi({ databasePath: 'mock/database.json', schemaPath: 'mock/database-schema.json', outputPath: 'mock/openapi-schema.yaml' });
|
|
263
|
+
await generateOpenApi({ databasePath: 'mock/database.json', host: '127.0.0.1', port: 4001, schemaPath: 'mock/database-schema.json', outputPath: 'mock/openapi-schema.yaml' });
|
|
253
264
|
```
|
|
254
265
|
|
|
255
266
|
`createServer()` is useful for tests because it returns a Fastify instance without opening a network port.
|
package/README.ru.md
CHANGED
|
@@ -201,16 +201,23 @@ GET /countries/1?_embed=users
|
|
|
201
201
|
|
|
202
202
|
```json
|
|
203
203
|
{
|
|
204
|
-
"
|
|
205
|
-
"
|
|
206
|
-
"
|
|
207
|
-
"coverSrc": "uri"
|
|
208
|
-
}
|
|
204
|
+
"$info": {
|
|
205
|
+
"title": "API каталога фильмов",
|
|
206
|
+
"version": "1.0.0"
|
|
209
207
|
},
|
|
210
|
-
"
|
|
211
|
-
"
|
|
212
|
-
"
|
|
213
|
-
"
|
|
208
|
+
"$schema": {
|
|
209
|
+
"movies": {
|
|
210
|
+
"required": ["actors", "actors.genreIds", "actors.userId", "publisherIds", "title"],
|
|
211
|
+
"formats": {
|
|
212
|
+
"coverSrc": "uri"
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
"users": {
|
|
216
|
+
"required": ["bornAt", "fullName"],
|
|
217
|
+
"formats": {
|
|
218
|
+
"avatarSrc": "uri",
|
|
219
|
+
"bornAt": "date"
|
|
220
|
+
}
|
|
214
221
|
}
|
|
215
222
|
}
|
|
216
223
|
}
|
|
@@ -219,17 +226,21 @@ GET /countries/1?_embed=users
|
|
|
219
226
|
Сгенерируйте OpenAPI 3.0.3 и завершите работу:
|
|
220
227
|
|
|
221
228
|
```bash
|
|
222
|
-
deep-json-server mock/database.json --generate mock/database-schema.json mock/openapi-schema.yaml
|
|
229
|
+
deep-json-server mock/database.json --generate mock/database-schema.json mock/openapi-schema.yaml --host 127.0.0.1 --port 4001
|
|
223
230
|
```
|
|
224
231
|
|
|
225
|
-
Генератор определяет ресурсы и типы полей по всем записям базы.
|
|
232
|
+
Генератор определяет ресурсы и типы полей по всем записям базы. По умолчанию все найденные поля необязательные, а первичный ключ `id` корневой записи ресурса всегда обязательный. Остальные обязательные поля перечисляются в `required`; для вложенных полей используются пути через точку, например `actors.userId`. Объект `formats` добавляет форматы OpenAPI, например `date` и `uri`.
|
|
233
|
+
|
|
234
|
+
`$info` становится объектом `info` в OpenAPI, а настройки ресурсов находятся внутри `$schema`. Поле `servers` в OpenAPI формируется автоматически из параметров `--host` и `--port`, соответствующих переменных окружения `HOST` и `PORT` или адреса по умолчанию `http://127.0.0.1:4001`.
|
|
226
235
|
|
|
227
236
|
Используйте `name`, если ресурсу нужно явно задать имя схемы вместо автоматически полученного имени в единственном числе:
|
|
228
237
|
|
|
229
238
|
```json
|
|
230
239
|
{
|
|
231
|
-
"
|
|
232
|
-
"
|
|
240
|
+
"$schema": {
|
|
241
|
+
"equipment": {
|
|
242
|
+
"name": "Equipment"
|
|
243
|
+
}
|
|
233
244
|
}
|
|
234
245
|
}
|
|
235
246
|
```
|
|
@@ -249,7 +260,7 @@ await server.close();
|
|
|
249
260
|
|
|
250
261
|
await startServer({ databasePath: 'mock/database.json', host: '127.0.0.1', port: 4001 });
|
|
251
262
|
|
|
252
|
-
await generateOpenApi({ databasePath: 'mock/database.json', schemaPath: 'mock/database-schema.json', outputPath: 'mock/openapi-schema.yaml' });
|
|
263
|
+
await generateOpenApi({ databasePath: 'mock/database.json', host: '127.0.0.1', port: 4001, schemaPath: 'mock/database-schema.json', outputPath: 'mock/openapi-schema.yaml' });
|
|
253
264
|
```
|
|
254
265
|
|
|
255
266
|
`createServer()` удобен для тестов: он возвращает экземпляр Fastify, не открывая сетевой порт.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ const HELP = `Deep JSON Server
|
|
|
6
6
|
|
|
7
7
|
Использование:
|
|
8
8
|
deep-json-server <database.json> [--host <host>] [--port <port>]
|
|
9
|
-
deep-json-server <database.json> --generate <database-schema.json> <openapi-schema.yaml>
|
|
9
|
+
deep-json-server <database.json> --generate <database-schema.json> <openapi-schema.yaml> [--host <host>] [--port <port>]
|
|
10
10
|
|
|
11
11
|
Параметры:
|
|
12
12
|
--generate Сгенерировать OpenAPI и завершить работу
|
|
@@ -53,11 +53,15 @@ export async function runCli(args = process.argv.slice(2)) {
|
|
|
53
53
|
const schemaPath = args[generateIndex + 1];
|
|
54
54
|
const outputPath = args[generateIndex + 2];
|
|
55
55
|
|
|
56
|
-
if (generateIndex !== 1 || schemaPath == null || outputPath == null
|
|
57
|
-
throw new Error('Используйте: deep-json-server <database.json> --generate <database-schema.json> <openapi-schema.yaml>');
|
|
56
|
+
if (generateIndex !== 1 || schemaPath == null || outputPath == null) {
|
|
57
|
+
throw new Error('Используйте: deep-json-server <database.json> --generate <database-schema.json> <openapi-schema.yaml> [--host <host>] [--port <port>]');
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
const options = parseServerOptions([databasePath, ...args.slice(4)]);
|
|
61
|
+
const host = options.host ?? process.env.HOST ?? '127.0.0.1';
|
|
62
|
+
const port = Number(options.port ?? process.env.PORT ?? 4001);
|
|
63
|
+
|
|
64
|
+
await generateOpenApi({ databasePath, host, outputPath, port, schemaPath });
|
|
61
65
|
process.stdout.write(`OpenAPI-схема сохранена в ${outputPath}\n`);
|
|
62
66
|
return;
|
|
63
67
|
}
|
package/src/openapi.js
CHANGED
|
@@ -13,6 +13,22 @@ const readJson = async(path, label) => {
|
|
|
13
13
|
return value;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
const getServerUrl = (host, port) => {
|
|
17
|
+
const serverPort = Number(port);
|
|
18
|
+
|
|
19
|
+
if (typeof host !== 'string' || host === '') {
|
|
20
|
+
throw new Error('Адрес сервера не должен быть пустым');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!Number.isInteger(serverPort) || serverPort < 1 || serverPort > 65_535) {
|
|
24
|
+
throw new Error('Порт должен быть целым числом от 1 до 65535');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const serverHost = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
|
|
28
|
+
|
|
29
|
+
return `http://${serverHost}:${serverPort}`;
|
|
30
|
+
};
|
|
31
|
+
|
|
16
32
|
const mergeSchemas = (schemas) => {
|
|
17
33
|
const uniqueSchemas = [...new Map(schemas.map((schema) => [JSON.stringify(schema), schema])).values()];
|
|
18
34
|
const nullable = uniqueSchemas.some((schema) => schema.type === 'null');
|
|
@@ -69,7 +85,7 @@ function inferObjectSchema(values, path, options) {
|
|
|
69
85
|
const required = keys.filter((key) => {
|
|
70
86
|
const fieldPath = path === '' ? key : `${path}.${key}`;
|
|
71
87
|
|
|
72
|
-
return
|
|
88
|
+
return path === '' && key === 'id' || options.required.has(fieldPath);
|
|
73
89
|
});
|
|
74
90
|
|
|
75
91
|
return { properties, type: 'object', ...(required.length > 0 && { required }) };
|
|
@@ -194,14 +210,15 @@ const createResourcePaths = (resource, componentName) => {
|
|
|
194
210
|
};
|
|
195
211
|
};
|
|
196
212
|
|
|
197
|
-
export function createOpenApiDocument(database, schemaConfig = {}) {
|
|
213
|
+
export function createOpenApiDocument(database, schemaConfig = {}, { host = '127.0.0.1', port = 4001 } = {}) {
|
|
198
214
|
if (!isObject(database) || !isObject(schemaConfig)) {
|
|
199
215
|
throw new Error('База данных и её схема должны содержать JSON-объекты');
|
|
200
216
|
}
|
|
201
217
|
|
|
202
218
|
const resources = getResourceNames(database);
|
|
219
|
+
const resourceConfigs = isObject(schemaConfig.$schema) ? schemaConfig.$schema : {};
|
|
203
220
|
const componentNames = Object.fromEntries(resources.map((resource) => {
|
|
204
|
-
const resourceConfig = isObject(
|
|
221
|
+
const resourceConfig = isObject(resourceConfigs[resource]) ? resourceConfigs[resource] : {};
|
|
205
222
|
const componentName = typeof resourceConfig.name === 'string' && resourceConfig.name !== '' ? resourceConfig.name : toPascalCase(singularize(resource));
|
|
206
223
|
|
|
207
224
|
return [resource, componentName];
|
|
@@ -212,10 +229,10 @@ export function createOpenApiDocument(database, schemaConfig = {}) {
|
|
|
212
229
|
|
|
213
230
|
resources.forEach((resource) => {
|
|
214
231
|
const componentName = componentNames[resource];
|
|
215
|
-
const resourceConfig = isObject(
|
|
232
|
+
const resourceConfig = isObject(resourceConfigs[resource]) ? resourceConfigs[resource] : {};
|
|
216
233
|
const options = {
|
|
217
234
|
formats: isObject(resourceConfig.formats) ? resourceConfig.formats : {},
|
|
218
|
-
|
|
235
|
+
required: new Set(Array.isArray(resourceConfig.required) ? resourceConfig.required : []),
|
|
219
236
|
};
|
|
220
237
|
const values = database[resource].filter(isObject);
|
|
221
238
|
const rawSchema = values.length === 0 ? { additionalProperties: true, type: 'object' } : inferObjectSchema(values, '', options);
|
|
@@ -244,15 +261,15 @@ export function createOpenApiDocument(database, schemaConfig = {}) {
|
|
|
244
261
|
info: isObject(schemaConfig.$info) ? schemaConfig.$info : { title: 'Deep JSON Server API', version: '1.0.0' },
|
|
245
262
|
openapi: '3.0.3',
|
|
246
263
|
paths: Object.assign({}, ...resources.map((resource) => createResourcePaths(resource, componentNames[resource]))),
|
|
247
|
-
servers:
|
|
264
|
+
servers: [{ url: getServerUrl(host, port) }],
|
|
248
265
|
tags: resources.map((resource) => ({ name: resource })),
|
|
249
266
|
};
|
|
250
267
|
}
|
|
251
268
|
|
|
252
|
-
export async function generateOpenApi({ databasePath, outputPath, schemaPath }) {
|
|
269
|
+
export async function generateOpenApi({ databasePath, host, outputPath, port, schemaPath }) {
|
|
253
270
|
const database = await readJson(databasePath, 'База данных');
|
|
254
271
|
const schemaConfig = await readJson(schemaPath, 'Схема базы данных');
|
|
255
|
-
const document = createOpenApiDocument(database, schemaConfig);
|
|
272
|
+
const document = createOpenApiDocument(database, schemaConfig, { host, port });
|
|
256
273
|
const resolvedOutputPath = resolve(outputPath);
|
|
257
274
|
|
|
258
275
|
await mkdir(dirname(resolvedOutputPath), { recursive: true });
|