@sdk-it/core 0.19.1 → 0.21.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.
@@ -0,0 +1,139 @@
1
+ import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
2
+ import { dirname, extname, isAbsolute, join, normalize } from "node:path";
3
+ async function getFile(filePath) {
4
+ if (await exist(filePath)) {
5
+ return readFile(filePath, "utf-8");
6
+ }
7
+ return null;
8
+ }
9
+ async function exist(file) {
10
+ return stat(file).then(() => true).catch(() => false);
11
+ }
12
+ async function readFolder(path, recursive = false) {
13
+ if (!await exist(path)) {
14
+ return [];
15
+ }
16
+ const entries = await readdir(path, { withFileTypes: true });
17
+ const results = [];
18
+ for (const entry of entries) {
19
+ if (entry.isDirectory()) {
20
+ if (recursive) {
21
+ const subFiles = await readFolder(join(path, entry.name), true);
22
+ for (const sub of subFiles) {
23
+ results.push(`${entry.name}/${sub}`);
24
+ }
25
+ }
26
+ } else {
27
+ results.push(entry.name);
28
+ }
29
+ }
30
+ return results;
31
+ }
32
+ async function writeFiles(dir, contents) {
33
+ await Promise.all(
34
+ Object.entries(contents).map(async ([file, content]) => {
35
+ if (content === null) {
36
+ return;
37
+ }
38
+ const filePath = isAbsolute(file) ? file : join(dir, file);
39
+ await mkdir(dirname(filePath), { recursive: true });
40
+ if (typeof content === "string") {
41
+ await writeFile(filePath, content, "utf-8");
42
+ } else {
43
+ if (content.ignoreIfExists) {
44
+ if (!await exist(filePath)) {
45
+ await writeFile(filePath, content.content, "utf-8");
46
+ }
47
+ } else {
48
+ await writeFile(filePath, content.content, "utf-8");
49
+ }
50
+ }
51
+ })
52
+ );
53
+ }
54
+ async function getFolderExports(folder, readFolder2, includeExtension = true, extensions = ["ts"], ignore = () => false) {
55
+ const files = await readFolder2(folder);
56
+ const exports = [];
57
+ for (const file of files) {
58
+ if (ignore(file)) {
59
+ continue;
60
+ }
61
+ if (file.isFolder) {
62
+ if (await exist(`${file.filePath}/index.ts`)) {
63
+ exports.push(
64
+ `export * from './${file.fileName}/index${includeExtension ? ".ts" : ""}';`
65
+ );
66
+ }
67
+ } else if (file.fileName !== "index.ts" && extensions.includes(getExt(file.fileName))) {
68
+ exports.push(
69
+ `export * from './${includeExtension ? file.fileName : file.fileName.replace(extname(file.fileName), "")}';`
70
+ );
71
+ }
72
+ }
73
+ return exports.join("\n");
74
+ }
75
+ async function getFolderExportsV2(folder, readFolder2, options = {
76
+ extensions: "ts",
77
+ ignore: () => false,
78
+ includeExtension: true,
79
+ exportSyntax: "export * from "
80
+ }) {
81
+ options.includeExtension ??= true;
82
+ if (!await exist(folder)) {
83
+ return "";
84
+ }
85
+ const files = await readFolder2(folder);
86
+ const exports = [];
87
+ for (const file of files) {
88
+ if (options.ignore?.(file)) {
89
+ continue;
90
+ }
91
+ if (file.isFolder) {
92
+ if (await exist(`${file.filePath}/index.${options.extensions}`)) {
93
+ exports.push(
94
+ `${options.exportSyntax} './${file.fileName}/index${options.includeExtension ? `.${options.extensions}` : ""}';`
95
+ );
96
+ }
97
+ } else if (file.fileName !== `index.${options.extensions}` && options.extensions.includes(getExt(file.fileName))) {
98
+ exports.push(
99
+ `${options.exportSyntax} './${options.includeExtension ? file.fileName : file.fileName.replace(extname(file.fileName), "")}';`
100
+ );
101
+ }
102
+ }
103
+ return exports.join("\n");
104
+ }
105
+ const getExt = (fileName) => {
106
+ if (!fileName) {
107
+ return "";
108
+ }
109
+ const lastDot = fileName.lastIndexOf(".");
110
+ if (lastDot === -1) {
111
+ return "";
112
+ }
113
+ const ext = fileName.slice(lastDot + 1).split("/").filter(Boolean).join("");
114
+ if (ext === fileName) {
115
+ return "";
116
+ }
117
+ return ext || "txt";
118
+ };
119
+ function addLeadingSlash(path) {
120
+ return normalize(join("/", path));
121
+ }
122
+ function removeTrialingSlashes(path, keepLastOne = false) {
123
+ while (path.endsWith("/")) {
124
+ path = path.slice(0, -1);
125
+ }
126
+ return path + (keepLastOne ? "/" : "");
127
+ }
128
+ export {
129
+ addLeadingSlash,
130
+ exist,
131
+ getExt,
132
+ getFile,
133
+ getFolderExports,
134
+ getFolderExportsV2,
135
+ readFolder,
136
+ removeTrialingSlashes,
137
+ writeFiles
138
+ };
139
+ //# sourceMappingURL=file-system.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/file-system.ts"],
4
+ "sourcesContent": ["import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';\nimport { dirname, extname, isAbsolute, join, normalize } from 'node:path';\n\nexport async function getFile(filePath: string) {\n if (await exist(filePath)) {\n return readFile(filePath, 'utf-8');\n }\n return null;\n}\n\nexport async function exist(file: string): Promise<boolean> {\n return stat(file)\n .then(() => true)\n .catch(() => false);\n}\n\nexport async function readFolder(\n path: string,\n recursive = false,\n): Promise<string[]> {\n if (!(await exist(path))) {\n return [];\n }\n const entries = await readdir(path, { withFileTypes: true });\n const results: string[] = [];\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (recursive) {\n const subFiles = await readFolder(join(path, entry.name), true);\n for (const sub of subFiles) {\n results.push(`${entry.name}/${sub}`);\n }\n }\n } else {\n results.push(entry.name);\n }\n }\n return results;\n}\n\nexport type WriteContent = Record<\n string,\n null | string | { content: string; ignoreIfExists?: boolean }\n>;\n\nexport type ReadFolderFn = (\n folder: string,\n) => Promise<{ filePath: string; fileName: string; isFolder: boolean }[]>;\nexport type Writer = (dir: string, contents: WriteContent) => Promise<void>;\n\nexport async function writeFiles(dir: string, contents: WriteContent) {\n await Promise.all(\n Object.entries(contents).map(async ([file, content]) => {\n if (content === null) {\n return;\n }\n const filePath = isAbsolute(file) ? file : join(dir, file);\n await mkdir(dirname(filePath), { recursive: true });\n if (typeof content === 'string') {\n await writeFile(filePath, content, 'utf-8');\n } else {\n if (content.ignoreIfExists) {\n if (!(await exist(filePath))) {\n await writeFile(filePath, content.content, 'utf-8');\n }\n } else {\n await writeFile(filePath, content.content, 'utf-8');\n }\n }\n }),\n );\n}\n\n/**\n * @deprecated use getFolderExportsV2 instead\n */\nexport async function getFolderExports(\n folder: string,\n readFolder: ReadFolderFn,\n includeExtension = true,\n extensions = ['ts'],\n ignore: (config: {\n filePath: string;\n fileName: string;\n isFolder: boolean;\n }) => boolean = () => false,\n) {\n const files = await readFolder(folder);\n const exports: string[] = [];\n for (const file of files) {\n if (ignore(file)) {\n continue;\n }\n if (file.isFolder) {\n if (await exist(`${file.filePath}/index.ts`)) {\n exports.push(\n `export * from './${file.fileName}/index${includeExtension ? '.ts' : ''}';`,\n );\n }\n } else if (\n file.fileName !== 'index.ts' &&\n extensions.includes(getExt(file.fileName))\n ) {\n exports.push(\n `export * from './${includeExtension ? file.fileName : file.fileName.replace(extname(file.fileName), '')}';`,\n );\n }\n }\n return exports.join('\\n');\n}\n\nexport async function getFolderExportsV2(\n folder: string,\n readFolder: ReadFolderFn,\n options: {\n includeExtension?: boolean;\n extensions: string;\n ignore?: (fileInfo: {\n filePath: string;\n fileName: string;\n isFolder: boolean;\n }) => boolean;\n exportSyntax: string;\n } = {\n extensions: 'ts',\n ignore: () => false,\n includeExtension: true,\n exportSyntax: 'export * from ',\n },\n) {\n options.includeExtension ??= true;\n if (!(await exist(folder))) {\n return '';\n }\n const files = await readFolder(folder);\n const exports: string[] = [];\n for (const file of files) {\n if (options.ignore?.(file)) {\n continue;\n }\n if (file.isFolder) {\n if (await exist(`${file.filePath}/index.${options.extensions}`)) {\n exports.push(\n `${options.exportSyntax} './${file.fileName}/index${\n options.includeExtension ? `.${options.extensions}` : ''\n }';`,\n );\n }\n } else if (\n file.fileName !== `index.${options.extensions}` &&\n options.extensions.includes(getExt(file.fileName))\n ) {\n exports.push(\n `${options.exportSyntax} './${options.includeExtension ? file.fileName : file.fileName.replace(extname(file.fileName), '')}';`,\n );\n }\n }\n return exports.join('\\n');\n}\n\nexport const getExt = (fileName?: string) => {\n if (!fileName) {\n return ''; // shouldn't happen as there will always be a file name\n }\n const lastDot = fileName.lastIndexOf('.');\n if (lastDot === -1) {\n return '';\n }\n const ext = fileName\n .slice(lastDot + 1)\n .split('/')\n .filter(Boolean)\n .join('');\n if (ext === fileName) {\n // files that have no extension\n return '';\n }\n return ext || 'txt';\n};\n\nexport function addLeadingSlash(path: string) {\n return normalize(join('/', path));\n}\n\nexport function removeTrialingSlashes(path: string, keepLastOne = false) {\n while (path.endsWith('/')) {\n path = path.slice(0, -1);\n }\n return path + (keepLastOne ? '/' : '');\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["readFolder"]
7
+ }
@@ -1,5 +1,5 @@
1
1
  import type { OperationObject, PathsObject } from 'openapi3-ts/oas31';
