@weclapp/sdk 2.0.0-dev.54 → 2.0.0-dev.56
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/dist/cli.js +1371 -1264
- package/package.json +14 -6
package/dist/cli.js
CHANGED
|
@@ -1,1390 +1,1497 @@
|
|
|
1
|
-
import { fileURLToPath } from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import { resolve, dirname } from 'path';
|
|
3
|
+
import { rollup } from 'rollup';
|
|
4
|
+
import terser from '@rollup/plugin-terser';
|
|
5
|
+
import ts from '@rollup/plugin-typescript';
|
|
6
|
+
import indentString from 'indent-string';
|
|
7
|
+
import { snakeCase, pascalCase, camelCase } from 'change-case';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
10
|
+
import { createHash } from 'crypto';
|
|
11
|
+
import { stat, readFile, rm, cp, writeFile, mkdir } from 'fs/promises';
|
|
12
|
+
import { config } from 'dotenv';
|
|
13
|
+
import yargs from 'yargs';
|
|
14
|
+
import { hideBin } from 'yargs/helpers';
|
|
15
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
16
|
+
import prettyMs from 'pretty-ms';
|
|
17
|
+
|
|
16
18
|
const currentDirname = () => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}({});
|
|
19
|
+
// Go one level up as the CLI is inside a folder
|
|
20
|
+
return fileURLToPath(new URL('..', import.meta.url));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
var Target;
|
|
24
|
+
(function (Target) {
|
|
25
|
+
Target["BROWSER_PROMISES"] = "browser";
|
|
26
|
+
Target["BROWSER_RX"] = "browser.rx";
|
|
27
|
+
Target["NODE_PROMISES"] = "node";
|
|
28
|
+
Target["NODE_RX"] = "node.rx";
|
|
29
|
+
})(Target || (Target = {}));
|
|
28
30
|
const isNodeTarget = (target) => {
|
|
29
|
-
|
|
31
|
+
return target === Target.NODE_PROMISES || target === Target.NODE_RX;
|
|
30
32
|
};
|
|
31
33
|
const isRXTarget = (target) => {
|
|
32
|
-
|
|
34
|
+
return target === Target.BROWSER_RX || target === Target.NODE_RX;
|
|
33
35
|
};
|
|
34
36
|
const resolveResponseType = (target) => {
|
|
35
|
-
|
|
37
|
+
return isRXTarget(target) ? 'Observable' : 'Promise';
|
|
36
38
|
};
|
|
37
39
|
const resolveBinaryType = (target) => {
|
|
38
|
-
|
|
40
|
+
return isNodeTarget(target) ? 'Buffer' : 'Blob';
|
|
39
41
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const resolveGlobals = (...globals) => Object.fromEntries(globals.map((v) => [v, "*"]));
|
|
42
|
+
|
|
43
|
+
const tsconfig = resolve(currentDirname(), './tsconfig.sdk.json');
|
|
44
|
+
const resolveGlobals = (...globals) => Object.fromEntries(globals.map((v) => [v, '*']));
|
|
44
45
|
const generateOutput = (config) => ({
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
sourcemap: false,
|
|
47
|
+
banner: `/* weclapp sdk */`,
|
|
48
|
+
...config
|
|
48
49
|
});
|
|
49
50
|
const bundle = async (workingDirectory, target) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
//#region src/ts/generateString.ts
|
|
51
|
+
const dist = (...paths) => resolve(workingDirectory, 'dist', ...paths);
|
|
52
|
+
const src = (...paths) => resolve(workingDirectory, 'src', ...paths);
|
|
53
|
+
const generateNodeOutput = () => [
|
|
54
|
+
generateOutput({
|
|
55
|
+
file: dist('index.cjs'),
|
|
56
|
+
format: 'cjs',
|
|
57
|
+
globals: resolveGlobals('node-fetch', 'url')
|
|
58
|
+
}),
|
|
59
|
+
generateOutput({
|
|
60
|
+
file: dist('index.js'),
|
|
61
|
+
format: 'es',
|
|
62
|
+
globals: resolveGlobals('node-fetch', 'url')
|
|
63
|
+
})
|
|
64
|
+
];
|
|
65
|
+
const bundles = {
|
|
66
|
+
[Target.BROWSER_PROMISES]: () => ({
|
|
67
|
+
input: src('index.ts'),
|
|
68
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
69
|
+
output: [
|
|
70
|
+
generateOutput({
|
|
71
|
+
file: dist('index.js'),
|
|
72
|
+
format: 'es'
|
|
73
|
+
})
|
|
74
|
+
]
|
|
75
|
+
}),
|
|
76
|
+
[Target.BROWSER_RX]: () => ({
|
|
77
|
+
input: src('index.ts'),
|
|
78
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
79
|
+
external: ['rxjs'],
|
|
80
|
+
output: [
|
|
81
|
+
generateOutput({
|
|
82
|
+
file: dist('index.js'),
|
|
83
|
+
format: 'es',
|
|
84
|
+
globals: resolveGlobals('rxjs')
|
|
85
|
+
})
|
|
86
|
+
]
|
|
87
|
+
}),
|
|
88
|
+
[Target.NODE_PROMISES]: () => ({
|
|
89
|
+
input: src('index.ts'),
|
|
90
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
91
|
+
external: ['node-fetch', 'url'],
|
|
92
|
+
output: generateNodeOutput()
|
|
93
|
+
}),
|
|
94
|
+
[Target.NODE_RX]: () => ({
|
|
95
|
+
input: src('index.ts'),
|
|
96
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
97
|
+
external: ['node-fetch', 'url', 'rxjs'],
|
|
98
|
+
output: generateNodeOutput()
|
|
99
|
+
})
|
|
100
|
+
};
|
|
101
|
+
const config = bundles[target]();
|
|
102
|
+
const bundle = await rollup(config);
|
|
103
|
+
if (Array.isArray(config.output)) {
|
|
104
|
+
await Promise.all(config.output.map(bundle.write));
|
|
105
|
+
}
|
|
106
|
+
else if (config.output) {
|
|
107
|
+
await bundle.write(config.output);
|
|
108
|
+
}
|
|
109
|
+
await bundle.close();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
112
|
const generateString = (str) => `'${str}'`;
|
|
113
113
|
const generateStrings = (str) => str.map(generateString);
|
|
114
|
-
|
|
115
|
-
//#region src/ts/generateImport.ts
|
|
114
|
+
|
|
116
115
|
const generateImport = (opt) => {
|
|
117
|
-
|
|
116
|
+
const imports = [opt.default, opt.imports?.length ? `{${opt.imports.join(', ')}}` : ''];
|
|
117
|
+
return `import ${imports.filter(Boolean).join(', ')} from ${generateString(opt.src)};`;
|
|
118
118
|
};
|
|
119
|
-
|
|
120
|
-
//#region src/utils/indent.ts
|
|
119
|
+
|
|
121
120
|
/**
|
|
122
|
-
* Indents each line of the given string
|
|
123
|
-
* @param s String to indent
|
|
124
|
-
* @param level Indentation level
|
|
125
|
-
*/
|
|
121
|
+
* Indents each line of the given string
|
|
122
|
+
* @param s String to indent
|
|
123
|
+
* @param level Indentation level
|
|
124
|
+
*/
|
|
126
125
|
const indent = (s, level = 1) => {
|
|
127
|
-
|
|
126
|
+
return indentString(s, 4 * level);
|
|
128
127
|
};
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
|
|
129
|
+
const generateStatements = (...statements) => statements
|
|
130
|
+
.map((v) => v.trim())
|
|
131
|
+
.filter((v) => v.length)
|
|
132
|
+
.join('\n\n');
|
|
132
133
|
const generateBlockStatements = (...statements) => `{\n${indent(generateStatements(...statements))}\n}`;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
var
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
var
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
var unique_ts_default = "export default \"const _unique = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, query?: UniqueQuery, requestOptions?: RequestOptions) =>\\n wrapResponse(() => raw({ ...cfg, multiRequest: false }, endpoint, { query }, requestOptions));\\n\";";
|
|
151
|
-
//#endregion
|
|
152
|
-
//#region src/generator/01-base/static/types.ts.txt
|
|
153
|
-
var types_ts_default = "export default \"export type DeepPartial<T> = T extends object\\n ? {\\n [P in keyof T]?: DeepPartial<T[P]>;\\n }\\n : T;\\n\\nexport type Sort<T> = {\\n [K in keyof T]?: {\\n [V in keyof T]?: V extends K\\n ? T[V] extends Array<infer U> | undefined\\n ? U extends object\\n ? Sort<U>\\n : never\\n : T[V] extends object | undefined\\n ? Sort<T[V]>\\n : 'asc' | 'desc'\\n : never;\\n };\\n}[keyof T];\\n\\nexport type CustomAttributeFilter = {\\n [K in number]:| string | number | boolean | { id: string } | { entityName: string; entityId: string };\\n};\\n\\nexport type QuerySelect<T> = {\\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\\n ? QuerySelect<U> | boolean\\n : T[P] extends Record<any, any> | undefined\\n ? QuerySelect<T[P]> | boolean\\n : boolean;\\n};\\n\\nexport type Select<T, Q extends QuerySelect<T> | undefined> =\\n Q extends QuerySelect<T>\\n ? {\\n // Filter out excluded properties beforehand\\n [P in keyof T as Q[P] extends boolean ? P : Q[P] extends object ? P : never]: // Property\\n Q[P] extends true\\n ? T[P]\\n : // Array\\n T[P] extends Array<infer U>\\n ? Select<U, Q[P] & QuerySelect<any>>[]\\n : // Object\\n T[P] extends Record<any, any>\\n ? Select<T[P], Q[P] & QuerySelect<any>>\\n : never;\\n }\\n : undefined;\\n\\nexport type MapKeys<T, S extends Record<keyof T, string>> = {\\n [K in keyof T as S[K]]: T[K];\\n};\\n\\nexport type ValueOf<T> = T[keyof T];\\n\\nexport type Pagination = {\\n page: number;\\n pageSize: number;\\n};\\n\\nexport type UniqueQuery = {\\n serializeNulls?: boolean;\\n};\\n\\nexport type SomeQueryReturn<E, R, P> = {\\n entities: E[];\\n references?: R;\\n properties?: P[];\\n};\\n\\nexport type GenericQuery<P, B> = {\\n params?: P;\\n body?: B;\\n};\\n\\nexport type UpdateQuery = {\\n ignoreMissingProperties?: boolean;\\n dryRun?: boolean;\\n};\\n\\nexport type RemoveQuery = {\\n dryRun?: boolean;\\n};\\n\\nexport type WEntityPropertyMeta =\\n | {\\n type: 'string';\\n format?: 'decimal' | 'html' | 'email' | 'password';\\n maxLength?: number;\\n pattern?: string;\\n entity?: WEntity;\\n service?: WService;\\n }\\n | {\\n type: 'integer';\\n format: 'int32' | 'int64' | 'duration' | 'date' | 'timestamp';\\n }\\n | { type: 'array'; format: 'reference'; entity: WEntity; service?: WService }\\n | { type: 'array'; format: 'reference'; enum: WEnum }\\n | { type: 'array'; format: 'string' }\\n | { type: 'number'; format: 'double' }\\n | { type: 'reference'; entity: WEntity }\\n | { type: 'reference'; enum: WEnum }\\n | { type: 'boolean' }\\n | { type: 'object' };\\n\";";
|
|
154
|
-
//#endregion
|
|
155
|
-
//#region src/generator/01-base/static/utils.ts.txt
|
|
156
|
-
var utils_ts_default = "export default \"const flattenSelect = (obj: Select<any, any> = {}): string[] => {\\n const entries: string[] = [];\\n\\n for (const [prop, value] of Object.entries(obj)) {\\n if (typeof value === 'object' && value) {\\n entries.push(...flattenSelect(value).map((v) => `${prop}.${v}`));\\n } else if (value) {\\n entries.push(prop);\\n }\\n }\\n\\n return entries;\\n};\\n\\nexport const flattenSort = (obj: Sort<any>[] = []): { sort?: string } => {\\n const flatten = (obj: Sort<any>, base = ''): string | undefined => {\\n const [key, value] = Object.entries(obj ?? {})[0] ?? [];\\n\\n if (key && value) {\\n const path = base + key;\\n\\n if (typeof value === 'object') {\\n return flatten(value, path ? `${path}.` : '');\\n } else if (['asc', 'desc'].includes(value)) {\\n return `${value === 'desc' ? '-' : ''}${path}`;\\n }\\n }\\n\\n return undefined;\\n };\\n\\n const sorts = obj.map((v) => flatten(v)).filter(Boolean);\\n return sorts.length ? { sort: sorts.join(',') } : {};\\n};\\n\";";
|
|
157
|
-
//#endregion
|
|
158
|
-
//#region src/generator/01-base/index.ts
|
|
134
|
+
|
|
135
|
+
var globalConfig = "export type RequestPayloadMethod =\n | 'GET'\n | 'HEAD'\n | 'POST'\n | 'PUT'\n | 'DELETE'\n | 'CONNECT'\n | 'OPTIONS'\n | 'TRACE'\n | 'PATCH';\n\nexport interface RequestPayload {\n method?: RequestPayloadMethod;\n query?: Record<string, any>;\n body?: any;\n unwrap?: boolean;\n forceBlob?: boolean;\n}\n\nexport interface ServiceConfig {\n // Your API-Key, this is optional in the sense of if you omit this, and you're in a browser, the\n // cookie-authentication (include-credentials) will be used.\n key?: string;\n\n // Your domain, if omitted location.host will be used (browser env).\n host?: string;\n\n // If you want to use https, defaults to location.protocol (browser env).\n secure?: boolean;\n\n // If you want that some and count requests are bundled into multi requests.\n multiRequest?: boolean;\n\n // If you want that the ignoreMissingProperties parameter to be set to true for every post request.\n ignoreMissingProperties?: boolean;\n\n // Optional request/response interceptors.\n interceptors?: {\n // Takes the generated request, you can either return a new request,\n // a response (which will be taken as \"the\" response) or nothing.\n // The payload contains the raw input generated by the SDK.\n request?: (\n request: Request,\n payload: RequestPayload\n ) => Request | Response | void | Promise<Request | Response | void>;\n\n // Takes the response. This can either be the one from the server or an\n // artificially-crafted one by the request interceptor.\n response?: (response: Response) => Response | void | Promise<Response | void>;\n };\n\n // Whether POST should be used instead of GET for some() and count() operations\n usePost?: boolean;\n}\n\nexport interface RequestOptions {\n signal?: AbortSignal;\n}\n\ntype ServiceConfigWithoutMultiRequest = Omit<ServiceConfig, 'multiRequest'>;\n\nlet globalConfig: ServiceConfig | undefined;\nexport const getGlobalConfig = (): ServiceConfig | undefined => globalConfig;\nexport const setGlobalConfig = (cfg?: ServiceConfig) => (globalConfig = cfg);\n\nexport const getHost = (cfg: ServiceConfig) => {\n let host = cfg.host?.replace(/^https?:\\/\\//, '');\n if (!host && typeof location !== 'undefined') {\n host = location.host;\n }\n\n if (!host) {\n throw new Error('Please specify a host');\n }\n\n return host;\n};\n\nexport const getProtocol = (cfg: ServiceConfig) => {\n const protocol =\n cfg.secure !== undefined\n ? cfg.secure\n ? 'https:'\n : 'http:'\n : typeof location !== 'undefined'\n ? location.protocol\n : undefined;\n\n if (!protocol) {\n throw new Error('Please specify a protocol (secure)');\n }\n\n return protocol;\n};\n";
|
|
136
|
+
|
|
137
|
+
var multiRequest = "type RequestTask = {\n uri: string;\n resolve: (result: unknown) => void;\n reject: (error: unknown) => void;\n};\n\ntype BatchRequestTask = RequestTask & {\n settled: boolean;\n};\n\ntype MultiRequestResponse = {\n status: number;\n body: object;\n};\n\nlet microtaskQueued: boolean = false;\nconst tasksSet: Set<RequestTask> = new Set<RequestTask>();\n\nconst SQUARE_BRACKET_OPEN = '['.charCodeAt(0);\nconst COMMA = ','.charCodeAt(0);\nconst DECODER = new TextDecoder();\n\nconst readNextResponse = (bytes: Uint8Array<ArrayBuffer>) => {\n let headerStart: number | undefined = undefined;\n let commasSeen = 0;\n\n for (let i = 0; i < bytes.length; i++) {\n const byte = bytes[i];\n if (headerStart === undefined) {\n if (byte === SQUARE_BRACKET_OPEN || byte === COMMA) {\n headerStart = i + 1;\n }\n } else {\n if (byte === COMMA) {\n commasSeen++;\n }\n if (commasSeen === 2) {\n const headerArrayString = `[${DECODER.decode(bytes.subarray(headerStart, i))}]`;\n const [index, jsonLength] = JSON.parse(headerArrayString);\n if (!(typeof index === 'number') || !(typeof jsonLength === 'number')) {\n throw new Error(`unexpected header: ${headerArrayString}`);\n }\n\n const endIndex = i + 1 + jsonLength;\n if (endIndex > bytes.length) {\n // not all bytes available yet\n return undefined;\n }\n const jsonString = DECODER.decode(bytes.subarray(i + 1, endIndex));\n const data = JSON.parse(jsonString) as MultiRequestResponse;\n return {\n index,\n data,\n remainingBytes: bytes.subarray(endIndex)\n };\n }\n }\n }\n return undefined;\n};\n\nconst fetchMultiRequest = async (requests: string[]) => {\n const cfg = getGlobalConfig();\n\n if (!cfg) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const host = getHost(cfg);\n const protocol = getProtocol(cfg);\n\n return await fetch(`${protocol}//${host}/webapp/api/v2/batch/query`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(cfg.key && { AuthenticationToken: cfg.key })\n },\n body: JSON.stringify({ requests })\n });\n};\n\nconst rejectTasks = (tasks: BatchRequestTask[], error: unknown) => {\n for (const task of tasks) {\n if (!task.settled) {\n task.reject(error);\n }\n }\n};\n\nconst processStream = (\n { value: chunk, done }: ReadableStreamReadResult<Uint8Array>,\n remainingBytes: Uint8Array,\n reader: ReadableStreamDefaultReader<Uint8Array>,\n tasks: BatchRequestTask[]\n) => {\n if (done) {\n return;\n }\n if (chunk) {\n let bytes = new Uint8Array(remainingBytes.length + chunk.length);\n bytes.set(remainingBytes);\n bytes.set(chunk, remainingBytes.length);\n\n while (bytes.length) {\n const result = readNextResponse(bytes);\n if (!result) {\n break;\n }\n const task = tasks[result.index];\n if (result.data.status >= 100 && result.data.status < 400) {\n task.resolve({\n ...result.data.body\n });\n } else {\n task.reject({\n ...result.data.body\n });\n }\n task.settled = true;\n bytes = result.remainingBytes;\n }\n reader\n .read()\n .then((readResult) => processStream(readResult, bytes, reader, tasks))\n .catch((error) => rejectTasks(tasks, error));\n }\n};\n\nconst batch = async (tasks: BatchRequestTask[]) => {\n try {\n const requests = tasks.map(({ uri }) => uri);\n const resp = await fetchMultiRequest(requests);\n const reader = resp.body?.getReader();\n\n if (!reader) {\n throw new Error('Stream reader is undefined');\n }\n reader\n .read()\n .then((readResult) => processStream(readResult, new Uint8Array(0), reader, tasks))\n .catch((error) => rejectTasks(tasks, error));\n } catch (e) {\n rejectTasks(tasks, e);\n throw e;\n }\n};\n\nconst addTask = (task: RequestTask) => {\n tasksSet.add(task);\n\n if (!microtaskQueued) {\n queueMicrotask(() => {\n microtaskQueued = false;\n if (tasksSet.size > 0) {\n const batchTasks = Array.from(tasksSet).map((task) => ({ ...task, settled: false }));\n void batch(batchTasks);\n tasksSet.clear();\n }\n });\n microtaskQueued = true;\n }\n};\n\nconst addRequest = (uri: string) => new Promise((resolve, reject) => addTask({ uri, resolve, reject }));\n";
|
|
138
|
+
|
|
139
|
+
var queriesWithFilter = "export type EqualityOperator = 'EQ' | 'NE';\n\nexport type ComparisonOperator =\n | 'LT'\n | 'GT'\n | 'LE'\n | 'GE'\n | 'LIKE'\n | 'ILIKE'\n | 'NOT_LIKE'\n | 'NOT_ILIKE'\n | 'IEQ'\n | 'NOT_IEQ';\n\nexport type ArrayOperator = 'IN' | 'NOT_IN';\n\nexport type Operator = EqualityOperator | ComparisonOperator | ArrayOperator;\n\nexport type MapOperators<T> = { [K in EqualityOperator]?: T | null } & { [K in ComparisonOperator]?: T } & {\n [K in ArrayOperator]?: T[];\n};\n\nexport type QueryFilter<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? U extends Record<any, any>\n ? QueryFilter<U>\n : MapOperators<U>\n : T[P] extends Record<any, any> | undefined\n ? QueryFilter<T[P]>\n : MapOperators<T[P]>;\n};\n\nexport type CountQuery<F> = {\n filter?: QueryFilter<F>;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\n};\n\nexport type SomeQuery<E, F, I, P> = {\n serializeNulls?: boolean;\n include?: QuerySelect<I>;\n properties?: P;\n filter?: QueryFilter<F> & CustomAttributeFilter;\n select?: QuerySelect<E>;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\n sort?: Sort<E>[];\n pagination?: Pagination;\n};\n\nconst equality: string[] = ['EQ', 'NE', 'IEQ', 'NOT_IEQ'];\n\nconst simple: string[] = [...equality, 'LT', 'GT', 'LE', 'GE', 'LIKE', 'NOT_LIKE', 'ILIKE', 'NOT_ILIKE'];\n\nconst array: string[] = ['IN', 'NOT_IN'];\n\nconst filterMap: Record<Operator, string> = {\n EQ: 'eq',\n NE: 'ne',\n LT: 'lt',\n GT: 'gt',\n LE: 'le',\n GE: 'ge',\n LIKE: 'like',\n NOT_LIKE: 'notlike',\n ILIKE: 'ilike',\n NOT_ILIKE: 'notilike',\n IN: 'in',\n NOT_IN: 'notin',\n IEQ: 'ieq',\n NOT_IEQ: 'notieq'\n};\n\nconst flattenCustomAttributes = (obj: CustomAttributeFilter = {}): [string, string][] => {\n const entries: [string, string][] = [];\n\n for (const [id, filter] of Object.entries(obj)) {\n const key = `customAttribute${id}`;\n\n if (typeof filter === 'object') {\n for (const [prop, value] of Object.entries(filter)) {\n entries.push([`${key}.${prop}-eq`, String(value)]);\n }\n } else if (filter !== undefined) {\n entries.push([`${key}-eq`, String(filter)]);\n }\n }\n\n return entries;\n};\n\nconst flatten = (obj: QueryFilter<any> = {}): [string, string][] => {\n const entries: [string, string][] = [];\n\n for (const [prop, propValue] of Object.entries(obj)) {\n for (const [filter, value] of Object.entries(propValue as object)) {\n if (value === undefined) continue;\n\n if (simple.includes(filter) || array.includes(filter)) {\n if (value === null && equality.includes(filter)) {\n entries.push([`${prop}-${filter === 'EQ' ? 'null' : 'notnull'}`, '']);\n } else {\n entries.push([`${prop}-${filterMap[filter as Operator]}`, value]);\n }\n } else {\n entries.push(\n ...(flatten(propValue as QueryFilter<any>).map((v) => [`${prop}.${v[0]}`, v[1]]) as [string, string][])\n );\n break;\n }\n }\n }\n\n return entries;\n};\n\nconst flattenFilter = (obj: QueryFilter<any> = {}): Record<string, string> => {\n const filter: [string, any][] = [],\n customAttributes: [string, any][] = [];\n\n Object.entries(obj).forEach((value) => {\n (value[0].match(/^\\d+$/) ? customAttributes : filter).push(value);\n });\n\n return Object.fromEntries([\n ...flatten(Object.fromEntries(filter)),\n ...flattenCustomAttributes(Object.fromEntries(customAttributes) as CustomAttributeFilter)\n ]);\n};\n\nconst flattenOrFilter = (obj: QueryFilter<any>[] = []): Record<string, string> => {\n const entries: [string, any][] = [];\n\n for (let i = 0; i < obj.length; i++) {\n entries.push(...(flatten(obj[i]).map((v) => [`or${i || ''}-${v[0]}`, v[1]]) as [string, string][]));\n }\n\n return Object.fromEntries(entries);\n};\n\nconst _count = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: CountQuery<any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n{\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n ...flattenFilter(query?.filter),\n ...flattenOrFilter(query?.or),\n ...query?.params\n }\n\n return wrapResponse(() =>\n raw(cfg, endpoint, {\n method: usePost ? 'POST' : 'GET',\n unwrap: true,\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions)\n )\n };\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n{\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n serializeNulls: query?.serializeNulls,\n additionalProperties: query?.properties?.join(','),\n properties: query?.select ? flattenSelect(query.select).join(',') : undefined,\n includeReferencedEntities: query?.include ? Object.keys(query.include).join(',') : undefined,\n ...flattenOrFilter(query?.or),\n ...flattenFilter(query?.filter),\n ...flattenSort(query?.sort),\n ...query?.params,\n ...query?.pagination\n }\n\n return wrapResponse(() =>\n raw(cfg, usePost ? `${endpoint}/query` : endpoint, {\n method: usePost ? 'POST' : 'GET',\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n )\n };\n";
|
|
140
|
+
|
|
141
|
+
var queriesWithQueryLanguage = "export type ComparisonOperator =\n | 'EQ'\n | 'NE'\n | 'LT'\n | 'GT'\n | 'LE'\n | 'GE'\n | 'LIKE';\n\nexport type LengthOperator = 'LENGTH';\n\nexport type ArrayOperator = 'IN';\n\nexport type NullOperator = 'NULL';\n\nexport type Operator = ComparisonOperator | ArrayOperator | NullOperator;\n\nexport type ModifierFunction = 'LOWER' | 'TRIM';\n\n// use globalThis to use typescript's 'Pick', since the SDK contains an interface which is also called 'Pick'\nexport type RequireAtLeastOne<T> = {\n [K in keyof T]-?: Required<globalThis.Pick<T, K>> & Partial<Omit<T, K>>;\n}[keyof T];\n\nexport type LengthExpr = {\n [K in LengthOperator]: {\n [K in Exclude<ComparisonOperator, 'LIKE'>]?: number\n }\n};\n\nexport type LengthExprWithModifier =\n LengthExpr &\n { [K in ComparisonOperator]?: never } &\n { [K in ArrayOperator]?: never } &\n { [K in NullOperator]?: never } &\n { [K in ModifierFunction]?: boolean };\n\nexport type FilterExpr<T> =\n { [K in ComparisonOperator]?: T } &\n { [K in ArrayOperator]?: T[] } &\n { [K in keyof LengthExpr]?: never };\n\nexport type FilterExprWithoutNull<T> =\n RequireAtLeastOne<FilterExpr<T>> &\n { [K in NullOperator]?: never } &\n { [K in ModifierFunction]?: boolean };\n\nexport type FilterExprWithNull<T> =\n FilterExpr<T> &\n { [K in NullOperator]?: boolean} &\n { [K in ModifierFunction]?: never };\n\nexport type MapOperators<T> = LengthExprWithModifier | FilterExprWithoutNull<T> | FilterExprWithNull<T>;\n\nexport type SingleFilterExpr<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? U extends Record<any, any>\n ? SingleFilterExpr<U> | { NOT?: SingleFilterExpr<U> }\n : MapOperators<U>\n : T[P] extends Record<any, any> | undefined\n ? SingleFilterExpr<T[P]> | { NOT?: SingleFilterExpr<T[P]> }\n : MapOperators<T[P]>;\n};\n\nexport type QueryFilter<T> = SingleFilterExpr<T> & {\n OR?: QueryFilter<T>[];\n AND?: QueryFilter<T>[];\n NOT?: QueryFilter<T>;\n};\n\nexport type CountQuery<F> = {\n where?: QueryFilter<F>;\n};\n\nexport type SomeQuery<E, F, I, P> = {\n serializeNulls?: boolean;\n include?: QuerySelect<I>;\n properties?: P;\n where?: QueryFilter<F>;\n select?: QuerySelect<E>;\n sort?: Sort<E>[];\n pagination?: Pagination;\n};\n\nconst comparisonOperatorList: ComparisonOperator[] = [\n 'EQ',\n 'NE',\n 'LT',\n 'GT',\n 'LE',\n 'GE',\n 'LIKE'\n];\n\nconst comparisonOperatorMap: Record<Operator, string> = {\n EQ: '=',\n NE: '!=',\n LT: '<',\n GT: '>',\n LE: '<=',\n GE: '>=',\n LIKE: '~',\n IN: 'in',\n NULL: 'null'\n};\n\nconst modifierFunctionList: ModifierFunction[] = ['LOWER', 'TRIM'];\n\nconst flattenWhere = (\n obj: QueryFilter<any> = {},\n nestedPaths: string[]\n): string[] => {\n const entries: string[] = [];\n for (const [prop, propValue] of Object.entries(obj)) {\n const setModifiers = findAllModifierFunctions(propValue ?? {}, modifierFunctionList).filter(\n (modifier) => modifier[1]\n );\n if (prop === 'OR') {\n const flattedOr: string[][] = [];\n for (let i = 0; i < (obj.OR?.length ?? 0); i++) {\n flattedOr.push(flattenWhere(obj.OR?.[i], nestedPaths));\n }\n entries.push(\n `(${flattedOr\n .map((x) => {\n const joined = x.join(' and ');\n\n if (x.length > 1) {\n return `(${joined})`;\n } else {\n return joined;\n }\n })\n .join(' or ')})`\n );\n } else if (prop === 'AND') {\n const flattedAnd: string[][] = [];\n for (let i = 0; i < (obj.AND?.length ?? 0); i++) {\n flattedAnd.push(flattenWhere(obj.AND?.[i], nestedPaths));\n }\n entries.push(\n `(${flattedAnd\n .map((x) => {\n const joined = x.join(' and ');\n\n if (x.length > 1) {\n return `(${joined})`;\n } else {\n return joined;\n }\n })\n .join(' and ')})`\n );\n } else if (prop === 'NOT') {\n const flattedNot = flattenWhere(obj.NOT, nestedPaths);\n entries.push(\n `not ${flattedNot.length > 1 ? '(' : ''}${flattedNot.join(' and ')}${flattedNot.length > 1 ? ')' : ''}`\n );\n } else if (propValue) {\n for (const [operator, value] of Object.entries(propValue)) {\n if (value === undefined) continue;\n if (comparisonOperatorList.includes(operator as ComparisonOperator)) {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first.toLowerCase()}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${comparisonOperatorMap[operator as Operator]} ${\n typeof value === 'string' ? JSON.stringify(value) : value\n }`\n );\n } else if ((operator as Operator) === 'NULL') {\n entries.push(\n `${!value ? 'not ' : ''}${nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')} ${comparisonOperatorMap[operator as Operator]}`\n );\n } else if ((operator as Operator) === 'IN') {\n if(value.length === 0) {\n entries.push('1 = 0')\n } else {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first.toLowerCase()}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${comparisonOperatorMap[operator as Operator]} [${value.map((v: string | number) =>\n typeof v === 'string' ? JSON.stringify(v) : v\n )}]`\n );\n }\n } else if ((operator as LengthOperator) === 'LENGTH') {\n const lengthProp = `length(${setModifiers.reduce(\n (acc, [first]) => `${first.toLowerCase()}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )})`;\n entries.push(...flattenWhere({ [lengthProp]: value } as QueryFilter<any>, []));\n } else if (\n !modifierFunctionList.includes(operator as ModifierFunction)\n && typeof value === 'object'\n ) {\n entries.push(\n ...flattenWhere(propValue as QueryFilter<any>, [\n ...nestedPaths,\n prop\n ])\n );\n break;\n }\n }\n }\n }\n return entries;\n};\n\nconst assembleFilterParam = (\n obj: QueryFilter<any> = {}\n): Record<string, string> => {\n const flattedFilter = flattenWhere(obj, []);\n return flattedFilter.length ? { filter: flattedFilter.join(' and ') } : {};\n};\n\nconst findAllModifierFunctions = (\n obj: Record<string, any>,\n types: ModifierFunction[]\n) => {\n const result: [string, any][] = [];\n for (const key in obj) {\n if (types.includes(key as ModifierFunction)) {\n result[types.indexOf(key as ModifierFunction)] = [key, obj[key]];\n }\n }\n return result.filter((modifierTuple) => modifierTuple);\n};\n\nconst _count = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: CountQuery<any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n {\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n ...assembleFilterParam(query?.where),\n ...query?.params\n }\n\n return wrapResponse(() =>\n raw(cfg, endpoint, {\n unwrap: true,\n method: usePost ? 'POST' : 'GET',\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions)\n )\n };\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n {\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n serializeNulls: query?.serializeNulls,\n additionalProperties: query?.properties?.join(','),\n properties: query?.select\n ? flattenSelect(query.select).join(',')\n : undefined,\n includeReferencedEntities: query?.include\n ? Object.keys(query.include).join(',')\n : undefined,\n ...assembleFilterParam(query?.where),\n ...flattenSort(query?.sort),\n ...query?.params,\n ...query?.pagination\n }\n\n return wrapResponse(() =>\n raw(cfg, usePost ? `${endpoint}/query` : endpoint, {\n method: usePost ? 'POST' : 'GET',\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n )\n };\n";
|
|
142
|
+
|
|
143
|
+
var root = "export const raw = async (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n payload: RequestPayload = {},\n requestOptions?: RequestOptions\n): Promise<any> => {\n if (!cfg && !globalConfig) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const localCfg = {\n ...globalConfig,\n ...cfg,\n interceptors: { ...globalConfig?.interceptors, ...cfg?.interceptors }\n };\n\n const isBinaryData = payload.body instanceof resolveBinaryObject();\n const params = new URLSearchParams(Object.entries(payload.query ?? {}).filter((v) => v[1] !== undefined)\n .map(([key, value]) => [key, typeof value === 'string' ? value : JSON.stringify(value)])\n );\n\n const protocol = getProtocol(localCfg);\n\n const interceptRequest = localCfg.interceptors?.request ?? ((v) => v);\n const interceptResponse = localCfg.interceptors?.response ?? ((v) => v);\n\n const host = getHost(localCfg);\n\n let data;\n if (!cfg && localCfg.multiRequest) {\n let ep = endpoint;\n if (endpoint.startsWith('/')) {\n ep = endpoint.replace('/', '');\n }\n data = await addRequest(`${ep}?${params}`);\n } else {\n const request = new Request(`${protocol}//${host}/webapp/api/v${apiVersion}${endpoint}?${params}`, {\n ...(payload.body && {\n body: isBinaryData\n ? payload.body\n : JSON.stringify(payload.body, (_key, value) => (value === undefined ? null : value))\n }),\n ...(!localCfg.key && { credentials: 'same-origin' }),\n method: payload.method ?? 'get',\n headers: {\n Accept: 'application/json',\n ...(localCfg.key && { AuthenticationToken: localCfg.key }),\n ...(!isBinaryData && { 'Content-Type': 'application/json' })\n }\n });\n let res = (await interceptRequest(request, payload)) ?? request;\n if (!(res instanceof Response)) {\n res = requestOptions?.signal ? await fetch(res, { signal: requestOptions.signal } ) : await fetch(res);\n }\n res = (await interceptResponse(res)) ?? res;\n\n if (res.ok) {\n data =\n payload.forceBlob || !res.headers?.get('content-type')?.includes('application/json')\n ? await res.blob()\n : await res.json();\n } else {\n data = res.headers?.get('content-type')?.includes('application/json')\n ? await res.json()\n : res;\n }\n\n // Check if response was successful\n if (!res.ok) {\n return Promise.reject(data);\n }\n }\n\n return payload.unwrap ? data.result : data;\n};\n\nconst _remove = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n { dryRun = false }: RemoveQuery = {},\n requestOptions?: RequestOptions\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'DELETE',\n query: { dryRun }\n }, requestOptions).then(() => undefined)\n );\n\nconst _create = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, data: any, requestOptions?: RequestOptions) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'POST',\n body: data\n }, requestOptions)\n );\n\nconst _update = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n data: any,\n { ignoreMissingProperties, dryRun = false }: UpdateQuery = {},\n requestOptions?: RequestOptions\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'PUT',\n body: data,\n query: {\n ignoreMissingProperties:\n ignoreMissingProperties ?? cfg?.ignoreMissingProperties ?? globalConfig?.ignoreMissingProperties,\n dryRun\n }\n }, requestOptions)\n );\n\nconst _generic = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n method: RequestPayloadMethod,\n endpoint: string,\n payload?: GenericQuery<any, any>,\n forceBlob?: boolean,\n requestOptions?: RequestOptions\n) =>\n{\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n return wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: usePost ? 'POST' : method,\n forceBlob,\n ...(usePost && method === 'GET' ?\n { body: { ...payload?.params } }\n :\n { body: payload?.body, query: payload?.params } )\n }, requestOptions))\n};\n";
|
|
144
|
+
|
|
145
|
+
var unique = "const _unique = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, query?: UniqueQuery, requestOptions?: RequestOptions) =>\n wrapResponse(() => raw({ ...cfg, multiRequest: false }, endpoint, { query }, requestOptions));\n";
|
|
146
|
+
|
|
147
|
+
var types = "export type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>;\n }\n : T;\n\nexport type Sort<T> = {\n [K in keyof T]?: {\n [V in keyof T]?: V extends K\n ? T[V] extends Array<infer U> | undefined\n ? U extends object\n ? Sort<U>\n : never\n : T[V] extends object | undefined\n ? Sort<T[V]>\n : 'asc' | 'desc'\n : never;\n };\n}[keyof T];\n\nexport type CustomAttributeFilter = {\n [K in number]:| string | number | boolean | { id: string } | { entityName: string; entityId: string };\n};\n\nexport type QuerySelect<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? QuerySelect<U> | boolean\n : T[P] extends Record<any, any> | undefined\n ? QuerySelect<T[P]> | boolean\n : boolean;\n};\n\nexport type Select<T, Q extends QuerySelect<T> | undefined> =\n Q extends QuerySelect<T>\n ? {\n // Filter out excluded properties beforehand\n [P in keyof T as Q[P] extends boolean ? P : Q[P] extends object ? P : never]: // Property\n Q[P] extends true\n ? T[P]\n : // Array\n T[P] extends Array<infer U>\n ? Select<U, Q[P] & QuerySelect<any>>[]\n : // Object\n T[P] extends Record<any, any>\n ? Select<T[P], Q[P] & QuerySelect<any>>\n : never;\n }\n : undefined;\n\nexport type MapKeys<T, S extends Record<keyof T, string>> = {\n [K in keyof T as S[K]]: T[K];\n};\n\nexport type ValueOf<T> = T[keyof T];\n\nexport type Pagination = {\n page: number;\n pageSize: number;\n};\n\nexport type UniqueQuery = {\n serializeNulls?: boolean;\n};\n\nexport type SomeQueryReturn<E, R, P> = {\n entities: E[];\n references?: R;\n properties?: P[];\n};\n\nexport type GenericQuery<P, B> = {\n params?: P;\n body?: B;\n};\n\nexport type UpdateQuery = {\n ignoreMissingProperties?: boolean;\n dryRun?: boolean;\n};\n\nexport type RemoveQuery = {\n dryRun?: boolean;\n};\n\nexport type WEntityPropertyMeta =\n | {\n type: 'string';\n format?: 'decimal' | 'html' | 'email' | 'password';\n maxLength?: number;\n pattern?: string;\n entity?: WEntity;\n service?: WService;\n }\n | {\n type: 'integer';\n format: 'int32' | 'int64' | 'duration' | 'date' | 'timestamp';\n }\n | { type: 'array'; format: 'reference'; entity: WEntity; service?: WService }\n | { type: 'array'; format: 'reference'; enum: WEnum }\n | { type: 'array'; format: 'string' }\n | { type: 'number'; format: 'double' }\n | { type: 'reference'; entity: WEntity }\n | { type: 'reference'; enum: WEnum }\n | { type: 'boolean' }\n | { type: 'object' };\n";
|
|
148
|
+
|
|
149
|
+
var utils = "const flattenSelect = (obj: Select<any, any> = {}): string[] => {\n const entries: string[] = [];\n\n for (const [prop, value] of Object.entries(obj)) {\n if (typeof value === 'object' && value) {\n entries.push(...flattenSelect(value).map((v) => `${prop}.${v}`));\n } else if (value) {\n entries.push(prop);\n }\n }\n\n return entries;\n};\n\nexport const flattenSort = (obj: Sort<any>[] = []): { sort?: string } => {\n const flatten = (obj: Sort<any>, base = ''): string | undefined => {\n const [key, value] = Object.entries(obj ?? {})[0] ?? [];\n\n if (key && value) {\n const path = base + key;\n\n if (typeof value === 'object') {\n return flatten(value, path ? `${path}.` : '');\n } else if (['asc', 'desc'].includes(value)) {\n return `${value === 'desc' ? '-' : ''}${path}`;\n }\n }\n\n return undefined;\n };\n\n const sorts = obj.map((v) => flatten(v)).filter(Boolean);\n return sorts.length ? { sort: sorts.join(',') } : {};\n};\n";
|
|
150
|
+
|
|
159
151
|
const resolveImports = (target) => {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
const resolveMappings = (target) => `const wrapResponse = ${isRXTarget(target) ? "defer" : "(v: (...args: any[]) => any) => v()"};`;
|
|
152
|
+
const imports = [];
|
|
153
|
+
if (isRXTarget(target)) {
|
|
154
|
+
imports.push(generateImport({ src: 'rxjs', imports: ['defer', 'Observable'] }));
|
|
155
|
+
}
|
|
156
|
+
return imports.join('\n');
|
|
157
|
+
};
|
|
158
|
+
const resolveMappings = (target) => `const wrapResponse = ${isRXTarget(target) ? 'defer' : '(v: (...args: any[]) => any) => v()'};`;
|
|
168
159
|
const resolveBinaryClass = (target) => `const resolveBinaryObject = () => ${resolveBinaryType(target)};`;
|
|
169
160
|
const generateBase = (apiVersion, { target, useQueryLanguage, generateUnique }) => {
|
|
170
|
-
|
|
161
|
+
return generateStatements(resolveImports(target), `const apiVersion = ${apiVersion}`, resolveMappings(target), resolveBinaryClass(target), globalConfig, types, utils, root, useQueryLanguage ? queriesWithQueryLanguage : queriesWithFilter, generateUnique ? unique : '', multiRequest);
|
|
171
162
|
};
|
|
172
|
-
|
|
173
|
-
//#region src/ts/generateEnum.ts
|
|
163
|
+
|
|
174
164
|
const transformKey = (s) => snakeCase(s).toUpperCase();
|
|
175
165
|
const generateEnum = (name, values) => {
|
|
176
|
-
|
|
166
|
+
const props = indent(values.map((v) => `${transformKey(v)} = ${generateString(v)}`).join(',\n'));
|
|
167
|
+
return `export enum ${name} {\n${props}\n}`;
|
|
177
168
|
};
|
|
178
|
-
|
|
179
|
-
|
|
169
|
+
|
|
170
|
+
// We can't use the pascalCase utility as it converts "cDBReminderType" to "CDbReminderType" which is incorrect.
|
|
180
171
|
const loosePascalCase = (str) => str[0].toUpperCase() + str.slice(1);
|
|
181
|
-
|
|
182
|
-
//#region src/utils/openapi/guards.ts
|
|
172
|
+
|
|
183
173
|
const isObject = (v) => {
|
|
184
|
-
|
|
174
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
185
175
|
};
|
|
186
176
|
const isParameterObject = (v) => {
|
|
187
|
-
|
|
177
|
+
return isObject(v) && typeof v.name === 'string' && typeof v.in === 'string';
|
|
188
178
|
};
|
|
189
179
|
const isReferenceObject = (v) => {
|
|
190
|
-
|
|
180
|
+
return isObject(v) && typeof v.$ref === 'string';
|
|
191
181
|
};
|
|
192
182
|
const isObjectSchemaObject = (v) => {
|
|
193
|
-
|
|
183
|
+
return isObject(v) && v.type === 'object' && isObject(v.properties);
|
|
194
184
|
};
|
|
195
185
|
const isEnumSchemaObject = (v) => {
|
|
196
|
-
|
|
186
|
+
return isObject(v) && v.type === 'string' && Array.isArray(v.enum);
|
|
197
187
|
};
|
|
198
188
|
const isArraySchemaObject = (v) => {
|
|
199
|
-
|
|
189
|
+
return isObject(v) && v.type === 'array' && typeof v.items === 'object';
|
|
200
190
|
};
|
|
201
191
|
const isResponseObject = (v) => {
|
|
202
|
-
|
|
192
|
+
return isObject(v) && typeof v.description === 'string';
|
|
203
193
|
};
|
|
204
|
-
|
|
205
|
-
//#region src/generator/02-enums/index.ts
|
|
194
|
+
|
|
206
195
|
const generateEnums = (context) => {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
196
|
+
const enums = new Map();
|
|
197
|
+
for (const [schemaName, schema] of context.schemas) {
|
|
198
|
+
if (isEnumSchemaObject(schema)) {
|
|
199
|
+
const enumName = loosePascalCase(schemaName);
|
|
200
|
+
if (!enums.has(enumName)) {
|
|
201
|
+
enums.set(enumName, {
|
|
202
|
+
name: enumName,
|
|
203
|
+
properties: schema.enum,
|
|
204
|
+
source: generateEnum(enumName, schema.enum)
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return enums;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const concat = (strings, separator = ', ', maxLength = 80) => {
|
|
213
|
+
const joined = strings.join(separator);
|
|
214
|
+
if (joined.length > maxLength) {
|
|
215
|
+
const length = strings.length - 1;
|
|
216
|
+
return `\n${indent(strings.map((value, index) => (index === length ? value : `${(value + separator).trim()}\n`)).join(''))}\n`;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
return joined;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
229
223
|
const convertParametersToSchemaObject = (parameters) => {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
224
|
+
const properties = [];
|
|
225
|
+
const required = [];
|
|
226
|
+
for (const param of parameters) {
|
|
227
|
+
if (param.in === 'query' && param.schema) {
|
|
228
|
+
properties.push([param.name, param.schema]);
|
|
229
|
+
if (param.required)
|
|
230
|
+
required.push(param.name);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
type: 'object',
|
|
235
|
+
properties: Object.fromEntries(properties),
|
|
236
|
+
required
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
|
|
244
240
|
const createReferenceType = (value) => ({
|
|
245
|
-
|
|
246
|
-
|
|
241
|
+
type: 'reference',
|
|
242
|
+
toString: () => loosePascalCase(value)
|
|
247
243
|
});
|
|
248
244
|
const createRawType = (value) => ({
|
|
249
|
-
|
|
250
|
-
|
|
245
|
+
type: 'raw',
|
|
246
|
+
toString: () => value
|
|
251
247
|
});
|
|
252
248
|
const createArrayType = (value) => ({
|
|
253
|
-
|
|
254
|
-
|
|
249
|
+
type: 'array',
|
|
250
|
+
toString: () => `(${value.toString()})[]`
|
|
255
251
|
});
|
|
256
252
|
const createTupleType = (value) => ({
|
|
257
|
-
|
|
258
|
-
|
|
253
|
+
type: 'tuple',
|
|
254
|
+
toString: () => concat([...new Set(value.map((v) => (typeof v === 'string' ? `'${v}'` : v.toString())))], ' | ')
|
|
259
255
|
});
|
|
260
256
|
const createObjectType = (value, required = []) => ({
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
257
|
+
type: 'object',
|
|
258
|
+
isFullyOptional: () => {
|
|
259
|
+
return (!required.length &&
|
|
260
|
+
Object.values(value)
|
|
261
|
+
.filter((v) => v?.type === 'object')
|
|
262
|
+
.every((v) => v.isFullyOptional()));
|
|
263
|
+
},
|
|
264
|
+
toString: (propertyPropagationOption = 'ignore') => {
|
|
265
|
+
const properties = Object.entries(value)
|
|
266
|
+
.filter((v) => v[1])
|
|
267
|
+
.map((v) => {
|
|
268
|
+
const name = v[0];
|
|
269
|
+
const value = v[1];
|
|
270
|
+
const isRequired = required.includes(name) ||
|
|
271
|
+
propertyPropagationOption === 'force' ||
|
|
272
|
+
(value.type === 'object' && !value.isFullyOptional() && propertyPropagationOption === 'propagate');
|
|
273
|
+
return `${name + (isRequired ? '' : '?')}: ${value.toString()};`;
|
|
274
|
+
});
|
|
275
|
+
return properties.length ? `{\n${indent(properties.join('\n'))}\n}` : '{}';
|
|
276
|
+
}
|
|
273
277
|
});
|
|
274
278
|
const getRefName = (obj) => {
|
|
275
|
-
|
|
279
|
+
return obj.$ref.replace(/.*\//, '');
|
|
276
280
|
};
|
|
277
281
|
const convertToTypeScriptType = (schema) => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
282
|
+
if (Array.isArray(schema)) {
|
|
283
|
+
return convertToTypeScriptType(convertParametersToSchemaObject(schema));
|
|
284
|
+
}
|
|
285
|
+
else if (isReferenceObject(schema)) {
|
|
286
|
+
return createReferenceType(getRefName(schema));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
switch (schema.type) {
|
|
290
|
+
case 'integer':
|
|
291
|
+
case 'number':
|
|
292
|
+
return createRawType('number');
|
|
293
|
+
case 'string':
|
|
294
|
+
if (schema.enum) {
|
|
295
|
+
return createTupleType(schema.enum);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
return schema.format === 'binary' ? createRawType('binary') : createRawType('string');
|
|
299
|
+
}
|
|
300
|
+
case 'boolean':
|
|
301
|
+
return createRawType('boolean');
|
|
302
|
+
case 'object': {
|
|
303
|
+
const { properties = {}, required = [] } = schema;
|
|
304
|
+
return createObjectType(Object.fromEntries(Object.entries(properties).map(([prop, propSchema]) => [prop, convertToTypeScriptType(propSchema)])), required);
|
|
305
|
+
}
|
|
306
|
+
case 'array':
|
|
307
|
+
return createArrayType(convertToTypeScriptType(schema.items));
|
|
308
|
+
default:
|
|
309
|
+
return createRawType('unknown');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
296
314
|
const setReferenceMeta = (prop, metaData, context) => {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
315
|
+
const referenceName = getRefName(prop);
|
|
316
|
+
const referenceSchema = context.schemas.get(referenceName);
|
|
317
|
+
if (isEnumSchemaObject(referenceSchema)) {
|
|
318
|
+
metaData.enum = loosePascalCase(referenceName);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
metaData.entity = referenceName;
|
|
322
|
+
}
|
|
300
323
|
};
|
|
301
324
|
const extractPropertyMetaData = (prop, context) => {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
+
const metaData = {};
|
|
326
|
+
const weclappExtension = prop['x-weclapp'];
|
|
327
|
+
if (weclappExtension) {
|
|
328
|
+
metaData.service = weclappExtension.service;
|
|
329
|
+
metaData.entity = weclappExtension.entity;
|
|
330
|
+
}
|
|
331
|
+
if (isReferenceObject(prop)) {
|
|
332
|
+
metaData.type = 'reference';
|
|
333
|
+
setReferenceMeta(prop, metaData, context);
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
metaData.type = prop.type;
|
|
337
|
+
metaData.format = prop.format;
|
|
338
|
+
metaData.maxLength = prop.maxLength;
|
|
339
|
+
metaData.pattern = prop.pattern;
|
|
340
|
+
if (isArraySchemaObject(prop)) {
|
|
341
|
+
if (isReferenceObject(prop.items)) {
|
|
342
|
+
metaData.format = 'reference';
|
|
343
|
+
setReferenceMeta(prop.items, metaData, context);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
metaData.format = 'string';
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return metaData;
|
|
351
|
+
};
|
|
352
|
+
|
|
325
353
|
const generateInlineComment = (comment) => `/** ${comment} */`;
|
|
326
|
-
const generateBlockComment = (comment, body) => `/**\n${comment.trim().replace(/^ */gm,
|
|
327
|
-
|
|
328
|
-
//#region src/ts/generateType.ts
|
|
354
|
+
const generateBlockComment = (comment, body) => `/**\n${comment.trim().replace(/^ */gm, ' * ')}\n */${body ? `\n${body}` : ''}`;
|
|
355
|
+
|
|
329
356
|
const generateType = (name, value) => {
|
|
330
|
-
|
|
357
|
+
return `export type ${name} = ${value.trim()};`;
|
|
331
358
|
};
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
//#endregion
|
|
336
|
-
//#region src/ts/generateInterface.ts
|
|
359
|
+
|
|
360
|
+
const arrayify = (v) => (Array.isArray(v) ? v : [v]);
|
|
361
|
+
|
|
337
362
|
const generateInterfaceProperties = (entries) => {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
363
|
+
const properties = entries
|
|
364
|
+
.filter((v) => v.type !== undefined)
|
|
365
|
+
.filter((value, index, array) => array.findIndex((v) => v.name === value.name) === index)
|
|
366
|
+
.map(({ name, type, required, readonly, comment }) => {
|
|
367
|
+
const cmd = comment ? `${generateInlineComment(comment)}\n` : '';
|
|
368
|
+
const req = required ? '' : '?';
|
|
369
|
+
const rol = readonly ? 'readonly ' : '';
|
|
370
|
+
return `${cmd + rol + name + req}: ${type};`;
|
|
371
|
+
})
|
|
372
|
+
.join('\n');
|
|
373
|
+
return properties.length ? `{\n${indent(properties)}\n}` : `{}`;
|
|
344
374
|
};
|
|
345
375
|
const generateInterfaceFromObject = (name, obj, propertyPropagationOption) => `export interface ${name} ${obj.toString(propertyPropagationOption)}`;
|
|
346
376
|
const generateInterface = (name, entries, extend) => {
|
|
347
|
-
|
|
377
|
+
const signature = `${name} ${extend ? `extends ${arrayify(extend).join(', ')}` : ''}`.trim();
|
|
378
|
+
const body = generateInterfaceProperties(entries);
|
|
379
|
+
return `export interface ${signature} ${body}`;
|
|
348
380
|
};
|
|
349
381
|
const generateInterfaceType = (name, entries, extend) => {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
382
|
+
const body = generateInterfaceProperties(entries);
|
|
383
|
+
const bases = extend ? arrayify(extend).join(' & ') : undefined;
|
|
384
|
+
let typeDefinition = '';
|
|
385
|
+
if (bases) {
|
|
386
|
+
typeDefinition = bases;
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
typeDefinition = body;
|
|
390
|
+
}
|
|
391
|
+
if (bases && body !== '{}') {
|
|
392
|
+
typeDefinition += ` & ${body}`;
|
|
393
|
+
}
|
|
394
|
+
return generateType(name, typeDefinition);
|
|
395
|
+
};
|
|
396
|
+
|
|
360
397
|
/**
|
|
361
|
-
* Pluralizes a word, most of the time correct.
|
|
362
|
-
* @param s String to pluralize.
|
|
363
|
-
*/
|
|
398
|
+
* Pluralizes a word, most of the time correct.
|
|
399
|
+
* @param s String to pluralize.
|
|
400
|
+
*/
|
|
364
401
|
const pluralize = (s) => {
|
|
365
|
-
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
402
|
+
return s.endsWith('s') ? s : s.endsWith('y') ? `${s.slice(0, -1)}ies` : `${s}s`;
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const logger = new (class {
|
|
406
|
+
active = true;
|
|
407
|
+
warnings = 0;
|
|
408
|
+
errors = 0;
|
|
409
|
+
write(str = '') {
|
|
410
|
+
process.stdout.write(str);
|
|
411
|
+
}
|
|
412
|
+
blankLn(str = '') {
|
|
413
|
+
this.blank(`${str}\n`);
|
|
414
|
+
}
|
|
415
|
+
warnLn(str) {
|
|
416
|
+
this.warn(`${str}\n`);
|
|
417
|
+
}
|
|
418
|
+
errorLn(str) {
|
|
419
|
+
this.error(`${str}\n`);
|
|
420
|
+
}
|
|
421
|
+
successLn(str) {
|
|
422
|
+
this.success(`${str}\n`);
|
|
423
|
+
}
|
|
424
|
+
infoLn(str) {
|
|
425
|
+
this.info(`${str}\n`);
|
|
426
|
+
}
|
|
427
|
+
debugLn(str) {
|
|
428
|
+
this.debug(`${str}\n`);
|
|
429
|
+
}
|
|
430
|
+
blank(str) {
|
|
431
|
+
this.write(str);
|
|
432
|
+
}
|
|
433
|
+
warn(str) {
|
|
434
|
+
this.write(`${chalk.yellowBright('[!]')} ${str}`);
|
|
435
|
+
this.warnings++;
|
|
436
|
+
}
|
|
437
|
+
error(str) {
|
|
438
|
+
this.write(`${chalk.redBright('[X]')} ${str}`);
|
|
439
|
+
this.errors++;
|
|
440
|
+
}
|
|
441
|
+
success(str) {
|
|
442
|
+
this.write(`${chalk.greenBright('[✓]')} ${str}`);
|
|
443
|
+
}
|
|
444
|
+
info(str) {
|
|
445
|
+
this.write(`${chalk.blueBright('[i]')} ${str}`);
|
|
446
|
+
}
|
|
447
|
+
debug(str) {
|
|
448
|
+
this.write(`[-] ${str}`);
|
|
449
|
+
}
|
|
450
|
+
printSummary() {
|
|
451
|
+
const format = (v, name, fail, ok) => {
|
|
452
|
+
const color = v ? fail : ok;
|
|
453
|
+
return v === 0
|
|
454
|
+
? `${color('zero')} ${pluralize(name)}`
|
|
455
|
+
: v === 1
|
|
456
|
+
? `${color('one')} ${name}`
|
|
457
|
+
: `${color(v)} ${pluralize(name)}`;
|
|
458
|
+
};
|
|
459
|
+
const warnings = format(this.warnings, 'warning', chalk.yellowBright, chalk.greenBright);
|
|
460
|
+
const errors = format(this.errors, 'error', chalk.redBright, chalk.greenBright);
|
|
461
|
+
const info = `Finished with ${warnings} and ${errors}.`;
|
|
462
|
+
this[this.errors ? 'errorLn' : this.warnings ? 'warnLn' : 'successLn'](info);
|
|
463
|
+
}
|
|
464
|
+
})();
|
|
465
|
+
|
|
466
|
+
const FILTER_PROPS_SUFFIX = 'Filter_Props';
|
|
426
467
|
const generateEntities = (context) => {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
468
|
+
const entities = new Map();
|
|
469
|
+
for (const [schemaName, schema] of context.schemas) {
|
|
470
|
+
// Enums are generated separately
|
|
471
|
+
if (isEnumSchemaObject(schema)) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const entityName = schemaName;
|
|
475
|
+
const entityInterfaceName = loosePascalCase(entityName);
|
|
476
|
+
const entityInterfaceProperties = [];
|
|
477
|
+
const properties = new Map();
|
|
478
|
+
let parentEntityName = undefined;
|
|
479
|
+
let parentEntityInterfaceName = undefined;
|
|
480
|
+
const entityFilterInterfaceName = `${entityInterfaceName}_${FILTER_PROPS_SUFFIX}`;
|
|
481
|
+
const entityFilterInterfaceProperties = [];
|
|
482
|
+
let parentEntityFilterInterfaceName = undefined;
|
|
483
|
+
const processProperties = (props = {}) => {
|
|
484
|
+
for (const [propertyName, propertySchema] of Object.entries(props)) {
|
|
485
|
+
const weclappExtension = propertySchema['x-weclapp'];
|
|
486
|
+
properties.set(propertyName, extractPropertyMetaData(propertySchema, context));
|
|
487
|
+
const type = convertToTypeScriptType(propertySchema).toString();
|
|
488
|
+
// cast to SchemaObject to access deprecated and readOnly properties (ReferenceObject can also include these props in OpenAPI 3.1)
|
|
489
|
+
const castedSchema = propertySchema;
|
|
490
|
+
const comment = castedSchema.deprecated
|
|
491
|
+
? '@deprecated will be removed.'
|
|
492
|
+
: castedSchema.format
|
|
493
|
+
? `format: ${castedSchema.format}`
|
|
494
|
+
: undefined;
|
|
495
|
+
if (weclappExtension?.filterable !== false) {
|
|
496
|
+
entityFilterInterfaceProperties.push({ name: propertyName, type, comment });
|
|
497
|
+
}
|
|
498
|
+
entityInterfaceProperties.push({
|
|
499
|
+
name: propertyName,
|
|
500
|
+
type,
|
|
501
|
+
required: weclappExtension?.required,
|
|
502
|
+
readonly: castedSchema.readOnly,
|
|
503
|
+
comment
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
const processExtraFilterProperties = (props = {}) => {
|
|
508
|
+
for (const [propertyName, propertySchema] of Object.entries(props)) {
|
|
509
|
+
if (isReferenceObject(propertySchema))
|
|
510
|
+
continue;
|
|
511
|
+
const type = convertToTypeScriptType(propertySchema).toString();
|
|
512
|
+
const comment = propertySchema.deprecated
|
|
513
|
+
? '@deprecated will be removed.'
|
|
514
|
+
: propertySchema.format
|
|
515
|
+
? `format: ${propertySchema.format}`
|
|
516
|
+
: undefined;
|
|
517
|
+
entityFilterInterfaceProperties.push({ name: propertyName, type, comment });
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
const processFilterPaths = (filterPaths = {}) => {
|
|
521
|
+
for (const [filterProp, entityName] of Object.entries(filterPaths)) {
|
|
522
|
+
if (!filterProp.includes('.') && context.schemas.get(entityName)) {
|
|
523
|
+
entityFilterInterfaceProperties.push({
|
|
524
|
+
name: filterProp,
|
|
525
|
+
type: `${loosePascalCase(entityName)}_${FILTER_PROPS_SUFFIX}`
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
if (schema.allOf?.length) {
|
|
531
|
+
if (schema.allOf.length > 2) {
|
|
532
|
+
logger.errorLn(`Failed to process schema for ${schemaName}: invalid allOf length`);
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
for (const item of schema.allOf) {
|
|
536
|
+
if (isReferenceObject(item)) {
|
|
537
|
+
parentEntityName = getRefName(item);
|
|
538
|
+
parentEntityInterfaceName = createReferenceType(parentEntityName).toString();
|
|
539
|
+
parentEntityFilterInterfaceName = `${parentEntityInterfaceName}_${FILTER_PROPS_SUFFIX}`;
|
|
540
|
+
}
|
|
541
|
+
else if (item.type === 'object') {
|
|
542
|
+
processProperties(item.properties);
|
|
543
|
+
processExtraFilterProperties(item['x-weclapp-filterProperties']);
|
|
544
|
+
processFilterPaths(item['x-weclapp-filterPaths']);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
logger.errorLn(`Failed to process schema for ${schemaName}: invalid schema type in allOf`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
processProperties(schema.properties);
|
|
553
|
+
}
|
|
554
|
+
entities.set(entityName, {
|
|
555
|
+
name: entityName,
|
|
556
|
+
interfaceName: entityInterfaceName,
|
|
557
|
+
properties,
|
|
558
|
+
source: generateStatements(generateInterface(entityInterfaceName, entityInterfaceProperties, parentEntityInterfaceName)),
|
|
559
|
+
filterInterfaceName: entityFilterInterfaceName,
|
|
560
|
+
filterSource: generateStatements(generateInterface(entityFilterInterfaceName, entityFilterInterfaceProperties, parentEntityFilterInterfaceName)),
|
|
561
|
+
parentName: parentEntityName,
|
|
562
|
+
parentInterfaceName: parentEntityInterfaceName
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
return entities;
|
|
566
|
+
};
|
|
567
|
+
|
|
508
568
|
/**
|
|
509
|
-
* ROOT => /article
|
|
510
|
-
* COUNT => /article/count
|
|
511
|
-
* ENTITY => /article/{id}
|
|
512
|
-
* SPECIAL_ROOT => /article/generateImage
|
|
513
|
-
* SPECIAL_ENTITY => /article/id/{id}/generateImag
|
|
514
|
-
*/
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}({});
|
|
569
|
+
* ROOT => /article
|
|
570
|
+
* COUNT => /article/count
|
|
571
|
+
* ENTITY => /article/{id}
|
|
572
|
+
* SPECIAL_ROOT => /article/generateImage
|
|
573
|
+
* SPECIAL_ENTITY => /article/id/{id}/generateImag
|
|
574
|
+
*/
|
|
575
|
+
var WeclappEndpointType;
|
|
576
|
+
(function (WeclappEndpointType) {
|
|
577
|
+
WeclappEndpointType["ROOT"] = "ROOT";
|
|
578
|
+
WeclappEndpointType["COUNT"] = "COUNT";
|
|
579
|
+
WeclappEndpointType["ENTITY"] = "ENTITY";
|
|
580
|
+
WeclappEndpointType["GENERIC_ROOT"] = "GENERIC_ROOT";
|
|
581
|
+
WeclappEndpointType["GENERIC_ENTITY"] = "GENERIC_ENTITY";
|
|
582
|
+
})(WeclappEndpointType || (WeclappEndpointType = {}));
|
|
523
583
|
const parseEndpointPath = (path) => {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
584
|
+
const [, service, ...rest] = path.split('/');
|
|
585
|
+
if (!service) {
|
|
586
|
+
return undefined;
|
|
587
|
+
}
|
|
588
|
+
if (!rest.length) {
|
|
589
|
+
return { path, service, type: WeclappEndpointType.ROOT };
|
|
590
|
+
}
|
|
591
|
+
else if (rest[0] === 'count') {
|
|
592
|
+
return { path, service, type: WeclappEndpointType.COUNT };
|
|
593
|
+
}
|
|
594
|
+
else if (rest[0] === 'id') {
|
|
595
|
+
return rest.length === 2
|
|
596
|
+
? { path, service, type: WeclappEndpointType.ENTITY }
|
|
597
|
+
: {
|
|
598
|
+
path,
|
|
599
|
+
service,
|
|
600
|
+
method: rest[2],
|
|
601
|
+
type: WeclappEndpointType.GENERIC_ENTITY
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
else if (rest.length === 1) {
|
|
605
|
+
return {
|
|
606
|
+
path,
|
|
607
|
+
service,
|
|
608
|
+
method: rest[1],
|
|
609
|
+
type: WeclappEndpointType.GENERIC_ROOT
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
return undefined;
|
|
552
613
|
};
|
|
553
614
|
const isMultiPartUploadPath = (path) => {
|
|
554
|
-
|
|
555
|
-
|
|
615
|
+
const [, entity, ...rest] = path.split('/');
|
|
616
|
+
return entity && rest.length === 2 && rest[1] === 'multipartUpload';
|
|
556
617
|
};
|
|
557
618
|
const parseEndpointsPaths = (paths) => {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
619
|
+
const endpoints = new Map();
|
|
620
|
+
for (const [rawPath, path] of Object.entries(paths)) {
|
|
621
|
+
const endpoint = parseEndpointPath(rawPath);
|
|
622
|
+
if (!endpoint || !path) {
|
|
623
|
+
// Todo: Should be removed if sdk supports multi part upload.
|
|
624
|
+
if (isMultiPartUploadPath(rawPath)) {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
logger.errorLn(`Failed to parse ${rawPath}`);
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (endpoints.has(endpoint.service)) {
|
|
631
|
+
endpoints.get(endpoint.service)?.push({ endpoint, path });
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
endpoints.set(endpoint.service, [{ endpoint, path }]);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return endpoints;
|
|
638
|
+
};
|
|
639
|
+
|
|
579
640
|
const generateArrowFunction = ({ name, signature, returns, params }) => {
|
|
580
|
-
|
|
641
|
+
return `const ${name}: ${signature} = (${params?.join(', ') ?? ''}) =>\n${indent(returns)};`;
|
|
581
642
|
};
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
const
|
|
585
|
-
|
|
643
|
+
|
|
644
|
+
const generateArrowFunctionType = ({ type, returns = 'void', generics, params }) => {
|
|
645
|
+
const genericsString = generics?.length ? `<\n${indent(generics.join(',\n'))}\n>` : '';
|
|
646
|
+
const paramsString = params?.length ? `(${params.join(', ')})` : `()`;
|
|
647
|
+
return generateType(type, `${genericsString + paramsString} =>\n${indent(returns)}`);
|
|
586
648
|
};
|
|
587
|
-
|
|
588
|
-
//#region src/generator/04-services/utils/resolveParameters.ts
|
|
649
|
+
|
|
589
650
|
const resolveParameters = (resolvableParameters = [], parameters) => {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
};
|
|
599
|
-
|
|
600
|
-
|
|
651
|
+
if (!resolvableParameters)
|
|
652
|
+
return [];
|
|
653
|
+
return resolvableParameters.flatMap((param) => {
|
|
654
|
+
if (isReferenceObject(param)) {
|
|
655
|
+
const resolved = parameters.get(getRefName(param));
|
|
656
|
+
return resolved ? [resolved] : [];
|
|
657
|
+
}
|
|
658
|
+
return [param];
|
|
659
|
+
});
|
|
660
|
+
};
|
|
661
|
+
|
|
601
662
|
const generateCountEndpoint = ({ endpoint, operationObject, entities, context, options }) => {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
|
|
663
|
+
const functionName = 'count';
|
|
664
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
665
|
+
const relatedEntityName = context.aliases.get(endpoint.service);
|
|
666
|
+
const relatedEntity = !!relatedEntityName && entities.get(relatedEntityName);
|
|
667
|
+
if (!relatedEntity) {
|
|
668
|
+
throw Error(`Related entity schema for service ${endpoint.service} not found`);
|
|
669
|
+
}
|
|
670
|
+
const parametersTypeName = `${functionTypeName}_Parameters`;
|
|
671
|
+
const parametersType = createObjectType({
|
|
672
|
+
params: convertToTypeScriptType(resolveParameters(operationObject.parameters, context.parameters))
|
|
673
|
+
});
|
|
674
|
+
const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
|
|
675
|
+
const filterTypeName = `${functionTypeName}_Filter`;
|
|
676
|
+
const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${relatedEntity.filterInterfaceName}`]);
|
|
677
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
678
|
+
type: functionTypeName,
|
|
679
|
+
params: [
|
|
680
|
+
`query${parametersType.isFullyOptional() ? '?' : ''}: CountQuery<${filterTypeName}>${operationObject.parameters?.length ? ' & ' + parametersTypeName : ''}`,
|
|
681
|
+
'requestOptions?: RequestOptions'
|
|
682
|
+
],
|
|
683
|
+
returns: `${resolveResponseType(options.target)}<number>`
|
|
684
|
+
});
|
|
685
|
+
const functionSource = generateArrowFunction({
|
|
686
|
+
name: functionName,
|
|
687
|
+
signature: functionTypeName,
|
|
688
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
|
|
689
|
+
params: ['query', 'requestOptions?: RequestOptions']
|
|
690
|
+
});
|
|
691
|
+
return {
|
|
692
|
+
name: functionName,
|
|
693
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
694
|
+
func: { name: functionName, source: functionSource },
|
|
695
|
+
interfaces: [
|
|
696
|
+
...(operationObject.parameters?.length ? [{ name: parametersTypeName, source: parametersTypeSource }] : []),
|
|
697
|
+
{ name: filterTypeName, source: filterTypeSource }
|
|
698
|
+
]
|
|
699
|
+
};
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
const generateContentType = (body, fallback = 'unknown') => {
|
|
703
|
+
if (!body?.content)
|
|
704
|
+
return createRawType(fallback);
|
|
705
|
+
const types = [];
|
|
706
|
+
for (const { schema } of Object.values(body.content)) {
|
|
707
|
+
if (schema) {
|
|
708
|
+
types.push(convertToTypeScriptType(schema));
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return (types.length > 1 ? createTupleType(types) : types[0]) ?? createRawType(fallback);
|
|
712
|
+
};
|
|
713
|
+
|
|
652
714
|
const generateRequestBodyType = ({ requestBody }, requestBodies) => {
|
|
653
|
-
|
|
715
|
+
const requestBodyObject = requestBody && isReferenceObject(requestBody) ? requestBodies.get(getRefName(requestBody)) : requestBody;
|
|
716
|
+
return generateContentType(requestBodyObject);
|
|
654
717
|
};
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
//#endregion
|
|
659
|
-
//#region src/generator/04-services/utils/generateResponseType.ts
|
|
718
|
+
|
|
719
|
+
const resolveResponsesObject = (responses) => Object.entries(responses).find(([statusCode]) => statusCode.startsWith('2'))?.[1];
|
|
720
|
+
|
|
660
721
|
const generateResponseType = (operationObject, responses) => {
|
|
661
|
-
|
|
662
|
-
|
|
722
|
+
const response = resolveResponsesObject(operationObject.responses);
|
|
723
|
+
const responseObject = response && isReferenceObject(response) ? responses.get(getRefName(response)) : response;
|
|
724
|
+
return generateContentType(responseObject, 'void');
|
|
663
725
|
};
|
|
664
|
-
|
|
665
|
-
//#region src/generator/04-services/endpoints/create.ts
|
|
726
|
+
|
|
666
727
|
const generateCreateEndpoint = ({ endpoint, operationObject, context, options }) => {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
};
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const generateGenericFunctionName = (path, suffix = "", prefix = "") => {
|
|
695
|
-
return camelCase(`${prefix}_` + path.replace(/.*\//, "").replace(/\W+/, "_").replace(/[_]+/, "_") + `_${suffix}`);
|
|
696
|
-
};
|
|
697
|
-
//#endregion
|
|
698
|
-
//#region src/generator/04-services/utils/insertPathPlaceholder.ts
|
|
728
|
+
const functionName = 'create';
|
|
729
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
730
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
731
|
+
type: functionTypeName,
|
|
732
|
+
params: [
|
|
733
|
+
`data: DeepPartial<${generateRequestBodyType(operationObject, context.requestBodies).toString()}>`,
|
|
734
|
+
'requestOptions?: RequestOptions'
|
|
735
|
+
],
|
|
736
|
+
returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
|
|
737
|
+
});
|
|
738
|
+
const functionSource = generateArrowFunction({
|
|
739
|
+
name: functionName,
|
|
740
|
+
signature: functionTypeName,
|
|
741
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, data, requestOptions)`,
|
|
742
|
+
params: ['data', 'requestOptions?: RequestOptions']
|
|
743
|
+
});
|
|
744
|
+
return {
|
|
745
|
+
name: functionName,
|
|
746
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
747
|
+
func: { name: functionName, source: functionSource }
|
|
748
|
+
};
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
const generateGenericFunctionName = (path, suffix = '', prefix = '') => {
|
|
752
|
+
return camelCase(`${prefix}_` + path.replace(/.*\//, '').replace(/\W+/, '_').replace(/[_]+/, '_') + `_${suffix}`);
|
|
753
|
+
};
|
|
754
|
+
|
|
699
755
|
const insertPathPlaceholder = (path, record) => {
|
|
700
|
-
|
|
756
|
+
return path.replace(/{(\w+)}/g, (_, name) => record[name]);
|
|
701
757
|
};
|
|
702
|
-
|
|
703
|
-
//#region src/generator/04-services/endpoints/generic.ts
|
|
758
|
+
|
|
704
759
|
const wrapBody = (type, target) => {
|
|
705
|
-
|
|
760
|
+
return type.toString() === 'binary' ? createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') : type; // node-fetch returns a Blob as well
|
|
706
761
|
};
|
|
707
762
|
const generateGenericEndpoint = (suffix) => ({ method, endpoint, operationObject, context, options }) => {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
source: generateInterfaceFromObject(entityQuery, params, "propagate")
|
|
749
|
-
}]
|
|
750
|
-
};
|
|
751
|
-
};
|
|
752
|
-
//#endregion
|
|
753
|
-
//#region src/generator/04-services/endpoints/remove.ts
|
|
763
|
+
const functionName = generateGenericFunctionName(endpoint.path, suffix, method);
|
|
764
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
765
|
+
const entityQuery = `${functionTypeName}_Query`;
|
|
766
|
+
const hasId = endpoint.path.includes('{id}');
|
|
767
|
+
const params = createObjectType({
|
|
768
|
+
params: operationObject.parameters &&
|
|
769
|
+
convertToTypeScriptType(resolveParameters(operationObject.parameters, context.parameters)),
|
|
770
|
+
body: method === 'get'
|
|
771
|
+
? undefined
|
|
772
|
+
: wrapBody(generateRequestBodyType(operationObject, context.requestBodies), options.target)
|
|
773
|
+
});
|
|
774
|
+
const responseBody = generateResponseType(operationObject, context.responses);
|
|
775
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
776
|
+
type: functionTypeName,
|
|
777
|
+
params: [
|
|
778
|
+
...(hasId ? ['id: string'] : []),
|
|
779
|
+
`query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`,
|
|
780
|
+
'requestOptions?: RequestOptions'
|
|
781
|
+
],
|
|
782
|
+
returns: `${resolveResponseType(options.target)}<${wrapBody(responseBody, options.target).toString('force')}>`
|
|
783
|
+
});
|
|
784
|
+
const functionSource = generateArrowFunction({
|
|
785
|
+
name: functionName,
|
|
786
|
+
signature: functionTypeName,
|
|
787
|
+
params: hasId ? ['id', 'query', 'requestOptions?: RequestOptions'] : ['query', 'requestOptions?: RequestOptions'],
|
|
788
|
+
returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${String(responseBody.toString() === 'binary')}, requestOptions)`
|
|
789
|
+
});
|
|
790
|
+
return {
|
|
791
|
+
name: functionName,
|
|
792
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
793
|
+
func: { name: functionName, source: functionSource },
|
|
794
|
+
interfaces: [
|
|
795
|
+
{
|
|
796
|
+
name: entityQuery,
|
|
797
|
+
source: generateInterfaceFromObject(entityQuery, params, 'propagate')
|
|
798
|
+
}
|
|
799
|
+
]
|
|
800
|
+
};
|
|
801
|
+
};
|
|
802
|
+
|
|
754
803
|
const generateRemoveEndpoint = ({ endpoint, options }) => {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
name: functionName,
|
|
778
|
-
type: {
|
|
779
|
-
name: functionTypeName,
|
|
780
|
-
source: functionTypeSource
|
|
781
|
-
},
|
|
782
|
-
func: {
|
|
783
|
-
name: functionName,
|
|
784
|
-
source: functionSource
|
|
785
|
-
}
|
|
786
|
-
};
|
|
787
|
-
};
|
|
788
|
-
//#endregion
|
|
789
|
-
//#region src/generator/04-services/endpoints/some.ts
|
|
804
|
+
const functionName = 'remove';
|
|
805
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
806
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
807
|
+
type: functionTypeName,
|
|
808
|
+
params: ['id: string', 'options?: RemoveQuery', 'requestOptions?: RequestOptions'],
|
|
809
|
+
returns: `${resolveResponseType(options.target)}<void>`
|
|
810
|
+
});
|
|
811
|
+
const functionSource = generateArrowFunction({
|
|
812
|
+
name: functionName,
|
|
813
|
+
signature: functionTypeName,
|
|
814
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options, requestOptions)`,
|
|
815
|
+
params: ['id', 'options?: RemoveQuery', 'requestOptions?: RequestOptions']
|
|
816
|
+
});
|
|
817
|
+
return {
|
|
818
|
+
name: functionName,
|
|
819
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
820
|
+
func: { name: functionName, source: functionSource }
|
|
821
|
+
};
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
const generateTupleArray = (values) => `(${concat(values.map(generateString), ' | ')})[]`;
|
|
825
|
+
|
|
790
826
|
const excludedParameters = [
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
827
|
+
'page',
|
|
828
|
+
'pageSize',
|
|
829
|
+
'sort',
|
|
830
|
+
'serializeNulls',
|
|
831
|
+
'properties',
|
|
832
|
+
'includeReferencedEntities',
|
|
833
|
+
'additionalProperties'
|
|
798
834
|
];
|
|
799
|
-
const resolveAdditionalPropertiesSchema = ({ responses }) => {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
835
|
+
const resolveAdditionalPropertiesSchema = ({ responses }, contextResponses) => {
|
|
836
|
+
const response = resolveResponsesObject(responses);
|
|
837
|
+
const body = response && isReferenceObject(response) ? contextResponses.get(getRefName(response)) : response;
|
|
838
|
+
if (isResponseObject(body)) {
|
|
839
|
+
const schema = body?.content?.['application/json']?.schema;
|
|
840
|
+
if (isObjectSchemaObject(schema)) {
|
|
841
|
+
const obj = schema?.properties?.additionalProperties;
|
|
842
|
+
if (isObjectSchemaObject(obj)) {
|
|
843
|
+
return obj;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return undefined;
|
|
808
848
|
};
|
|
809
849
|
const resolveReferences = (entity, entities) => {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
850
|
+
const references = [];
|
|
851
|
+
const generatedEntity = entities.get(entity);
|
|
852
|
+
if (generatedEntity) {
|
|
853
|
+
for (const [property, propertyMetaData] of generatedEntity.properties) {
|
|
854
|
+
if (propertyMetaData.service) {
|
|
855
|
+
references.push({
|
|
856
|
+
name: property,
|
|
857
|
+
type: generateString(propertyMetaData.service),
|
|
858
|
+
required: true
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (generatedEntity.parentName) {
|
|
863
|
+
references.push(...resolveReferences(generatedEntity.parentName, entities));
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return references;
|
|
821
867
|
};
|
|
822
868
|
const resolveReferencedEntities = (entity, entities) => {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
869
|
+
const referencedEntities = [];
|
|
870
|
+
const generatedEntity = entities.get(entity);
|
|
871
|
+
if (generatedEntity) {
|
|
872
|
+
for (const [, propertyMetaData] of generatedEntity.properties) {
|
|
873
|
+
if (propertyMetaData.service && propertyMetaData.entity) {
|
|
874
|
+
const referencedEntity = entities.get(propertyMetaData.entity);
|
|
875
|
+
if (referencedEntity)
|
|
876
|
+
referencedEntities.push({
|
|
877
|
+
name: propertyMetaData.service,
|
|
878
|
+
type: `${referencedEntity.interfaceName}[]`,
|
|
879
|
+
required: true
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (generatedEntity.parentName) {
|
|
884
|
+
referencedEntities.push(...resolveReferencedEntities(generatedEntity.parentName, entities));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return referencedEntities;
|
|
837
888
|
};
|
|
838
889
|
const generateSomeEndpoint = ({ endpoint, operationObject, entities, context, options }) => {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
name: referencesTypeName,
|
|
893
|
-
source: referencesTypeSource
|
|
894
|
-
},
|
|
895
|
-
{
|
|
896
|
-
name: additionalPropertyTypeName,
|
|
897
|
-
source: additionalPropertyTypeSource
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
name: queryTypeName,
|
|
901
|
-
source: queryTypeSource
|
|
902
|
-
},
|
|
903
|
-
{
|
|
904
|
-
name: referencedEntitiesTypeName,
|
|
905
|
-
source: referencedEntitiesTypeSource
|
|
906
|
-
},
|
|
907
|
-
{
|
|
908
|
-
name: additionalPropertiesTypeName,
|
|
909
|
-
source: additionalPropertiesTypeSource
|
|
910
|
-
}
|
|
911
|
-
]
|
|
912
|
-
};
|
|
913
|
-
};
|
|
914
|
-
//#endregion
|
|
915
|
-
//#region src/generator/04-services/endpoints/unique.ts
|
|
890
|
+
const functionName = 'some';
|
|
891
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
892
|
+
const relatedEntityName = context.aliases.get(endpoint.service);
|
|
893
|
+
const relatedEntity = !!relatedEntityName && entities.get(relatedEntityName);
|
|
894
|
+
if (!relatedEntity) {
|
|
895
|
+
throw Error(`Related entity schema for service ${endpoint.service} not found`);
|
|
896
|
+
}
|
|
897
|
+
const parametersTypeName = `${functionTypeName}_Parameters`;
|
|
898
|
+
const parameters = operationObject.parameters?.filter((v) => isParameterObject(v) ? !excludedParameters.includes(v.name) : false);
|
|
899
|
+
const parametersType = createObjectType({
|
|
900
|
+
params: parameters && convertToTypeScriptType(resolveParameters(parameters, context.parameters))
|
|
901
|
+
});
|
|
902
|
+
const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
|
|
903
|
+
const filterTypeName = `${functionTypeName}_Filter`;
|
|
904
|
+
const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${relatedEntity.filterInterfaceName}`]);
|
|
905
|
+
const referencesTypeName = `${functionTypeName}_References`;
|
|
906
|
+
const referencesTypeSource = generateInterfaceType(referencesTypeName, resolveReferences(endpoint.service, entities));
|
|
907
|
+
const additionalPropertiesSchema = resolveAdditionalPropertiesSchema(operationObject, context.responses);
|
|
908
|
+
const additionalPropertyTypeName = `${functionTypeName}_AdditionalPropertyNames`;
|
|
909
|
+
const additionalPropertyTypeSource = generateType(additionalPropertyTypeName, additionalPropertiesSchema ? generateTupleArray(Object.keys(additionalPropertiesSchema?.properties)) : '[]');
|
|
910
|
+
const queryTypeName = `${functionTypeName}_Query`;
|
|
911
|
+
const queryTypeSource = generateType(queryTypeName, `SomeQuery<${relatedEntity.interfaceName}, ${filterTypeName}, ${referencesTypeName}, ${additionalPropertyTypeName}> & ${parametersTypeName}`);
|
|
912
|
+
const referencedEntitiesTypeName = `${functionTypeName}_ReferencedEntities`;
|
|
913
|
+
const referencedEntitiesTypeSource = generateInterfaceType(referencedEntitiesTypeName, resolveReferencedEntities(endpoint.service, entities));
|
|
914
|
+
const additionalPropertiesTypeName = `${functionTypeName}_AdditionalProperties`;
|
|
915
|
+
const additionalPropertiesTypeSource = generateType(additionalPropertiesTypeName, additionalPropertiesSchema ? convertToTypeScriptType(additionalPropertiesSchema).toString() : '{}');
|
|
916
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
917
|
+
type: functionTypeName,
|
|
918
|
+
params: [`query${parametersType.isFullyOptional() ? '?' : ''}: ${queryTypeName}, requestOptions?: RequestOptions`],
|
|
919
|
+
returns: `${resolveResponseType(options.target)}<SomeQueryReturn<${relatedEntity.interfaceName}, ${referencedEntitiesTypeName}, ${additionalPropertiesTypeName}>>`
|
|
920
|
+
});
|
|
921
|
+
const functionSource = generateArrowFunction({
|
|
922
|
+
name: functionName,
|
|
923
|
+
signature: functionTypeName,
|
|
924
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
|
|
925
|
+
params: ['query', 'requestOptions?: RequestOptions']
|
|
926
|
+
});
|
|
927
|
+
return {
|
|
928
|
+
name: functionName,
|
|
929
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
930
|
+
func: { name: functionName, source: functionSource },
|
|
931
|
+
interfaces: [
|
|
932
|
+
{ name: parametersTypeName, source: parametersTypeSource },
|
|
933
|
+
{ name: filterTypeName, source: filterTypeSource },
|
|
934
|
+
{ name: referencesTypeName, source: referencesTypeSource },
|
|
935
|
+
{ name: additionalPropertyTypeName, source: additionalPropertyTypeSource },
|
|
936
|
+
{ name: queryTypeName, source: queryTypeSource },
|
|
937
|
+
{ name: referencedEntitiesTypeName, source: referencedEntitiesTypeSource },
|
|
938
|
+
{ name: additionalPropertiesTypeName, source: additionalPropertiesTypeSource }
|
|
939
|
+
]
|
|
940
|
+
};
|
|
941
|
+
};
|
|
942
|
+
|
|
916
943
|
const generateUniqueEndpoint = ({ operationObject, endpoint, context, options }) => {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
});
|
|
939
|
-
return {
|
|
940
|
-
name: functionName,
|
|
941
|
-
type: {
|
|
942
|
-
name: functionTypeName,
|
|
943
|
-
source: functionTypeSource
|
|
944
|
-
},
|
|
945
|
-
func: {
|
|
946
|
-
name: functionName,
|
|
947
|
-
source: functionSource
|
|
948
|
-
}
|
|
949
|
-
};
|
|
950
|
-
};
|
|
951
|
-
//#endregion
|
|
952
|
-
//#region src/generator/04-services/endpoints/update.ts
|
|
944
|
+
const functionName = 'unique';
|
|
945
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
946
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
947
|
+
type: functionTypeName,
|
|
948
|
+
params: ['id: string', 'query?: Q', 'requestOptions?: RequestOptions'],
|
|
949
|
+
generics: ['Q extends UniqueQuery'],
|
|
950
|
+
returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
|
|
951
|
+
});
|
|
952
|
+
const functionSource = generateArrowFunction({
|
|
953
|
+
name: functionName,
|
|
954
|
+
signature: functionTypeName,
|
|
955
|
+
params: ['id', 'query', 'requestOptions?: RequestOptions'],
|
|
956
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, requestOptions)`
|
|
957
|
+
});
|
|
958
|
+
return {
|
|
959
|
+
name: functionName,
|
|
960
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
961
|
+
func: { name: functionName, source: functionSource }
|
|
962
|
+
};
|
|
963
|
+
};
|
|
964
|
+
|
|
953
965
|
const generateUpdateEndpoint = ({ endpoint, operationObject, context, options }) => {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
type: {
|
|
980
|
-
name: functionTypeName,
|
|
981
|
-
source: functionTypeSource
|
|
982
|
-
},
|
|
983
|
-
func: {
|
|
984
|
-
name: functionName,
|
|
985
|
-
source: functionSource
|
|
986
|
-
}
|
|
987
|
-
};
|
|
988
|
-
};
|
|
989
|
-
//#endregion
|
|
990
|
-
//#region src/generator/04-services/index.ts
|
|
966
|
+
const functionName = 'update';
|
|
967
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
968
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
969
|
+
type: functionTypeName,
|
|
970
|
+
params: [
|
|
971
|
+
'id: string',
|
|
972
|
+
`data: DeepPartial<${generateRequestBodyType(operationObject, context.requestBodies).toString()}>`,
|
|
973
|
+
'options?: UpdateQuery',
|
|
974
|
+
'requestOptions?: RequestOptions'
|
|
975
|
+
],
|
|
976
|
+
returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
|
|
977
|
+
});
|
|
978
|
+
const functionSource = generateArrowFunction({
|
|
979
|
+
name: functionName,
|
|
980
|
+
signature: functionTypeName,
|
|
981
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options, requestOptions)`,
|
|
982
|
+
params: ['id', 'data', 'options', 'requestOptions?: RequestOptions']
|
|
983
|
+
});
|
|
984
|
+
return {
|
|
985
|
+
name: functionName,
|
|
986
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
987
|
+
func: { name: functionName, source: functionSource }
|
|
988
|
+
};
|
|
989
|
+
};
|
|
990
|
+
|
|
991
991
|
const generators = {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
992
|
+
/* /article */
|
|
993
|
+
[WeclappEndpointType.ROOT]: {
|
|
994
|
+
[OpenAPIV3.HttpMethods.GET]: generateSomeEndpoint,
|
|
995
|
+
[OpenAPIV3.HttpMethods.POST]: generateCreateEndpoint
|
|
996
|
+
},
|
|
997
|
+
/* /article/count */
|
|
998
|
+
[WeclappEndpointType.COUNT]: {
|
|
999
|
+
[OpenAPIV3.HttpMethods.GET]: generateCountEndpoint
|
|
1000
|
+
},
|
|
1001
|
+
/* /article/:id */
|
|
1002
|
+
[WeclappEndpointType.ENTITY]: {
|
|
1003
|
+
[OpenAPIV3.HttpMethods.GET]: generateUniqueEndpoint,
|
|
1004
|
+
[OpenAPIV3.HttpMethods.PUT]: generateUpdateEndpoint,
|
|
1005
|
+
[OpenAPIV3.HttpMethods.DELETE]: generateRemoveEndpoint
|
|
1006
|
+
},
|
|
1007
|
+
/* /article/:id/method */
|
|
1008
|
+
[WeclappEndpointType.GENERIC_ENTITY]: {
|
|
1009
|
+
[OpenAPIV3.HttpMethods.GET]: generateGenericEndpoint('ById'),
|
|
1010
|
+
[OpenAPIV3.HttpMethods.POST]: generateGenericEndpoint('ById')
|
|
1011
|
+
},
|
|
1012
|
+
/* /article/method */
|
|
1013
|
+
[WeclappEndpointType.GENERIC_ROOT]: {
|
|
1014
|
+
[OpenAPIV3.HttpMethods.GET]: generateGenericEndpoint(),
|
|
1015
|
+
[OpenAPIV3.HttpMethods.POST]: generateGenericEndpoint()
|
|
1016
|
+
}
|
|
1010
1017
|
};
|
|
1011
1018
|
const generateServices = (entities, context, options) => {
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1019
|
+
const services = new Map();
|
|
1020
|
+
for (const [serviceName, serviceEndpoints] of context.endpoints) {
|
|
1021
|
+
const serviceFnName = camelCase(`${serviceName}Service`);
|
|
1022
|
+
const serviceTypeName = pascalCase(`${serviceName}Service`);
|
|
1023
|
+
const functions = [];
|
|
1024
|
+
for (const { path, endpoint } of serviceEndpoints) {
|
|
1025
|
+
for (const method of [
|
|
1026
|
+
OpenAPIV3.HttpMethods.GET,
|
|
1027
|
+
OpenAPIV3.HttpMethods.POST,
|
|
1028
|
+
OpenAPIV3.HttpMethods.PUT,
|
|
1029
|
+
OpenAPIV3.HttpMethods.DELETE
|
|
1030
|
+
]) {
|
|
1031
|
+
if ((method === OpenAPIV3.HttpMethods.GET &&
|
|
1032
|
+
endpoint.type === WeclappEndpointType.ENTITY &&
|
|
1033
|
+
!options.generateUnique) ||
|
|
1034
|
+
(method === OpenAPIV3.HttpMethods.POST &&
|
|
1035
|
+
(endpoint.type === WeclappEndpointType.COUNT || endpoint.path.endsWith('query')))) {
|
|
1036
|
+
// Skip unique endpoints if generateUnique option is not set or if POST is used for filter queries
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
const operationObject = path[method];
|
|
1040
|
+
const generatorFn = generators[endpoint.type][method];
|
|
1041
|
+
if (operationObject && generatorFn) {
|
|
1042
|
+
if (!operationObject.deprecated || options.deprecated) {
|
|
1043
|
+
functions.push({
|
|
1044
|
+
...generatorFn({
|
|
1045
|
+
method,
|
|
1046
|
+
endpoint,
|
|
1047
|
+
operationObject,
|
|
1048
|
+
entities,
|
|
1049
|
+
context,
|
|
1050
|
+
options
|
|
1051
|
+
}),
|
|
1052
|
+
path: operationObject
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (!functions.length) {
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
const serviceTypes = generateStatements(...functions.flatMap((v) => generateBlockComment(`${serviceTypeName} - ${pascalCase(v.name)}`, generateStatements(...[...(v.interfaces?.map((v) => v.source) ?? []), v.type.source]))), generateBlockComment(`${serviceTypeName}`, generateInterface(serviceTypeName, [
|
|
1062
|
+
...functions.map((v) => ({
|
|
1063
|
+
required: true,
|
|
1064
|
+
comment: v.path.deprecated ? '@deprecated' : undefined,
|
|
1065
|
+
name: v.func.name,
|
|
1066
|
+
type: v.type.name
|
|
1067
|
+
}))
|
|
1068
|
+
])));
|
|
1069
|
+
const serviceFn = `export const ${serviceFnName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${generateBlockStatements(...functions.map((v) => v.func.source), `return {${concat(functions.map((v) => v.func.name))}};`)};`;
|
|
1070
|
+
const relatedEntityName = context.aliases.get(serviceName);
|
|
1071
|
+
const relatedEntity = relatedEntityName ? entities.get(relatedEntityName) : undefined;
|
|
1072
|
+
services.set(serviceName, {
|
|
1073
|
+
name: serviceName,
|
|
1074
|
+
serviceFnName,
|
|
1075
|
+
functions,
|
|
1076
|
+
source: generateStatements(serviceTypes, serviceFn),
|
|
1077
|
+
deprecated: functions.every((v) => v.path.deprecated),
|
|
1078
|
+
relatedEntity
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
return services;
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1063
1084
|
const generateCustomValueServices = (services) => {
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1085
|
+
const customValueEntities = [];
|
|
1086
|
+
for (const service of services) {
|
|
1087
|
+
const relatedEntity = service.relatedEntity;
|
|
1088
|
+
if (relatedEntity?.name === 'customValue') {
|
|
1089
|
+
customValueEntities.push(service.name);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return generateStatements(generateType('WCustomValueService', concat(generateStrings(customValueEntities), ' | ')), `export const wCustomValueServiceNames: WCustomValueService[] = [${concat(generateStrings(customValueEntities))}];`, `export const isWCustomValueService = (service: string | undefined): service is WCustomValueService =>\n${indent('wCustomValueServiceNames.includes(service as WCustomValueService);')}`);
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1070
1095
|
const generateObject = (properties) => {
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1096
|
+
const body = [];
|
|
1097
|
+
for (const { key, value, comment } of properties) {
|
|
1098
|
+
if (value === undefined) {
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
if (Array.isArray(value)) {
|
|
1102
|
+
const str = generateObject(value);
|
|
1103
|
+
if (str.length > 2) {
|
|
1104
|
+
body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${str}`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${String(value)}`);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return body.length ? `{\n${indent(body.join(',\n'))}\n}` : `{}`;
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1083
1114
|
const resolveInheritedEntities = (root, entities) => {
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
};
|
|
1087
|
-
const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1115
|
+
const parent = root.parentName ? entities.get(root.parentName) : undefined;
|
|
1116
|
+
return parent ? [parent, ...resolveInheritedEntities(parent, entities)] : [];
|
|
1117
|
+
};
|
|
1118
|
+
const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
|
|
1119
|
+
.filter(([, meta]) => {
|
|
1120
|
+
// If we generate deprecated things we can skip the filtering
|
|
1121
|
+
if (options.deprecated) {
|
|
1122
|
+
return true;
|
|
1123
|
+
}
|
|
1124
|
+
// Check if corresponding service is deprecated and can be removed
|
|
1125
|
+
const service = services.find((v) => v.name === meta.service);
|
|
1126
|
+
return !meta.service || (service && !service.deprecated);
|
|
1127
|
+
})
|
|
1128
|
+
.map(([property, meta]) => ({
|
|
1129
|
+
key: property,
|
|
1130
|
+
value: Object.entries(meta).map(([key, value]) => ({
|
|
1131
|
+
key,
|
|
1132
|
+
value: value !== undefined ? (typeof value === 'number' ? value : generateString(value)) : undefined
|
|
1133
|
+
}))
|
|
1097
1134
|
}));
|
|
1098
1135
|
const generateEntityProperties = (entities, services, options) => {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1136
|
+
const typeName = 'WEntityProperties';
|
|
1137
|
+
const propertyMap = [
|
|
1138
|
+
...entities.entries(),
|
|
1139
|
+
...services
|
|
1140
|
+
.filter(({ relatedEntity }) => !!relatedEntity)
|
|
1141
|
+
.filter(({ name }) => !entities.get(name))
|
|
1142
|
+
.map(({ name, relatedEntity }) => {
|
|
1143
|
+
return [name, relatedEntity];
|
|
1144
|
+
})
|
|
1145
|
+
].map(([entityName, entity]) => ({
|
|
1146
|
+
key: entityName,
|
|
1147
|
+
value: generatePropertyDescriptors(entity, entities, services, options)
|
|
1148
|
+
}));
|
|
1149
|
+
return generateStatements(`export type ${typeName} = Partial<Record<WEntity, Partial<Record<string, WEntityPropertyMeta>>>>;`, `export const wEntityProperties: ${typeName} = ${generateObject(propertyMap)};`);
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1110
1152
|
const generateArray = (values) => {
|
|
1111
|
-
|
|
1153
|
+
return `[${concat(values.map((v) => generateString(String(v))))}]`;
|
|
1112
1154
|
};
|
|
1113
|
-
|
|
1114
|
-
|
|
1155
|
+
|
|
1156
|
+
// Only functions matching this regex are included in the generation.
|
|
1115
1157
|
const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
|
|
1116
1158
|
/**
|
|
1117
|
-
* Generates for each function a map with the entity-name as key and service type as value.
|
|
1118
|
-
* E.g. WServicesWith[Function] where [Function] may be something like "some" or "create".
|
|
1119
|
-
*
|
|
1120
|
-
* This function also generates an exported array with the names of each service for each name.
|
|
1121
|
-
*/
|
|
1159
|
+
* Generates for each function a map with the entity-name as key and service type as value.
|
|
1160
|
+
* E.g. WServicesWith[Function] where [Function] may be something like "some" or "create".
|
|
1161
|
+
*
|
|
1162
|
+
* This function also generates an exported array with the names of each service for each name.
|
|
1163
|
+
*/
|
|
1122
1164
|
const generateGroupedServices = (services) => {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
};
|
|
1144
|
-
|
|
1145
|
-
|
|
1165
|
+
const entityDescriptors = new Map();
|
|
1166
|
+
for (const service of services) {
|
|
1167
|
+
for (const fn of service.functions) {
|
|
1168
|
+
if (!FILTER_REGEX.test(fn.name)) {
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
entityDescriptors.set(fn.name, [
|
|
1172
|
+
...(entityDescriptors.get(fn.name) ?? []),
|
|
1173
|
+
{
|
|
1174
|
+
name: service.name,
|
|
1175
|
+
required: true,
|
|
1176
|
+
type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}`
|
|
1177
|
+
}
|
|
1178
|
+
]);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
const descriptors = [...entityDescriptors.entries()];
|
|
1182
|
+
const typeGuards = [];
|
|
1183
|
+
for (const [name] of descriptors) {
|
|
1184
|
+
const constant = camelCase(`wServiceWith_${name}_Names`);
|
|
1185
|
+
const service = pascalCase(`WServiceWith_${name}`);
|
|
1186
|
+
const guard = `(service: string | undefined): service is ${service} =>\n${indent(`${constant}.includes(service as ${service});`)}`;
|
|
1187
|
+
typeGuards.push(`export const is${service} = ${guard}`);
|
|
1188
|
+
}
|
|
1189
|
+
return generateStatements(...descriptors.map(([name, props]) => generateInterface(pascalCase(`WServicesWith_${name}`), props)), ...descriptors.map(([name]) => generateType(pascalCase(`WServiceWith_${name}`), `keyof ${pascalCase(`WServicesWith_${name}`)}`)), ...descriptors.map(([name, props]) => {
|
|
1190
|
+
const constant = camelCase(`wServiceWith_${name}_Names`);
|
|
1191
|
+
const type = pascalCase(`WServiceWith_${name}`);
|
|
1192
|
+
const value = generateArray(props.map((v) => v.name));
|
|
1193
|
+
return `export const ${constant}: ${type}[] = ${value};`;
|
|
1194
|
+
}), ...typeGuards);
|
|
1195
|
+
};
|
|
1196
|
+
|
|
1146
1197
|
const generateMaps = (enums, entities, services, context, options) => {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1198
|
+
const enumInstances = `export const wEnums = ${generateObject([...enums.keys()].map((v) => ({ key: v, value: v })))};`;
|
|
1199
|
+
const entityNames = `export const wEntityNames: WEntity[] = ${generateArray([...entities.keys()])};`;
|
|
1200
|
+
const generatedServices = [...services.values()];
|
|
1201
|
+
const serviceInstances = `export const wServices = ${generateObject(generatedServices.map((v) => ({
|
|
1202
|
+
key: v.name,
|
|
1203
|
+
value: `${v.serviceFnName}()`,
|
|
1204
|
+
comment: v.deprecated ? '@deprecated' : undefined
|
|
1205
|
+
})))};`;
|
|
1206
|
+
const serviceFactories = `export const wServiceFactories = ${generateObject(generatedServices.map((v) => ({
|
|
1207
|
+
key: v.name,
|
|
1208
|
+
value: v.serviceFnName,
|
|
1209
|
+
comment: v.deprecated ? '@deprecated' : undefined
|
|
1210
|
+
})))};`;
|
|
1211
|
+
return generateStatements(
|
|
1212
|
+
/* Enums */
|
|
1213
|
+
generateInterface('WEnums', [...enums.keys()].map((name) => ({ name, type: name, required: true }))), generateType('WEnum', 'keyof WEnums'), enumInstances,
|
|
1214
|
+
/* Entities */
|
|
1215
|
+
generateInterface('WEntities', [
|
|
1216
|
+
...[...entities.entries()].map(([name, entity]) => ({
|
|
1217
|
+
name,
|
|
1218
|
+
type: entity.interfaceName,
|
|
1219
|
+
required: true
|
|
1220
|
+
})),
|
|
1221
|
+
...generatedServices
|
|
1222
|
+
.filter(({ relatedEntity }) => !!relatedEntity)
|
|
1223
|
+
.filter(({ name }) => !entities.get(name))
|
|
1224
|
+
.map(({ name, relatedEntity }) => ({
|
|
1225
|
+
name,
|
|
1226
|
+
type: relatedEntity.interfaceName,
|
|
1227
|
+
required: true
|
|
1228
|
+
}))
|
|
1229
|
+
].sort((a, b) => (a.name > b.name ? 1 : -1))), generateType('WEntity', 'keyof WEntities'), entityNames,
|
|
1230
|
+
/* Services */
|
|
1231
|
+
serviceInstances, generateType('WServices', 'typeof wServices'), generateType('WService', 'keyof WServices'), serviceFactories, generateType('WServiceFactories', 'typeof wServiceFactories'),
|
|
1232
|
+
/* Service Utils */
|
|
1233
|
+
generateGroupedServices(generatedServices), generateCustomValueServices(generatedServices),
|
|
1234
|
+
/* Entity Properties (Runtime Meta Infos) */
|
|
1235
|
+
generateEntityProperties(entities, generatedServices, options));
|
|
1236
|
+
};
|
|
1237
|
+
|
|
1179
1238
|
function extractRelatedEntityName(serviceEndpoints, responses) {
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1239
|
+
const rootEndpoint = serviceEndpoints.find((v) => v.endpoint.type === WeclappEndpointType.ROOT);
|
|
1240
|
+
if (!rootEndpoint)
|
|
1241
|
+
return;
|
|
1242
|
+
const response = rootEndpoint?.path.get?.responses['200'];
|
|
1243
|
+
if (!response)
|
|
1244
|
+
return;
|
|
1245
|
+
let responseObject;
|
|
1246
|
+
if (isReferenceObject(response)) {
|
|
1247
|
+
const refName = getRefName(response);
|
|
1248
|
+
responseObject = responses.get(refName);
|
|
1249
|
+
}
|
|
1250
|
+
else {
|
|
1251
|
+
responseObject = response;
|
|
1252
|
+
}
|
|
1253
|
+
const responseSchema = responseObject?.content?.['application/json'].schema;
|
|
1254
|
+
if (responseSchema) {
|
|
1255
|
+
if (isReferenceObject(responseSchema)) {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
const resultSchema = responseSchema.properties?.result;
|
|
1259
|
+
if (!resultSchema) {
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
if (isReferenceObject(resultSchema)) {
|
|
1263
|
+
return getRefName(resultSchema);
|
|
1264
|
+
}
|
|
1265
|
+
else if (isArraySchemaObject(resultSchema)) {
|
|
1266
|
+
const resultItemSchema = resultSchema.items;
|
|
1267
|
+
if (isReferenceObject(resultItemSchema)) {
|
|
1268
|
+
return getRefName(resultItemSchema);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1200
1272
|
}
|
|
1201
|
-
|
|
1202
|
-
//#region src/utils/weclapp/extractContext.ts
|
|
1273
|
+
|
|
1203
1274
|
const extractServiceAliases = (endpoints, responses) => {
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1275
|
+
const aliases = new Map();
|
|
1276
|
+
for (const [serviceName, serviceEndpoints] of endpoints) {
|
|
1277
|
+
const relatedEntityName = extractRelatedEntityName(serviceEndpoints, responses);
|
|
1278
|
+
if (relatedEntityName)
|
|
1279
|
+
aliases.set(serviceName, relatedEntityName);
|
|
1280
|
+
}
|
|
1281
|
+
return aliases;
|
|
1210
1282
|
};
|
|
1211
1283
|
const extractContext = (doc) => {
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1284
|
+
const endpoints = parseEndpointsPaths(doc.paths);
|
|
1285
|
+
const schemas = new Map();
|
|
1286
|
+
for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
|
|
1287
|
+
if (!isReferenceObject(schema)) {
|
|
1288
|
+
schemas.set(name, schema);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
const responses = new Map();
|
|
1292
|
+
for (const [name, response] of Object.entries(doc.components?.responses ?? {})) {
|
|
1293
|
+
if (!isReferenceObject(response)) {
|
|
1294
|
+
responses.set(name, response);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
const parameters = new Map();
|
|
1298
|
+
for (const [name, parameter] of Object.entries(doc.components?.parameters ?? {})) {
|
|
1299
|
+
if (!isReferenceObject(parameter)) {
|
|
1300
|
+
parameters.set(name, parameter);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const requestBodies = new Map();
|
|
1304
|
+
for (const [name, requestBody] of Object.entries(doc.components?.requestBodies ?? {})) {
|
|
1305
|
+
if (!isReferenceObject(requestBody)) {
|
|
1306
|
+
requestBodies.set(name, requestBody);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
const aliases = extractServiceAliases(endpoints, responses);
|
|
1310
|
+
return { endpoints, schemas, responses, parameters, requestBodies, aliases };
|
|
1311
|
+
};
|
|
1312
|
+
|
|
1232
1313
|
const generate = (doc, options) => {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
};
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
const hash = (
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1314
|
+
const context = extractContext(doc);
|
|
1315
|
+
const base = generateBase(doc.info.version, options);
|
|
1316
|
+
const enums = generateEnums(context);
|
|
1317
|
+
const entities = generateEntities(context);
|
|
1318
|
+
const services = generateServices(entities, context, options);
|
|
1319
|
+
const maps = generateMaps(enums, entities, services, context, options);
|
|
1320
|
+
return generateStatements(generateBlockComment('BASE', base), generateBlockComment('ENUMS', generateStatements(...[...enums.values()].map((v) => v.source))), generateBlockComment('ENTITIES', generateStatements(...[...entities.values()].map((v) => v.source))), generateBlockComment('FILTERS', generateStatements(...[...entities.values()].map((v) => v.filterSource))), generateBlockComment('SERVICES', generateStatements(...[...services.values()].map((v) => v.source))), generateBlockComment('MAPS', maps));
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
const hash = (content, algorithm = 'sha256') => {
|
|
1324
|
+
const hash = createHash(algorithm);
|
|
1325
|
+
if (Array.isArray(content)) {
|
|
1326
|
+
content.map(hash.update.bind(hash));
|
|
1327
|
+
}
|
|
1328
|
+
else {
|
|
1329
|
+
hash.update(content);
|
|
1330
|
+
}
|
|
1331
|
+
return hash.digest('hex');
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
1251
1335
|
const cli = async () => {
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
};
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
const
|
|
1341
|
-
const
|
|
1342
|
-
(
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1336
|
+
const version = pkg.version;
|
|
1337
|
+
const { argv } = yargs(hideBin(process.argv))
|
|
1338
|
+
.scriptName('build-weclapp-sdk')
|
|
1339
|
+
.usage('Usage: $0 <source> [flags]')
|
|
1340
|
+
.version(version)
|
|
1341
|
+
.example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
|
|
1342
|
+
.example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
|
|
1343
|
+
.example('$0 xxx.weclapp.com --key ...', 'Generate the SDK based on the openapi file from the given weclapp instance')
|
|
1344
|
+
.help('h')
|
|
1345
|
+
.alias('v', 'version')
|
|
1346
|
+
.alias('h', 'help')
|
|
1347
|
+
.option('k', {
|
|
1348
|
+
alias: 'key',
|
|
1349
|
+
describe: 'API Key (only needed when not using a local file)',
|
|
1350
|
+
type: 'string'
|
|
1351
|
+
})
|
|
1352
|
+
.option('c', {
|
|
1353
|
+
alias: 'cache',
|
|
1354
|
+
describe: 'If the generated SDK should cached',
|
|
1355
|
+
type: 'boolean'
|
|
1356
|
+
})
|
|
1357
|
+
.option('q', {
|
|
1358
|
+
alias: 'query',
|
|
1359
|
+
describe: 'Extra query params when fetching the openapi.json from a server',
|
|
1360
|
+
type: 'string'
|
|
1361
|
+
})
|
|
1362
|
+
.option('generate-unique', {
|
|
1363
|
+
describe: 'Generate .unique functions',
|
|
1364
|
+
type: 'boolean'
|
|
1365
|
+
})
|
|
1366
|
+
.option('d', {
|
|
1367
|
+
alias: 'deprecated',
|
|
1368
|
+
describe: 'Include deprecated functions and services',
|
|
1369
|
+
type: 'boolean'
|
|
1370
|
+
})
|
|
1371
|
+
.option('e', {
|
|
1372
|
+
alias: 'from-env',
|
|
1373
|
+
describe: 'Use env variables WECLAPP_BACKEND_URL and WECLAPP_API_KEY as credentials',
|
|
1374
|
+
type: 'boolean'
|
|
1375
|
+
})
|
|
1376
|
+
.option('t', {
|
|
1377
|
+
alias: 'target',
|
|
1378
|
+
describe: 'Specify the target platform',
|
|
1379
|
+
type: 'string',
|
|
1380
|
+
choices: ['browser', 'browser.rx', 'node', 'node.rx']
|
|
1381
|
+
})
|
|
1382
|
+
.option('use-query-language', {
|
|
1383
|
+
describe: 'Generate the new where property for some and count queries',
|
|
1384
|
+
type: 'boolean'
|
|
1385
|
+
})
|
|
1386
|
+
.option('apiVersion', {
|
|
1387
|
+
describe: 'Specify the api version (only needed when not using a local file)',
|
|
1388
|
+
type: 'string'
|
|
1389
|
+
})
|
|
1390
|
+
.epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
|
|
1391
|
+
if (argv.fromEnv) {
|
|
1392
|
+
config();
|
|
1393
|
+
}
|
|
1394
|
+
const { WECLAPP_API_KEY, WECLAPP_BACKEND_URL } = process.env;
|
|
1395
|
+
const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, apiVersion, _: [src = WECLAPP_BACKEND_URL] } = argv;
|
|
1396
|
+
const options = {
|
|
1397
|
+
deprecated,
|
|
1398
|
+
generateUnique: argv.generateUnique ?? false,
|
|
1399
|
+
target: argv.target ?? Target.BROWSER_PROMISES,
|
|
1400
|
+
useQueryLanguage: argv.useQueryLanguage ?? false
|
|
1401
|
+
};
|
|
1402
|
+
if (!src || typeof src === 'number') {
|
|
1403
|
+
return Promise.reject(new Error('Expected string as command'));
|
|
1404
|
+
}
|
|
1405
|
+
if (!Object.values(Target).includes(options.target)) {
|
|
1406
|
+
logger.errorLn(`Unknown target: ${options.target}. Possible values are ${Object.values(Target).join(', ')}`);
|
|
1407
|
+
return Promise.reject(new Error());
|
|
1408
|
+
}
|
|
1409
|
+
if (await stat(src).catch(() => false)) {
|
|
1410
|
+
logger.infoLn(`Source is a file`);
|
|
1411
|
+
const content = JSON.parse(await readFile(src, 'utf-8'));
|
|
1412
|
+
return { cache, content, options };
|
|
1413
|
+
}
|
|
1414
|
+
logger.infoLn(`Source is a URL`);
|
|
1415
|
+
if (!key) {
|
|
1416
|
+
return Promise.reject(new Error('API key is missing'));
|
|
1417
|
+
}
|
|
1418
|
+
if (!apiVersion) {
|
|
1419
|
+
return Promise.reject(new Error('API version is missing'));
|
|
1420
|
+
}
|
|
1421
|
+
const url = new URL(src.startsWith('http') ? src : `https://${src}`);
|
|
1422
|
+
url.pathname = `/webapp/api/${apiVersion}/meta/openapi.json`;
|
|
1423
|
+
if (query?.length) {
|
|
1424
|
+
for (const param of query.split(',')) {
|
|
1425
|
+
const [name, value] = param.split('=');
|
|
1426
|
+
url.searchParams.set(name, value);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
const content = await fetch(url.toString(), {
|
|
1430
|
+
headers: { Accept: 'application/json', AuthenticationToken: key }
|
|
1431
|
+
}).then((res) => (res.ok ? res.json() : undefined));
|
|
1432
|
+
if (!content) {
|
|
1433
|
+
logger.errorLn(`Couldn't fetch file ${url.toString()} `);
|
|
1434
|
+
return Promise.reject(new Error());
|
|
1435
|
+
}
|
|
1436
|
+
else {
|
|
1437
|
+
logger.infoLn(`Use remote file: ${url.toString()}`);
|
|
1438
|
+
}
|
|
1439
|
+
return { cache, content, options };
|
|
1440
|
+
};
|
|
1441
|
+
|
|
1442
|
+
const workingDir = resolve(currentDirname(), './sdk');
|
|
1443
|
+
const cacheDir = resolve(currentDirname(), './.cache');
|
|
1444
|
+
void (async () => {
|
|
1445
|
+
const start = process.hrtime.bigint();
|
|
1446
|
+
const { content: doc, cache: useCache, options } = await cli();
|
|
1447
|
+
const workingDirPath = async (...paths) => {
|
|
1448
|
+
const fullPath = resolve(workingDir, ...paths);
|
|
1449
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
1450
|
+
return fullPath;
|
|
1451
|
+
};
|
|
1452
|
+
// Resolve cache dir and key
|
|
1453
|
+
const cacheKey = hash([pkg.version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
|
|
1454
|
+
const cachedSdkDir = resolve(cacheDir, cacheKey);
|
|
1455
|
+
// Remove old SDK
|
|
1456
|
+
await rm(workingDir, { recursive: true, force: true });
|
|
1457
|
+
if (useCache) {
|
|
1458
|
+
logger.infoLn(`Cache ID: ${cacheKey}`);
|
|
1459
|
+
}
|
|
1460
|
+
if (useCache && (await stat(cachedSdkDir).catch(() => false))) {
|
|
1461
|
+
// Copy cached SDK to working dir
|
|
1462
|
+
logger.successLn(`Cache match! (${cachedSdkDir})`);
|
|
1463
|
+
await cp(cachedSdkDir, workingDir, { recursive: true });
|
|
1464
|
+
}
|
|
1465
|
+
else {
|
|
1466
|
+
// Write openapi.json file
|
|
1467
|
+
await writeFile(await workingDirPath('openapi.json'), JSON.stringify(doc, null, 2));
|
|
1468
|
+
logger.infoLn(`Generate sdk (target: ${options.target})`);
|
|
1469
|
+
// Generate and write SDK (index.ts)
|
|
1470
|
+
const sdk = generate(doc, options);
|
|
1471
|
+
await writeFile(await workingDirPath('src', 'index.ts'), sdk.trim() + '\n');
|
|
1472
|
+
// Bundle and write SDK
|
|
1473
|
+
logger.infoLn('Bundle... (this may take some time)');
|
|
1474
|
+
await bundle(workingDir, options.target);
|
|
1475
|
+
// Remove index.ts (only bundle is required)
|
|
1476
|
+
await rm(await workingDirPath('src'), { recursive: true, force: true });
|
|
1477
|
+
if (useCache) {
|
|
1478
|
+
// Copy SDK to cache
|
|
1479
|
+
logger.successLn(`Caching SDK: (${cachedSdkDir})`);
|
|
1480
|
+
await mkdir(cachedSdkDir, { recursive: true });
|
|
1481
|
+
await cp(workingDir, cachedSdkDir, { recursive: true });
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
// Print job summary
|
|
1485
|
+
const duration = (process.hrtime.bigint() - start) / 1000000n;
|
|
1486
|
+
logger.successLn(`SDK built in ${prettyMs(Number(duration))}`);
|
|
1487
|
+
logger.printSummary();
|
|
1488
|
+
})()
|
|
1489
|
+
.catch((error) => {
|
|
1490
|
+
logger.errorLn(`Fatal error:`);
|
|
1491
|
+
/* eslint-disable no-console */
|
|
1492
|
+
console.error(error);
|
|
1493
|
+
})
|
|
1494
|
+
.finally(() => {
|
|
1495
|
+
if (logger.errors)
|
|
1496
|
+
process.exit(1);
|
|
1389
1497
|
});
|
|
1390
|
-
//#endregion
|