@kollors/deep-json-server 0.5.0 → 0.7.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 +229 -113
- package/README.ru.md +229 -113
- package/index.js +12 -2
- package/package.json +9 -2
- package/src/cli.js +45 -44
- package/src/config.js +141 -35
- package/src/constants.js +1 -1
- package/src/database.js +40 -20
- package/src/files.js +445 -90
- package/src/openapi/config.js +15 -1
- package/src/openapi/document.js +124 -61
- package/src/openapi/index.js +29 -18
- package/src/query/filter.js +11 -10
- package/src/query/pagination.js +3 -9
- package/src/relation-metadata.js +1 -0
- package/src/server.js +104 -92
- package/src/utils.js +30 -0
- package/types/index.d.ts +21 -2
- package/types/src/config.d.ts +73 -0
- package/types/src/constants.d.ts +1 -1
- package/types/src/database.d.ts +28 -3
- package/types/src/files.d.ts +63 -4
- package/types/src/openapi/config.d.ts +1 -1
- package/types/src/openapi/document.d.ts +6 -7
- package/types/src/openapi/index.d.ts +9 -14
- package/types/src/query/pagination.d.ts +1 -6
- package/types/src/server.d.ts +12 -28
- package/types/src/utils.d.ts +8 -0
package/src/server.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import Fastify from 'fastify';
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeServerConfig } from './config.js';
|
|
3
|
+
import { DEFAULT_HOST, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_PAGE_SIZE, DEFAULT_PORT } from './constants.js';
|
|
3
4
|
import { createDatabaseStore, createId, findItem, getCollection } from './database.js';
|
|
4
|
-
import { registerFileRoutes } from './files.js';
|
|
5
|
-
import {
|
|
6
|
-
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';
|
|
7
9
|
import { matchesWhere, paginateItems, parsePagination, parseWhere, sortItems, validateWhere } from './query/index.js';
|
|
8
10
|
import { embedItem, parseEmbedPaths, validateEmbedPaths } from './relations.js';
|
|
9
|
-
import { createHttpError, getResourceNames, isObject
|
|
11
|
+
import { createHttpError, getResourceNames, isObject } from './utils.js';
|
|
12
|
+
|
|
13
|
+
/** @typedef {Record<string, unknown>} OpenapiDocument */
|
|
14
|
+
/** @typedef {{ fastify: () => import('fastify').FastifyInstance, openapi: () => Promise<OpenapiDocument> }} ServerFacade */
|
|
10
15
|
|
|
11
16
|
const CORS_HEADERS = {
|
|
12
|
-
'Access-Control-Allow-Headers': 'Content-Name, Content-Type',
|
|
17
|
+
'Access-Control-Allow-Headers': 'Content-Directory, Content-Name, Content-Override, Content-Type',
|
|
13
18
|
'Access-Control-Allow-Methods': 'DELETE, GET, OPTIONS, PATCH, POST, PUT',
|
|
14
19
|
'Access-Control-Allow-Origin': '*',
|
|
15
20
|
};
|
|
16
21
|
|
|
17
|
-
const
|
|
22
|
+
const getJsonObjectBody = (body) => {
|
|
18
23
|
if (!isObject(body)) {
|
|
19
24
|
throw createHttpError(400, 'Тело запроса должно быть JSON-объектом');
|
|
20
25
|
}
|
|
@@ -24,7 +29,7 @@ const getRequestBody = (body) => {
|
|
|
24
29
|
|
|
25
30
|
const getSchemaName = (reference) => reference.split('/').at(-1);
|
|
26
31
|
|
|
27
|
-
const addRequestSchemas = (
|
|
32
|
+
const addRequestSchemas = (fastify, document, resources) => {
|
|
28
33
|
const requestSchemaNames = new Set(
|
|
29
34
|
resources.flatMap((resource) => {
|
|
30
35
|
const resourcePath = document.paths[`/${resource}`];
|
|
@@ -35,17 +40,17 @@ const addRequestSchemas = (server, document, resources) => {
|
|
|
35
40
|
);
|
|
36
41
|
|
|
37
42
|
requestSchemaNames.forEach((schemaName) => {
|
|
38
|
-
|
|
43
|
+
fastify.addSchema({ $id: schemaName, ...document.components.schemas[schemaName] });
|
|
39
44
|
});
|
|
40
45
|
};
|
|
41
46
|
|
|
42
|
-
const registerResourceRoutes = (
|
|
47
|
+
const registerResourceRoutes = (fastify, store, resource, document, maxPageSize) => {
|
|
43
48
|
const resourcePath = `/${resource}`;
|
|
44
49
|
const itemPath = `/${resource}/:id`;
|
|
45
50
|
const createSchemaName = getSchemaName(document.paths[resourcePath].post.requestBody.content['application/json'].schema.$ref);
|
|
46
51
|
const updateSchemaName = getSchemaName(document.paths[`/${resource}/{id}`].patch.requestBody.content['application/json'].schema.$ref);
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
fastify.get(resourcePath, async (request) => {
|
|
49
54
|
const collection = getCollection(store.database, resource);
|
|
50
55
|
const where = parseWhere(request.query);
|
|
51
56
|
const embedPaths = parseEmbedPaths(request.query._embed);
|
|
@@ -64,7 +69,7 @@ const registerResourceRoutes = (server, store, resource, document, maxPageSize)
|
|
|
64
69
|
return paginateItems(sortedItems, pagination.page, pagination.pageSize);
|
|
65
70
|
});
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
fastify.get(itemPath, async (request) => {
|
|
68
73
|
const collection = getCollection(store.database, resource);
|
|
69
74
|
const item = findItem(collection, request.params.id);
|
|
70
75
|
const embedPaths = parseEmbedPaths(request.query._embed);
|
|
@@ -78,10 +83,10 @@ const registerResourceRoutes = (server, store, resource, document, maxPageSize)
|
|
|
78
83
|
return embedItem(store.database, item, resource, embedPaths);
|
|
79
84
|
});
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
fastify.post(resourcePath, { schema: { body: { $ref: `${createSchemaName}#` } } }, async (request, reply) => {
|
|
82
87
|
const item = await store.update((database) => {
|
|
83
88
|
const collection = getCollection(database, resource);
|
|
84
|
-
const createdItem = { ...
|
|
89
|
+
const createdItem = { ...getJsonObjectBody(request.body), id: createId(collection) };
|
|
85
90
|
|
|
86
91
|
collection.push(createdItem);
|
|
87
92
|
|
|
@@ -91,7 +96,7 @@ const registerResourceRoutes = (server, store, resource, document, maxPageSize)
|
|
|
91
96
|
return reply.code(201).send(item);
|
|
92
97
|
});
|
|
93
98
|
|
|
94
|
-
|
|
99
|
+
fastify.put(itemPath, { schema: { body: { $ref: `${createSchemaName}#` } } }, async (request) =>
|
|
95
100
|
store.update((database) => {
|
|
96
101
|
const collection = getCollection(database, resource);
|
|
97
102
|
const currentItem = findItem(collection, request.params.id);
|
|
@@ -100,7 +105,7 @@ const registerResourceRoutes = (server, store, resource, document, maxPageSize)
|
|
|
100
105
|
throw createHttpError(404, 'Запись не найдена');
|
|
101
106
|
}
|
|
102
107
|
|
|
103
|
-
const item = { ...
|
|
108
|
+
const item = { ...getJsonObjectBody(request.body), id: currentItem.id };
|
|
104
109
|
|
|
105
110
|
collection.splice(collection.indexOf(currentItem), 1, item);
|
|
106
111
|
|
|
@@ -108,7 +113,7 @@ const registerResourceRoutes = (server, store, resource, document, maxPageSize)
|
|
|
108
113
|
}),
|
|
109
114
|
);
|
|
110
115
|
|
|
111
|
-
|
|
116
|
+
fastify.patch(itemPath, { schema: { body: { $ref: `${updateSchemaName}#` } } }, async (request) =>
|
|
112
117
|
store.update((database) => {
|
|
113
118
|
const collection = getCollection(database, resource);
|
|
114
119
|
const currentItem = findItem(collection, request.params.id);
|
|
@@ -117,7 +122,7 @@ const registerResourceRoutes = (server, store, resource, document, maxPageSize)
|
|
|
117
122
|
throw createHttpError(404, 'Запись не найдена');
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
const item = { ...currentItem, ...
|
|
125
|
+
const item = { ...currentItem, ...getJsonObjectBody(request.body), id: currentItem.id };
|
|
121
126
|
|
|
122
127
|
collection.splice(collection.indexOf(currentItem), 1, item);
|
|
123
128
|
|
|
@@ -125,7 +130,7 @@ const registerResourceRoutes = (server, store, resource, document, maxPageSize)
|
|
|
125
130
|
}),
|
|
126
131
|
);
|
|
127
132
|
|
|
128
|
-
|
|
133
|
+
fastify.delete(itemPath, async (request) =>
|
|
129
134
|
store.update((database) => {
|
|
130
135
|
const collection = getCollection(database, resource);
|
|
131
136
|
const currentItem = findItem(collection, request.params.id);
|
|
@@ -142,97 +147,104 @@ const registerResourceRoutes = (server, store, resource, document, maxPageSize)
|
|
|
142
147
|
};
|
|
143
148
|
|
|
144
149
|
/**
|
|
145
|
-
* Creates
|
|
146
|
-
* @param {
|
|
147
|
-
* @
|
|
150
|
+
* Creates lazy Fastify and OpenAPI accessors from one configuration.
|
|
151
|
+
* @param {import('./config.js').DeepJsonServerConfig} config Server configuration.
|
|
152
|
+
* @param {{ files?: boolean }} [features] Optional feature switches.
|
|
153
|
+
* @returns {Promise<ServerFacade>} Server facade.
|
|
148
154
|
*/
|
|
149
|
-
export async function createServer(
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
export async function createServer(config, features = {}) {
|
|
156
|
+
const normalizedConfig = normalizeServerConfig(config);
|
|
157
|
+
const filesEnabled = features.files ?? normalizedConfig.files != null;
|
|
158
|
+
const { logger = true, maxFileSize = DEFAULT_MAX_FILE_SIZE, maxPageSize = DEFAULT_MAX_PAGE_SIZE } = normalizedConfig.server;
|
|
159
|
+
const store = await createDatabaseStore(normalizedConfig.database);
|
|
160
|
+
const schema = await resolveSchemaConfig(normalizedConfig.database.schema);
|
|
161
|
+
const fileStore = filesEnabled && normalizedConfig.files != null ? await createFileStore(normalizedConfig.files) : undefined;
|
|
162
|
+
let fastifyInstance;
|
|
163
|
+
|
|
164
|
+
if (filesEnabled && normalizedConfig.files == null) {
|
|
165
|
+
throw new Error('Для файловых маршрутов укажите секцию config.files');
|
|
154
166
|
}
|
|
155
167
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
const buildDocument = () =>
|
|
169
|
+
buildOpenapiDocument({
|
|
170
|
+
database: store.database.data,
|
|
171
|
+
files: filesEnabled,
|
|
172
|
+
maxPageSize,
|
|
173
|
+
schema,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const getFastify = () => {
|
|
177
|
+
if (fastifyInstance != null) {
|
|
178
|
+
return fastifyInstance;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const document = buildDocument();
|
|
182
|
+
const resources = getResourceNames(store.database.data);
|
|
183
|
+
const fastify = Fastify({ logger });
|
|
184
|
+
const originalListen = fastify.listen.bind(fastify);
|
|
159
185
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const document = createOpenApiDocument(store.database.data, schemaConfig);
|
|
163
|
-
const resources = getResourceNames(store.database.data);
|
|
164
|
-
const server = Fastify({ logger });
|
|
186
|
+
// Calling fastify().listen() without arguments uses config defaults.
|
|
187
|
+
fastify.listen = (...args) => originalListen(...(args.length === 0 ? [{ host: normalizedConfig.server.host ?? DEFAULT_HOST, port: normalizedConfig.server.port ?? DEFAULT_PORT }] : args));
|
|
165
188
|
|
|
166
|
-
|
|
189
|
+
addRequestSchemas(fastify, document, resources);
|
|
167
190
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
191
|
+
fastify.addHook('onRequest', async (_request, reply) => {
|
|
192
|
+
Object.entries(CORS_HEADERS).forEach(([header, value]) => {
|
|
193
|
+
reply.header(header, value);
|
|
194
|
+
});
|
|
171
195
|
});
|
|
172
|
-
});
|
|
173
196
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
197
|
+
fastify.addHook('preHandler', async (request) => {
|
|
198
|
+
if (request.method === 'GET') {
|
|
199
|
+
await store.read();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
179
202
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
203
|
+
fastify.options('/', async (_request, reply) => reply.code(204).send());
|
|
204
|
+
fastify.options('/*', async (_request, reply) => reply.code(204).send());
|
|
205
|
+
fastify.get('/', async () => ({ resources }));
|
|
183
206
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
207
|
+
resources.forEach((resource) => {
|
|
208
|
+
registerResourceRoutes(fastify, store, resource, document, maxPageSize);
|
|
209
|
+
});
|
|
187
210
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
211
|
+
if (fileStore != null) {
|
|
212
|
+
registerFileRoutes(fastify, { maxFileSize, store: fileStore });
|
|
213
|
+
}
|
|
191
214
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
215
|
+
fastify.setErrorHandler((error, request, reply) => {
|
|
216
|
+
const statusCode = Number.isInteger(error.statusCode) ? error.statusCode : 500;
|
|
195
217
|
|
|
196
|
-
|
|
197
|
-
|
|
218
|
+
if (statusCode === 500) {
|
|
219
|
+
request.log.error(error);
|
|
220
|
+
}
|
|
198
221
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
222
|
+
return reply.code(statusCode).send({ error: error.message });
|
|
223
|
+
});
|
|
202
224
|
|
|
203
|
-
|
|
204
|
-
});
|
|
225
|
+
fastifyInstance = fastify;
|
|
205
226
|
|
|
206
|
-
|
|
207
|
-
}
|
|
227
|
+
return fastifyInstance;
|
|
228
|
+
};
|
|
208
229
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
* @param {{ databasePath: string, filesDirectoryPath?: string, filesMetadataPath?: string, host?: string, logger?: boolean | Record<string, unknown>, maxFileSize?: number, maxPageSize?: number, port?: number, schemaPath?: string }} options Server options.
|
|
212
|
-
* @returns {Promise<import('fastify').FastifyInstance>} Listening Fastify server.
|
|
213
|
-
*/
|
|
214
|
-
export async function startServer(options) {
|
|
215
|
-
const {
|
|
216
|
-
databasePath,
|
|
217
|
-
filesDirectoryPath,
|
|
218
|
-
filesMetadataPath,
|
|
219
|
-
host = DEFAULT_HOST,
|
|
220
|
-
logger = true,
|
|
221
|
-
maxFileSize = DEFAULT_MAX_FILE_SIZE,
|
|
222
|
-
maxPageSize = MAX_PAGE_SIZE,
|
|
223
|
-
port = DEFAULT_PORT,
|
|
224
|
-
schemaPath,
|
|
225
|
-
} = options ?? {};
|
|
226
|
-
|
|
227
|
-
if (!Number.isInteger(port) || port < 0 || port > 65_535) {
|
|
228
|
-
throw new Error('Порт должен быть целым числом от 0 до 65535');
|
|
229
|
-
}
|
|
230
|
+
const openapi = async () => {
|
|
231
|
+
await store.read();
|
|
230
232
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
const document = createOpenapi({
|
|
234
|
+
database: store.database.data,
|
|
235
|
+
files: filesEnabled,
|
|
236
|
+
host: normalizedConfig.server.host,
|
|
237
|
+
maxPageSize,
|
|
238
|
+
port: normalizedConfig.server.port,
|
|
239
|
+
schema,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (normalizedConfig.openapi.path != null) {
|
|
243
|
+
await writeOpenapi(document, normalizedConfig.openapi.path);
|
|
244
|
+
}
|
|
233
245
|
|
|
234
|
-
|
|
235
|
-
|
|
246
|
+
return document;
|
|
247
|
+
};
|
|
236
248
|
|
|
237
|
-
return
|
|
249
|
+
return { fastify: getFastify, openapi };
|
|
238
250
|
}
|
package/src/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
1
2
|
import { resolve } from 'node:path';
|
|
2
3
|
import pluralize from 'pluralize';
|
|
3
4
|
|
|
@@ -11,6 +12,35 @@ export const createHttpError = (statusCode, message) => {
|
|
|
11
12
|
return error;
|
|
12
13
|
};
|
|
13
14
|
|
|
15
|
+
/** @returns {<T>(operation: () => T | Promise<T>) => Promise<T>} Serialized operation scheduler. */
|
|
16
|
+
export const createSerialQueue = () => {
|
|
17
|
+
let queue = Promise.resolve();
|
|
18
|
+
|
|
19
|
+
return (operation) => {
|
|
20
|
+
const pendingOperation = queue.then(operation);
|
|
21
|
+
|
|
22
|
+
// Keep the queue usable after a failed operation.
|
|
23
|
+
queue = pendingOperation.catch(() => undefined);
|
|
24
|
+
|
|
25
|
+
return pendingOperation;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a random ID that is not currently in use.
|
|
31
|
+
* @param {(id: string) => boolean} isUsed Checks whether an ID already exists.
|
|
32
|
+
* @returns {string} Unique ID.
|
|
33
|
+
*/
|
|
34
|
+
export const createUniqueId = (isUsed) => {
|
|
35
|
+
let id;
|
|
36
|
+
|
|
37
|
+
do {
|
|
38
|
+
id = randomBytes(8).toString('base64url');
|
|
39
|
+
} while (isUsed(id));
|
|
40
|
+
|
|
41
|
+
return id;
|
|
42
|
+
};
|
|
43
|
+
|
|
14
44
|
export const getResourceNames = (data) => Object.keys(data);
|
|
15
45
|
export const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
46
|
export const isSafeKey = (key) => !UNSAFE_KEYS.has(key);
|
package/types/index.d.ts
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
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/config.js').OpenapiConfig} OpenapiConfig */
|
|
6
|
+
/** @typedef {import('./src/config.js').ServerConfig} ServerConfig */
|
|
7
|
+
/** @typedef {import('./src/files.js').FileMetadata} FileMetadata */
|
|
8
|
+
/** @typedef {import('./src/files.js').FileUpdate} FileUpdate */
|
|
9
|
+
/** @typedef {import('./src/server.js').OpenapiDocument} OpenapiDocument */
|
|
10
|
+
/** @typedef {import('./src/server.js').ServerFacade} ServerFacade */
|
|
11
|
+
export type DeepJsonServerConfig = import('./src/config.js').DeepJsonServerConfig;
|
|
12
|
+
export type DatabaseConfig = import('./src/config.js').DatabaseConfig;
|
|
13
|
+
export type FilesConfig = import('./src/config.js').FilesConfig;
|
|
14
|
+
export type MemoryFile = import('./src/config.js').MemoryFile;
|
|
15
|
+
export type OpenapiConfig = import('./src/config.js').OpenapiConfig;
|
|
16
|
+
export type ServerConfig = import('./src/config.js').ServerConfig;
|
|
17
|
+
export type FileMetadata = import('./src/files.js').FileMetadata;
|
|
18
|
+
export type FileUpdate = import('./src/files.js').FileUpdate;
|
|
19
|
+
export type OpenapiDocument = import('./src/server.js').OpenapiDocument;
|
|
20
|
+
export type ServerFacade = import('./src/server.js').ServerFacade;
|
|
21
|
+
export { createServer } from './src/server.js';
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
directory?: 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 OpenapiConfig = {
|
|
28
|
+
path?: string;
|
|
29
|
+
};
|
|
30
|
+
export type ServerConfig = {
|
|
31
|
+
host?: string;
|
|
32
|
+
logger?: boolean | Record<string, unknown>;
|
|
33
|
+
maxFileSize?: number;
|
|
34
|
+
maxPageSize?: number;
|
|
35
|
+
port?: number;
|
|
36
|
+
};
|
|
37
|
+
export type DeepJsonServerConfig = {
|
|
38
|
+
/**
|
|
39
|
+
* Database source and optional schema.
|
|
40
|
+
*/
|
|
41
|
+
database: DatabaseConfig;
|
|
42
|
+
/**
|
|
43
|
+
* Binary-file storage.
|
|
44
|
+
*/
|
|
45
|
+
files?: FilesConfig;
|
|
46
|
+
/**
|
|
47
|
+
* Generated OpenAPI file.
|
|
48
|
+
*/
|
|
49
|
+
openapi?: OpenapiConfig;
|
|
50
|
+
/**
|
|
51
|
+
* Runtime settings.
|
|
52
|
+
*/
|
|
53
|
+
server?: ServerConfig;
|
|
54
|
+
};
|
|
55
|
+
export type NormalizedServerConfig = {
|
|
56
|
+
database: DatabaseConfig;
|
|
57
|
+
files?: FilesConfig;
|
|
58
|
+
openapi: OpenapiConfig;
|
|
59
|
+
server: ServerConfig;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Validates configuration and resolves relative paths.
|
|
63
|
+
* @param {DeepJsonServerConfig} config Server configuration.
|
|
64
|
+
* @param {string} [directoryPath] Base directory for relative paths.
|
|
65
|
+
* @returns {NormalizedServerConfig} Normalized configuration.
|
|
66
|
+
*/
|
|
67
|
+
export declare const normalizeServerConfig: (config: DeepJsonServerConfig, directoryPath?: string) => NormalizedServerConfig;
|
|
68
|
+
/**
|
|
69
|
+
* Loads an ES module config and resolves paths from its directory.
|
|
70
|
+
* @param {string} configPath Configuration module path.
|
|
71
|
+
* @returns {Promise<NormalizedServerConfig>} Normalized configuration.
|
|
72
|
+
*/
|
|
73
|
+
export declare function readServerConfig(configPath: string): Promise<NormalizedServerConfig>;
|
package/types/src/constants.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const DEFAULT_HOST = "127.0.0.1";
|
|
2
2
|
export declare const DEFAULT_MAX_FILE_SIZE: number;
|
|
3
|
+
export declare const DEFAULT_MAX_PAGE_SIZE = 1000;
|
|
3
4
|
export declare const DEFAULT_PAGE_SIZE = 10;
|
|
4
5
|
export declare const DEFAULT_PORT = 4001;
|
|
5
|
-
export declare const MAX_PAGE_SIZE = 1000;
|
package/types/src/database.d.ts
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
|
+
export type DatabaseContainer = {
|
|
2
|
+
data: import('./config.js').DatabaseData;
|
|
3
|
+
};
|
|
4
|
+
export type DatabaseStore = {
|
|
5
|
+
/**
|
|
6
|
+
* Current database container.
|
|
7
|
+
*/
|
|
8
|
+
database: DatabaseContainer;
|
|
9
|
+
/**
|
|
10
|
+
* Resolved database file path.
|
|
11
|
+
*/
|
|
12
|
+
path?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Returns current data and reloads disk-backed sources.
|
|
15
|
+
*/
|
|
16
|
+
read: () => Promise<import('./config.js').DatabaseData>;
|
|
17
|
+
/**
|
|
18
|
+
* Runs a serialized update.
|
|
19
|
+
*/
|
|
20
|
+
update: <T>(operation: (database: DatabaseContainer) => T) => Promise<T>;
|
|
21
|
+
};
|
|
1
22
|
export declare const validateDatabase: (data: any) => any;
|
|
2
23
|
export declare const readJsonObject: (path: any, label: any) => Promise<any>;
|
|
3
24
|
export declare const readDatabaseFile: (databasePath: any) => Promise<any>;
|
|
4
|
-
/**
|
|
5
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Creates a disk- or memory-backed database with serialized updates.
|
|
27
|
+
* @param {import('./config.js').DatabaseConfig} config Database source.
|
|
28
|
+
* @returns {Promise<DatabaseStore>} Database store.
|
|
29
|
+
*/
|
|
30
|
+
export declare const createDatabaseStore: (config: import('./config.js').DatabaseConfig) => Promise<DatabaseStore>;
|
|
6
31
|
export declare const getCollection: (database: any, resource: any) => any[];
|
|
7
32
|
export declare const findItem: (collection: any, id: any) => any;
|
|
8
|
-
export declare const createId: (collection: any) =>
|
|
33
|
+
export declare const createId: (collection: any) => string;
|
package/types/src/files.d.ts
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
export type StoredFileMetadata = {
|
|
3
|
+
directory: string;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
name: string;
|
|
6
|
+
};
|
|
7
|
+
export type FileRecord = StoredFileMetadata & {
|
|
8
|
+
size: number;
|
|
9
|
+
};
|
|
10
|
+
export type FileMetadata = FileRecord & {
|
|
11
|
+
downloadUrl: string;
|
|
12
|
+
metadataUrl: string;
|
|
13
|
+
url: string;
|
|
14
|
+
};
|
|
15
|
+
export type FileUpload = {
|
|
16
|
+
directory: string;
|
|
17
|
+
maxFileSize: number;
|
|
18
|
+
mimeType: string;
|
|
19
|
+
name: string;
|
|
20
|
+
override: boolean;
|
|
21
|
+
stream: Readable;
|
|
22
|
+
};
|
|
23
|
+
export type FileUpdate = {
|
|
24
|
+
directory?: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
};
|
|
27
|
+
export type FileStore = {
|
|
28
|
+
/**
|
|
29
|
+
* Returns file metadata and contents.
|
|
30
|
+
*/
|
|
31
|
+
get: (path: string) => Promise<{
|
|
32
|
+
file: FileRecord;
|
|
33
|
+
stream: Readable;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Returns file metadata.
|
|
37
|
+
*/
|
|
38
|
+
metadata: (path: string) => Promise<FileRecord>;
|
|
39
|
+
/**
|
|
40
|
+
* Deletes a file.
|
|
41
|
+
*/
|
|
42
|
+
remove: (path: string) => Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Renames or moves a file.
|
|
45
|
+
*/
|
|
46
|
+
update: (path: string, update: FileUpdate) => Promise<FileRecord>;
|
|
47
|
+
/**
|
|
48
|
+
* Stores a file.
|
|
49
|
+
*/
|
|
50
|
+
upload: (upload: FileUpload) => Promise<{
|
|
51
|
+
created: boolean;
|
|
52
|
+
file: FileRecord;
|
|
53
|
+
}>;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Creates a disk- or memory-backed file store with serialized operations.
|
|
57
|
+
* @param {import('./config.js').FilesConfig} config File storage configuration.
|
|
58
|
+
* @returns {Promise<FileStore>} File store.
|
|
59
|
+
*/
|
|
60
|
+
export declare const createFileStore: (config: import('./config.js').FilesConfig) => Promise<FileStore>;
|
|
61
|
+
export declare const registerFileRoutes: (fastify: any, { maxFileSize, store }: {
|
|
3
62
|
maxFileSize: any;
|
|
4
|
-
|
|
5
|
-
}) =>
|
|
63
|
+
store: any;
|
|
64
|
+
}) => 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,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @param {Record<string, Array<Record<string, unknown
|
|
4
|
-
* @param {Record<string, unknown>} [schemaConfig] Schema configuration.
|
|
5
|
-
* @param {{ files?: boolean, host?: string, port?: number }} [serverOptions] Server address and optional features used in the generated document.
|
|
2
|
+
* Builds an OpenAPI document without runtime server addresses.
|
|
3
|
+
* @param {{ database: Record<string, Array<Record<string, unknown>>>, files?: boolean, maxPageSize?: number, schema?: Record<string, unknown> }} options Source data and schema settings.
|
|
6
4
|
* @returns {Record<string, unknown>} OpenAPI document.
|
|
7
5
|
*/
|
|
8
|
-
export declare function
|
|
6
|
+
export declare function buildOpenapiDocument(options: {
|
|
7
|
+
database: Record<string, Array<Record<string, unknown>>>;
|
|
9
8
|
files?: boolean;
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
maxPageSize?: number;
|
|
10
|
+
schema?: Record<string, unknown>;
|
|
12
11
|
}): Record<string, unknown>;
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
host?: string;
|
|
11
|
-
outputPath: string;
|
|
12
|
-
port?: number;
|
|
13
|
-
schemaPath?: string;
|
|
14
|
-
}): 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
|
};
|