2
- import { $types } from './deriver.ts';
2
+ import { $types } from './deriver.js';
3
3
  export type Method = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'trace' | 'head';
4
4
  export declare const methods: readonly ["get", "post", "put", "patch", "delete", "trace", "head"];
5
5
  export type SemanticSource = 'query' | 'queries' | 'body' | 'params' | 'headers';
@@ -0,0 +1,237 @@
1
+ import { $types } from "./deriver.js";
2
+ const methods = [
3
+ "get",
4
+ "post",
5
+ "put",
6
+ "patch",
7
+ "delete",
8
+ "trace",
9
+ "head"
10
+ ];
11
+ const semanticSourceToOpenAPI = {
12
+ queries: "query",
13
+ query: "query",
14
+ headers: "header",
15
+ params: "path"
16
+ };
17
+ class Paths {
18
+ #commonZodImport;
19
+ #onOperation;
20
+ #operations = [];
21
+ constructor(config) {
22
+ this.#commonZodImport = config.commonZodImport;
23
+ this.#onOperation = config.onOperation;
24
+ }
25
+ addPath(name, path, method, contentType, selectors, responses, sourceFile, tags, description) {
26
+ const responsesObject = this.#responseItemToResponses(responses);
27
+ this.#operations.push({
28
+ name,
29
+ path: this.#tunePath(path),
30
+ sourceFile,
31
+ contentType,
32
+ method,
33
+ selectors,
34
+ responses: responsesObject,
35
+ tags,
36
+ description
37
+ });
38
+ return this;
39
+ }
40
+ #responseItemToResponses(responses) {
41
+ const responsesObject = {};
42
+ for (const item of responses) {
43
+ const ct = item.contentType;
44
+ const schema = item.response ? toSchema(item.response) : {};
45
+ if (!responsesObject[item.statusCode]) {
46
+ responsesObject[item.statusCode] = {
47
+ description: `Response for ${item.statusCode}`,
48
+ content: ct !== "empty" ? {
49
+ [ct]: ct === "application/octet-stream" ? { schema: { type: "string", format: "binary" } } : { schema }
50
+ } : void 0,
51
+ headers: item.headers.length ? item.headers.reduce((acc, current) => {
52
+ const headers = typeof current === "string" ? { [current]: [] } : current;
53
+ return Object.entries(headers).reduce(
54
+ (subAcc, [key, value]) => {
55
+ const header = {
56
+ [key]: {
57
+ schema: {
58
+ type: "string",
59
+ enum: value.length ? value : void 0
60
+ }
61
+ }
62
+ };
63
+ return { ...subAcc, ...header };
64
+ },
65
+ acc
66
+ );
67
+ }, {}) : void 0
68
+ };
69
+ } else {
70
+ if (!responsesObject[item.statusCode].content[ct]) {
71
+ responsesObject[item.statusCode].content[ct] = { schema };
72
+ } else {
73
+ const existing = responsesObject[item.statusCode].content[ct].schema;
74
+ if (existing.oneOf) {
75
+ if (!existing.oneOf.find(
76
+ (it) => JSON.stringify(it) === JSON.stringify(schema)
77
+ )) {
78
+ existing.oneOf.push(schema);
79
+ }
80
+ } else if (JSON.stringify(existing) !== JSON.stringify(schema)) {
81
+ responsesObject[item.statusCode].content[ct].schema = {
82
+ oneOf: [existing, schema]
83
+ };
84
+ }
85
+ }
86
+ }
87
+ }
88
+ return responsesObject;
89
+ }
90
+ async #selectosToParameters(selectors) {
91
+ const parameters = [];
92
+ const bodySchemaProps = {};
93
+ for (const selector of selectors) {
94
+ if (selector.source === "body") {
95
+ bodySchemaProps[selector.name] = {
96
+ required: selector.required,
97
+ schema: await evalZod(selector.against, this.#commonZodImport)
98
+ };
99
+ continue;
100
+ }
101
+ const parameter = {
102
+ in: semanticSourceToOpenAPI[selector.source],
103
+ name: selector.name,
104
+ required: selector.required,
105
+ schema: await evalZod(selector.against, this.#commonZodImport)
106
+ };
107
+ parameters.push(parameter);
108
+ }
109
+ return { parameters, bodySchemaProps };
110
+ }
111
+ async getPaths() {
112
+ const operations = {};
113
+ for (const operation of this.#operations) {
114
+ const { path, method, selectors } = operation;
115
+ const { parameters, bodySchemaProps } = await this.#selectosToParameters(selectors);
116
+ const bodySchema = {};
117
+ const required = [];
118
+ for (const [key, value] of Object.entries(bodySchemaProps)) {
119
+ if (value.required) {
120
+ required.push(key);
121
+ }
122
+ bodySchema[key] = value.schema;
123
+ }
124
+ const operationObject = {
125
+ operationId: operation.name,
126
+ parameters,
127
+ tags: operation.tags,
128
+ description: operation.description,
129
+ requestBody: Object.keys(bodySchema).length ? {
130
+ required: required.length ? true : false,
131
+ content: {
132
+ [operation.contentType || "application/json"]: {
133
+ schema: {
134
+ required: required.length ? required : void 0,
135
+ type: "object",
136
+ properties: bodySchema
137
+ }
138
+ }
139
+ }
140
+ } : void 0,
141
+ responses: Object.keys(operation.responses).length === 0 ? void 0 : operation.responses
142
+ };
143
+ if (!operations[path]) {
144
+ operations[path] = {};
145
+ }
146
+ operations[path][method] = operationObject;
147
+ if (this.#onOperation) {
148
+ const paths = this.#onOperation?.(
149
+ operation.sourceFile,
150
+ method,
151
+ path,
152
+ operationObject
153
+ );
154
+ Object.assign(operations, paths ?? {});
155
+ }
156
+ }
157
+ return operations;
158
+ }
159
+ /**
160
+ * Converts Express/Node.js style path parameters (/path/:param) to OpenAPI style (/path/{param})
161
+ */
162
+ #tunePath(path) {
163
+ return path.replace(/:([^/]+)/g, "{$1}");
164
+ }
165
+ }
166
+ async function evalZod(schema, commonZodImport) {
167
+ const lines = [
168
+ `import { createRequire } from "node:module";`,
169
+ `const filename = "${import.meta.url}";`,
170
+ `const require = createRequire(filename);`,
171
+ `const z = require("zod");`,
172
+ commonZodImport ? `const commonZod = require('${commonZodImport}');` : "",
173
+ `const {zodToJsonSchema} = require('zod-to-json-schema');`,
174
+ `const schema = ${schema.replace(".optional()", "").replaceAll("instanceof(File)", "string().base64()")};`,
175
+ `const jsonSchema = zodToJsonSchema(schema, {
176
+ $refStrategy: 'root',
177
+ basePath: ['#', 'components', 'schemas'],
178
+ target: 'jsonSchema7',
179
+ base64Strategy: 'format:binary',
180
+ });`,
181
+ `export default jsonSchema;`
182
+ ];
183
+ const base64 = Buffer.from(lines.join("\n")).toString("base64");
184
+ return import(`data:text/javascript;base64,${base64}`).then((mod) => mod.default).then(({ $schema, ...result }) => result);
185
+ }
186
+ function toSchema(data) {
187
+ if (data === null || data === void 0) {
188
+ return { type: "any" };
189
+ } else if (typeof data === "string") {
190
+ const isRef = data.startsWith("#");
191
+ if (isRef) {
192
+ return { $ref: data };
193
+ }
194
+ return { type: data };
195
+ } else if (data.kind === "literal") {
196
+ return { enum: [data.value], type: data[$types][0] };
197
+ } else if (data.kind === "record") {
198
+ return {
199
+ type: "object",
200
+ additionalProperties: toSchema(data[$types][0])
201
+ };
202
+ } else if (data.kind === "array") {
203
+ const items = data[$types].map(toSchema);
204
+ return { type: "array", items: data[$types].length ? items[0] : {} };
205
+ } else if (data.kind === "union") {
206
+ return { anyOf: data[$types].map(toSchema) };
207
+ } else if (data.kind === "intersection") {
208
+ return { allOf: data[$types].map(toSchema) };
209
+ } else if ($types in data) {
210
+ return data[$types].map(toSchema)[0] ?? {};
211
+ } else {
212
+ const props = {};
213
+ const required = [];
214
+ for (const [key, value] of Object.entries(data)) {
215
+ props[key] = toSchema(value);
216
+ if (!value.optional) {
217
+ required.push(key);
218
+ }
219
+ }
220
+ return {
221
+ type: "object",
222
+ properties: props,
223
+ required,
224
+ additionalProperties: false
225
+ };
226
+ }
227
+ }
228
+ function isHttpMethod(name) {
229
+ return ["get", "post", "put", "delete", "patch"].includes(name);
230
+ }
231
+ export {
232
+ Paths,
233
+ isHttpMethod,
234
+ methods,
235
+ toSchema
236
+ };
237
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/paths.ts"],
4
+ "sourcesContent": ["import type {\n HeadersObject,\n OperationObject,\n ParameterObject,\n PathsObject,\n ResponseObject,\n ResponsesObject,\n SchemaObject,\n} from 'openapi3-ts/oas31';\n\nimport { $types } from './deriver.js';\n\nexport type Method =\n | 'get'\n | 'post'\n | 'put'\n | 'patch'\n | 'delete'\n | 'trace'\n | 'head';\nexport const methods = [\n 'get',\n 'post',\n 'put',\n 'patch',\n 'delete',\n 'trace',\n 'head',\n] as const;\nexport type SemanticSource =\n | 'query'\n | 'queries'\n | 'body'\n | 'params'\n | 'headers';\n\nconst semanticSourceToOpenAPI = {\n queries: 'query',\n query: 'query',\n headers: 'header',\n params: 'path',\n} as const;\nexport interface Selector {\n name: string;\n select: string;\n against: string;\n source: SemanticSource;\n nullable: boolean;\n required: boolean;\n}\n\nexport interface ResponseItem {\n statusCode: string;\n response?: DateType;\n contentType: string;\n headers: (string | Record<string, string[]>)[];\n}\n\nexport type OnOperation = (\n sourceFile: string,\n method: Method,\n path: string,\n operation: OperationObject,\n) => PathsObject;\nexport class Paths {\n #commonZodImport?: string;\n #onOperation?: OnOperation;\n #operations: Array<{\n sourceFile: string;\n name: string;\n path: string;\n method: Method;\n selectors: Selector[];\n responses: ResponsesObject;\n contentType?: string;\n tags?: string[];\n description?: string;\n }> = [];\n\n constructor(config: { commonZodImport?: string; onOperation?: OnOperation }) {\n this.#commonZodImport = config.commonZodImport;\n this.#onOperation = config.onOperation;\n }\n\n addPath(\n name: string,\n path: string,\n method: Method,\n contentType: string | undefined,\n selectors: Selector[],\n responses: ResponseItem[],\n sourceFile: string,\n tags?: string[],\n description?: string,\n ) {\n const responsesObject = this.#responseItemToResponses(responses);\n\n this.#operations.push({\n name,\n path: this.#tunePath(path),\n sourceFile,\n contentType: contentType,\n method,\n selectors,\n responses: responsesObject,\n tags,\n description,\n });\n return this;\n }\n\n #responseItemToResponses(responses: ResponseItem[]): ResponsesObject {\n const responsesObject: ResponsesObject = {};\n for (const item of responses) {\n const ct = item.contentType;\n const schema = item.response ? toSchema(item.response) : {};\n if (!responsesObject[item.statusCode]) {\n responsesObject[item.statusCode] = {\n description: `Response for ${item.statusCode}`,\n content:\n ct !== 'empty'\n ? {\n [ct]:\n ct === 'application/octet-stream'\n ? { schema: { type: 'string', format: 'binary' } }\n : { schema },\n }\n : undefined,\n headers: item.headers.length\n ? item.headers.reduce<HeadersObject>((acc, current) => {\n const headers =\n typeof current === 'string' ? { [current]: [] } : current;\n return Object.entries(headers).reduce<HeadersObject>(\n (subAcc, [key, value]) => {\n const header: HeadersObject = {\n [key]: {\n schema: {\n type: 'string',\n enum: value.length ? value : undefined,\n },\n },\n };\n return { ...subAcc, ...header };\n },\n acc,\n );\n }, {})\n : undefined,\n } satisfies ResponseObject;\n } else {\n if (!responsesObject[item.statusCode].content[ct]) {\n responsesObject[item.statusCode].content[ct] = { schema };\n } else {\n const existing = responsesObject[item.statusCode].content[ct]\n .schema as SchemaObject;\n if (existing.oneOf) {\n if (\n !existing.oneOf.find(\n (it) => JSON.stringify(it) === JSON.stringify(schema),\n )\n ) {\n existing.oneOf.push(schema);\n }\n } else if (JSON.stringify(existing) !== JSON.stringify(schema)) {\n responsesObject[item.statusCode].content[ct].schema = {\n oneOf: [existing, schema],\n };\n }\n }\n }\n }\n return responsesObject;\n }\n\n async #selectosToParameters(selectors: Selector[]) {\n const parameters: ParameterObject[] = [];\n const bodySchemaProps: Record<\n string,\n { required: boolean; schema: SchemaObject }\n > = {};\n for (const selector of selectors) {\n if (selector.source === 'body') {\n bodySchemaProps[selector.name] = {\n required: selector.required,\n schema: await evalZod(selector.against, this.#commonZodImport),\n };\n continue;\n }\n\n const parameter: ParameterObject = {\n in: semanticSourceToOpenAPI[selector.source],\n name: selector.name,\n required: selector.required,\n schema: await evalZod(selector.against, this.#commonZodImport),\n };\n parameters.push(parameter);\n }\n return { parameters, bodySchemaProps };\n }\n\n async getPaths() {\n const operations: PathsObject = {};\n for (const operation of this.#operations) {\n const { path, method, selectors } = operation;\n const { parameters, bodySchemaProps } =\n await this.#selectosToParameters(selectors);\n const bodySchema: Record<string, SchemaObject> = {};\n const required: string[] = [];\n for (const [key, value] of Object.entries(bodySchemaProps)) {\n if (value.required) {\n required.push(key);\n }\n bodySchema[key] = value.schema;\n }\n const operationObject: OperationObject = {\n operationId: operation.name,\n parameters,\n tags: operation.tags,\n description: operation.description,\n requestBody: Object.keys(bodySchema).length\n ? {\n required: required.length ? true : false,\n content: {\n [operation.contentType || 'application/json']: {\n schema: {\n required: required.length ? required : undefined,\n type: 'object',\n properties: bodySchema,\n },\n },\n },\n }\n : undefined,\n responses:\n Object.keys(operation.responses).length === 0\n ? undefined\n : operation.responses,\n };\n if (!operations[path]) {\n operations[path] = {};\n }\n operations[path][method] = operationObject;\n if (this.#onOperation) {\n const paths = this.#onOperation?.(\n operation.sourceFile,\n method,\n path,\n operationObject,\n );\n Object.assign(operations, paths ?? {});\n }\n }\n return operations;\n }\n\n /**\n * Converts Express/Node.js style path parameters (/path/:param) to OpenAPI style (/path/{param})\n */\n #tunePath(path: string): string {\n return path.replace(/:([^/]+)/g, '{$1}');\n }\n}\n\nasync function evalZod(schema: string, commonZodImport?: string) {\n // https://github.com/nodejs/node/issues/51956\n const lines = [\n `import { createRequire } from \"node:module\";`,\n `const filename = \"${import.meta.url}\";`,\n `const require = createRequire(filename);`,\n `const z = require(\"zod\");`,\n commonZodImport ? `const commonZod = require('${commonZodImport}');` : '',\n `const {zodToJsonSchema} = require('zod-to-json-schema');`,\n `const schema = ${schema.replace('.optional()', '').replaceAll('instanceof(File)', 'string().base64()')};`,\n `const jsonSchema = zodToJsonSchema(schema, {\n $refStrategy: 'root',\n basePath: ['#', 'components', 'schemas'],\n target: 'jsonSchema7',\n base64Strategy: 'format:binary',\n });`,\n `export default jsonSchema;`,\n ];\n\n const base64 = Buffer.from(lines.join('\\n')).toString('base64');\n return import(`data:text/javascript;base64,${base64}`)\n .then((mod) => mod.default)\n .then(({ $schema, ...result }) => result);\n}\n\ninterface DateType {\n [$types]: any[];\n kind: string;\n optional: boolean;\n value?: string;\n}\n\nexport function toSchema(data: DateType | string | null | undefined): any {\n if (data === null || data === undefined) {\n return { type: 'any' };\n } else if (typeof data === 'string') {\n const isRef = data.startsWith('#');\n if (isRef) {\n return { $ref: data };\n }\n return { type: data };\n } else if (data.kind === 'literal') {\n return { enum: [data.value], type: data[$types][0] };\n } else if (data.kind === 'record') {\n return {\n type: 'object',\n additionalProperties: toSchema(data[$types][0]),\n };\n } else if (data.kind === 'array') {\n const items = data[$types].map(toSchema);\n return { type: 'array', items: data[$types].length ? items[0] : {} };\n } else if (data.kind === 'union') {\n return { anyOf: data[$types].map(toSchema) };\n } else if (data.kind === 'intersection') {\n return { allOf: data[$types].map(toSchema) };\n } else if ($types in data) {\n return data[$types].map(toSchema)[0] ?? {};\n } else {\n const props: Record<string, unknown> = {};\n const required: string[] = [];\n for (const [key, value] of Object.entries(data)) {\n props[key] = toSchema(value as any);\n if (!(value as any).optional) {\n required.push(key);\n }\n }\n return {\n type: 'object',\n properties: props,\n required,\n additionalProperties: false,\n };\n }\n}\n\nexport function isHttpMethod(name: string): name is Method {\n return ['get', 'post', 'put', 'delete', 'patch'].includes(name);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,75 @@
1
+ import debug from "debug";
2
+ import { dirname, join } from "node:path";
3
+ import ts from "typescript";
4
+ const logger = debug("january:client");
5
+ function parseTsConfig(tsconfigPath) {
6
+ logger(`Using TypeScript version: ${ts.version}`);
7
+ const configContent = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
8
+ if (configContent.error) {
9
+ console.error(
10
+ `Failed to read tsconfig file:`,
11
+ ts.formatDiagnosticsWithColorAndContext([configContent.error], {
12
+ getCanonicalFileName: (path) => path,
13
+ getCurrentDirectory: ts.sys.getCurrentDirectory,
14
+ getNewLine: () => ts.sys.newLine
15
+ })
16
+ );
17
+ throw new Error("Failed to parse tsconfig.json");
18
+ }
19
+ const parsed = ts.parseJsonConfigFileContent(
20
+ configContent.config,
21
+ ts.sys,
22
+ dirname(tsconfigPath)
23
+ );
24
+ if (parsed.errors.length > 0) {
25
+ console.error(
26
+ `Errors found in tsconfig.json:`,
27
+ ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
28
+ getCanonicalFileName: (path) => path,
29
+ getCurrentDirectory: ts.sys.getCurrentDirectory,
30
+ getNewLine: () => ts.sys.newLine
31
+ })
32
+ );
33
+ throw new Error("Failed to parse tsconfig.json");
34
+ }
35
+ return parsed;
36
+ }
37
+ function getProgram(tsconfigPath) {
38
+ const tsConfigParseResult = parseTsConfig(tsconfigPath);
39
+ logger(`Parsing tsconfig`);
40
+ return ts.createProgram({
41
+ options: {
42
+ ...tsConfigParseResult.options,
43
+ noEmit: true,
44
+ incremental: true,
45
+ tsBuildInfoFile: join(dirname(tsconfigPath), "./.tsbuildinfo")
46
+ // not working atm
47
+ },
48
+ rootNames: tsConfigParseResult.fileNames,
49
+ projectReferences: tsConfigParseResult.projectReferences,
50
+ configFileParsingDiagnostics: tsConfigParseResult.errors
51
+ });
52
+ }
53
+ function getPropertyAssignment(node, name) {
54
+ if (ts.isObjectLiteralExpression(node)) {
55
+ return node.properties.filter((prop) => ts.isPropertyAssignment(prop)).find((prop) => prop.name.getText() === name);
56
+ }
57
+ return void 0;
58
+ }
59
+ function isCallExpression(node, name) {
60
+ return ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && node.expression.text === name;
61
+ }
62
+ function isInterfaceType(type) {
63
+ if (type.isClassOrInterface()) {
64
+ return !!(type.symbol.flags & ts.SymbolFlags.Interface);
65
+ }
66
+ return false;
67
+ }
68
+ export {
69
+ getProgram,
70
+ getPropertyAssignment,
71
+ isCallExpression,
72
+ isInterfaceType,
73
+ parseTsConfig
74
+ };
75
+ //# sourceMappingURL=program.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/program.ts"],
4
+ "sourcesContent": ["import debug from 'debug';\nimport { dirname, join } from 'node:path';\nimport ts from 'typescript';\n\nconst logger = debug('january:client');\n\nexport function parseTsConfig(tsconfigPath: string) {\n logger(`Using TypeScript version: ${ts.version}`);\n const configContent = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\n\n if (configContent.error) {\n console.error(\n `Failed to read tsconfig file:`,\n ts.formatDiagnosticsWithColorAndContext([configContent.error], {\n getCanonicalFileName: (path) => path,\n getCurrentDirectory: ts.sys.getCurrentDirectory,\n getNewLine: () => ts.sys.newLine,\n }),\n );\n throw new Error('Failed to parse tsconfig.json');\n }\n\n const parsed = ts.parseJsonConfigFileContent(\n configContent.config,\n ts.sys,\n dirname(tsconfigPath),\n );\n\n if (parsed.errors.length > 0) {\n console.error(\n `Errors found in tsconfig.json:`,\n ts.formatDiagnosticsWithColorAndContext(parsed.errors, {\n getCanonicalFileName: (path) => path,\n getCurrentDirectory: ts.sys.getCurrentDirectory,\n getNewLine: () => ts.sys.newLine,\n }),\n );\n throw new Error('Failed to parse tsconfig.json');\n }\n return parsed;\n}\nexport function getProgram(tsconfigPath: string) {\n const tsConfigParseResult = parseTsConfig(tsconfigPath);\n logger(`Parsing tsconfig`);\n return ts.createProgram({\n options: {\n ...tsConfigParseResult.options,\n noEmit: true,\n incremental: true,\n tsBuildInfoFile: join(dirname(tsconfigPath), './.tsbuildinfo'), // not working atm\n },\n rootNames: tsConfigParseResult.fileNames,\n projectReferences: tsConfigParseResult.projectReferences,\n configFileParsingDiagnostics: tsConfigParseResult.errors,\n });\n}\nexport function getPropertyAssignment(node: ts.Node, name: string) {\n if (ts.isObjectLiteralExpression(node)) {\n return node.properties\n .filter((prop) => ts.isPropertyAssignment(prop))\n .find((prop) => prop.name!.getText() === name);\n }\n return undefined;\n}\nexport function isCallExpression(\n node: ts.Node,\n name: string,\n): node is ts.CallExpression {\n return (\n ts.isCallExpression(node) &&\n node.expression &&\n ts.isIdentifier(node.expression) &&\n node.expression.text === name\n );\n}\n\nexport function isInterfaceType(type: ts.Type): boolean {\n if (type.isClassOrInterface()) {\n // Check if it's an interface\n return !!(type.symbol.flags & ts.SymbolFlags.Interface);\n }\n return false;\n}\n"],
5
+ "mappings": "AAAA,OAAO,WAAW;AAClB,SAAS,SAAS,YAAY;AAC9B,OAAO,QAAQ;AAEf,MAAM,SAAS,MAAM,gBAAgB;AAE9B,SAAS,cAAc,cAAsB;AAClD,SAAO,6BAA6B,GAAG,OAAO,EAAE;AAChD,QAAM,gBAAgB,GAAG,eAAe,cAAc,GAAG,IAAI,QAAQ;AAErE,MAAI,cAAc,OAAO;AACvB,YAAQ;AAAA,MACN;AAAA,MACA,GAAG,qCAAqC,CAAC,cAAc,KAAK,GAAG;AAAA,QAC7D,sBAAsB,CAAC,SAAS;AAAA,QAChC,qBAAqB,GAAG,IAAI;AAAA,QAC5B,YAAY,MAAM,GAAG,IAAI;AAAA,MAC3B,CAAC;AAAA,IACH;AACA,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AAEA,QAAM,SAAS,GAAG;AAAA,IAChB,cAAc;AAAA,IACd,GAAG;AAAA,IACH,QAAQ,YAAY;AAAA,EACtB;AAEA,MAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,YAAQ;AAAA,MACN;AAAA,MACA,GAAG,qCAAqC,OAAO,QAAQ;AAAA,QACrD,sBAAsB,CAAC,SAAS;AAAA,QAChC,qBAAqB,GAAG,IAAI;AAAA,QAC5B,YAAY,MAAM,GAAG,IAAI;AAAA,MAC3B,CAAC;AAAA,IACH;AACA,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACA,SAAO;AACT;AACO,SAAS,WAAW,cAAsB;AAC/C,QAAM,sBAAsB,cAAc,YAAY;AACtD,SAAO,kBAAkB;AACzB,SAAO,GAAG,cAAc;AAAA,IACtB,SAAS;AAAA,MACP,GAAG,oBAAoB;AAAA,MACvB,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,iBAAiB,KAAK,QAAQ,YAAY,GAAG,gBAAgB;AAAA;AAAA,IAC/D;AAAA,IACA,WAAW,oBAAoB;AAAA,IAC/B,mBAAmB,oBAAoB;AAAA,IACvC,8BAA8B,oBAAoB;AAAA,EACpD,CAAC;AACH;AACO,SAAS,sBAAsB,MAAe,MAAc;AACjE,MAAI,GAAG,0BAA0B,IAAI,GAAG;AACtC,WAAO,KAAK,WACT,OAAO,CAAC,SAAS,GAAG,qBAAqB,IAAI,CAAC,EAC9C,KAAK,CAAC,SAAS,KAAK,KAAM,QAAQ,MAAM,IAAI;AAAA,EACjD;AACA,SAAO;AACT;AACO,SAAS,iBACd,MACA,MAC2B;AAC3B,SACE,GAAG,iBAAiB,IAAI,KACxB,KAAK,cACL,GAAG,aAAa,KAAK,UAAU,KAC/B,KAAK,WAAW,SAAS;AAE7B;AAEO,SAAS,gBAAgB,MAAwB;AACtD,MAAI,KAAK,mBAAmB,GAAG;AAE7B,WAAO,CAAC,EAAE,KAAK,OAAO,QAAQ,GAAG,YAAY;AAAA,EAC/C;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
package/dist/lib/ref.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OpenAPIObject, ParameterObject, ReferenceObject, RequestBodyObject, SchemaObject } from 'openapi3-ts/oas31';
1
+ import type { HeaderObject, OpenAPIObject, ParameterObject, ReferenceObject, RequestBodyObject, SchemaObject } from 'openapi3-ts/oas31';
2
2
  export declare function isRef(obj: any): obj is ReferenceObject;
