@kollors/deep-json-server 0.4.0 → 0.6.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 +317 -67
- package/README.ru.md +316 -66
- package/index.js +7 -2
- package/package.json +1 -1
- package/src/cli.js +61 -48
- package/src/config.js +194 -0
- package/src/constants.js +1 -0
- package/src/database.js +31 -2
- package/src/files.js +368 -0
- package/src/openapi/config.js +15 -1
- package/src/openapi/document.js +69 -39
- package/src/openapi/index.js +29 -18
- package/src/query/pagination.js +1 -7
- package/src/server.js +91 -58
- package/types/index.d.ts +11 -2
- package/types/src/config.d.ts +86 -0
- package/types/src/constants.d.ts +1 -0
- package/types/src/database.d.ts +5 -2
- package/types/src/files.d.ts +24 -0
- package/types/src/openapi/config.d.ts +1 -1
- package/types/src/openapi/document.d.ts +7 -7
- package/types/src/openapi/index.d.ts +9 -13
- package/types/src/query/pagination.d.ts +1 -6
- package/types/src/server.d.ts +11 -22
package/src/server.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import Fastify from 'fastify';
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeServerConfig } from './config.js';
|
|
3
|
+
import { DEFAULT_HOST, DEFAULT_MAX_FILE_SIZE, DEFAULT_PORT, MAX_PAGE_SIZE } from './constants.js';
|
|
3
4
|
import { createDatabaseStore, createId, findItem, getCollection } from './database.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { createFileStore, registerFileRoutes } from './files.js';
|
|
6
|
+
import { resolveSchemaConfig } from './openapi/config.js';
|
|
7
|
+
import { buildOpenapiDocument } from './openapi/document.js';
|
|
8
|
+
import { createOpenapi, writeOpenapi } from './openapi/index.js';
|
|
6
9
|
import { matchesWhere, paginateItems, parsePagination, parseWhere, sortItems, validateWhere } from './query/index.js';
|
|
7
10
|
import { embedItem, parseEmbedPaths, validateEmbedPaths } from './relations.js';
|
|
8
|
-
import { createHttpError, getResourceNames, isObject
|
|
11
|
+
import { createHttpError, getResourceNames, isObject } from './utils.js';
|
|
12
|
+
|
|
13
|
+
/** @typedef {Record<string, unknown>} OpenapiDocument */
|
|
9
14
|
|
|
10
15
|
const CORS_HEADERS = {
|
|
11
|
-
'Access-Control-Allow-Headers': 'Content-Type',
|
|
16
|
+
'Access-Control-Allow-Headers': 'Content-Name, Content-Type',
|
|
12
17
|
'Access-Control-Allow-Methods': 'DELETE, GET, OPTIONS, PATCH, POST, PUT',
|
|
13
18
|
'Access-Control-Allow-Origin': '*',
|
|
14
19
|
};
|
|
@@ -141,75 +146,103 @@ const registerResourceRoutes = (server, store, resource, document, maxPageSize)
|
|
|
141
146
|
};
|
|
142
147
|
|
|
143
148
|
/**
|
|
144
|
-
* Creates a
|
|
145
|
-
* @param {
|
|
146
|
-
* @
|
|
149
|
+
* Creates a Deep JSON Server facade.
|
|
150
|
+
* @param {import('./config.js').DeepJsonServerConfig} options Server options.
|
|
151
|
+
* @param {{ files?: boolean }} [features] Optional feature switches.
|
|
152
|
+
* @returns {Promise<{ fastify: () => import('fastify').FastifyInstance, openapi: () => Promise<OpenapiDocument> }>} Server facade.
|
|
147
153
|
*/
|
|
148
|
-
export async function createServer(options) {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
export async function createServer(options, features = {}) {
|
|
155
|
+
const config = normalizeServerConfig(options);
|
|
156
|
+
const filesEnabled = features.files ?? config.files != null;
|
|
157
|
+
const { logger = true, maxFileSize = DEFAULT_MAX_FILE_SIZE, maxPageSize = MAX_PAGE_SIZE } = config.server;
|
|
158
|
+
const store = await createDatabaseStore(config.database);
|
|
159
|
+
const schema = await resolveSchemaConfig(config.database.schema);
|
|
160
|
+
const fileStore = filesEnabled && config.files != null ? await createFileStore(config.files) : undefined;
|
|
161
|
+
let fastifyInstance;
|
|
162
|
+
|
|
163
|
+
if (filesEnabled && config.files == null) {
|
|
164
|
+
throw new Error('Для файловых маршрутов укажите секцию config.files');
|
|
153
165
|
}
|
|
154
166
|
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
addRequestSchemas(server, document, resources);
|
|
162
|
-
|
|
163
|
-
server.addHook('onRequest', async (_request, reply) => {
|
|
164
|
-
Object.entries(CORS_HEADERS).forEach(([header, value]) => {
|
|
165
|
-
reply.header(header, value);
|
|
167
|
+
const buildDocument = () =>
|
|
168
|
+
buildOpenapiDocument({
|
|
169
|
+
database: store.database.data,
|
|
170
|
+
files: filesEnabled,
|
|
171
|
+
maxPageSize,
|
|
172
|
+
schema,
|
|
166
173
|
});
|
|
167
|
-
});
|
|
168
174
|
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
|
|
175
|
+
const fastify = () => {
|
|
176
|
+
if (fastifyInstance != null) {
|
|
177
|
+
return fastifyInstance;
|
|
172
178
|
}
|
|
173
|
-
});
|
|
174
179
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
180
|
+
const document = buildDocument();
|
|
181
|
+
const resources = getResourceNames(store.database.data);
|
|
182
|
+
const server = Fastify({ logger });
|
|
183
|
+
const listen = server.listen.bind(server);
|
|
178
184
|
|
|
179
|
-
|
|
180
|
-
registerResourceRoutes(server, store, resource, document, maxPageSize);
|
|
181
|
-
});
|
|
185
|
+
server.listen = (...args) => listen(...(args.length === 0 ? [{ host: config.server.host ?? DEFAULT_HOST, port: config.server.port ?? DEFAULT_PORT }] : args));
|
|
182
186
|
|
|
183
|
-
|
|
184
|
-
const statusCode = Number.isInteger(error.statusCode) ? error.statusCode : 500;
|
|
187
|
+
addRequestSchemas(server, document, resources);
|
|
185
188
|
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
server.addHook('onRequest', async (_request, reply) => {
|
|
190
|
+
Object.entries(CORS_HEADERS).forEach(([header, value]) => {
|
|
191
|
+
reply.header(header, value);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
server.addHook('preHandler', async (request) => {
|
|
196
|
+
if (request.method === 'GET') {
|
|
197
|
+
await store.read();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
server.options('/', async (_request, reply) => reply.code(204).send());
|
|
202
|
+
server.options('/*', async (_request, reply) => reply.code(204).send());
|
|
203
|
+
server.get('/', async () => ({ resources }));
|
|
204
|
+
|
|
205
|
+
resources.forEach((resource) => {
|
|
206
|
+
registerResourceRoutes(server, store, resource, document, maxPageSize);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
if (fileStore != null) {
|
|
210
|
+
registerFileRoutes(server, { maxFileSize, store: fileStore });
|
|
188
211
|
}
|
|
189
212
|
|
|
190
|
-
|
|
191
|
-
|
|
213
|
+
server.setErrorHandler((error, request, reply) => {
|
|
214
|
+
const statusCode = Number.isInteger(error.statusCode) ? error.statusCode : 500;
|
|
192
215
|
|
|
193
|
-
|
|
194
|
-
|
|
216
|
+
if (statusCode === 500) {
|
|
217
|
+
request.log.error(error);
|
|
218
|
+
}
|
|
195
219
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
* @param {{ databasePath: string, host?: string, logger?: boolean | Record<string, unknown>, maxPageSize?: number, port?: number, schemaPath?: string }} options Server options.
|
|
199
|
-
* @returns {Promise<import('fastify').FastifyInstance>} Listening Fastify server.
|
|
200
|
-
*/
|
|
201
|
-
export async function startServer(options) {
|
|
202
|
-
const { databasePath, host = DEFAULT_HOST, logger = true, maxPageSize = MAX_PAGE_SIZE, port = DEFAULT_PORT, schemaPath } = options ?? {};
|
|
220
|
+
return reply.code(statusCode).send({ error: error.message });
|
|
221
|
+
});
|
|
203
222
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
223
|
+
fastifyInstance = server;
|
|
224
|
+
|
|
225
|
+
return fastifyInstance;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const openapi = async () => {
|
|
229
|
+
await store.read();
|
|
207
230
|
|
|
208
|
-
|
|
209
|
-
|
|
231
|
+
const document = createOpenapi({
|
|
232
|
+
database: store.database.data,
|
|
233
|
+
files: filesEnabled,
|
|
234
|
+
host: config.server.host,
|
|
235
|
+
maxPageSize,
|
|
236
|
+
port: config.server.port,
|
|
237
|
+
schema,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (config.openapi.path != null) {
|
|
241
|
+
await writeOpenapi(document, config.openapi.path);
|
|
242
|
+
}
|
|
210
243
|
|
|
211
|
-
|
|
212
|
-
|
|
244
|
+
return document;
|
|
245
|
+
};
|
|
213
246
|
|
|
214
|
-
return
|
|
247
|
+
return { fastify, openapi };
|
|
215
248
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** @typedef {import('./src/config.js').DeepJsonServerConfig} DeepJsonServerConfig */
|
|
2
|
+
/** @typedef {import('./src/config.js').DatabaseConfig} DatabaseConfig */
|
|
3
|
+
/** @typedef {import('./src/config.js').FilesConfig} FilesConfig */
|
|
4
|
+
/** @typedef {import('./src/config.js').MemoryFile} MemoryFile */
|
|
5
|
+
/** @typedef {import('./src/server.js').OpenapiDocument} OpenapiDocument */
|
|
6
|
+
export type DeepJsonServerConfig = import('./src/config.js').DeepJsonServerConfig;
|
|
7
|
+
export type DatabaseConfig = import('./src/config.js').DatabaseConfig;
|
|
8
|
+
export type FilesConfig = import('./src/config.js').FilesConfig;
|
|
9
|
+
export type MemoryFile = import('./src/config.js').MemoryFile;
|
|
10
|
+
export type OpenapiDocument = import('./src/server.js').OpenapiDocument;
|
|
11
|
+
export { createServer } from './src/server.js';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export type DatabaseData = Record<string, Array<Record<string, unknown>>>;
|
|
2
|
+
export type DatabaseSchema = Record<string, unknown>;
|
|
3
|
+
export type DatabaseConfig = {
|
|
4
|
+
data: DatabaseData;
|
|
5
|
+
path?: never;
|
|
6
|
+
schema?: DatabaseSchema | string;
|
|
7
|
+
} | {
|
|
8
|
+
data?: never;
|
|
9
|
+
path: string;
|
|
10
|
+
schema?: DatabaseSchema | string;
|
|
11
|
+
};
|
|
12
|
+
export type MemoryFile = {
|
|
13
|
+
content: Uint8Array;
|
|
14
|
+
id: string;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
name: string;
|
|
17
|
+
};
|
|
18
|
+
export type FilesConfig = {
|
|
19
|
+
data: MemoryFile[];
|
|
20
|
+
directory?: never;
|
|
21
|
+
metadata?: never;
|
|
22
|
+
} | {
|
|
23
|
+
data?: never;
|
|
24
|
+
directory: string;
|
|
25
|
+
metadata: string;
|
|
26
|
+
};
|
|
27
|
+
export type DeepJsonServerConfig = {
|
|
28
|
+
/**
|
|
29
|
+
* Database source and optional schema.
|
|
30
|
+
*/
|
|
31
|
+
database: DatabaseConfig;
|
|
32
|
+
/**
|
|
33
|
+
* Binary-file storage.
|
|
34
|
+
*/
|
|
35
|
+
files?: FilesConfig;
|
|
36
|
+
/**
|
|
37
|
+
* Generated OpenAPI file.
|
|
38
|
+
*/
|
|
39
|
+
openapi?: {
|
|
40
|
+
path?: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Runtime settings.
|
|
44
|
+
*/
|
|
45
|
+
server?: {
|
|
46
|
+
host?: string;
|
|
47
|
+
logger?: boolean | Record<string, unknown>;
|
|
48
|
+
maxFileSize?: number;
|
|
49
|
+
maxPageSize?: number;
|
|
50
|
+
port?: number;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* @param {DeepJsonServerConfig} config Server configuration.
|
|
55
|
+
* @param {string} [directoryPath] Base directory for relative paths.
|
|
56
|
+
* @returns {{ database: DatabaseConfig, files?: FilesConfig, openapi: { path?: string }, server: { host?: string, logger?: boolean | Record<string, unknown>, maxFileSize?: number, maxPageSize?: number, port?: number } }} Normalized configuration.
|
|
57
|
+
*/
|
|
58
|
+
export declare const normalizeServerConfig: (config: DeepJsonServerConfig, directoryPath?: string) => {
|
|
59
|
+
database: DatabaseConfig;
|
|
60
|
+
files?: FilesConfig;
|
|
61
|
+
openapi: {
|
|
62
|
+
path?: string;
|
|
63
|
+
};
|
|
64
|
+
server: {
|
|
65
|
+
host?: string;
|
|
66
|
+
logger?: boolean | Record<string, unknown>;
|
|
67
|
+
maxFileSize?: number;
|
|
68
|
+
maxPageSize?: number;
|
|
69
|
+
port?: number;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
/** @param {string} configPath Configuration module path. */
|
|
73
|
+
export declare function readServerConfig(configPath: string): Promise<{
|
|
74
|
+
database: DatabaseConfig;
|
|
75
|
+
files?: FilesConfig;
|
|
76
|
+
openapi: {
|
|
77
|
+
path?: string;
|
|
78
|
+
};
|
|
79
|
+
server: {
|
|
80
|
+
host?: string;
|
|
81
|
+
logger?: boolean | Record<string, unknown>;
|
|
82
|
+
maxFileSize?: number;
|
|
83
|
+
maxPageSize?: number;
|
|
84
|
+
port?: number;
|
|
85
|
+
};
|
|
86
|
+
}>;
|
package/types/src/constants.d.ts
CHANGED
package/types/src/database.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export declare const validateDatabase: (data: any) => any;
|
|
2
2
|
export declare const readJsonObject: (path: any, label: any) => Promise<any>;
|
|
3
3
|
export declare const readDatabaseFile: (databasePath: any) => Promise<any>;
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* @param {import('./config.js').DatabaseConfig} config Database source.
|
|
6
|
+
* @returns {Promise<any>} Internal database store.
|
|
7
|
+
*/
|
|
8
|
+
export declare const createDatabaseStore: (config: import('./config.js').DatabaseConfig) => Promise<any>;
|
|
6
9
|
export declare const getCollection: (database: any, resource: any) => any[];
|
|
7
10
|
export declare const findItem: (collection: any, id: any) => any;
|
|
8
11
|
export declare const createId: (collection: any) => any;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** @param {import('./config.js').FilesConfig} config File storage configuration. */
|
|
2
|
+
export declare const createFileStore: (config: import('./config.js').FilesConfig) => Promise<{
|
|
3
|
+
get: (id: any) => Promise<void>;
|
|
4
|
+
remove: (id: any) => Promise<void>;
|
|
5
|
+
upload: ({ maxFileSize, mimeType, name, stream }: {
|
|
6
|
+
maxFileSize: any;
|
|
7
|
+
mimeType: any;
|
|
8
|
+
name: any;
|
|
9
|
+
stream: any;
|
|
10
|
+
}) => Promise<void>;
|
|
11
|
+
} | {
|
|
12
|
+
get: (id: any) => Promise<void>;
|
|
13
|
+
remove: (id: any) => Promise<void>;
|
|
14
|
+
upload: ({ maxFileSize, mimeType, name, stream }: {
|
|
15
|
+
maxFileSize: any;
|
|
16
|
+
mimeType: any;
|
|
17
|
+
name: any;
|
|
18
|
+
stream: any;
|
|
19
|
+
}) => Promise<void>;
|
|
20
|
+
}>;
|
|
21
|
+
export declare const registerFileRoutes: (server: any, { maxFileSize, store }: {
|
|
22
|
+
maxFileSize: any;
|
|
23
|
+
store: any;
|
|
24
|
+
}) => void;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const validateSchemaConfig: (schemaConfig: any, resources: any) => any;
|
|
2
2
|
export declare const applyConfiguredFields: (schema: any, resource: any, resourceConfig: any) => any;
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const resolveSchemaConfig: (schema: any) => Promise<any>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @param {Record<string, Array<Record<string, unknown
|
|
4
|
-
* @param {Record<string, unknown>} [schemaConfig] Schema configuration.
|
|
5
|
-
* @param {{ host?: string, port?: number }} [serverOptions] Server address used in the generated document.
|
|
2
|
+
* Builds an OpenAPI 3.0 document from resolved server data.
|
|
3
|
+
* @param {{ database: Record<string, Array<Record<string, unknown>>>, files?: boolean, maxPageSize?: number, schema?: Record<string, unknown> }} options Document options.
|
|
6
4
|
* @returns {Record<string, unknown>} OpenAPI document.
|
|
7
5
|
*/
|
|
8
|
-
export declare function
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
export declare function buildOpenapiDocument(options: {
|
|
7
|
+
database: Record<string, Array<Record<string, unknown>>>;
|
|
8
|
+
files?: boolean;
|
|
9
|
+
maxPageSize?: number;
|
|
10
|
+
schema?: Record<string, unknown>;
|
|
11
11
|
}): Record<string, unknown>;
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
outputPath: string;
|
|
11
|
-
port?: number;
|
|
12
|
-
schemaPath: string;
|
|
13
|
-
}): Promise<Record<string, unknown>>;
|
|
1
|
+
export declare const createOpenapi: ({ database, files, host, maxPageSize, port, schema }: {
|
|
2
|
+
database: any;
|
|
3
|
+
files: any;
|
|
4
|
+
host?: string | undefined;
|
|
5
|
+
maxPageSize: any;
|
|
6
|
+
port?: number | undefined;
|
|
7
|
+
schema: any;
|
|
8
|
+
}) => Record<string, unknown>;
|
|
9
|
+
export declare const writeOpenapi: (document: any, outputPath: any) => Promise<void>;
|
|
@@ -4,10 +4,5 @@ export declare const parsePagination: (query: any, maxPageSize?: number) => {
|
|
|
4
4
|
};
|
|
5
5
|
export declare const paginateItems: (items: any, page: any, pageSize: any) => {
|
|
6
6
|
data: any;
|
|
7
|
-
|
|
8
|
-
items: any;
|
|
9
|
-
last: number;
|
|
10
|
-
next: any;
|
|
11
|
-
pages: number;
|
|
12
|
-
prev: number | null;
|
|
7
|
+
total: any;
|
|
13
8
|
};
|
package/types/src/server.d.ts
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
|
+
export type OpenapiDocument = Record<string, unknown>;
|
|
1
2
|
/**
|
|
2
|
-
* Creates a
|
|
3
|
-
* @param {
|
|
4
|
-
* @
|
|
3
|
+
* Creates a Deep JSON Server facade.
|
|
4
|
+
* @param {import('./config.js').DeepJsonServerConfig} options Server options.
|
|
5
|
+
* @param {{ files?: boolean }} [features] Optional feature switches.
|
|
6
|
+
* @returns {Promise<{ fastify: () => import('fastify').FastifyInstance, openapi: () => Promise<OpenapiDocument> }>} Server facade.
|
|
5
7
|
*/
|
|
6
|
-
export declare function createServer(options: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Creates and starts a Fastify server.
|
|
14
|
-
* @param {{ databasePath: string, host?: string, logger?: boolean | Record<string, unknown>, maxPageSize?: number, port?: number, schemaPath?: string }} options Server options.
|
|
15
|
-
* @returns {Promise<import('fastify').FastifyInstance>} Listening Fastify server.
|
|
16
|
-
*/
|
|
17
|
-
export declare function startServer(options: {
|
|
18
|
-
databasePath: string;
|
|
19
|
-
host?: string;
|
|
20
|
-
logger?: boolean | Record<string, unknown>;
|
|
21
|
-
maxPageSize?: number;
|
|
22
|
-
port?: number;
|
|
23
|
-
schemaPath?: string;
|
|
24
|
-
}): Promise<import('fastify').FastifyInstance>;
|
|
8
|
+
export declare function createServer(options: import('./config.js').DeepJsonServerConfig, features?: {
|
|
9
|
+
files?: boolean;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
fastify: () => import('fastify').FastifyInstance;
|
|
12
|
+
openapi: () => Promise<OpenapiDocument>;
|
|
13
|
+
}>;
|