@kollors/deep-json-server 0.7.0 → 0.8.1
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 +19 -16
- package/README.ru.md +20 -17
- package/dist/bin/deep-json-server.d.ts +2 -0
- package/dist/bin/deep-json-server.js +11 -0
- package/dist/bin/deep-json-server.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/src/cli.d.ts +4 -0
- package/dist/src/cli.js +80 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config.d.ts +54 -0
- package/dist/src/config.js +145 -0
- package/dist/src/config.js.map +1 -0
- package/{src → dist/src}/constants.js +1 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/database.d.ts +20 -0
- package/dist/src/database.js +136 -0
- package/dist/src/database.js.map +1 -0
- package/dist/src/files/contract.d.ts +67 -0
- package/dist/src/files/contract.js +106 -0
- package/dist/src/files/contract.js.map +1 -0
- package/dist/src/files/disk-store.d.ts +5 -0
- package/dist/src/files/disk-store.js +313 -0
- package/dist/src/files/disk-store.js.map +1 -0
- package/dist/src/files/index.d.ts +5 -0
- package/dist/src/files/index.js +6 -0
- package/dist/src/files/index.js.map +1 -0
- package/dist/src/files/memory-store.d.ts +3 -0
- package/dist/src/files/memory-store.js +80 -0
- package/dist/src/files/memory-store.js.map +1 -0
- package/dist/src/files/routes.d.ts +6 -0
- package/dist/src/files/routes.js +77 -0
- package/dist/src/files/routes.js.map +1 -0
- package/dist/src/openapi/config.d.ts +11 -0
- package/dist/src/openapi/config.js +204 -0
- package/dist/src/openapi/config.js.map +1 -0
- package/dist/src/openapi/document.d.ts +9 -0
- package/dist/src/openapi/document.js +338 -0
- package/dist/src/openapi/document.js.map +1 -0
- package/dist/src/openapi/index.d.ts +10 -0
- package/dist/src/openapi/index.js +27 -0
- package/dist/src/openapi/index.js.map +1 -0
- package/dist/src/openapi/inference.d.ts +14 -0
- package/dist/src/openapi/inference.js +145 -0
- package/dist/src/openapi/inference.js.map +1 -0
- package/dist/src/query/filter.d.ts +6 -0
- package/dist/src/query/filter.js +248 -0
- package/dist/src/query/filter.js.map +1 -0
- package/{src → dist/src}/query/index.js +1 -0
- package/dist/src/query/index.js.map +1 -0
- package/dist/src/query/pagination.d.ts +11 -0
- package/dist/src/query/pagination.js +28 -0
- package/dist/src/query/pagination.js.map +1 -0
- package/dist/src/query/sort.d.ts +1 -0
- package/dist/src/query/sort.js +54 -0
- package/dist/src/query/sort.js.map +1 -0
- package/dist/src/relation-metadata.d.ts +10 -0
- package/dist/src/relation-metadata.js +32 -0
- package/dist/src/relation-metadata.js.map +1 -0
- package/dist/src/relations.d.ts +12 -0
- package/dist/src/relations.js +155 -0
- package/dist/src/relations.js.map +1 -0
- package/dist/src/server.d.ts +13 -0
- package/dist/src/server.js +188 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/types.d.ts +52 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils.d.ts +20 -0
- package/dist/src/utils.js +63 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +17 -16
- package/bin/deep-json-server.js +0 -11
- package/index.js +0 -12
- package/src/cli.js +0 -91
- package/src/config.js +0 -203
- package/src/database.js +0 -148
- package/src/files.js +0 -633
- package/src/openapi/config.js +0 -124
- package/src/openapi/document.js +0 -394
- package/src/openapi/index.js +0 -36
- package/src/openapi/inference.js +0 -187
- package/src/query/filter.js +0 -283
- package/src/query/pagination.js +0 -44
- package/src/query/sort.js +0 -63
- package/src/relation-metadata.js +0 -41
- package/src/relations.js +0 -173
- package/src/server.js +0 -250
- package/src/utils.js +0 -84
- package/types/index.d.ts +0 -21
- package/types/src/config.d.ts +0 -73
- package/types/src/database.d.ts +0 -33
- package/types/src/files.d.ts +0 -64
- package/types/src/openapi/config.d.ts +0 -3
- package/types/src/openapi/document.d.ts +0 -11
- package/types/src/openapi/index.d.ts +0 -9
- package/types/src/openapi/inference.d.ts +0 -9
- package/types/src/query/filter.d.ts +0 -3
- package/types/src/query/pagination.d.ts +0 -8
- package/types/src/query/sort.d.ts +0 -1
- package/types/src/relation-metadata.d.ts +0 -9
- package/types/src/relations.d.ts +0 -3
- package/types/src/server.d.ts +0 -14
- package/types/src/utils.d.ts +0 -18
- /package/{types → dist}/src/constants.d.ts +0 -0
- /package/{types → dist}/src/query/index.d.ts +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { assertKnownKeys, isObject } from './utils.js';
|
|
4
|
+
const CONFIG_KEYS = new Set(['database', 'files', 'openapi', 'server']);
|
|
5
|
+
const DATABASE_KEYS = new Set(['data', 'path', 'schema']);
|
|
6
|
+
const FILES_KEYS = new Set(['data', 'directory', 'metadata']);
|
|
7
|
+
const OPENAPI_KEYS = new Set(['path']);
|
|
8
|
+
const SERVER_KEYS = new Set(['cors', 'host', 'logger', 'maxFileSize', 'maxPageSize', 'port']);
|
|
9
|
+
let configImportIndex = 0;
|
|
10
|
+
function getObject(value, path, required = false) {
|
|
11
|
+
if (value == null && !required) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
if (!isObject(value)) {
|
|
15
|
+
throw new Error(`Ключ ${path} должен быть JSON-объектом`);
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function getString(value, path, required = false) {
|
|
20
|
+
if (value == null && !required) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
24
|
+
throw new Error(`Ключ ${path} должен содержать непустую строку`);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
const resolveConfigPath = (value, directoryPath) => (value == null ? undefined : resolve(directoryPath, value));
|
|
29
|
+
const getPositiveInteger = (value, path) => {
|
|
30
|
+
if (value == null) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
|
|
34
|
+
throw new Error(`Ключ ${path} должен быть положительным целым числом`);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
};
|
|
38
|
+
const normalizeSchema = (schema, directoryPath) => {
|
|
39
|
+
if (schema == null) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
if (typeof schema === 'string') {
|
|
43
|
+
return resolveConfigPath(getString(schema, 'config.database.schema', true), directoryPath);
|
|
44
|
+
}
|
|
45
|
+
return getObject(schema, 'config.database.schema', true);
|
|
46
|
+
};
|
|
47
|
+
const normalizeDatabase = (value, directoryPath) => {
|
|
48
|
+
const database = getObject(value, 'config.database', true);
|
|
49
|
+
assertKnownKeys(database, DATABASE_KEYS, 'config.database');
|
|
50
|
+
const hasData = database.data != null;
|
|
51
|
+
const hasPath = database.path != null;
|
|
52
|
+
if (hasData === hasPath) {
|
|
53
|
+
throw new Error('Укажите ровно один из ключей config.database.path и config.database.data');
|
|
54
|
+
}
|
|
55
|
+
const schema = normalizeSchema(database.schema, directoryPath);
|
|
56
|
+
if (hasData) {
|
|
57
|
+
return { data: getObject(database.data, 'config.database.data', true), schema };
|
|
58
|
+
}
|
|
59
|
+
return { path: resolveConfigPath(getString(database.path, 'config.database.path', true), directoryPath), schema };
|
|
60
|
+
};
|
|
61
|
+
const normalizeFiles = (value, directoryPath) => {
|
|
62
|
+
const files = getObject(value, 'config.files');
|
|
63
|
+
if (files == null) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
assertKnownKeys(files, FILES_KEYS, 'config.files');
|
|
67
|
+
const hasData = files.data != null;
|
|
68
|
+
const hasDiskStorage = files.directory != null || files.metadata != null;
|
|
69
|
+
if (hasData === hasDiskStorage) {
|
|
70
|
+
throw new Error('Укажите либо config.files.data, либо пару config.files.directory и config.files.metadata');
|
|
71
|
+
}
|
|
72
|
+
if (hasData) {
|
|
73
|
+
if (!Array.isArray(files.data)) {
|
|
74
|
+
throw new Error('Ключ config.files.data должен содержать массив');
|
|
75
|
+
}
|
|
76
|
+
return { data: files.data };
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
directory: resolveConfigPath(getString(files.directory, 'config.files.directory', true), directoryPath),
|
|
80
|
+
metadata: resolveConfigPath(getString(files.metadata, 'config.files.metadata', true), directoryPath),
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
const normalizeConfig = (config, directoryPath = '.') => {
|
|
84
|
+
if (!isObject(config)) {
|
|
85
|
+
throw new Error('Конфигурация сервера должна содержать JSON-объект');
|
|
86
|
+
}
|
|
87
|
+
assertKnownKeys(config, CONFIG_KEYS, 'config');
|
|
88
|
+
const database = normalizeDatabase(config.database, directoryPath);
|
|
89
|
+
const files = normalizeFiles(config.files, directoryPath);
|
|
90
|
+
const openapi = getObject(config.openapi, 'config.openapi') ?? {};
|
|
91
|
+
const server = getObject(config.server, 'config.server') ?? {};
|
|
92
|
+
assertKnownKeys(openapi, OPENAPI_KEYS, 'config.openapi');
|
|
93
|
+
assertKnownKeys(server, SERVER_KEYS, 'config.server');
|
|
94
|
+
const openapiPath = getString(openapi.path, 'config.openapi.path');
|
|
95
|
+
const cors = server.cors;
|
|
96
|
+
const host = getString(server.host, 'config.server.host');
|
|
97
|
+
const logger = server.logger;
|
|
98
|
+
const maxFileSize = getPositiveInteger(server.maxFileSize, 'config.server.maxFileSize');
|
|
99
|
+
const maxPageSize = getPositiveInteger(server.maxPageSize, 'config.server.maxPageSize');
|
|
100
|
+
const port = server.port;
|
|
101
|
+
if (port != null && (typeof port !== 'number' || !Number.isInteger(port) || port < 0 || port > 65_535)) {
|
|
102
|
+
throw new Error('Ключ config.server.port должен быть целым числом от 0 до 65535');
|
|
103
|
+
}
|
|
104
|
+
if (logger != null && typeof logger !== 'boolean' && !isObject(logger)) {
|
|
105
|
+
throw new Error('Ключ config.server.logger должен содержать boolean или JSON-объект');
|
|
106
|
+
}
|
|
107
|
+
if (cors != null && typeof cors !== 'boolean') {
|
|
108
|
+
throw new Error('Ключ config.server.cors должен содержать boolean');
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
database,
|
|
112
|
+
files,
|
|
113
|
+
openapi: { path: resolveConfigPath(openapiPath, directoryPath) },
|
|
114
|
+
server: {
|
|
115
|
+
cors: cors,
|
|
116
|
+
host,
|
|
117
|
+
logger: logger,
|
|
118
|
+
maxFileSize,
|
|
119
|
+
maxPageSize,
|
|
120
|
+
port: port,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
/** Validates configuration and resolves relative paths. */
|
|
125
|
+
export const normalizeServerConfig = (config, directoryPath) => normalizeConfig(config, directoryPath);
|
|
126
|
+
/** Loads an ES module config and resolves paths from its directory. */
|
|
127
|
+
export async function readServerConfig(configPath) {
|
|
128
|
+
const resolvedConfigPath = resolve(getString(configPath, 'config', true));
|
|
129
|
+
let config;
|
|
130
|
+
try {
|
|
131
|
+
const configUrl = pathToFileURL(resolvedConfigPath);
|
|
132
|
+
// Bypass the module cache so repeated reads use the latest config.
|
|
133
|
+
configUrl.searchParams.set('deep-json-server-import', String(configImportIndex++));
|
|
134
|
+
config = (await import(configUrl.href)).default;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
138
|
+
throw new Error(`Не удалось загрузить конфигурацию ${resolvedConfigPath}: ${message}`, { cause: error });
|
|
139
|
+
}
|
|
140
|
+
if (!isObject(config)) {
|
|
141
|
+
throw new Error('Конфигурация сервера должна экспортировать JSON-объект через export default');
|
|
142
|
+
}
|
|
143
|
+
return normalizeConfig(config, dirname(resolvedConfigPath));
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEvD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;AACxE,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC1D,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;AAC9D,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AACvC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;AAC9F,IAAI,iBAAiB,GAAG,CAAC,CAAC;AAqC1B,SAAS,SAAS,CAAC,KAAc,EAAE,IAAY,EAAE,QAAQ,GAAG,KAAK;IAC/D,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,4BAA4B,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAID,SAAS,SAAS,CAAC,KAAc,EAAE,IAAY,EAAE,QAAQ,GAAG,KAAK;IAC/D,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,mCAAmC,CAAC,CAAC;IACnE,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,iBAAiB,GAAG,CAAC,KAAyB,EAAE,aAAqB,EAAsB,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC;AAEhK,MAAM,kBAAkB,GAAG,CAAC,KAAc,EAAE,IAAY,EAAsB,EAAE;IAC9E,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,yCAAyC,CAAC,CAAC;IACzE,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,MAAe,EAAE,aAAqB,EAAuC,EAAE;IACtG,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;QACnB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,iBAAiB,CAAC,SAAS,CAAC,MAAM,EAAE,wBAAwB,EAAE,IAAI,CAAC,EAAE,aAAa,CAAC,CAAC;IAC7F,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,EAAE,wBAAwB,EAAE,IAAI,CAAmB,CAAC;AAC7E,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAAE,aAAqB,EAAkB,EAAE;IAClF,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,EAAE,iBAAiB,EAAE,IAAI,CAAC,CAAC;IAE3D,eAAe,CAAC,QAAQ,EAAE,aAAa,EAAE,iBAAiB,CAAC,CAAC;IAE5D,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC;IACtC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC;IAEtC,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAE/D,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,EAAE,IAAI,CAAiB,EAAE,MAAM,EAAE,CAAC;IAClG,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,EAAE,IAAI,CAAC,EAAE,aAAa,CAAW,EAAE,MAAM,EAAE,CAAC;AAC9H,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,KAAc,EAAE,aAAqB,EAA2B,EAAE;IACxF,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;IAE/C,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC;IAEnD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC;IACnC,MAAM,cAAc,GAAG,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC;IAEzE,IAAI,OAAO,KAAK,cAAc,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,0FAA0F,CAAC,CAAC;IAC9G,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAoB,EAAE,CAAC;IAC9C,CAAC;IAED,OAAO;QACL,SAAS,EAAE,iBAAiB,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,wBAAwB,EAAE,IAAI,CAAC,EAAE,aAAa,CAAW;QACjH,QAAQ,EAAE,iBAAiB,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,uBAAuB,EAAE,IAAI,CAAC,EAAE,aAAa,CAAW;KAC/G,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,MAAe,EAAE,aAAa,GAAG,GAAG,EAA0B,EAAE;IACvF,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IAED,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IAE/C,MAAM,QAAQ,GAAG,iBAAiB,CAAC,MAAM,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC;IAClE,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,IAAI,EAAE,CAAC;IAE/D,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;IACzD,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC;IAEtD,MAAM,WAAW,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IACnE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,MAAM,WAAW,GAAG,kBAAkB,CAAC,MAAM,CAAC,WAAW,EAAE,2BAA2B,CAAC,CAAC;IACxF,MAAM,WAAW,GAAG,kBAAkB,CAAC,MAAM,CAAC,WAAW,EAAE,2BAA2B,CAAC,CAAC;IACxF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IAEzB,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC;QACvG,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IACpF,CAAC;IAED,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,MAAM,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACxF,CAAC;IAED,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IAED,OAAO;QACL,QAAQ;QACR,KAAK;QACL,OAAO,EAAE,EAAE,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE;QAChE,MAAM,EAAE;YACN,IAAI,EAAE,IAA2B;YACjC,IAAI;YACJ,MAAM,EAAE,MAAgC;YACxC,WAAW;YACX,WAAW;YACX,IAAI,EAAE,IAA0B;SACjC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,2DAA2D;AAC3D,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,MAA4B,EAAE,aAAsB,EAA0B,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE9J,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,UAAkB;IACvD,MAAM,kBAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;IAC1E,IAAI,MAAe,CAAC;IAEpB,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,aAAa,CAAC,kBAAkB,CAAC,CAAC;QAEpD,mEAAmE;QACnE,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,yBAAyB,EAAE,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC;QACnF,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IAClD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAEvE,MAAM,IAAI,KAAK,CAAC,qCAAqC,kBAAkB,KAAK,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3G,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;IACjG,CAAC;IAED,OAAO,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC;AACxC,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AACvD,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAC1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AACpC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DatabaseConfig } from './config.js';
|
|
2
|
+
import type { DatabaseData, DatabaseRecord } from './types.js';
|
|
3
|
+
export interface DatabaseContainer {
|
|
4
|
+
data: DatabaseData;
|
|
5
|
+
}
|
|
6
|
+
export interface DatabaseStore {
|
|
7
|
+
database: DatabaseContainer;
|
|
8
|
+
path?: string;
|
|
9
|
+
read(): Promise<DatabaseData>;
|
|
10
|
+
update<T>(operation: (database: DatabaseContainer) => T): Promise<T>;
|
|
11
|
+
}
|
|
12
|
+
export declare const validateJsonValue: (value: unknown, path: string, ancestors?: WeakSet<object>) => void;
|
|
13
|
+
export declare const validateDatabase: (data: unknown) => DatabaseData;
|
|
14
|
+
export declare const readJsonObjectFile: (path: string, label: string) => Promise<Record<string, unknown>>;
|
|
15
|
+
export declare const readDatabaseFile: (databasePath: string) => Promise<DatabaseData>;
|
|
16
|
+
/** Creates a disk- or memory-backed database with serialized updates. */
|
|
17
|
+
export declare const createDatabaseStore: (config: DatabaseConfig) => Promise<DatabaseStore>;
|
|
18
|
+
export declare const getCollection: (database: DatabaseContainer, resource: string) => DatabaseRecord[];
|
|
19
|
+
export declare const findItemIndex: (collection: DatabaseRecord[], id: unknown) => number;
|
|
20
|
+
export declare const createId: (collection: DatabaseRecord[]) => string;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { JSONFilePreset } from 'lowdb/node';
|
|
4
|
+
import { createHttpError, createSerialQueue, createUniqueId, isObject, isSafeKey, isSystemError, resolveDatabasePath } from './utils.js';
|
|
5
|
+
const RESOURCE_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
6
|
+
export const validateJsonValue = (value, path, ancestors = new WeakSet()) => {
|
|
7
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (typeof value === 'number') {
|
|
11
|
+
if (!Number.isFinite(value)) {
|
|
12
|
+
throw new Error(`${path} должен содержать конечное число`);
|
|
13
|
+
}
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (typeof value !== 'object') {
|
|
17
|
+
throw new Error(`${path} содержит значение, несовместимое с JSON`);
|
|
18
|
+
}
|
|
19
|
+
if (ancestors.has(value)) {
|
|
20
|
+
throw new Error(`${path} содержит циклическую ссылку`);
|
|
21
|
+
}
|
|
22
|
+
const prototype = Object.getPrototypeOf(value);
|
|
23
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
|
|
24
|
+
throw new Error(`${path} должен содержать обычный JSON-объект`);
|
|
25
|
+
}
|
|
26
|
+
ancestors.add(value);
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
29
|
+
if (!Object.hasOwn(value, index)) {
|
|
30
|
+
throw new Error(`${path}[${index}] отсутствует; разреженные массивы несовместимы с JSON`);
|
|
31
|
+
}
|
|
32
|
+
validateJsonValue(value[index], `${path}[${index}]`, ancestors);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
Object.entries(value).forEach(([key, item]) => {
|
|
37
|
+
validateJsonValue(item, `${path}.${key}`, ancestors);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
ancestors.delete(value);
|
|
41
|
+
};
|
|
42
|
+
export const validateDatabase = (data) => {
|
|
43
|
+
if (!isObject(data)) {
|
|
44
|
+
throw new Error('База данных должна содержать JSON-объект');
|
|
45
|
+
}
|
|
46
|
+
validateJsonValue(data, 'База данных');
|
|
47
|
+
Object.entries(data).forEach(([resource, records]) => {
|
|
48
|
+
if (!RESOURCE_NAME_PATTERN.test(resource) || !isSafeKey(resource)) {
|
|
49
|
+
throw new Error(`Недопустимое имя ресурса «${resource}»`);
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(records)) {
|
|
52
|
+
throw new Error(`Ресурс «${resource}» должен содержать JSON-массив`);
|
|
53
|
+
}
|
|
54
|
+
const ids = new Set();
|
|
55
|
+
records.forEach((record, index) => {
|
|
56
|
+
if (!isObject(record)) {
|
|
57
|
+
throw new Error(`Запись ${index} ресурса «${resource}» должна содержать JSON-объект`);
|
|
58
|
+
}
|
|
59
|
+
if (typeof record.id !== 'string' && !(typeof record.id === 'number' && Number.isFinite(record.id))) {
|
|
60
|
+
throw new Error(`Запись ${index} ресурса «${resource}» должна содержать строковый или числовой id`);
|
|
61
|
+
}
|
|
62
|
+
if (String(record.id) === '') {
|
|
63
|
+
throw new Error(`Запись ${index} ресурса «${resource}» должна содержать непустой id`);
|
|
64
|
+
}
|
|
65
|
+
const id = String(record.id);
|
|
66
|
+
if (ids.has(id)) {
|
|
67
|
+
throw new Error(`Ресурс «${resource}» содержит повторяющийся id «${id}»`);
|
|
68
|
+
}
|
|
69
|
+
ids.add(id);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
return data;
|
|
73
|
+
};
|
|
74
|
+
export const readJsonObjectFile = async (path, label) => {
|
|
75
|
+
const resolvedPath = resolve(path);
|
|
76
|
+
let source;
|
|
77
|
+
try {
|
|
78
|
+
source = await readFile(resolvedPath, 'utf8');
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (isSystemError(error) && error.code === 'ENOENT') {
|
|
82
|
+
throw new Error(`${label} не найден: ${resolvedPath}`);
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
const value = JSON.parse(source);
|
|
87
|
+
if (!isObject(value)) {
|
|
88
|
+
throw new Error(`${label} должен содержать JSON-объект`);
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
};
|
|
92
|
+
export const readDatabaseFile = async (databasePath) => validateDatabase(await readJsonObjectFile(databasePath, 'Файл базы данных'));
|
|
93
|
+
const createDiskDatabaseStore = async (databasePath) => {
|
|
94
|
+
const resolvedDatabasePath = resolveDatabasePath(databasePath);
|
|
95
|
+
const initialData = await readDatabaseFile(resolvedDatabasePath);
|
|
96
|
+
const database = await JSONFilePreset(resolvedDatabasePath, initialData);
|
|
97
|
+
const schedule = createSerialQueue();
|
|
98
|
+
const read = async () => {
|
|
99
|
+
database.data = await readDatabaseFile(resolvedDatabasePath);
|
|
100
|
+
return database.data;
|
|
101
|
+
};
|
|
102
|
+
const update = (operation) => schedule(async () => {
|
|
103
|
+
await read();
|
|
104
|
+
const result = operation(database);
|
|
105
|
+
validateDatabase(database.data);
|
|
106
|
+
await database.write();
|
|
107
|
+
return result;
|
|
108
|
+
});
|
|
109
|
+
return { database, path: resolvedDatabasePath, read, update };
|
|
110
|
+
};
|
|
111
|
+
const createMemoryDatabaseStore = (sourceData) => {
|
|
112
|
+
validateDatabase(sourceData);
|
|
113
|
+
const database = { data: structuredClone(sourceData) };
|
|
114
|
+
const schedule = createSerialQueue();
|
|
115
|
+
const read = async () => database.data;
|
|
116
|
+
const update = (operation) => schedule(() => {
|
|
117
|
+
const draft = { data: structuredClone(database.data) };
|
|
118
|
+
const result = operation(draft);
|
|
119
|
+
validateDatabase(draft.data);
|
|
120
|
+
database.data = draft.data;
|
|
121
|
+
return result;
|
|
122
|
+
});
|
|
123
|
+
return { database, read, update };
|
|
124
|
+
};
|
|
125
|
+
/** Creates a disk- or memory-backed database with serialized updates. */
|
|
126
|
+
export const createDatabaseStore = async (config) => (config.data != null ? createMemoryDatabaseStore(config.data) : createDiskDatabaseStore(config.path));
|
|
127
|
+
export const getCollection = (database, resource) => {
|
|
128
|
+
const collection = isSafeKey(resource) ? database.data[resource] : undefined;
|
|
129
|
+
if (!Array.isArray(collection)) {
|
|
130
|
+
throw createHttpError(404, 'Ресурс не найден');
|
|
131
|
+
}
|
|
132
|
+
return collection;
|
|
133
|
+
};
|
|
134
|
+
export const findItemIndex = (collection, id) => collection.findIndex((item) => String(item.id) === String(id));
|
|
135
|
+
export const createId = (collection) => createUniqueId((id) => findItemIndex(collection, id) !== -1);
|
|
136
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAYzI,MAAM,qBAAqB,GAAG,0BAA0B,CAAC;AAEzD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAAE,IAAY,EAAE,SAAS,GAAG,IAAI,OAAO,EAAU,EAAQ,EAAE;IACzG,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9E,OAAO;IACT,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,kCAAkC,CAAC,CAAC;QAC7D,CAAC;QAED,OAAO;IACT,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,0CAA0C,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,8BAA8B,CAAC,CAAC;IACzD,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAE/C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QAClF,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,uCAAuC,CAAC,CAAC;IAClE,CAAC;IAED,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAErB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YACrD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;gBACjC,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,IAAI,KAAK,wDAAwD,CAAC,CAAC;YAC5F,CAAC;YAED,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,SAAS,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE;YAC5C,iBAAiB,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAa,EAAgB,EAAE;IAC9D,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IAED,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IAEvC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,EAAE;QACnD,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClE,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,GAAG,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,WAAW,QAAQ,gCAAgC,CAAC,CAAC;QACvE,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;QAEtB,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;YAChC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,UAAU,KAAK,aAAa,QAAQ,gCAAgC,CAAC,CAAC;YACxF,CAAC;YAED,IAAI,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;gBACpG,MAAM,IAAI,KAAK,CAAC,UAAU,KAAK,aAAa,QAAQ,8CAA8C,CAAC,CAAC;YACtG,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CAAC,UAAU,KAAK,aAAa,QAAQ,gCAAgC,CAAC,CAAC;YACxF,CAAC;YAED,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAE7B,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBAChB,MAAM,IAAI,KAAK,CAAC,WAAW,QAAQ,gCAAgC,EAAE,GAAG,CAAC,CAAC;YAC5E,CAAC;YAED,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,IAAoB,CAAC;AAC9B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EAAE,IAAY,EAAE,KAAa,EAAoC,EAAE;IACxG,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,MAAc,CAAC;IAEnB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,eAAe,YAAY,EAAE,CAAC,CAAC;QACzD,CAAC;QAED,MAAM,KAAK,CAAC;IACd,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAEjC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,+BAA+B,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EAAE,YAAoB,EAAyB,EAAE,CAAC,gBAAgB,CAAC,MAAM,kBAAkB,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAEpK,MAAM,uBAAuB,GAAG,KAAK,EAAE,YAAoB,EAA0B,EAAE;IACrF,MAAM,oBAAoB,GAAG,mBAAmB,CAAC,YAAY,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,MAAM,gBAAgB,CAAC,oBAAoB,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IAErC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACtB,QAAQ,CAAC,IAAI,GAAG,MAAM,gBAAgB,CAAC,oBAAoB,CAAC,CAAC;QAE7D,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAI,SAA6C,EAAc,EAAE,CAC9E,QAAQ,CAAC,KAAK,IAAI,EAAE;QAClB,MAAM,IAAI,EAAE,CAAC;QAEb,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;QAEnC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;QAEvB,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEL,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAChE,CAAC,CAAC;AAEF,MAAM,yBAAyB,GAAG,CAAC,UAAwB,EAAiB,EAAE;IAC5E,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAE7B,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,eAAe,CAAC,UAAU,CAAC,EAAE,CAAC;IACvD,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IAErC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;IACvC,MAAM,MAAM,GAAG,CAAI,SAA6C,EAAc,EAAE,CAC9E,QAAQ,CAAC,GAAG,EAAE;QACZ,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACvD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAEhC,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7B,QAAQ,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QAE3B,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEL,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC,CAAC;AAEF,yEAAyE;AACzE,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,EAAE,MAAsB,EAA0B,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,yBAAyB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AAEnM,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,QAA2B,EAAE,QAAgB,EAAoB,EAAE;IAC/F,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE7E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,eAAe,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IACjD,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,UAA4B,EAAE,EAAW,EAAU,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;AAEnJ,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,UAA4B,EAAU,EAAE,CAAC,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
import type { OpenapiSchema } from '../types.js';
|
|
3
|
+
export interface StoredFileMetadata {
|
|
4
|
+
directory: string;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
export interface FileRecord extends StoredFileMetadata {
|
|
9
|
+
size: number;
|
|
10
|
+
}
|
|
11
|
+
export interface FileMetadata extends FileRecord {
|
|
12
|
+
downloadUrl: string;
|
|
13
|
+
metadataUrl: string;
|
|
14
|
+
url: string;
|
|
15
|
+
}
|
|
16
|
+
export interface FileUpload extends StoredFileMetadata {
|
|
17
|
+
maxFileSize: number;
|
|
18
|
+
override: boolean;
|
|
19
|
+
stream: Readable;
|
|
20
|
+
}
|
|
21
|
+
export interface FileUpdate {
|
|
22
|
+
directory?: string;
|
|
23
|
+
name?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface FileStore {
|
|
26
|
+
get(path: string): Promise<{
|
|
27
|
+
file: FileRecord;
|
|
28
|
+
stream: Readable;
|
|
29
|
+
}>;
|
|
30
|
+
metadata(path: string): Promise<FileRecord>;
|
|
31
|
+
remove(path: string): Promise<void>;
|
|
32
|
+
update(path: string, update: FileUpdate): Promise<FileRecord>;
|
|
33
|
+
upload(upload: FileUpload): Promise<{
|
|
34
|
+
created: boolean;
|
|
35
|
+
file: FileRecord;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
export declare const FILE_HEADERS: {
|
|
39
|
+
readonly directory: {
|
|
40
|
+
readonly key: 'content-directory';
|
|
41
|
+
readonly name: 'Content-Directory';
|
|
42
|
+
};
|
|
43
|
+
readonly name: {
|
|
44
|
+
readonly key: 'content-name';
|
|
45
|
+
readonly name: 'Content-Name';
|
|
46
|
+
};
|
|
47
|
+
readonly override: {
|
|
48
|
+
readonly key: 'content-override';
|
|
49
|
+
readonly name: 'Content-Override';
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export declare const FILE_ROUTES: {
|
|
53
|
+
readonly download: '/_files/download';
|
|
54
|
+
readonly metadata: '/_files/metadata';
|
|
55
|
+
readonly storage: '/_files/storage';
|
|
56
|
+
};
|
|
57
|
+
export declare const PATCH_BODY_LIMIT: number;
|
|
58
|
+
export declare const FILE_METADATA_SCHEMA: OpenapiSchema;
|
|
59
|
+
export declare const FILE_UPDATE_SCHEMA: OpenapiSchema;
|
|
60
|
+
export declare const getFileKey: ({ directory, name }: Pick<StoredFileMetadata, 'directory' | 'name'>) => string;
|
|
61
|
+
export declare const validateName: (value: unknown, source: string) => string;
|
|
62
|
+
export declare const validateDirectory: (value: unknown, source: string) => string;
|
|
63
|
+
export declare const normalizeMimeType: (value: unknown, source?: string) => string;
|
|
64
|
+
export declare const normalizeStoredFileMetadata: (value: unknown, source: string) => StoredFileMetadata;
|
|
65
|
+
export declare const getPathLocation: (path: string) => Pick<StoredFileMetadata, 'directory' | 'name'>;
|
|
66
|
+
export declare const createFileMetadata: (file: FileRecord) => FileMetadata;
|
|
67
|
+
export declare const getDownloadName: (name: string) => string;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import { createHttpError, isObject } from '../utils.js';
|
|
3
|
+
export const FILE_HEADERS = {
|
|
4
|
+
directory: { key: 'content-directory', name: 'Content-Directory' },
|
|
5
|
+
name: { key: 'content-name', name: 'Content-Name' },
|
|
6
|
+
override: { key: 'content-override', name: 'Content-Override' },
|
|
7
|
+
};
|
|
8
|
+
export const FILE_ROUTES = {
|
|
9
|
+
download: '/_files/download',
|
|
10
|
+
metadata: '/_files/metadata',
|
|
11
|
+
storage: '/_files/storage',
|
|
12
|
+
};
|
|
13
|
+
export const PATCH_BODY_LIMIT = 64 * 1024;
|
|
14
|
+
const MIME_TYPE_PATTERN = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+\/[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
15
|
+
export const FILE_METADATA_SCHEMA = {
|
|
16
|
+
properties: {
|
|
17
|
+
directory: { type: 'string' },
|
|
18
|
+
downloadUrl: { format: 'uri-reference', type: 'string' },
|
|
19
|
+
metadataUrl: { format: 'uri-reference', type: 'string' },
|
|
20
|
+
mimeType: { type: 'string' },
|
|
21
|
+
name: { type: 'string' },
|
|
22
|
+
size: { minimum: 0, type: 'integer' },
|
|
23
|
+
url: { format: 'uri-reference', type: 'string' },
|
|
24
|
+
},
|
|
25
|
+
required: ['directory', 'downloadUrl', 'metadataUrl', 'mimeType', 'name', 'size', 'url'],
|
|
26
|
+
type: 'object',
|
|
27
|
+
};
|
|
28
|
+
export const FILE_UPDATE_SCHEMA = {
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
anyOf: [{ required: ['directory'] }, { required: ['name'] }],
|
|
31
|
+
properties: { directory: { type: 'string' }, name: { type: 'string' } },
|
|
32
|
+
type: 'object',
|
|
33
|
+
};
|
|
34
|
+
export const getFileKey = ({ directory, name }) => [directory, name].filter(Boolean).join('/');
|
|
35
|
+
const WINDOWS_RESERVED_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i;
|
|
36
|
+
const hasControlCharacter = (value) => [...value].some((character) => {
|
|
37
|
+
const code = character.charCodeAt(0);
|
|
38
|
+
return code <= 31 || code === 127;
|
|
39
|
+
});
|
|
40
|
+
export const validateName = (value, source) => {
|
|
41
|
+
if (typeof value !== 'string' ||
|
|
42
|
+
value === '' ||
|
|
43
|
+
value === '.' ||
|
|
44
|
+
value === '..' ||
|
|
45
|
+
value.endsWith('.') ||
|
|
46
|
+
value.endsWith(' ') ||
|
|
47
|
+
/[<>:"/\\|?*]/.test(value) ||
|
|
48
|
+
hasControlCharacter(value) ||
|
|
49
|
+
WINDOWS_RESERVED_NAME.test(value)) {
|
|
50
|
+
throw createHttpError(400, `${source} должен содержать безопасное имя файла`);
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
};
|
|
54
|
+
export const validateDirectory = (value, source) => {
|
|
55
|
+
if (typeof value !== 'string') {
|
|
56
|
+
throw createHttpError(400, `${source} должен содержать безопасный относительный путь`);
|
|
57
|
+
}
|
|
58
|
+
if (value === '') {
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
const parts = value.split('/');
|
|
62
|
+
if (value.startsWith('/') || value.endsWith('/') || value.includes('\\') || value.includes('\0') || parts.some((part) => part === '' || part === '.' || part === '..')) {
|
|
63
|
+
throw createHttpError(400, `${source} должен содержать безопасный относительный путь`);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
};
|
|
67
|
+
export const normalizeMimeType = (value, source = 'Заголовок Content-Type') => {
|
|
68
|
+
const mimeType = typeof value === 'string' ? value.split(';', 1)[0].trim().toLowerCase() : '';
|
|
69
|
+
if (mimeType === '' || !MIME_TYPE_PATTERN.test(mimeType)) {
|
|
70
|
+
throw createHttpError(400, `${source} должен содержать корректный MIME-тип`);
|
|
71
|
+
}
|
|
72
|
+
return mimeType;
|
|
73
|
+
};
|
|
74
|
+
export const normalizeStoredFileMetadata = (value, source) => {
|
|
75
|
+
if (!isObject(value)) {
|
|
76
|
+
throw new Error(`${source} должна содержать JSON-объект`);
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
return {
|
|
80
|
+
directory: validateDirectory(value.directory ?? '', `${source}.directory`),
|
|
81
|
+
mimeType: normalizeMimeType(value.mimeType, `${source}.mimeType`),
|
|
82
|
+
name: validateName(value.name, `${source}.name`),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw new Error(error instanceof Error ? error.message : String(error), { cause: error });
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
export const getPathLocation = (path) => {
|
|
90
|
+
const normalizedPath = validateDirectory(path, 'Путь файла');
|
|
91
|
+
const parts = normalizedPath.split('/');
|
|
92
|
+
const name = validateName(parts.pop(), 'Путь файла');
|
|
93
|
+
return { directory: parts.join('/'), name };
|
|
94
|
+
};
|
|
95
|
+
const encodeFilePath = (file) => getFileKey(file).split('/').map(encodeURIComponent).join('/');
|
|
96
|
+
export const createFileMetadata = (file) => {
|
|
97
|
+
const path = encodeFilePath(file);
|
|
98
|
+
return {
|
|
99
|
+
...file,
|
|
100
|
+
downloadUrl: `${FILE_ROUTES.download}/${path}`,
|
|
101
|
+
metadataUrl: `${FILE_ROUTES.metadata}/${path}`,
|
|
102
|
+
url: `${FILE_ROUTES.storage}/${path}`,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
export const getDownloadName = (name) => encodeURIComponent(basename(name)).replaceAll("'", '%27');
|
|
106
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../src/files/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAgCxD,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,SAAS,EAAE,EAAE,GAAG,EAAE,mBAAmB,EAAE,IAAI,EAAE,mBAAmB,EAAE;IAClE,IAAI,EAAE,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,EAAE;IACnD,QAAQ,EAAE,EAAE,GAAG,EAAE,kBAAkB,EAAE,IAAI,EAAE,kBAAkB,EAAE;CACvD,CAAC;AAEX,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,QAAQ,EAAE,kBAAkB;IAC5B,QAAQ,EAAE,kBAAkB;IAC5B,OAAO,EAAE,iBAAiB;CAClB,CAAC;AAEX,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,GAAG,IAAI,CAAC;AAC1C,MAAM,iBAAiB,GAAG,4DAA4D,CAAC;AAEvF,MAAM,CAAC,MAAM,oBAAoB,GAAkB;IACjD,UAAU,EAAE;QACV,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC7B,WAAW,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxD,WAAW,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxD,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC5B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE;QACrC,GAAG,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE;KACjD;IACD,QAAQ,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC;IACxF,IAAI,EAAE,QAAQ;CACf,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAkB;IAC/C,oBAAoB,EAAE,KAAK;IAC3B,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;IAC5D,UAAU,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IACvE,IAAI,EAAE,QAAQ;CACf,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAkD,EAAU,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEvJ,MAAM,qBAAqB,GAAG,mDAAmD,CAAC;AAClF,MAAM,mBAAmB,GAAG,CAAC,KAAa,EAAW,EAAE,CACrD,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;IAC5B,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAErC,OAAO,IAAI,IAAI,EAAE,IAAI,IAAI,KAAK,GAAG,CAAC;AACpC,CAAC,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAc,EAAE,MAAc,EAAU,EAAE;IACrE,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,EAAE;QACZ,KAAK,KAAK,GAAG;QACb,KAAK,KAAK,IAAI;QACd,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QACnB,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QACnB,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC;QAC1B,mBAAmB,CAAC,KAAK,CAAC;QAC1B,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,EACjC,CAAC;QACD,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,MAAM,wCAAwC,CAAC,CAAC;IAChF,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAAE,MAAc,EAAU,EAAE;IAC1E,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,MAAM,iDAAiD,CAAC,CAAC;IACzF,CAAC;IAED,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACjB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE/B,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACvK,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,MAAM,iDAAiD,CAAC,CAAC;IACzF,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAAE,MAAM,GAAG,wBAAwB,EAAU,EAAE;IAC7F,MAAM,QAAQ,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAE9F,IAAI,QAAQ,KAAK,EAAE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzD,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,MAAM,uCAAuC,CAAC,CAAC;IAC/E,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,KAAc,EAAE,MAAc,EAAsB,EAAE;IAChG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,+BAA+B,CAAC,CAAC;IAC5D,CAAC;IAED,IAAI,CAAC;QACH,OAAO;YACL,SAAS,EAAE,iBAAiB,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,EAAE,GAAG,MAAM,YAAY,CAAC;YAC1E,QAAQ,EAAE,iBAAiB,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,MAAM,WAAW,CAAC;YACjE,IAAI,EAAE,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC;SACjD,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5F,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAY,EAAkD,EAAE;IAC9F,MAAM,cAAc,GAAG,iBAAiB,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAC;IAErD,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;AAC9C,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,IAAwB,EAAU,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE3H,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAgB,EAAgB,EAAE;IACnE,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAElC,OAAO;QACL,GAAG,IAAI;QACP,WAAW,EAAE,GAAG,WAAW,CAAC,QAAQ,IAAI,IAAI,EAAE;QAC9C,WAAW,EAAE,GAAG,WAAW,CAAC,QAAQ,IAAI,IAAI,EAAE;QAC9C,GAAG,EAAE,GAAG,WAAW,CAAC,OAAO,IAAI,IAAI,EAAE;KACtC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC"}
|