3
3
  export declare function notRef(obj: any): obj is SchemaObject;
4
4
  export declare function cleanRef(ref: string): string;
@@ -7,5 +7,6 @@ export declare function parseRef(ref: string): {
7
7
  namespace: string;
8
8
  path: string;
9
9
  };
10
- export declare function followRef<T extends SchemaObject | ParameterObject | RequestBodyObject = SchemaObject>(spec: OpenAPIObject, ref: string): T;
10
+ export declare function followRef<T extends SchemaObject | HeaderObject | ParameterObject | ReferenceObject | RequestBodyObject = SchemaObject>(spec: OpenAPIObject, ref: string): T;
11
+ export declare function distillRef<T extends SchemaObject | HeaderObject | ParameterObject | ReferenceObject | RequestBodyObject = SchemaObject>(spec: OpenAPIObject, ref: string): T;
11
12
  //# sourceMappingURL=ref.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../../src/lib/ref.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,YAAY,EACb,MAAM,mBAAmB,CAAC;AAE3B,wBAAgB,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,eAAe,CAEtD;AACD,wBAAgB,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,YAAY,CAEpD;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,UAEnC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM;;;;EASnC;AACD,wBAAgB,SAAS,CACvB,CAAC,SAAS,YAAY,GAAG,eAAe,GAAG,iBAAiB,GAAG,YAAY,EAC3E,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAOrC"}
