@weclapp/sdk 2.0.0-dev.5 → 2.0.0-dev.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +418 -23
- package/dist/cli.js +589 -473
- package/package.json +43 -35
- package/tsconfig.sdk.json +14 -0
- package/tsconfig.lib.json +0 -17
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import { fileURLToPath } from 'url';
|
|
2
2
|
import { resolve, dirname } from 'path';
|
|
3
|
-
import { rmdir, stat, readFile, writeFile, rm, cp, mkdir } from 'fs/promises';
|
|
4
3
|
import { rollup } from 'rollup';
|
|
5
4
|
import terser from '@rollup/plugin-terser';
|
|
6
|
-
import ts from 'rollup
|
|
5
|
+
import ts from '@rollup/plugin-typescript';
|
|
7
6
|
import indentString from 'indent-string';
|
|
8
|
-
import { snakeCase,
|
|
7
|
+
import { snakeCase, camelCase, pascalCase } from 'change-case';
|
|
9
8
|
import chalk from 'chalk';
|
|
10
9
|
import { OpenAPIV3 } from 'openapi-types';
|
|
11
10
|
import { createHash } from 'crypto';
|
|
11
|
+
import { stat, readFile, rm, cp, writeFile, mkdir } from 'fs/promises';
|
|
12
12
|
import { config } from 'dotenv';
|
|
13
13
|
import yargs from 'yargs';
|
|
14
14
|
import { hideBin } from 'yargs/helpers';
|
|
15
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
15
16
|
import prettyMs from 'pretty-ms';
|
|
16
17
|
|
|
18
|
+
const currentDirname = () => {
|
|
19
|
+
// Go one level up as the CLI is inside a folder
|
|
20
|
+
return fileURLToPath(new URL('..', import.meta.url));
|
|
21
|
+
};
|
|
22
|
+
|
|
17
23
|
var Target;
|
|
18
24
|
(function (Target) {
|
|
19
25
|
Target["BROWSER_PROMISES"] = "browser";
|
|
@@ -34,15 +40,10 @@ const resolveBinaryType = (target) => {
|
|
|
34
40
|
return isNodeTarget(target) ? 'Buffer' : 'Blob';
|
|
35
41
|
};
|
|
36
42
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
return fileURLToPath(new URL('..', import.meta.url));
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const tsconfig = resolve(currentDirname(), './tsconfig.lib.json');
|
|
43
|
-
const resolveGlobals = (...globals) => Object.fromEntries(globals.map(v => [v, '*']));
|
|
43
|
+
const tsconfig = resolve(currentDirname(), './tsconfig.sdk.json');
|
|
44
|
+
const resolveGlobals = (...globals) => Object.fromEntries(globals.map((v) => [v, '*']));
|
|
44
45
|
const generateOutput = (config) => ({
|
|
45
|
-
sourcemap:
|
|
46
|
+
sourcemap: false,
|
|
46
47
|
banner: `/* weclapp sdk */`,
|
|
47
48
|
...config
|
|
48
49
|
});
|
|
@@ -61,35 +62,22 @@ const bundle = async (workingDirectory, target) => {
|
|
|
61
62
|
globals: resolveGlobals('node-fetch', 'url')
|
|
62
63
|
})
|
|
63
64
|
];
|
|
64
|
-
// Remove build dir
|
|
65
|
-
await rmdir(dist()).catch(() => void 0);
|
|
66
65
|
const bundles = {
|
|
67
66
|
[Target.BROWSER_PROMISES]: () => ({
|
|
68
|
-
input: src('
|
|
67
|
+
input: src('index.ts'),
|
|
68
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
69
69
|
output: [
|
|
70
|
-
generateOutput({
|
|
71
|
-
file: dist('index.cjs'),
|
|
72
|
-
name: 'Weclapp',
|
|
73
|
-
format: 'umd'
|
|
74
|
-
}),
|
|
75
70
|
generateOutput({
|
|
76
71
|
file: dist('index.js'),
|
|
77
72
|
format: 'es'
|
|
78
73
|
})
|
|
79
|
-
]
|
|
80
|
-
plugins: [ts({ tsconfig }), terser()]
|
|
74
|
+
]
|
|
81
75
|
}),
|
|
82
76
|
[Target.BROWSER_RX]: () => ({
|
|
77
|
+
input: src('index.ts'),
|
|
78
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
83
79
|
external: ['rxjs'],
|
|
84
|
-
input: src('browser.rx.ts'),
|
|
85
|
-
plugins: [ts({ tsconfig }), terser()],
|
|
86
80
|
output: [
|
|
87
|
-
generateOutput({
|
|
88
|
-
file: dist('index.cjs'),
|
|
89
|
-
name: 'Weclapp',
|
|
90
|
-
format: 'umd',
|
|
91
|
-
globals: resolveGlobals('rxjs')
|
|
92
|
-
}),
|
|
93
81
|
generateOutput({
|
|
94
82
|
file: dist('index.js'),
|
|
95
83
|
format: 'es',
|
|
@@ -98,16 +86,16 @@ const bundle = async (workingDirectory, target) => {
|
|
|
98
86
|
]
|
|
99
87
|
}),
|
|
100
88
|
[Target.NODE_PROMISES]: () => ({
|
|
101
|
-
input: src('
|
|
102
|
-
|
|
89
|
+
input: src('index.ts'),
|
|
90
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
103
91
|
external: ['node-fetch', 'url'],
|
|
104
|
-
|
|
92
|
+
output: generateNodeOutput()
|
|
105
93
|
}),
|
|
106
94
|
[Target.NODE_RX]: () => ({
|
|
107
|
-
input: src('
|
|
108
|
-
|
|
95
|
+
input: src('index.ts'),
|
|
96
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
109
97
|
external: ['node-fetch', 'url', 'rxjs'],
|
|
110
|
-
|
|
98
|
+
output: generateNodeOutput()
|
|
111
99
|
})
|
|
112
100
|
};
|
|
113
101
|
const config = bundles[target]();
|
|
@@ -139,14 +127,26 @@ const indent = (s, level = 1) => {
|
|
|
139
127
|
};
|
|
140
128
|
|
|
141
129
|
const generateStatements = (...statements) => statements
|
|
142
|
-
.map(v => v.trim())
|
|
143
|
-
.filter(v => v.length)
|
|
130
|
+
.map((v) => v.trim())
|
|
131
|
+
.filter((v) => v.length)
|
|
144
132
|
.join('\n\n');
|
|
145
133
|
const generateBlockStatements = (...statements) => `{\n${indent(generateStatements(...statements))}\n}`;
|
|
146
134
|
|
|
147
|
-
var
|
|
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 ArrayOperator = 'IN';\n\nexport type NullOperator = 'NULL';\n\nexport type Operator = ComparisonOperator | ArrayOperator | NullOperator;\n\nexport type ModifierFunction = 'lower';\n\nexport type MapOperators<T> =\n | ({ [K in ComparisonOperator]?: T } & { [K in ArrayOperator]?: T[] } & {\n [K in NullOperator]?: never } & {\n [K in ModifierFunction]?: boolean\n })\n | ({ [K in ComparisonOperator]?: T } & { [K in ArrayOperator]?: T[] } & {\n [K in NullOperator]?: boolean } & {\n [K in ModifierFunction]?: never\n });\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'];\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}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${comparisonOperatorMap[operator as Operator]} ${\n typeof value === 'string'\n ? setModifiers.reduce((acc, [first]) => `${first}(${acc})`, JSON.stringify(value))\n : 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}(${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' ? setModifiers.reduce((acc, [first]) => `${first}(${acc})`, JSON.stringify(v)) : v\n )}]`\n );\n }\n } else if (\n !modifierFunctionList.includes(operator as ModifierFunction)\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: Record<string, any> = {};\n for (const key in obj) {\n if (types.includes(key as ModifierFunction)) {\n result[key] = obj[key];\n }\n }\n return Object.entries(result);\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";
|
|
148
146
|
|
|
149
|
-
var types = "export type DeepPartial<T> = T extends object
|
|
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
150
|
|
|
151
151
|
const resolveImports = (target) => {
|
|
152
152
|
const imports = [];
|
|
@@ -157,13 +157,13 @@ const resolveImports = (target) => {
|
|
|
157
157
|
};
|
|
158
158
|
const resolveMappings = (target) => `const wrapResponse = ${isRXTarget(target) ? 'defer' : '(v: (...args: any[]) => any) => v()'};`;
|
|
159
159
|
const resolveBinaryClass = (target) => `const resolveBinaryObject = () => ${resolveBinaryType(target)};`;
|
|
160
|
-
const generateBase = (target) => {
|
|
161
|
-
return generateStatements(resolveImports(target), resolveMappings(target), resolveBinaryClass(target), types, root);
|
|
160
|
+
const generateBase = (target, apiVersion, options) => {
|
|
161
|
+
return generateStatements(resolveImports(target), `const apiVersion = ${apiVersion}`, resolveMappings(target), resolveBinaryClass(target), globalConfig, types, utils, root, options.useQueryLanguage ? queriesWithQueryLanguage : queriesWithFilter, options.generateUnique ? unique : '', multiRequest);
|
|
162
162
|
};
|
|
163
163
|
|
|
164
164
|
const transformKey = (s) => snakeCase(s).toUpperCase();
|
|
165
165
|
const generateEnum = (name, values) => {
|
|
166
|
-
const props = indent(values.map(v => `${transformKey(v)} = ${generateString(v)}`).join(',\n'));
|
|
166
|
+
const props = indent(values.map((v) => `${transformKey(v)} = ${generateString(v)}`).join(',\n'));
|
|
167
167
|
return `export enum ${name} {\n${props}\n}`;
|
|
168
168
|
};
|
|
169
169
|
|
|
@@ -194,19 +194,26 @@ const isResponseObject = (v) => {
|
|
|
194
194
|
const isNonArraySchemaObject = (v) => {
|
|
195
195
|
return isObject(v) && ['string', 'undefined'].includes(typeof v.type);
|
|
196
196
|
};
|
|
197
|
+
const isFilterPathsSchemaObject = (v) => {
|
|
198
|
+
return isObject(v) && v.type === 'object' && isObject(v['x-weclapp-filterPaths']);
|
|
199
|
+
};
|
|
200
|
+
const isFilterPropertySchemaObject = (v) => {
|
|
201
|
+
return isObject(v) && v.type === 'object' && isObject(v['x-weclapp-filterProperties']);
|
|
202
|
+
};
|
|
197
203
|
const isRelatedEntitySchema = (v) => {
|
|
198
204
|
return isObject(v) && isNonArraySchemaObject(v) && 'x-weclapp' in v && isObject(v['x-weclapp']);
|
|
199
205
|
};
|
|
200
206
|
|
|
201
207
|
const generateEnums = (schemas) => {
|
|
202
208
|
const enums = new Map();
|
|
203
|
-
for (const [
|
|
209
|
+
for (const [schemaName, schema] of schemas) {
|
|
204
210
|
if (isEnumSchemaObject(schema)) {
|
|
205
|
-
const
|
|
206
|
-
if (!enums.has(
|
|
207
|
-
enums.set(
|
|
211
|
+
const enumName = loosePascalCase(schemaName);
|
|
212
|
+
if (!enums.has(enumName)) {
|
|
213
|
+
enums.set(enumName, {
|
|
214
|
+
name: enumName,
|
|
208
215
|
properties: schema.enum,
|
|
209
|
-
source: generateEnum(
|
|
216
|
+
source: generateEnum(enumName, schema.enum)
|
|
210
217
|
});
|
|
211
218
|
}
|
|
212
219
|
}
|
|
@@ -218,16 +225,13 @@ const concat = (strings, separator = ', ', maxLength = 80) => {
|
|
|
218
225
|
const joined = strings.join(separator);
|
|
219
226
|
if (joined.length > maxLength) {
|
|
220
227
|
const length = strings.length - 1;
|
|
221
|
-
return `\n${indent(strings
|
|
222
|
-
.map((value, index) => index === length ? value : `${(value + separator).trim()}\n`)
|
|
223
|
-
.join(''))}\n`;
|
|
228
|
+
return `\n${indent(strings.map((value, index) => (index === length ? value : `${(value + separator).trim()}\n`)).join(''))}\n`;
|
|
224
229
|
}
|
|
225
230
|
else {
|
|
226
231
|
return joined;
|
|
227
232
|
}
|
|
228
233
|
};
|
|
229
234
|
|
|
230
|
-
/* eslint-disable no-use-before-define */
|
|
231
235
|
const createReferenceType = (value) => ({
|
|
232
236
|
type: 'reference',
|
|
233
237
|
toString: () => loosePascalCase(value)
|
|
@@ -242,22 +246,25 @@ const createArrayType = (value) => ({
|
|
|
242
246
|
});
|
|
243
247
|
const createTupleType = (value) => ({
|
|
244
248
|
type: 'tuple',
|
|
245
|
-
toString: () => concat([...new Set(value.map(v => typeof v === 'string' ? `'${v}'` : v.toString()))], ' | ')
|
|
249
|
+
toString: () => concat([...new Set(value.map((v) => (typeof v === 'string' ? `'${v}'` : v.toString())))], ' | ')
|
|
246
250
|
});
|
|
247
251
|
const createObjectType = (value, required = []) => ({
|
|
248
252
|
type: 'object',
|
|
249
253
|
isFullyOptional: () => {
|
|
250
|
-
return !required.length &&
|
|
251
|
-
.
|
|
252
|
-
|
|
254
|
+
return (!required.length &&
|
|
255
|
+
Object.values(value)
|
|
256
|
+
.filter((v) => v?.type === 'object')
|
|
257
|
+
.every((v) => v.isFullyOptional()));
|
|
253
258
|
},
|
|
254
|
-
toString: (
|
|
259
|
+
toString: (propertyPropagationOption = 'ignore') => {
|
|
255
260
|
const properties = Object.entries(value)
|
|
256
|
-
.filter(v => v[1])
|
|
257
|
-
.map(v => {
|
|
261
|
+
.filter((v) => v[1])
|
|
262
|
+
.map((v) => {
|
|
258
263
|
const name = v[0];
|
|
259
264
|
const value = v[1];
|
|
260
|
-
const isRequired = required.includes(name) ||
|
|
265
|
+
const isRequired = required.includes(name) ||
|
|
266
|
+
propertyPropagationOption === 'force' ||
|
|
267
|
+
(value.type === 'object' && !value.isFullyOptional() && propertyPropagationOption === 'propagate');
|
|
261
268
|
return `${name + (isRequired ? '' : '?')}: ${value.toString()};`;
|
|
262
269
|
});
|
|
263
270
|
return properties.length ? `{\n${indent(properties.join('\n'))}\n}` : '{}';
|
|
@@ -266,7 +273,7 @@ const createObjectType = (value, required = []) => ({
|
|
|
266
273
|
const getRefName = (obj) => {
|
|
267
274
|
return obj.$ref.replace(/.*\//, '');
|
|
268
275
|
};
|
|
269
|
-
const convertToTypeScriptType = (schema
|
|
276
|
+
const convertToTypeScriptType = (schema) => {
|
|
270
277
|
if (isReferenceObject(schema)) {
|
|
271
278
|
return createReferenceType(getRefName(schema));
|
|
272
279
|
}
|
|
@@ -277,7 +284,7 @@ const convertToTypeScriptType = (schema, property) => {
|
|
|
277
284
|
return createRawType('number');
|
|
278
285
|
case 'string':
|
|
279
286
|
if (schema.enum) {
|
|
280
|
-
return
|
|
287
|
+
return createTupleType(schema.enum);
|
|
281
288
|
}
|
|
282
289
|
else {
|
|
283
290
|
return schema.format === 'binary' ? createRawType('binary') : createRawType('string');
|
|
@@ -286,11 +293,10 @@ const convertToTypeScriptType = (schema, property) => {
|
|
|
286
293
|
return createRawType('boolean');
|
|
287
294
|
case 'object': {
|
|
288
295
|
const { properties = {}, required = [] } = schema;
|
|
289
|
-
return createObjectType(Object.fromEntries(Object.entries(properties)
|
|
290
|
-
.map(v => [v[0], convertToTypeScriptType(v[1])])), required);
|
|
296
|
+
return createObjectType(Object.fromEntries(Object.entries(properties).map((v) => [v[0], convertToTypeScriptType(v[1])])), required);
|
|
291
297
|
}
|
|
292
298
|
case 'array':
|
|
293
|
-
return createArrayType(convertToTypeScriptType(schema.items
|
|
299
|
+
return createArrayType(convertToTypeScriptType(schema.items));
|
|
294
300
|
default:
|
|
295
301
|
return createRawType('unknown');
|
|
296
302
|
}
|
|
@@ -308,14 +314,19 @@ const setEntityEnumProperty = (enums, prop, meta) => {
|
|
|
308
314
|
}
|
|
309
315
|
};
|
|
310
316
|
const extractPropertyMetaData = (enums, meta, prop) => {
|
|
311
|
-
const result = {
|
|
317
|
+
const result = {
|
|
318
|
+
service: meta.service,
|
|
319
|
+
entity: meta.entity
|
|
320
|
+
};
|
|
312
321
|
if (isReferenceObject(prop)) {
|
|
313
322
|
setEntityEnumProperty(enums, prop, result);
|
|
314
323
|
result.type = 'reference';
|
|
315
324
|
return result;
|
|
316
325
|
}
|
|
317
|
-
result.format = prop.format;
|
|
318
326
|
result.type = prop.type;
|
|
327
|
+
result.format = prop.format;
|
|
328
|
+
result.maxLength = prop.maxLength;
|
|
329
|
+
result.pattern = prop.pattern;
|
|
319
330
|
if (isArraySchemaObject(prop)) {
|
|
320
331
|
if (isReferenceObject(prop.items)) {
|
|
321
332
|
setEntityEnumProperty(enums, prop.items, result);
|
|
@@ -335,12 +346,12 @@ const generateType = (name, value) => {
|
|
|
335
346
|
return `export type ${name} = ${value.trim()};`;
|
|
336
347
|
};
|
|
337
348
|
|
|
338
|
-
const arrayify = (v) => Array.isArray(v) ? v : [v];
|
|
349
|
+
const arrayify = (v) => (Array.isArray(v) ? v : [v]);
|
|
339
350
|
|
|
340
351
|
const generateInterfaceProperties = (entries) => {
|
|
341
352
|
const properties = entries
|
|
342
|
-
.filter(v => v.type !== undefined)
|
|
343
|
-
.filter((value, index, array) => array.findIndex(v => v.name === value.name) === index)
|
|
353
|
+
.filter((v) => v.type !== undefined)
|
|
354
|
+
.filter((value, index, array) => array.findIndex((v) => v.name === value.name) === index)
|
|
344
355
|
.map(({ name, type, required, readonly, comment }) => {
|
|
345
356
|
const cmd = comment ? `${generateInlineComment(comment)}\n` : '';
|
|
346
357
|
const req = required ? '' : '?';
|
|
@@ -350,7 +361,7 @@ const generateInterfaceProperties = (entries) => {
|
|
|
350
361
|
.join('\n');
|
|
351
362
|
return properties.length ? `{\n${indent(properties)}\n}` : `{}`;
|
|
352
363
|
};
|
|
353
|
-
const generateInterfaceFromObject = (name, obj,
|
|
364
|
+
const generateInterfaceFromObject = (name, obj, propertyPropagationOption) => `export interface ${name} ${obj.toString(propertyPropagationOption)}`;
|
|
354
365
|
const generateInterface = (name, entries, extend) => {
|
|
355
366
|
const signature = `${name} ${extend ? `extends ${arrayify(extend).join(', ')}` : ''}`.trim();
|
|
356
367
|
const body = generateInterfaceProperties(entries);
|
|
@@ -359,9 +370,20 @@ const generateInterface = (name, entries, extend) => {
|
|
|
359
370
|
const generateInterfaceType = (name, entries, extend) => {
|
|
360
371
|
const body = generateInterfaceProperties(entries);
|
|
361
372
|
const bases = extend ? arrayify(extend).join(' & ') : undefined;
|
|
362
|
-
|
|
373
|
+
let typeDefinition = '';
|
|
374
|
+
if (bases) {
|
|
375
|
+
typeDefinition = bases;
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
typeDefinition = body;
|
|
379
|
+
}
|
|
380
|
+
if (bases && body !== '{}') {
|
|
381
|
+
typeDefinition += ` & ${body}`;
|
|
382
|
+
}
|
|
383
|
+
return generateType(name, typeDefinition);
|
|
363
384
|
};
|
|
364
385
|
|
|
386
|
+
const FILTER_PROPS_SUFFIX = 'Filter_Props';
|
|
365
387
|
const generateEntities = (schemas, enums) => {
|
|
366
388
|
const entities = new Map();
|
|
367
389
|
for (const [schemaName, schema] of schemas) {
|
|
@@ -369,73 +391,109 @@ const generateEntities = (schemas, enums) => {
|
|
|
369
391
|
if (isEnumSchemaObject(schema)) {
|
|
370
392
|
continue;
|
|
371
393
|
}
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
const
|
|
376
|
-
// Referenced entities and property-to-referenced-entity mapping
|
|
377
|
-
const referenceInterface = [];
|
|
378
|
-
const referenceMappingsInterface = [];
|
|
394
|
+
const entityInterfaceName = loosePascalCase(schemaName);
|
|
395
|
+
let parentEntityInterfaceName = undefined;
|
|
396
|
+
const entityInterfaceProperties = [];
|
|
397
|
+
const filterableInterfaceProperties = [];
|
|
379
398
|
const properties = new Map();
|
|
380
|
-
|
|
381
|
-
let extend = undefined;
|
|
382
|
-
const processProperties = (props = {}) => {
|
|
399
|
+
const processProperties = (props = {}, isXweclappFilterProp) => {
|
|
383
400
|
for (const [name, property] of Object.entries(props)) {
|
|
384
401
|
const meta = isRelatedEntitySchema(property) ? property['x-weclapp'] : {};
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
402
|
+
const type = convertToTypeScriptType(property).toString();
|
|
403
|
+
const comment = isNonArraySchemaObject(property)
|
|
404
|
+
? property.deprecated
|
|
405
|
+
? '@deprecated will be removed.'
|
|
406
|
+
: property.format
|
|
407
|
+
? `format: ${property.format}`
|
|
408
|
+
: undefined
|
|
409
|
+
: undefined;
|
|
410
|
+
if (meta.filterable !== false) {
|
|
411
|
+
filterableInterfaceProperties.push({ name, type });
|
|
389
412
|
}
|
|
390
|
-
if (
|
|
391
|
-
|
|
413
|
+
if (!isXweclappFilterProp) {
|
|
414
|
+
entityInterfaceProperties.push({
|
|
415
|
+
name,
|
|
416
|
+
type,
|
|
417
|
+
comment,
|
|
418
|
+
required: meta.required,
|
|
419
|
+
filterable: meta.filterable ?? true,
|
|
420
|
+
readonly: !isReferenceObject(property) && property.readOnly
|
|
421
|
+
});
|
|
422
|
+
properties.set(name, extractPropertyMetaData(enums, meta, property));
|
|
392
423
|
}
|
|
393
|
-
const type = convertToTypeScriptType(property, name).toString();
|
|
394
|
-
const comment = isNonArraySchemaObject(property) ?
|
|
395
|
-
property.deprecated ? '@deprecated will be removed.' :
|
|
396
|
-
property.format ? `format: ${property.format}` :
|
|
397
|
-
undefined : undefined;
|
|
398
|
-
entityInterface.push({
|
|
399
|
-
name, type, comment,
|
|
400
|
-
required: meta.required,
|
|
401
|
-
readonly: !isReferenceObject(property) && property.readOnly
|
|
402
|
-
});
|
|
403
|
-
properties.set(name, extractPropertyMetaData(enums, meta, property));
|
|
404
424
|
}
|
|
405
425
|
};
|
|
406
426
|
if (schema.allOf?.length) {
|
|
407
427
|
for (const item of schema.allOf) {
|
|
408
428
|
if (isReferenceObject(item)) {
|
|
409
|
-
|
|
429
|
+
parentEntityInterfaceName = convertToTypeScriptType(item).toString();
|
|
410
430
|
}
|
|
411
431
|
else if (isObjectSchemaObject(item)) {
|
|
412
432
|
processProperties(item.properties);
|
|
433
|
+
if (isFilterPropertySchemaObject(item)) {
|
|
434
|
+
processProperties(item['x-weclapp-filterProperties'], true);
|
|
435
|
+
}
|
|
436
|
+
if (isFilterPathsSchemaObject(item)) {
|
|
437
|
+
const fPaths = item['x-weclapp-filterPaths'];
|
|
438
|
+
for (const path in fPaths) {
|
|
439
|
+
if (!path.includes('.')) {
|
|
440
|
+
filterableInterfaceProperties.push({ name: path, type: fPaths[path] });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
413
444
|
}
|
|
414
445
|
}
|
|
415
446
|
}
|
|
416
447
|
processProperties(schema.properties);
|
|
417
|
-
const source = generateStatements(generateInterface(entity, entityInterface, extend), generateInterface(`${entity}_References`, referenceInterface, extend ? [`${extend}_References`] : undefined), generateInterface(`${entity}_Mappings`, referenceMappingsInterface, extend ? [`${extend}_Mappings`] : undefined), generateInterfaceType(`${entity}_Filter`, filterInterface, extend ? [entity, `${extend}_Filter`] : undefined));
|
|
418
448
|
entities.set(schemaName, {
|
|
419
|
-
|
|
449
|
+
name: schemaName,
|
|
420
450
|
properties,
|
|
421
|
-
|
|
451
|
+
filterableInterfaceProperties: filterableInterfaceProperties.sort((propA, propB) => propA.name.localeCompare(propB.name)),
|
|
452
|
+
parentName: parentEntityInterfaceName ? camelCase(parentEntityInterfaceName) : undefined,
|
|
453
|
+
source: generateStatements(generateInterface(entityInterfaceName, entityInterfaceProperties, parentEntityInterfaceName))
|
|
422
454
|
});
|
|
423
455
|
}
|
|
424
456
|
return entities;
|
|
425
457
|
};
|
|
458
|
+
const generateEntityFilterProps = (entities, enums) => {
|
|
459
|
+
const entityFilterProps = new Map();
|
|
460
|
+
const transformFilterProps = (props) => {
|
|
461
|
+
return props.map((prop) => {
|
|
462
|
+
if (!prop.type ||
|
|
463
|
+
enums.has(prop.type) ||
|
|
464
|
+
prop.type === 'string' ||
|
|
465
|
+
prop.type === 'number' ||
|
|
466
|
+
prop.type === 'boolean' ||
|
|
467
|
+
prop.type === '{}' ||
|
|
468
|
+
prop.type.endsWith('[]') ||
|
|
469
|
+
prop.type.includes("'")) {
|
|
470
|
+
return prop;
|
|
471
|
+
}
|
|
472
|
+
return { ...prop, type: `${pascalCase(prop.type)}_${FILTER_PROPS_SUFFIX}` };
|
|
473
|
+
});
|
|
474
|
+
};
|
|
475
|
+
entities.forEach((entity, name) => {
|
|
476
|
+
const entityFilterName = `${pascalCase(name)}_${FILTER_PROPS_SUFFIX}`;
|
|
477
|
+
const parentName = entity.parentName ? `${pascalCase(entity.parentName)}_${FILTER_PROPS_SUFFIX}` : undefined;
|
|
478
|
+
const filterableInterfaceProperties = transformFilterProps(entity.filterableInterfaceProperties);
|
|
479
|
+
entityFilterProps.set(entityFilterName, {
|
|
480
|
+
name: entityFilterName,
|
|
481
|
+
parentName,
|
|
482
|
+
source: generateStatements(generateInterface(entityFilterName, filterableInterfaceProperties, parentName))
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
return entityFilterProps;
|
|
486
|
+
};
|
|
426
487
|
|
|
427
488
|
/**
|
|
428
489
|
* Pluralizes a word, most of the time correct.
|
|
429
490
|
* @param s String to pluralize.
|
|
430
491
|
*/
|
|
431
492
|
const pluralize = (s) => {
|
|
432
|
-
return s.endsWith('s') ? s :
|
|
433
|
-
s.endsWith('y') ? `${s.slice(0, -1)}ies` :
|
|
434
|
-
`${s}s`;
|
|
493
|
+
return s.endsWith('s') ? s : s.endsWith('y') ? `${s.slice(0, -1)}ies` : `${s}s`;
|
|
435
494
|
};
|
|
436
495
|
|
|
437
|
-
|
|
438
|
-
const logger = new class {
|
|
496
|
+
const logger = new (class {
|
|
439
497
|
active = true;
|
|
440
498
|
warnings = 0;
|
|
441
499
|
errors = 0;
|
|
@@ -483,21 +541,24 @@ const logger = new class {
|
|
|
483
541
|
printSummary() {
|
|
484
542
|
const format = (v, name, fail, ok) => {
|
|
485
543
|
const color = v ? fail : ok;
|
|
486
|
-
return v === 0
|
|
487
|
-
|
|
544
|
+
return v === 0
|
|
545
|
+
? `${color('zero')} ${pluralize(name)}`
|
|
546
|
+
: v === 1
|
|
547
|
+
? `${color('one')} ${name}`
|
|
548
|
+
: `${color(v)} ${pluralize(name)}`;
|
|
488
549
|
};
|
|
489
550
|
const warnings = format(this.warnings, 'warning', chalk.yellowBright, chalk.greenBright);
|
|
490
551
|
const errors = format(this.errors, 'error', chalk.redBright, chalk.greenBright);
|
|
491
552
|
const info = `Finished with ${warnings} and ${errors}.`;
|
|
492
553
|
this[this.errors ? 'errorLn' : this.warnings ? 'warnLn' : 'successLn'](info);
|
|
493
554
|
}
|
|
494
|
-
};
|
|
555
|
+
})();
|
|
495
556
|
|
|
496
557
|
/**
|
|
497
|
-
* ROOT
|
|
498
|
-
* COUNT
|
|
499
|
-
* ENTITY
|
|
500
|
-
* SPECIAL_ROOT
|
|
558
|
+
* ROOT => /article
|
|
559
|
+
* COUNT => /article/count
|
|
560
|
+
* ENTITY => /article/{id}
|
|
561
|
+
* SPECIAL_ROOT => /article/generateImage
|
|
501
562
|
* SPECIAL_ENTITY => /article/id/{id}/generateImag
|
|
502
563
|
*/
|
|
503
564
|
var WeclappEndpointType;
|
|
@@ -509,23 +570,33 @@ var WeclappEndpointType;
|
|
|
509
570
|
WeclappEndpointType["GENERIC_ENTITY"] = "GENERIC_ENTITY";
|
|
510
571
|
})(WeclappEndpointType || (WeclappEndpointType = {}));
|
|
511
572
|
const parseEndpointPath = (path) => {
|
|
512
|
-
const [,
|
|
513
|
-
if (!
|
|
573
|
+
const [, service, ...rest] = path.split('/');
|
|
574
|
+
if (!service) {
|
|
514
575
|
return undefined;
|
|
515
576
|
}
|
|
516
577
|
if (!rest.length) {
|
|
517
|
-
return { path,
|
|
578
|
+
return { path, service, type: WeclappEndpointType.ROOT };
|
|
518
579
|
}
|
|
519
580
|
else if (rest[0] === 'count') {
|
|
520
|
-
return { path,
|
|
581
|
+
return { path, service, type: WeclappEndpointType.COUNT };
|
|
521
582
|
}
|
|
522
583
|
else if (rest[0] === 'id') {
|
|
523
|
-
return rest.length === 2
|
|
524
|
-
{ path,
|
|
525
|
-
|
|
584
|
+
return rest.length === 2
|
|
585
|
+
? { path, service, type: WeclappEndpointType.ENTITY }
|
|
586
|
+
: {
|
|
587
|
+
path,
|
|
588
|
+
service,
|
|
589
|
+
method: rest[2],
|
|
590
|
+
type: WeclappEndpointType.GENERIC_ENTITY
|
|
591
|
+
};
|
|
526
592
|
}
|
|
527
593
|
else if (rest.length === 1) {
|
|
528
|
-
return {
|
|
594
|
+
return {
|
|
595
|
+
path,
|
|
596
|
+
service,
|
|
597
|
+
method: rest[1],
|
|
598
|
+
type: WeclappEndpointType.GENERIC_ROOT
|
|
599
|
+
};
|
|
529
600
|
}
|
|
530
601
|
return undefined;
|
|
531
602
|
};
|
|
@@ -547,48 +618,51 @@ const convertParametersToSchema = (parameters = []) => {
|
|
|
547
618
|
if (isParameterObject(param) && param.in === 'query') {
|
|
548
619
|
if (param.schema) {
|
|
549
620
|
properties.push([param.name, param.schema]);
|
|
550
|
-
|
|
621
|
+
if (param.required)
|
|
622
|
+
required.push(param.name);
|
|
551
623
|
}
|
|
552
624
|
}
|
|
553
625
|
}
|
|
554
626
|
return {
|
|
555
|
-
type: 'object',
|
|
627
|
+
type: 'object',
|
|
628
|
+
required,
|
|
556
629
|
properties: Object.fromEntries(properties)
|
|
557
630
|
};
|
|
558
631
|
};
|
|
559
632
|
|
|
560
|
-
const functionName$5 = 'count';
|
|
561
633
|
const generateCountEndpoint = ({ aliases, path, target, endpoint }) => {
|
|
562
|
-
const
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
const
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
const parameters = createObjectType({
|
|
569
|
-
params: convertToTypeScriptType(parameterSchema)
|
|
570
|
-
});
|
|
571
|
-
const functionSource = generateArrowFunction({
|
|
572
|
-
name: functionName$5,
|
|
573
|
-
signature: interfaceName,
|
|
574
|
-
returns: `_${functionName$5}(cfg, ${generateString(endpoint.path)}, query)`,
|
|
575
|
-
params: ['query']
|
|
634
|
+
const functionName = 'count';
|
|
635
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
636
|
+
const entity = aliases.get(endpoint.service) ?? pascalCase(endpoint.service);
|
|
637
|
+
const parametersTypeName = `${functionTypeName}_Parameters`;
|
|
638
|
+
const parametersType = createObjectType({
|
|
639
|
+
params: convertToTypeScriptType(convertParametersToSchema(path.parameters))
|
|
576
640
|
});
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
641
|
+
const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
|
|
642
|
+
const filterTypeName = `${functionTypeName}_Filter`;
|
|
643
|
+
const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${entity}_${FILTER_PROPS_SUFFIX}`]);
|
|
644
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
645
|
+
type: functionTypeName,
|
|
646
|
+
params: [
|
|
647
|
+
`query${parametersType.isFullyOptional() ? '?' : ''}: CountQuery<${filterTypeName}>${path.parameters?.length ? ' & ' + parametersTypeName : ''}`,
|
|
648
|
+
'requestOptions?: RequestOptions'
|
|
649
|
+
],
|
|
580
650
|
returns: `${resolveResponseType(target)}<number>`
|
|
581
651
|
});
|
|
652
|
+
const functionSource = generateArrowFunction({
|
|
653
|
+
name: functionName,
|
|
654
|
+
signature: functionTypeName,
|
|
655
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
|
|
656
|
+
params: ['query', 'requestOptions?: RequestOptions']
|
|
657
|
+
});
|
|
582
658
|
return {
|
|
583
659
|
entity,
|
|
584
|
-
name: functionName
|
|
585
|
-
type: { name:
|
|
586
|
-
func: { name: functionName
|
|
660
|
+
name: functionName,
|
|
661
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
662
|
+
func: { name: functionName, source: functionSource },
|
|
587
663
|
interfaces: [
|
|
588
|
-
{
|
|
589
|
-
|
|
590
|
-
source: generateInterfaceFromObject(entityParameters, parameters, true)
|
|
591
|
-
}
|
|
664
|
+
...(path.parameters?.length ? [{ name: parametersTypeName, source: parametersTypeSource }] : []),
|
|
665
|
+
{ name: filterTypeName, source: filterTypeSource }
|
|
592
666
|
]
|
|
593
667
|
};
|
|
594
668
|
};
|
|
@@ -603,46 +677,40 @@ const generateBodyType = (body) => {
|
|
|
603
677
|
types.push(convertToTypeScriptType(schema));
|
|
604
678
|
}
|
|
605
679
|
}
|
|
606
|
-
return types.length ? createTupleType(types) : undefined;
|
|
680
|
+
return types.length ? (types.length === 1 ? types[0] : createTupleType(types)) : undefined;
|
|
607
681
|
};
|
|
608
682
|
|
|
609
683
|
const generateRequestBodyType = ({ requestBody }) => {
|
|
610
684
|
return generateBodyType(requestBody) ?? createRawType('unknown');
|
|
611
685
|
};
|
|
612
686
|
|
|
613
|
-
const resolveBodyType = ({ responses }) => Object.entries(responses)
|
|
614
|
-
.filter(v => v[0].startsWith('2'))[0]?.[1];
|
|
687
|
+
const resolveBodyType = ({ responses }) => Object.entries(responses).filter((v) => v[0].startsWith('2'))[0]?.[1];
|
|
615
688
|
const generateResponseBodyType = (object) => generateBodyType(resolveBodyType(object)) ?? createRawType('void');
|
|
616
689
|
|
|
617
|
-
const functionName$4 = 'create';
|
|
618
690
|
const generateCreateEndpoint = ({ target, path, endpoint }) => {
|
|
619
|
-
const
|
|
620
|
-
const
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
returns: `_${functionName$4}(cfg, ${generateString(endpoint.path)}, data)`,
|
|
625
|
-
params: ['data']
|
|
626
|
-
});
|
|
627
|
-
const interfaceSource = generateArrowFunctionType({
|
|
628
|
-
type: interfaceName,
|
|
629
|
-
params: [`data: DeepPartial<${generateRequestBodyType(path).toString()}>`],
|
|
691
|
+
const functionName = 'create';
|
|
692
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
693
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
694
|
+
type: functionTypeName,
|
|
695
|
+
params: [`data: DeepPartial<${generateRequestBodyType(path).toString()}>`, 'requestOptions?: RequestOptions'],
|
|
630
696
|
returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
|
|
631
697
|
});
|
|
698
|
+
const functionSource = generateArrowFunction({
|
|
699
|
+
name: functionName,
|
|
700
|
+
signature: functionTypeName,
|
|
701
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, data, requestOptions)`,
|
|
702
|
+
params: ['data', 'requestOptions?: RequestOptions']
|
|
703
|
+
});
|
|
632
704
|
return {
|
|
633
|
-
entity,
|
|
634
|
-
name: functionName
|
|
635
|
-
type: { name:
|
|
636
|
-
func: { name: functionName
|
|
705
|
+
entity: pascalCase(endpoint.service),
|
|
706
|
+
name: functionName,
|
|
707
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
708
|
+
func: { name: functionName, source: functionSource }
|
|
637
709
|
};
|
|
638
710
|
};
|
|
639
711
|
|
|
640
712
|
const generateGenericFunctionName = (path, suffix = '', prefix = '') => {
|
|
641
|
-
return camelCase(`${prefix}_` +
|
|
642
|
-
path
|
|
643
|
-
.replace(/.*\//, '')
|
|
644
|
-
.replace(/\W+/, '_')
|
|
645
|
-
.replace(/[_]+/, '_') + `_${suffix}`);
|
|
713
|
+
return camelCase(`${prefix}_` + path.replace(/.*\//, '').replace(/\W+/, '_').replace(/[_]+/, '_') + `_${suffix}`);
|
|
646
714
|
};
|
|
647
715
|
|
|
648
716
|
const insertPathPlaceholder = (path, record) => {
|
|
@@ -650,76 +718,79 @@ const insertPathPlaceholder = (path, record) => {
|
|
|
650
718
|
};
|
|
651
719
|
|
|
652
720
|
const wrapBody = (type, target) => {
|
|
653
|
-
return type.toString() === 'binary' ?
|
|
654
|
-
createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') :
|
|
655
|
-
type; // node-fetch returns a Blob as well
|
|
721
|
+
return type.toString() === 'binary' ? createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') : type; // node-fetch returns a Blob as well
|
|
656
722
|
};
|
|
657
723
|
const generateGenericEndpoint = (suffix) => ({ target, method, path, endpoint }) => {
|
|
658
724
|
const functionName = generateGenericFunctionName(endpoint.path, suffix, method);
|
|
659
|
-
const
|
|
660
|
-
const
|
|
661
|
-
const entityQuery = `${interfaceName}_Query`;
|
|
725
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
726
|
+
const entityQuery = `${functionTypeName}_Query`;
|
|
662
727
|
const hasId = endpoint.path.includes('{id}');
|
|
663
728
|
const params = createObjectType({
|
|
664
729
|
params: convertToTypeScriptType(convertParametersToSchema(path.parameters)),
|
|
665
730
|
body: method === 'get' ? undefined : wrapBody(generateRequestBodyType(path), target)
|
|
666
731
|
});
|
|
667
732
|
const responseBody = generateResponseBodyType(path);
|
|
668
|
-
const
|
|
733
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
734
|
+
type: functionTypeName,
|
|
735
|
+
params: [
|
|
736
|
+
...(hasId ? ['id: string'] : []),
|
|
737
|
+
`query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`,
|
|
738
|
+
'requestOptions?: RequestOptions'
|
|
739
|
+
],
|
|
740
|
+
returns: `${resolveResponseType(target)}<${wrapBody(responseBody, target).toString('force')}>`
|
|
741
|
+
});
|
|
669
742
|
const functionSource = generateArrowFunction({
|
|
670
743
|
name: functionName,
|
|
671
|
-
signature:
|
|
672
|
-
params: hasId ? ['id', 'query'] : ['query'],
|
|
673
|
-
returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${
|
|
674
|
-
});
|
|
675
|
-
const interfaceSource = generateArrowFunctionType({
|
|
676
|
-
type: interfaceName,
|
|
677
|
-
params: [...(hasId ? ['id: string'] : []), `query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`],
|
|
678
|
-
returns: `${resolveResponseType(target)}<${wrapBody(responseBody, target).toString()}>`
|
|
744
|
+
signature: functionTypeName,
|
|
745
|
+
params: hasId ? ['id', 'query', 'requestOptions?: RequestOptions'] : ['query', 'requestOptions?: RequestOptions'],
|
|
746
|
+
returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${String(responseBody.toString() === 'binary')}, requestOptions)`
|
|
679
747
|
});
|
|
680
748
|
return {
|
|
681
|
-
entity,
|
|
749
|
+
entity: pascalCase(endpoint.service),
|
|
682
750
|
name: functionName,
|
|
683
|
-
type: { name:
|
|
751
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
684
752
|
func: { name: functionName, source: functionSource },
|
|
685
753
|
interfaces: [
|
|
686
754
|
{
|
|
687
755
|
name: entityQuery,
|
|
688
|
-
source: generateInterfaceFromObject(entityQuery, params,
|
|
756
|
+
source: generateInterfaceFromObject(entityQuery, params, 'propagate')
|
|
689
757
|
}
|
|
690
758
|
]
|
|
691
759
|
};
|
|
692
760
|
};
|
|
693
761
|
|
|
694
|
-
const functionName$3 = 'remove';
|
|
695
762
|
const generateRemoveEndpoint = ({ target, endpoint }) => {
|
|
696
|
-
const
|
|
697
|
-
const
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
returns: `_${functionName$3}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options)`,
|
|
702
|
-
params: ['id', 'options?: RemoveQuery']
|
|
703
|
-
});
|
|
704
|
-
const interfaceSource = generateArrowFunctionType({
|
|
705
|
-
type: interfaceName,
|
|
706
|
-
params: ['id: string', 'options?: RemoveQuery'],
|
|
763
|
+
const functionName = 'remove';
|
|
764
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
765
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
766
|
+
type: functionTypeName,
|
|
767
|
+
params: ['id: string', 'options?: RemoveQuery', 'requestOptions?: RequestOptions'],
|
|
707
768
|
returns: `${resolveResponseType(target)}<void>`
|
|
708
769
|
});
|
|
770
|
+
const functionSource = generateArrowFunction({
|
|
771
|
+
name: functionName,
|
|
772
|
+
signature: functionTypeName,
|
|
773
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options, requestOptions)`,
|
|
774
|
+
params: ['id', 'options?: RemoveQuery', 'requestOptions?: RequestOptions']
|
|
775
|
+
});
|
|
709
776
|
return {
|
|
710
|
-
entity,
|
|
711
|
-
name: functionName
|
|
712
|
-
type: { name:
|
|
713
|
-
func: { name: functionName
|
|
777
|
+
entity: pascalCase(endpoint.service),
|
|
778
|
+
name: functionName,
|
|
779
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
780
|
+
func: { name: functionName, source: functionSource }
|
|
714
781
|
};
|
|
715
782
|
};
|
|
716
783
|
|
|
717
|
-
const functionName$2 = 'some';
|
|
718
784
|
const excludedParameters = [
|
|
719
|
-
'page',
|
|
720
|
-
'
|
|
785
|
+
'page',
|
|
786
|
+
'pageSize',
|
|
787
|
+
'sort',
|
|
788
|
+
'serializeNulls',
|
|
789
|
+
'properties',
|
|
790
|
+
'includeReferencedEntities',
|
|
791
|
+
'additionalProperties'
|
|
721
792
|
];
|
|
722
|
-
const
|
|
793
|
+
const resolveAdditionalPropertiesSchema = (path) => {
|
|
723
794
|
const body = resolveBodyType(path);
|
|
724
795
|
if (isResponseObject(body)) {
|
|
725
796
|
const schema = body?.content?.['application/json']?.schema;
|
|
@@ -732,115 +803,166 @@ const resolveAdditionalProperties = (path) => {
|
|
|
732
803
|
}
|
|
733
804
|
return undefined;
|
|
734
805
|
};
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
806
|
+
const resolveReferences = (entity, entities) => {
|
|
807
|
+
const references = [];
|
|
808
|
+
const generatedEntity = entities.get(entity);
|
|
809
|
+
if (generatedEntity) {
|
|
810
|
+
for (const [property, propertyMetaData] of generatedEntity.properties) {
|
|
811
|
+
if (propertyMetaData.service) {
|
|
812
|
+
references.push({
|
|
813
|
+
name: property,
|
|
814
|
+
type: generateString(propertyMetaData.service),
|
|
815
|
+
required: true
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (generatedEntity.parentName) {
|
|
820
|
+
references.push(...resolveReferences(generatedEntity.parentName, entities));
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return references;
|
|
824
|
+
};
|
|
825
|
+
const resolveReferencedEntities = (entity, entities) => {
|
|
826
|
+
const referencedEntities = [];
|
|
827
|
+
const generatedEntity = entities.get(entity);
|
|
828
|
+
if (generatedEntity) {
|
|
829
|
+
for (const [, propertyMetaData] of generatedEntity.properties) {
|
|
830
|
+
if (propertyMetaData.entity && propertyMetaData.service) {
|
|
831
|
+
referencedEntities.push({
|
|
832
|
+
name: propertyMetaData.service,
|
|
833
|
+
type: `${pascalCase(propertyMetaData.entity)}[]`,
|
|
834
|
+
required: true
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (generatedEntity.parentName) {
|
|
839
|
+
referencedEntities.push(...resolveReferencedEntities(generatedEntity.parentName, entities));
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return referencedEntities;
|
|
843
|
+
};
|
|
844
|
+
const generateSomeEndpoint = ({ endpoint, target, path, entities, aliases }) => {
|
|
845
|
+
const functionName = 'some';
|
|
846
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
847
|
+
const entity = aliases.get(endpoint.service) ?? pascalCase(endpoint.service);
|
|
848
|
+
const parametersTypeName = `${functionTypeName}_Parameters`;
|
|
849
|
+
const parameters = path.parameters?.filter((v) => (isParameterObject(v) ? !excludedParameters.includes(v.name) : false)) ?? [];
|
|
850
|
+
const parametersType = createObjectType({
|
|
851
|
+
params: convertToTypeScriptType(convertParametersToSchema(parameters))
|
|
753
852
|
});
|
|
754
|
-
const
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
853
|
+
const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
|
|
854
|
+
const filterTypeName = `${functionTypeName}_Filter`;
|
|
855
|
+
const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${entity}_${FILTER_PROPS_SUFFIX}`]);
|
|
856
|
+
const referencesTypeName = `${functionTypeName}_References`;
|
|
857
|
+
const referencesTypeSource = generateInterfaceType(referencesTypeName, resolveReferences(endpoint.service, entities));
|
|
858
|
+
const additionalPropertyTypeName = `${functionTypeName}_AdditionalProperty`;
|
|
859
|
+
const additionalPropertyTypeSource = generateType(additionalPropertyTypeName, 'string');
|
|
860
|
+
const queryTypeName = `${functionTypeName}_Query`;
|
|
861
|
+
const queryTypeSource = generateType(queryTypeName, `SomeQuery<${entity}, ${filterTypeName}, ${referencesTypeName}, ${additionalPropertyTypeName}> & ${parametersTypeName}`);
|
|
862
|
+
const referencedEntitiesTypeName = `${functionTypeName}_ReferencedEntities`;
|
|
863
|
+
const referencedEntitiesTypeSource = generateInterfaceType(referencedEntitiesTypeName, resolveReferencedEntities(endpoint.service, entities));
|
|
864
|
+
const additionalPropertiesTypeName = `${functionTypeName}_AdditionalProperties`;
|
|
865
|
+
const additionalPropertiesSchema = resolveAdditionalPropertiesSchema(path);
|
|
866
|
+
const additionalPropertiesTypeSource = generateType(additionalPropertiesTypeName, additionalPropertiesSchema ? convertToTypeScriptType(additionalPropertiesSchema).toString() : '{}');
|
|
867
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
868
|
+
type: functionTypeName,
|
|
869
|
+
params: [`query${parametersType.isFullyOptional() ? '?' : ''}: ${queryTypeName}, requestOptions?: RequestOptions`],
|
|
870
|
+
returns: `${resolveResponseType(target)}<SomeQueryReturn<${entity}, ${referencedEntitiesTypeName}, ${additionalPropertiesTypeName}>>`
|
|
763
871
|
});
|
|
764
872
|
const functionSource = generateArrowFunction({
|
|
765
|
-
name: functionName
|
|
766
|
-
signature:
|
|
767
|
-
returns: `_${functionName
|
|
768
|
-
params: ['query']
|
|
873
|
+
name: functionName,
|
|
874
|
+
signature: functionTypeName,
|
|
875
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
|
|
876
|
+
params: ['query', 'requestOptions?: RequestOptions']
|
|
769
877
|
});
|
|
770
878
|
return {
|
|
771
879
|
entity,
|
|
772
|
-
name: functionName
|
|
773
|
-
type: { name:
|
|
774
|
-
func: { name: functionName
|
|
880
|
+
name: functionName,
|
|
881
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
882
|
+
func: { name: functionName, source: functionSource },
|
|
775
883
|
interfaces: [
|
|
776
|
-
{
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
884
|
+
{ name: parametersTypeName, source: parametersTypeSource },
|
|
885
|
+
{ name: filterTypeName, source: filterTypeSource },
|
|
886
|
+
{ name: referencesTypeName, source: referencesTypeSource },
|
|
887
|
+
{ name: additionalPropertyTypeName, source: additionalPropertyTypeSource },
|
|
888
|
+
{ name: queryTypeName, source: queryTypeSource },
|
|
889
|
+
{ name: referencedEntitiesTypeName, source: referencedEntitiesTypeSource },
|
|
890
|
+
{ name: additionalPropertiesTypeName, source: additionalPropertiesTypeSource }
|
|
780
891
|
]
|
|
781
892
|
};
|
|
782
893
|
};
|
|
783
894
|
|
|
784
|
-
const functionName$1 = 'unique';
|
|
785
895
|
const generateUniqueEndpoint = ({ target, path, endpoint }) => {
|
|
786
|
-
const
|
|
787
|
-
const
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
params: ['id', 'query'],
|
|
792
|
-
returns: `_${functionName$1}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query)`
|
|
793
|
-
});
|
|
794
|
-
const interfaceSource = generateArrowFunctionType({
|
|
795
|
-
type: interfaceName,
|
|
796
|
-
params: ['id: string', 'query?: Q'],
|
|
896
|
+
const functionName = 'unique';
|
|
897
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
898
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
899
|
+
type: functionTypeName,
|
|
900
|
+
params: ['id: string', 'query?: Q', 'requestOptions?: RequestOptions'],
|
|
797
901
|
generics: ['Q extends UniqueQuery'],
|
|
798
902
|
returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
|
|
799
903
|
});
|
|
904
|
+
const functionSource = generateArrowFunction({
|
|
905
|
+
name: functionName,
|
|
906
|
+
signature: functionTypeName,
|
|
907
|
+
params: ['id', 'query', 'requestOptions?: RequestOptions'],
|
|
908
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, requestOptions)`
|
|
909
|
+
});
|
|
800
910
|
return {
|
|
801
|
-
entity,
|
|
802
|
-
name: functionName
|
|
803
|
-
type: { name:
|
|
804
|
-
func: { name: functionName
|
|
911
|
+
entity: pascalCase(endpoint.service),
|
|
912
|
+
name: functionName,
|
|
913
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
914
|
+
func: { name: functionName, source: functionSource }
|
|
805
915
|
};
|
|
806
916
|
};
|
|
807
917
|
|
|
808
|
-
const functionName = 'update';
|
|
809
918
|
const generateUpdateEndpoint = ({ target, path, endpoint }) => {
|
|
810
|
-
const
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
type:
|
|
814
|
-
params: [
|
|
919
|
+
const functionName = 'update';
|
|
920
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
921
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
922
|
+
type: functionTypeName,
|
|
923
|
+
params: [
|
|
924
|
+
'id: string',
|
|
925
|
+
`data: DeepPartial<${generateRequestBodyType(path).toString()}>`,
|
|
926
|
+
'options?: UpdateQuery',
|
|
927
|
+
'requestOptions?: RequestOptions'
|
|
928
|
+
],
|
|
815
929
|
returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
|
|
816
930
|
});
|
|
817
931
|
const functionSource = generateArrowFunction({
|
|
818
932
|
name: functionName,
|
|
819
|
-
signature:
|
|
820
|
-
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options)`,
|
|
821
|
-
params: ['id', 'data', 'options']
|
|
933
|
+
signature: functionTypeName,
|
|
934
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options, requestOptions)`,
|
|
935
|
+
params: ['id', 'data', 'options', 'requestOptions?: RequestOptions']
|
|
822
936
|
});
|
|
823
937
|
return {
|
|
824
|
-
entity,
|
|
938
|
+
entity: pascalCase(endpoint.service),
|
|
825
939
|
name: functionName,
|
|
826
|
-
type: { name:
|
|
940
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
827
941
|
func: { name: functionName, source: functionSource }
|
|
828
942
|
};
|
|
829
943
|
};
|
|
830
944
|
|
|
831
|
-
const
|
|
945
|
+
const isMultiPartUploadPath = (path) => {
|
|
946
|
+
const [, entity, ...rest] = path.split('/');
|
|
947
|
+
return entity && rest.length === 2 && rest[1] === 'multipartUpload';
|
|
948
|
+
};
|
|
949
|
+
const parseEndpointsAndGroupByEntity = (paths) => {
|
|
832
950
|
const endpoints = new Map();
|
|
833
951
|
for (const [rawPath, path] of Object.entries(paths)) {
|
|
834
952
|
const endpoint = parseEndpointPath(rawPath);
|
|
835
953
|
if (!endpoint || !path) {
|
|
954
|
+
// Todo: Should be removed if sdk supports multi part upload.
|
|
955
|
+
if (isMultiPartUploadPath(rawPath)) {
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
836
958
|
logger.errorLn(`Failed to parse ${rawPath}`);
|
|
837
959
|
continue;
|
|
838
960
|
}
|
|
839
|
-
if (endpoints.has(endpoint.
|
|
840
|
-
endpoints.get(endpoint.
|
|
961
|
+
if (endpoints.has(endpoint.service)) {
|
|
962
|
+
endpoints.get(endpoint.service)?.push({ endpoint, path });
|
|
841
963
|
}
|
|
842
964
|
else {
|
|
843
|
-
endpoints.set(endpoint.
|
|
965
|
+
endpoints.set(endpoint.service, [{ endpoint, path }]);
|
|
844
966
|
}
|
|
845
967
|
}
|
|
846
968
|
return endpoints;
|
|
@@ -873,26 +995,35 @@ const generators = {
|
|
|
873
995
|
post: generateGenericEndpoint()
|
|
874
996
|
}
|
|
875
997
|
};
|
|
876
|
-
const generateServices = (
|
|
998
|
+
const generateServices = (paths, entities, aliases, options) => {
|
|
877
999
|
const services = new Map();
|
|
878
|
-
const
|
|
879
|
-
for (const [
|
|
880
|
-
const
|
|
881
|
-
const serviceTypeName = pascalCase(`${
|
|
882
|
-
// Service functions
|
|
1000
|
+
const endpoints = parseEndpointsAndGroupByEntity(paths);
|
|
1001
|
+
for (const [serviceName, paths] of endpoints) {
|
|
1002
|
+
const serviceFnName = camelCase(`${serviceName}Service`);
|
|
1003
|
+
const serviceTypeName = pascalCase(`${serviceName}Service`);
|
|
883
1004
|
const functions = [];
|
|
884
1005
|
for (const { path, endpoint } of paths) {
|
|
885
|
-
const
|
|
1006
|
+
const generator = generators[endpoint.type];
|
|
886
1007
|
for (const [method, config] of Object.entries(path)) {
|
|
887
|
-
if (method === 'get' && endpoint.type === WeclappEndpointType.ENTITY && !options.generateUnique)
|
|
1008
|
+
if ((method === 'get' && endpoint.type === WeclappEndpointType.ENTITY && !options.generateUnique) ||
|
|
1009
|
+
(method === 'post' && (endpoint.type === WeclappEndpointType.COUNT || endpoint.path.endsWith('query')))) {
|
|
1010
|
+
// Skip unique endpoints if generateUnique option is not set or if POST is used for filter queries
|
|
888
1011
|
continue;
|
|
889
1012
|
}
|
|
890
|
-
|
|
1013
|
+
const generatorFn = generator[method];
|
|
1014
|
+
if (generatorFn) {
|
|
891
1015
|
const path = config;
|
|
892
1016
|
const target = options.target;
|
|
893
1017
|
if (!path.deprecated || options.deprecated) {
|
|
894
1018
|
functions.push({
|
|
895
|
-
...
|
|
1019
|
+
...generatorFn({
|
|
1020
|
+
endpoint,
|
|
1021
|
+
method,
|
|
1022
|
+
target,
|
|
1023
|
+
path,
|
|
1024
|
+
entities,
|
|
1025
|
+
aliases
|
|
1026
|
+
}),
|
|
896
1027
|
path
|
|
897
1028
|
});
|
|
898
1029
|
}
|
|
@@ -905,26 +1036,28 @@ const generateServices = (doc, aliases, options) => {
|
|
|
905
1036
|
if (!functions.length) {
|
|
906
1037
|
continue;
|
|
907
1038
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
...functions.map(v => ({
|
|
1039
|
+
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, [
|
|
1040
|
+
...functions.map((v) => ({
|
|
911
1041
|
required: true,
|
|
912
1042
|
comment: v.path.deprecated ? '@deprecated' : undefined,
|
|
913
1043
|
name: v.func.name,
|
|
914
1044
|
type: v.type.name
|
|
915
1045
|
}))
|
|
916
|
-
]));
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1046
|
+
])));
|
|
1047
|
+
const serviceFn = `export const ${serviceFnName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${generateBlockStatements(...functions.map((v) => v.func.source), `return {${concat(functions.map((v) => v.func.name))}};`)};`;
|
|
1048
|
+
services.set(serviceName, {
|
|
1049
|
+
name: serviceName,
|
|
1050
|
+
serviceFnName,
|
|
1051
|
+
serviceTypeName,
|
|
1052
|
+
functions,
|
|
1053
|
+
source: generateStatements(serviceTypes, serviceFn),
|
|
1054
|
+
deprecated: functions.every((v) => v.path.deprecated)
|
|
1055
|
+
});
|
|
923
1056
|
}
|
|
924
1057
|
return services;
|
|
925
1058
|
};
|
|
926
1059
|
|
|
927
|
-
const
|
|
1060
|
+
const generateCustomValueServices = (entities, services) => {
|
|
928
1061
|
const customValueEntity = entities.get('customValue');
|
|
929
1062
|
const customValueEntities = [];
|
|
930
1063
|
if (!customValueEntity) {
|
|
@@ -932,7 +1065,7 @@ const generateCustomValueUtilities = (entities, services) => {
|
|
|
932
1065
|
return '';
|
|
933
1066
|
}
|
|
934
1067
|
serviceLoop: for (const service of services) {
|
|
935
|
-
const someFunction = service.functions.find(v => v.name === 'some');
|
|
1068
|
+
const someFunction = service.functions.find((v) => v.name === 'some');
|
|
936
1069
|
if (!someFunction) {
|
|
937
1070
|
continue;
|
|
938
1071
|
}
|
|
@@ -945,55 +1078,57 @@ const generateCustomValueUtilities = (entities, services) => {
|
|
|
945
1078
|
continue serviceLoop;
|
|
946
1079
|
}
|
|
947
1080
|
}
|
|
948
|
-
customValueEntities.push(service.
|
|
1081
|
+
customValueEntities.push(service.name);
|
|
949
1082
|
}
|
|
950
|
-
return
|
|
1083
|
+
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);')}`);
|
|
951
1084
|
};
|
|
952
1085
|
|
|
953
1086
|
const generateObject = (properties) => {
|
|
954
1087
|
const body = [];
|
|
955
|
-
for (const { key, value } of properties) {
|
|
1088
|
+
for (const { key, value, comment } of properties) {
|
|
956
1089
|
if (value === undefined) {
|
|
957
1090
|
continue;
|
|
958
1091
|
}
|
|
959
1092
|
if (Array.isArray(value)) {
|
|
960
1093
|
const str = generateObject(value);
|
|
961
1094
|
if (str.length > 2) {
|
|
962
|
-
body.push(`${key}: ${str}`);
|
|
1095
|
+
body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${str}`);
|
|
963
1096
|
}
|
|
964
1097
|
}
|
|
965
1098
|
else {
|
|
966
|
-
body.push(`${key}: ${String(value)}`);
|
|
1099
|
+
body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${String(value)}`);
|
|
967
1100
|
}
|
|
968
1101
|
}
|
|
969
1102
|
return body.length ? `{\n${indent(body.join(',\n'))}\n}` : `{}`;
|
|
970
1103
|
};
|
|
971
1104
|
|
|
972
1105
|
const resolveInheritedEntities = (root, entities) => {
|
|
973
|
-
const parent = root.
|
|
1106
|
+
const parent = root.parentName ? entities.get(root.parentName) : undefined;
|
|
974
1107
|
return parent ? [parent, ...resolveInheritedEntities(parent, entities)] : [];
|
|
975
1108
|
};
|
|
976
|
-
const generatePropertyDescriptors = (entity, entities, services, options) => [
|
|
977
|
-
|
|
978
|
-
...entity.properties
|
|
979
|
-
].filter(([, meta]) => {
|
|
1109
|
+
const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
|
|
1110
|
+
.filter(([, meta]) => {
|
|
980
1111
|
// If we generate deprecated things we can skip the filtering
|
|
981
1112
|
if (options.deprecated) {
|
|
982
1113
|
return true;
|
|
983
1114
|
}
|
|
984
1115
|
// Check if corresponding service is deprecated and can be removed
|
|
985
|
-
const service = services.find(v => v.
|
|
1116
|
+
const service = services.find((v) => v.name === meta.service);
|
|
986
1117
|
return !meta.service || (service && !service.deprecated);
|
|
987
|
-
})
|
|
1118
|
+
})
|
|
1119
|
+
.map(([property, meta]) => ({
|
|
988
1120
|
key: property,
|
|
989
1121
|
value: Object.entries(meta).map(([key, value]) => ({
|
|
990
1122
|
key,
|
|
991
|
-
value: value ? generateString(value) : undefined
|
|
1123
|
+
value: value !== undefined ? (typeof value === 'number' ? value : generateString(value)) : undefined
|
|
992
1124
|
}))
|
|
993
1125
|
}));
|
|
994
|
-
const
|
|
1126
|
+
const generateEntityProperties = (entities, aliases, services, options) => {
|
|
995
1127
|
const typeName = 'WEntityProperties';
|
|
996
|
-
const propertyMap = [
|
|
1128
|
+
const propertyMap = [
|
|
1129
|
+
...entities.entries(),
|
|
1130
|
+
...[...aliases.entries()].map(([service, type]) => [service, entities.get(camelCase(type))])
|
|
1131
|
+
].map(([entity, data]) => ({
|
|
997
1132
|
key: entity,
|
|
998
1133
|
value: generatePropertyDescriptors(data, entities, services, options)
|
|
999
1134
|
}));
|
|
@@ -1001,7 +1136,7 @@ const generateEntityPropertyMap = (entities, services, options) => {
|
|
|
1001
1136
|
};
|
|
1002
1137
|
|
|
1003
1138
|
const generateArray = (values) => {
|
|
1004
|
-
return `[${concat(values.map(v => generateString(String(v))))}]`;
|
|
1139
|
+
return `[${concat(values.map((v) => generateString(String(v))))}]`;
|
|
1005
1140
|
};
|
|
1006
1141
|
|
|
1007
1142
|
// Only functions matching this regex are included in the generation.
|
|
@@ -1014,16 +1149,17 @@ const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
|
|
|
1014
1149
|
*/
|
|
1015
1150
|
const generateGroupedServices = (services) => {
|
|
1016
1151
|
const entityDescriptors = new Map();
|
|
1017
|
-
for (const
|
|
1018
|
-
for (const
|
|
1019
|
-
if (!FILTER_REGEX.test(name)) {
|
|
1152
|
+
for (const service of services) {
|
|
1153
|
+
for (const fn of service.functions) {
|
|
1154
|
+
if (!FILTER_REGEX.test(fn.name)) {
|
|
1020
1155
|
continue;
|
|
1021
1156
|
}
|
|
1022
|
-
entityDescriptors.set(name, [
|
|
1023
|
-
...(entityDescriptors.get(name) ?? []),
|
|
1024
|
-
|
|
1157
|
+
entityDescriptors.set(fn.name, [
|
|
1158
|
+
...(entityDescriptors.get(fn.name) ?? []),
|
|
1159
|
+
{
|
|
1160
|
+
name: service.name,
|
|
1025
1161
|
required: true,
|
|
1026
|
-
type: `${pascalCase(
|
|
1162
|
+
type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}`
|
|
1027
1163
|
}
|
|
1028
1164
|
]);
|
|
1029
1165
|
}
|
|
@@ -1036,123 +1172,86 @@ const generateGroupedServices = (services) => {
|
|
|
1036
1172
|
const guard = `(service: string | undefined): service is ${service} =>\n${indent(`${constant}.includes(service as ${service});`)}`;
|
|
1037
1173
|
typeGuards.push(`export const is${service} = ${guard}`);
|
|
1038
1174
|
}
|
|
1039
|
-
return [
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
const value = generateArray(props.map(v => v.name));
|
|
1046
|
-
return `export const ${constant}: ${type}[] = ${value};`;
|
|
1047
|
-
}),
|
|
1048
|
-
generateBlockComment('Type guards for service classes.', generateStatements(...typeGuards))
|
|
1049
|
-
];
|
|
1175
|
+
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]) => {
|
|
1176
|
+
const constant = camelCase(`wServiceWith_${name}_Names`);
|
|
1177
|
+
const type = pascalCase(`WServiceWith_${name}`);
|
|
1178
|
+
const value = generateArray(props.map((v) => v.name));
|
|
1179
|
+
return `export const ${constant}: ${type}[] = ${value};`;
|
|
1180
|
+
}), ...typeGuards);
|
|
1050
1181
|
};
|
|
1051
1182
|
|
|
1052
|
-
const
|
|
1053
|
-
const
|
|
1054
|
-
const
|
|
1055
|
-
const
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
name: entity,
|
|
1067
|
-
type: pascalCase(entity),
|
|
1068
|
-
required: true
|
|
1069
|
-
})),
|
|
1070
|
-
...services.map(service => {
|
|
1071
|
-
const alias = aliases.get(service.entity);
|
|
1072
|
-
return {
|
|
1073
|
-
name: service.entity,
|
|
1074
|
-
type: alias ?? 'never',
|
|
1075
|
-
required: true,
|
|
1076
|
-
comment: alias ? undefined : 'no response defined or inlined'
|
|
1077
|
-
};
|
|
1078
|
-
})
|
|
1079
|
-
];
|
|
1080
|
-
const createMappingType = (type, prefix) => type !== 'never' ? `${type}_${prefix}` : type;
|
|
1081
|
-
const entitiesList = generateInterface('WEntities', entityInterfaces);
|
|
1082
|
-
const entityReferences = generateInterface('WEntityReferences', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'References') })));
|
|
1083
|
-
const entityMappings = generateInterface('WEntityMappings', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'Mappings') })));
|
|
1084
|
-
const entityFilter = generateInterface('WEntityFilters', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'Filter') })));
|
|
1183
|
+
const generateMaps = (enums, entities, services, aliases, options) => {
|
|
1184
|
+
const enumInstances = `export const wEnums = ${generateObject([...enums.keys()].map((v) => ({ key: v, value: v })))};`;
|
|
1185
|
+
const entityNames = `export const wEntityNames: WEntity[] = ${generateArray([...entities.keys()])};`;
|
|
1186
|
+
const generatedServices = [...services.values()];
|
|
1187
|
+
const serviceInstances = `export const wServices = ${generateObject(generatedServices.map((v) => ({
|
|
1188
|
+
key: v.name,
|
|
1189
|
+
value: `${v.serviceFnName}()`,
|
|
1190
|
+
comment: v.deprecated ? '@deprecated' : undefined
|
|
1191
|
+
})))};`;
|
|
1192
|
+
const serviceFactories = `export const wServiceFactories = ${generateObject(generatedServices.map((v) => ({
|
|
1193
|
+
key: v.name,
|
|
1194
|
+
value: v.serviceFnName,
|
|
1195
|
+
comment: v.deprecated ? '@deprecated' : undefined
|
|
1196
|
+
})))};`;
|
|
1085
1197
|
return {
|
|
1086
1198
|
source: generateStatements(
|
|
1087
|
-
/*
|
|
1088
|
-
|
|
1089
|
-
/*
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
Where [serviceName] is the name of an endpoint (e.g. for /article its 'article')
|
|
1102
|
-
|
|
1103
|
-
Where [entityInterfaceName] is
|
|
1104
|
-
- the underlying type for this entity
|
|
1105
|
-
- the type for what is returned by the api
|
|
1106
|
-
`, entitiesList),
|
|
1107
|
-
/* type-ofs and types */
|
|
1108
|
-
generateType('WServices', 'typeof wServices'), generateType('WServiceFactories', 'typeof wServiceFactories'), generateType('WService', 'keyof WServices'), generateType('WEntity', 'keyof WEntities'), generateType('WEnums', 'typeof wEnums'), generateType('WEnum', 'keyof WEnums'),
|
|
1109
|
-
/* Utilities. */
|
|
1110
|
-
generateCustomValueUtilities(entities, services),
|
|
1111
|
-
/* All functions grouped by service supporting it */
|
|
1112
|
-
...generateGroupedServices(services))
|
|
1199
|
+
/* Enums */
|
|
1200
|
+
generateInterface('WEnums', [...enums.keys()].map((name) => ({ name, type: name, required: true }))), generateType('WEnum', 'keyof WEnums'), enumInstances,
|
|
1201
|
+
/* Entities */
|
|
1202
|
+
generateInterface('WEntities', [
|
|
1203
|
+
...[...entities.keys()].map((name) => ({ name, type: loosePascalCase(name), required: true })),
|
|
1204
|
+
...[...aliases.entries()].map(([name, type]) => ({ name, type, required: true }))
|
|
1205
|
+
].sort((a, b) => (a.name > b.name ? 1 : -1))), generateType('WEntity', 'keyof WEntities'), entityNames,
|
|
1206
|
+
/* Services */
|
|
1207
|
+
serviceInstances, generateType('WServices', 'typeof wServices'), generateType('WService', 'keyof WServices'), serviceFactories, generateType('WServiceFactories', 'typeof wServiceFactories'),
|
|
1208
|
+
/* Service Utils */
|
|
1209
|
+
generateGroupedServices(generatedServices), generateCustomValueServices(entities, generatedServices),
|
|
1210
|
+
/* Entity Properties (Runtime Meta Infos) */
|
|
1211
|
+
generateEntityProperties(entities, aliases, generatedServices, options))
|
|
1113
1212
|
};
|
|
1114
1213
|
};
|
|
1115
1214
|
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
const extractSchemas = (doc) => {
|
|
1119
|
-
const schemas = new Map();
|
|
1215
|
+
const parseReferencedEntityType = (obj) => pascalCase(obj.$ref.replace(/.*\//, ''));
|
|
1216
|
+
const extractServiceAliases = (doc, schemas) => {
|
|
1120
1217
|
const aliases = new Map();
|
|
1121
|
-
for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
|
|
1122
|
-
if (!isReferenceObject(schema)) {
|
|
1123
|
-
schemas.set(name, schema);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
/**
|
|
1127
|
-
* Referenced schemas in responses, in some case the response from the root endpoint
|
|
1128
|
-
* refers to a schema with a different name
|
|
1129
|
-
*/
|
|
1130
1218
|
for (const [path, methods] of Object.entries(doc.paths)) {
|
|
1131
1219
|
const parsed = parseEndpointPath(path);
|
|
1132
|
-
if (!parsed || schemas.has(parsed.
|
|
1220
|
+
if (!parsed || !methods || parsed.type !== WeclappEndpointType.ROOT || schemas.has(parsed.service)) {
|
|
1133
1221
|
continue;
|
|
1134
1222
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1223
|
+
const body = methods[OpenAPIV3.HttpMethods.GET]?.responses['200'];
|
|
1224
|
+
if (isResponseObject(body) && body.content?.['application/json']) {
|
|
1225
|
+
const responseSchema = body.content['application/json'].schema;
|
|
1226
|
+
if (!responseSchema || isReferenceObject(responseSchema)) {
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
const resultSchema = responseSchema.properties?.result;
|
|
1230
|
+
if (!resultSchema) {
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
if (isReferenceObject(resultSchema)) {
|
|
1234
|
+
aliases.set(parsed.service, parseReferencedEntityType(resultSchema));
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
if (isArraySchemaObject(resultSchema)) {
|
|
1238
|
+
const resultItemSchema = resultSchema.items;
|
|
1239
|
+
if (isReferenceObject(resultItemSchema)) {
|
|
1240
|
+
aliases.set(parsed.service, parseReferencedEntityType(resultItemSchema));
|
|
1152
1241
|
}
|
|
1153
1242
|
}
|
|
1154
1243
|
}
|
|
1155
1244
|
}
|
|
1245
|
+
return aliases;
|
|
1246
|
+
};
|
|
1247
|
+
const extractSchemas = (doc) => {
|
|
1248
|
+
const schemas = new Map();
|
|
1249
|
+
for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
|
|
1250
|
+
if (!isReferenceObject(schema)) {
|
|
1251
|
+
schemas.set(name, schema);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
const aliases = extractServiceAliases(doc, schemas);
|
|
1156
1255
|
return { schemas, aliases };
|
|
1157
1256
|
};
|
|
1158
1257
|
|
|
@@ -1160,14 +1259,10 @@ const generate = (doc, options) => {
|
|
|
1160
1259
|
const { schemas, aliases } = extractSchemas(doc);
|
|
1161
1260
|
const enums = generateEnums(schemas);
|
|
1162
1261
|
const entities = generateEntities(schemas, enums);
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
options,
|
|
1168
|
-
entities,
|
|
1169
|
-
aliases
|
|
1170
|
-
}).source));
|
|
1262
|
+
const entityFilterProps = generateEntityFilterProps(entities, enums);
|
|
1263
|
+
const services = generateServices(doc.paths, entities, aliases, options);
|
|
1264
|
+
const maps = generateMaps(enums, entities, services, aliases, options);
|
|
1265
|
+
return generateStatements(generateBase(options.target, doc.info.version, options), generateBlockComment('ENUMS', generateStatements(...[...enums.values()].map((v) => v.source))), generateBlockComment('ENTITY FILTER PROPS', generateStatements(...[...entityFilterProps.values()].map((v) => v.source))), generateBlockComment('ENTITIES', generateStatements(...[...entities.values()].map((v) => v.source))), generateBlockComment('SERVICES', generateStatements(...[...services.values()].map((v) => v.source))), generateBlockComment('MAPS', generateStatements(maps.source)));
|
|
1171
1266
|
};
|
|
1172
1267
|
|
|
1173
1268
|
const hash = (content, algorithm = 'sha256') => {
|
|
@@ -1183,12 +1278,13 @@ const hash = (content, algorithm = 'sha256') => {
|
|
|
1183
1278
|
|
|
1184
1279
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
1185
1280
|
const cli = async () => {
|
|
1186
|
-
const
|
|
1281
|
+
const version = pkg.version;
|
|
1187
1282
|
const { argv } = yargs(hideBin(process.argv))
|
|
1188
1283
|
.scriptName('build-weclapp-sdk')
|
|
1189
1284
|
.usage('Usage: $0 <source> [flags]')
|
|
1190
1285
|
.version(version)
|
|
1191
1286
|
.example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
|
|
1287
|
+
.example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
|
|
1192
1288
|
.example('$0 xxx.weclapp.com --key ...', 'Generate the SDK based on the openapi file from the given weclapp instance')
|
|
1193
1289
|
.help('h')
|
|
1194
1290
|
.alias('v', 'version')
|
|
@@ -1228,36 +1324,47 @@ const cli = async () => {
|
|
|
1228
1324
|
type: 'string',
|
|
1229
1325
|
choices: ['browser', 'browser.rx', 'node', 'node.rx']
|
|
1230
1326
|
})
|
|
1231
|
-
.option('
|
|
1232
|
-
|
|
1233
|
-
describe: 'Include deprecated functions and services',
|
|
1327
|
+
.option('use-query-language', {
|
|
1328
|
+
describe: 'Generate the new where property for some and count queries',
|
|
1234
1329
|
type: 'boolean'
|
|
1330
|
+
})
|
|
1331
|
+
.option('apiVersion', {
|
|
1332
|
+
describe: 'Specify the api version (only needed when not using a local file)',
|
|
1333
|
+
type: 'string'
|
|
1235
1334
|
})
|
|
1236
1335
|
.epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
|
|
1237
1336
|
if (argv.fromEnv) {
|
|
1238
1337
|
config();
|
|
1239
1338
|
}
|
|
1240
1339
|
const { WECLAPP_API_KEY, WECLAPP_BACKEND_URL } = process.env;
|
|
1241
|
-
const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, _: [src = WECLAPP_BACKEND_URL] } = argv;
|
|
1340
|
+
const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, apiVersion, _: [src = WECLAPP_BACKEND_URL] } = argv;
|
|
1242
1341
|
const options = {
|
|
1243
1342
|
deprecated,
|
|
1244
1343
|
generateUnique: argv.generateUnique ?? false,
|
|
1245
|
-
target: argv.target ?? Target.BROWSER_PROMISES
|
|
1344
|
+
target: argv.target ?? Target.BROWSER_PROMISES,
|
|
1345
|
+
useQueryLanguage: argv.useQueryLanguage ?? false
|
|
1246
1346
|
};
|
|
1247
|
-
if (typeof src === 'number') {
|
|
1248
|
-
return Promise.reject('Expected string as command');
|
|
1347
|
+
if (!src || typeof src === 'number') {
|
|
1348
|
+
return Promise.reject(new Error('Expected string as command'));
|
|
1249
1349
|
}
|
|
1250
1350
|
if (!Object.values(Target).includes(options.target)) {
|
|
1251
1351
|
logger.errorLn(`Unknown target: ${options.target}. Possible values are ${Object.values(Target).join(', ')}`);
|
|
1252
|
-
return Promise.reject();
|
|
1352
|
+
return Promise.reject(new Error());
|
|
1253
1353
|
}
|
|
1254
1354
|
if (await stat(src).catch(() => false)) {
|
|
1255
|
-
logger.infoLn(`Source is a file
|
|
1355
|
+
logger.infoLn(`Source is a file`);
|
|
1256
1356
|
const content = JSON.parse(await readFile(src, 'utf-8'));
|
|
1257
1357
|
return { cache, content, options };
|
|
1258
1358
|
}
|
|
1359
|
+
logger.infoLn(`Source is a URL`);
|
|
1360
|
+
if (!key) {
|
|
1361
|
+
return Promise.reject(new Error('API key is missing'));
|
|
1362
|
+
}
|
|
1363
|
+
if (!apiVersion) {
|
|
1364
|
+
return Promise.reject(new Error('API version is missing'));
|
|
1365
|
+
}
|
|
1259
1366
|
const url = new URL(src.startsWith('http') ? src : `https://${src}`);
|
|
1260
|
-
url.pathname =
|
|
1367
|
+
url.pathname = `/webapp/api/${apiVersion}/meta/openapi.json`;
|
|
1261
1368
|
if (query?.length) {
|
|
1262
1369
|
for (const param of query.split(',')) {
|
|
1263
1370
|
const [name, value] = param.split('=');
|
|
@@ -1265,11 +1372,11 @@ const cli = async () => {
|
|
|
1265
1372
|
}
|
|
1266
1373
|
}
|
|
1267
1374
|
const content = await fetch(url.toString(), {
|
|
1268
|
-
headers: {
|
|
1269
|
-
}).then(res => res.ok ? res.json() : undefined);
|
|
1375
|
+
headers: { Accept: 'application/json', AuthenticationToken: key }
|
|
1376
|
+
}).then((res) => (res.ok ? res.json() : undefined));
|
|
1270
1377
|
if (!content) {
|
|
1271
1378
|
logger.errorLn(`Couldn't fetch file ${url.toString()} `);
|
|
1272
|
-
return Promise.reject();
|
|
1379
|
+
return Promise.reject(new Error());
|
|
1273
1380
|
}
|
|
1274
1381
|
else {
|
|
1275
1382
|
logger.infoLn(`Use remote file: ${url.toString()}`);
|
|
@@ -1277,50 +1384,59 @@ const cli = async () => {
|
|
|
1277
1384
|
return { cache, content, options };
|
|
1278
1385
|
};
|
|
1279
1386
|
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1387
|
+
const workingDir = resolve(currentDirname(), './sdk');
|
|
1388
|
+
const cacheDir = resolve(currentDirname(), './.cache');
|
|
1282
1389
|
void (async () => {
|
|
1283
1390
|
const start = process.hrtime.bigint();
|
|
1284
|
-
const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
|
|
1285
1391
|
const { content: doc, cache: useCache, options } = await cli();
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
const dist = (...paths) => resolve(workingDirectory, ...paths);
|
|
1290
|
-
const tmp = async (...paths) => {
|
|
1291
|
-
const fullPath = resolve(cacheDir, ...paths);
|
|
1292
|
-
await mkdir(dirname(fullPath), { recursive: true }).catch(() => null);
|
|
1392
|
+
const workingDirPath = async (...paths) => {
|
|
1393
|
+
const fullPath = resolve(workingDir, ...paths);
|
|
1394
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
1293
1395
|
return fullPath;
|
|
1294
1396
|
};
|
|
1397
|
+
// Resolve cache dir and key
|
|
1398
|
+
const cacheKey = hash([pkg.version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
|
|
1399
|
+
const cachedSdkDir = resolve(cacheDir, cacheKey);
|
|
1400
|
+
// Remove old SDK
|
|
1401
|
+
await rm(workingDir, { recursive: true, force: true });
|
|
1295
1402
|
if (useCache) {
|
|
1296
1403
|
logger.infoLn(`Cache ID: ${cacheKey}`);
|
|
1297
1404
|
}
|
|
1298
|
-
if (useCache && await stat(
|
|
1299
|
-
|
|
1405
|
+
if (useCache && (await stat(cachedSdkDir).catch(() => false))) {
|
|
1406
|
+
// Copy cached SDK to working dir
|
|
1407
|
+
logger.successLn(`Cache match! (${cachedSdkDir})`);
|
|
1408
|
+
await cp(cachedSdkDir, workingDir, { recursive: true });
|
|
1300
1409
|
}
|
|
1301
1410
|
else {
|
|
1302
|
-
//
|
|
1303
|
-
await writeFile(await
|
|
1411
|
+
// Write openapi.json file
|
|
1412
|
+
await writeFile(await workingDirPath('openapi.json'), JSON.stringify(doc, null, 2));
|
|
1304
1413
|
logger.infoLn(`Generate sdk (target: ${options.target})`);
|
|
1305
|
-
// Generate
|
|
1414
|
+
// Generate and write SDK (index.ts)
|
|
1306
1415
|
const sdk = generate(doc, options);
|
|
1307
|
-
await writeFile(await
|
|
1308
|
-
// Bundle
|
|
1416
|
+
await writeFile(await workingDirPath('src', 'index.ts'), sdk.trim() + '\n');
|
|
1417
|
+
// Bundle and write SDK
|
|
1309
1418
|
logger.infoLn('Bundle... (this may take some time)');
|
|
1310
|
-
await bundle(
|
|
1311
|
-
// Remove
|
|
1312
|
-
await
|
|
1419
|
+
await bundle(workingDir, options.target);
|
|
1420
|
+
// Remove index.ts (only bundle is required)
|
|
1421
|
+
await rm(await workingDirPath('src'), { recursive: true, force: true });
|
|
1422
|
+
if (useCache) {
|
|
1423
|
+
// Copy SDK to cache
|
|
1424
|
+
logger.successLn(`Caching SDK: (${cachedSdkDir})`);
|
|
1425
|
+
await mkdir(cachedSdkDir, { recursive: true });
|
|
1426
|
+
await cp(workingDir, cachedSdkDir, { recursive: true });
|
|
1427
|
+
}
|
|
1313
1428
|
}
|
|
1314
|
-
// Copy bundled SDK
|
|
1315
|
-
await cp(cacheDir, workingDirectory, { recursive: true });
|
|
1316
1429
|
// Print job summary
|
|
1317
1430
|
const duration = (process.hrtime.bigint() - start) / 1000000n;
|
|
1318
1431
|
logger.successLn(`SDK built in ${prettyMs(Number(duration))}`);
|
|
1319
1432
|
logger.printSummary();
|
|
1320
|
-
})()
|
|
1433
|
+
})()
|
|
1434
|
+
.catch((error) => {
|
|
1321
1435
|
logger.errorLn(`Fatal error:`);
|
|
1322
1436
|
/* eslint-disable no-console */
|
|
1323
1437
|
console.error(error);
|
|
1324
|
-
})
|
|
1325
|
-
|
|
1438
|
+
})
|
|
1439
|
+
.finally(() => {
|
|
1440
|
+
if (logger.errors)
|
|
1441
|
+
process.exit(1);
|
|
1326
1442
|
});
|