@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/src/server.js CHANGED
@@ -1,14 +1,19 @@
1
1
  import Fastify from 'fastify';
2
- import { DEFAULT_HOST, DEFAULT_PORT, MAX_PAGE_SIZE } from './constants.js';
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 { readSchemaConfig } from './openapi/config.js';
5
- import { createOpenApiDocument } from './openapi/index.js';
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, resolveDatabasePath } from './utils.js';
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 Fastify server without opening a network port.
145
- * @param {{ databasePath: string, logger?: boolean | Record<string, unknown>, maxPageSize?: number, schemaPath?: string }} options Server options.
146
- * @returns {Promise<import('fastify').FastifyInstance>} Fastify server.
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 { databasePath, logger = true, maxPageSize = MAX_PAGE_SIZE, schemaPath } = options ?? {};
150
-
151
- if (!Number.isInteger(maxPageSize) || maxPageSize < 1) {
152
- throw new Error('Максимальный размер страницы должен быть положительным целым числом');
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 store = await createDatabaseStore(databasePath);
156
- const schemaConfig = await readSchemaConfig(schemaPath);
157
- const document = createOpenApiDocument(store.database.data, schemaConfig);
158
- const resources = getResourceNames(store.database.data);
159
- const server = Fastify({ logger });
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
- server.addHook('preHandler', async (request) => {
170
- if (request.method === 'GET') {
171
- await store.read();
175
+ const fastify = () => {
176
+ if (fastifyInstance != null) {
177
+ return fastifyInstance;
172
178
  }
173
- });
174
179
 
175
- server.options('/', async (_request, reply) => reply.code(204).send());
176
- server.options('/*', async (_request, reply) => reply.code(204).send());
177
- server.get('/', async () => ({ resources }));
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
- resources.forEach((resource) => {
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
- server.setErrorHandler((error, request, reply) => {
184
- const statusCode = Number.isInteger(error.statusCode) ? error.statusCode : 500;
187
+ addRequestSchemas(server, document, resources);
185
188
 
186
- if (statusCode === 500) {
187
- request.log.error(error);
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
- return reply.code(statusCode).send({ error: error.message });
191
- });
213
+ server.setErrorHandler((error, request, reply) => {
214
+ const statusCode = Number.isInteger(error.statusCode) ? error.statusCode : 500;
192
215
 
193
- return server;
194
- }
216
+ if (statusCode === 500) {
217
+ request.log.error(error);
218
+ }
195
219
 
196
- /**
197
- * Creates and starts a Fastify server.
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
- if (!Number.isInteger(port) || port < 0 || port > 65_535) {
205
- throw new Error('Порт должен быть целым числом от 0 до 65535');
206
- }
223
+ fastifyInstance = server;
224
+
225
+ return fastifyInstance;
226
+ };
227
+
228
+ const openapi = async () => {
229
+ await store.read();
207
230
 
208
- const resolvedDatabasePath = resolveDatabasePath(databasePath);
209
- const server = await createServer({ databasePath: resolvedDatabasePath, logger, maxPageSize, schemaPath });
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
- await server.listen({ host, port });
212
- server.log.info({ database: resolvedDatabasePath }, 'Deep JSON Server запущен');
244
+ return document;
245
+ };
213
246
 
214
- return server;
247
+ return { fastify, openapi };
215
248
  }
package/types/index.d.ts CHANGED
@@ -1,2 +1,11 @@
1
- export { createOpenApiDocument, generateOpenApi } from './src/openapi/index.js';
2
- export { createServer, startServer } from './src/server.js';
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
+ }>;
@@ -1,4 +1,5 @@
1
1
  export declare const DEFAULT_HOST = "127.0.0.1";
2
+ export declare const DEFAULT_MAX_FILE_SIZE: number;
2
3
  export declare const DEFAULT_PAGE_SIZE = 10;
3
4
  export declare const DEFAULT_PORT = 4001;
4
5
  export declare const MAX_PAGE_SIZE = 1000;
@@ -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
- /** @returns {Promise<any>} Internal LowDB-backed store. */
5
- export declare const createDatabaseStore: (databasePath: any) => Promise<any>;
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 readSchemaConfig: (schemaPath: any) => Promise<any>;
3
+ export declare const resolveSchemaConfig: (schema: any) => Promise<any>;
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Creates an OpenAPI 3.0 document from a database and optional schema configuration.
3
- * @param {Record<string, Array<Record<string, unknown>>>} database Database contents.
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 createOpenApiDocument(database: Record<string, Array<Record<string, unknown>>>, schemaConfig?: Record<string, unknown>, { host, port }?: {
9
- host?: string;
10
- port?: number;
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 { createOpenApiDocument } from './document.js';
2
- /**
3
- * Generates an OpenAPI YAML file.
4
- * @param {{ databasePath: string, host?: string, outputPath: string, port?: number, schemaPath: string }} options Generation options.
5
- * @returns {Promise<Record<string, unknown>>} Generated OpenAPI document.
6
- */
7
- export declare function generateOpenApi({ databasePath, host, outputPath, port, schemaPath }: {
8
- databasePath: string;
9
- host?: string;
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
- first: number;
8
- items: any;
9
- last: number;
10
- next: any;
11
- pages: number;
12
- prev: number | null;
7
+ total: any;
13
8
  };
@@ -1,24 +1,13 @@
1
+ export type OpenapiDocument = Record<string, unknown>;
1
2
  /**
2
- * Creates a Fastify server without opening a network port.
3
- * @param {{ databasePath: string, logger?: boolean | Record<string, unknown>, maxPageSize?: number, schemaPath?: string }} options Server options.
4
- * @returns {Promise<import('fastify').FastifyInstance>} Fastify server.
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
- databasePath: string;
8
- logger?: boolean | Record<string, unknown>;
9
- maxPageSize?: number;
10
- schemaPath?: string;
11
- }): Promise<import('fastify').FastifyInstance>;
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
+ }>;