1
+ {"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../../src/lib/ref.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,YAAY,EACb,MAAM,mBAAmB,CAAC;AAI3B,wBAAgB,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,eAAe,CAEtD;AACD,wBAAgB,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,YAAY,CAEpD;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,UAEnC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM;;;;EASnC;AACD,wBAAgB,SAAS,CACvB,CAAC,SACG,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,eAAe,GACf,iBAAiB,GAAG,YAAY,EACpC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAOrC;AACD,wBAAgB,UAAU,CACxB,CAAC,SACG,YAAY,GACZ,YAAY,GACZ,eAAe,GACf,eAAe,GACf,iBAAiB,GAAG,YAAY,EACpC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CA8CrC"}
@@ -0,0 +1,83 @@
1
+ import { get } from "lodash-es";
2
+ import { isEmpty } from "./utils.js";
3
+ function isRef(obj) {
4
+ return obj && "$ref" in obj;
5
+ }
6
+ function notRef(obj) {
7
+ return !isRef(obj);
8
+ }
9
+ function cleanRef(ref) {
10
+ return ref.replace(/^#\//, "");
11
+ }
12
+ function parseRef(ref) {
13
+ const parts = ref.split("/");
14
+ const [model] = parts.splice(-1);
15
+ const [namespace] = parts.splice(-1);
16
+ return {
17
+ model,
18
+ namespace,
19
+ path: cleanRef(parts.join("/"))
20
+ };
21
+ }
22
+ function followRef(spec, ref) {
23
+ const pathParts = cleanRef(ref).split("/");
24
+ const entry = get(spec, pathParts);
25
+ if (entry && "$ref" in entry) {
26
+ return followRef(spec, entry.$ref);
27
+ }
28
+ return entry;
29
+ }
30
+ function distillRef(spec, ref) {
31
+ const def = followRef(spec, ref);
32
+ if (!def) {
33
+ return def;
34
+ }
35
+ if ("properties" in def) {
36
+ def.properties ??= {};
37
+ for (const key in def.properties) {
38
+ const prop = def.properties[key];
39
+ if (isRef(prop)) {
40
+ def.properties[key] = distillRef(spec, prop.$ref);
41
+ }
42
+ }
43
+ }
44
+ if ("items" in def) {
45
+ if (isRef(def.items)) {
46
+ def.items = distillRef(spec, def.items.$ref);
47
+ }
48
+ }
49
+ if ("allOf" in def && !isEmpty(def.allOf)) {
50
+ def.allOf = def.allOf.map((item) => {
51
+ if (isRef(item)) {
52
+ return distillRef(spec, item.$ref);
53
+ }
54
+ return item;
55
+ });
56
+ }
57
+ if ("oneOf" in def && !isEmpty(def.oneOf)) {
58
+ def.oneOf = def.oneOf.map((item) => {
59
+ if (isRef(item)) {
60
+ return distillRef(spec, item.$ref);
61
+ }
62
+ return item;
63
+ });
64
+ }
65
+ if ("anyOf" in def && !isEmpty(def.anyOf)) {
66
+ def.anyOf = def.anyOf.map((item) => {
67
+ if (isRef(item)) {
68
+ return distillRef(spec, item.$ref);
69
+ }
70
+ return item;
71
+ });
72
+ }
73
+ return def;
74
+ }
75
+ export {
76
+ cleanRef,
77
+ distillRef,
78
+ followRef,
79
+ isRef,
80
+ notRef,
81
+ parseRef
82
+ };
83
+ //# sourceMappingURL=ref.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/ref.ts"],
4
+ "sourcesContent": ["import { get } from 'lodash-es';\nimport type {\n HeaderObject,\n OpenAPIObject,\n ParameterObject,\n ReferenceObject,\n RequestBodyObject,\n SchemaObject,\n} from 'openapi3-ts/oas31';\n\nimport { isEmpty } from './utils.js';\n\nexport function isRef(obj: any): obj is ReferenceObject {\n return obj && '$ref' in obj;\n}\nexport function notRef(obj: any): obj is SchemaObject {\n return !isRef(obj);\n}\n\nexport function cleanRef(ref: string) {\n return ref.replace(/^#\\//, '');\n}\n\nexport function parseRef(ref: string) {\n const parts = ref.split('/');\n const [model] = parts.splice(-1);\n const [namespace] = parts.splice(-1);\n return {\n model,\n namespace,\n path: cleanRef(parts.join('/')),\n };\n}\nexport function followRef<\n T extends\n | SchemaObject\n | HeaderObject\n | ParameterObject\n | ReferenceObject\n | RequestBodyObject = SchemaObject,\n>(spec: OpenAPIObject, ref: string): T {\n const pathParts = cleanRef(ref).split('/');\n const entry = get(spec, pathParts) as T | ReferenceObject;\n if (entry && '$ref' in entry) {\n return followRef<T>(spec, entry.$ref!);\n }\n return entry;\n}\nexport function distillRef<\n T extends\n | SchemaObject\n | HeaderObject\n | ParameterObject\n | ReferenceObject\n | RequestBodyObject = SchemaObject,\n>(spec: OpenAPIObject, ref: string): T {\n const def = followRef<T>(spec, ref);\n if (!def) {\n return def;\n }\n\n if ('properties' in def) {\n def.properties ??= {};\n for (const key in def.properties) {\n const prop = def.properties[key];\n if (isRef(prop)) {\n def.properties[key] = distillRef(spec, prop.$ref);\n }\n }\n }\n if ('items' in def) {\n if (isRef(def.items)) {\n def.items = distillRef<SchemaObject>(spec, def.items.$ref);\n }\n }\n if ('allOf' in def && !isEmpty(def.allOf)) {\n def.allOf = def.allOf.map((item) => {\n if (isRef(item)) {\n return distillRef<SchemaObject>(spec, item.$ref);\n }\n return item;\n });\n }\n if ('oneOf' in def && !isEmpty(def.oneOf)) {\n def.oneOf = def.oneOf.map((item) => {\n if (isRef(item)) {\n return distillRef<SchemaObject>(spec, item.$ref);\n }\n return item;\n });\n }\n if ('anyOf' in def && !isEmpty(def.anyOf)) {\n def.anyOf = def.anyOf.map((item) => {\n if (isRef(item)) {\n return distillRef<SchemaObject>(spec, item.$ref);\n }\n return item;\n });\n }\n\n return def;\n}\n"],
5
+ "mappings": "AAAA,SAAS,WAAW;AAUpB,SAAS,eAAe;AAEjB,SAAS,MAAM,KAAkC;AACtD,SAAO,OAAO,UAAU;AAC1B;AACO,SAAS,OAAO,KAA+B;AACpD,SAAO,CAAC,MAAM,GAAG;AACnB;AAEO,SAAS,SAAS,KAAa;AACpC,SAAO,IAAI,QAAQ,QAAQ,EAAE;AAC/B;AAEO,SAAS,SAAS,KAAa;AACpC,QAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,QAAM,CAAC,KAAK,IAAI,MAAM,OAAO,EAAE;AAC/B,QAAM,CAAC,SAAS,IAAI,MAAM,OAAO,EAAE;AACnC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,SAAS,MAAM,KAAK,GAAG,CAAC;AAAA,EAChC;AACF;AACO,SAAS,UAOd,MAAqB,KAAgB;AACrC,QAAM,YAAY,SAAS,GAAG,EAAE,MAAM,GAAG;AACzC,QAAM,QAAQ,IAAI,MAAM,SAAS;AACjC,MAAI,SAAS,UAAU,OAAO;AAC5B,WAAO,UAAa,MAAM,MAAM,IAAK;AAAA,EACvC;AACA,SAAO;AACT;AACO,SAAS,WAOd,MAAqB,KAAgB;AACrC,QAAM,MAAM,UAAa,MAAM,GAAG;AAClC,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AAEA,MAAI,gBAAgB,KAAK;AACvB,QAAI,eAAe,CAAC;AACpB,eAAW,OAAO,IAAI,YAAY;AAChC,YAAM,OAAO,IAAI,WAAW,GAAG;AAC/B,UAAI,MAAM,IAAI,GAAG;AACf,YAAI,WAAW,GAAG,IAAI,WAAW,MAAM,KAAK,IAAI;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AACA,MAAI,WAAW,KAAK;AAClB,QAAI,MAAM,IAAI,KAAK,GAAG;AACpB,UAAI,QAAQ,WAAyB,MAAM,IAAI,MAAM,IAAI;AAAA,IAC3D;AAAA,EACF;AACA,MAAI,WAAW,OAAO,CAAC,QAAQ,IAAI,KAAK,GAAG;AACzC,QAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,SAAS;AAClC,UAAI,MAAM,IAAI,GAAG;AACf,eAAO,WAAyB,MAAM,KAAK,IAAI;AAAA,MACjD;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,WAAW,OAAO,CAAC,QAAQ,IAAI,KAAK,GAAG;AACzC,QAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,SAAS;AAClC,UAAI,MAAM,IAAI,GAAG;AACf,eAAO,WAAyB,MAAM,KAAK,IAAI;AAAA,MACjD;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,WAAW,OAAO,CAAC,QAAQ,IAAI,KAAK,GAAG;AACzC,QAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,SAAS;AAClC,UAAI,MAAM,IAAI,GAAG;AACf,eAAO,WAAyB,MAAM,KAAK,IAAI;AAAA,MACjD;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO;AACT;",
6
+ "names": []
7
+ }