@weclapp/sdk 2.0.0-dev.5 → 2.0.0-dev.51
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 +787 -619
- package/package.json +47 -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
7
|
import { snakeCase, pascalCase, camelCase } 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";
|
|
148
140
|
|
|
149
|
-
var
|
|
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";
|
|
146
|
+
|
|
147
|
+
var types = "export type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>;\n }\n : T;\n\nexport type Sort<T> = {\n [K in keyof T]?: {\n [V in keyof T]?: V extends K\n ? T[V] extends Array<infer U> | undefined\n ? U extends object\n ? Sort<U>\n : never\n : T[V] extends object | undefined\n ? Sort<T[V]>\n : 'asc' | 'desc'\n : never;\n };\n}[keyof T];\n\nexport type CustomAttributeFilter = {\n [K in number]:| string | number | boolean | { id: string } | { entityName: string; entityId: string };\n};\n\nexport type QuerySelect<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? QuerySelect<U> | boolean\n : T[P] extends Record<any, any> | undefined\n ? QuerySelect<T[P]> | boolean\n : boolean;\n};\n\nexport type Select<T, Q extends QuerySelect<T> | undefined> =\n Q extends QuerySelect<T>\n ? {\n // Filter out excluded properties beforehand\n [P in keyof T as Q[P] extends boolean ? P : Q[P] extends object ? P : never]: // Property\n Q[P] extends true\n ? T[P]\n : // Array\n T[P] extends Array<infer U>\n ? Select<U, Q[P] & QuerySelect<any>>[]\n : // Object\n T[P] extends Record<any, any>\n ? Select<T[P], Q[P] & QuerySelect<any>>\n : never;\n }\n : undefined;\n\nexport type MapKeys<T, S extends Record<keyof T, string>> = {\n [K in keyof T as S[K]]: T[K];\n};\n\nexport type ValueOf<T> = T[keyof T];\n\nexport type Pagination = {\n page: number;\n pageSize: number;\n};\n\nexport type UniqueQuery = {\n serializeNulls?: boolean;\n};\n\nexport type SomeQueryReturn<E, R, P> = {\n entities: E[];\n references?: R;\n properties?: P[];\n};\n\nexport type GenericQuery<P, B> = {\n params?: P;\n body?: B;\n};\n\nexport type UpdateQuery = {\n ignoreMissingProperties?: boolean;\n dryRun?: boolean;\n};\n\nexport type RemoveQuery = {\n dryRun?: boolean;\n};\n\nexport type WEntityPropertyMeta =\n | {\n type: 'string';\n format?: 'decimal' | 'html' | 'email' | 'password';\n maxLength?: number;\n pattern?: string;\n entity?: WEntity;\n service?: WService;\n }\n | {\n type: 'integer';\n format: 'int32' | 'int64' | 'duration' | 'date' | 'timestamp';\n }\n | { type: 'array'; format: 'reference'; entity: WEntity; service?: WService }\n | { type: 'array'; format: 'reference'; enum: WEnum }\n | { type: 'array'; format: 'string' }\n | { type: 'number'; format: 'double' }\n | { type: 'reference'; entity: WEntity }\n | { type: 'reference'; enum: WEnum }\n | { type: 'boolean' }\n | { type: 'object' };\n";
|
|
148
|
+
|
|
149
|
+
var utils = "const flattenSelect = (obj: Select<any, any> = {}): string[] => {\n const entries: string[] = [];\n\n for (const [prop, value] of Object.entries(obj)) {\n if (typeof value === 'object' && value) {\n entries.push(...flattenSelect(value).map((v) => `${prop}.${v}`));\n } else if (value) {\n entries.push(prop);\n }\n }\n\n return entries;\n};\n\nexport const flattenSort = (obj: Sort<any>[] = []): { sort?: string } => {\n const flatten = (obj: Sort<any>, base = ''): string | undefined => {\n const [key, value] = Object.entries(obj ?? {})[0] ?? [];\n\n if (key && value) {\n const path = base + key;\n\n if (typeof value === 'object') {\n return flatten(value, path ? `${path}.` : '');\n } else if (['asc', 'desc'].includes(value)) {\n return `${value === 'desc' ? '-' : ''}${path}`;\n }\n }\n\n return undefined;\n };\n\n const sorts = obj.map((v) => flatten(v)).filter(Boolean);\n return sorts.length ? { sort: sorts.join(',') } : {};\n};\n";
|
|
150
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 = (apiVersion, { target, useQueryLanguage, generateUnique }) => {
|
|
161
|
+
return generateStatements(resolveImports(target), `const apiVersion = ${apiVersion}`, resolveMappings(target), resolveBinaryClass(target), globalConfig, types, utils, root, useQueryLanguage ? queriesWithQueryLanguage : queriesWithFilter, 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
|
|
|
@@ -191,22 +191,17 @@ const isArraySchemaObject = (v) => {
|
|
|
191
191
|
const isResponseObject = (v) => {
|
|
192
192
|
return isObject(v) && typeof v.description === 'string';
|
|
193
193
|
};
|
|
194
|
-
const isNonArraySchemaObject = (v) => {
|
|
195
|
-
return isObject(v) && ['string', 'undefined'].includes(typeof v.type);
|
|
196
|
-
};
|
|
197
|
-
const isRelatedEntitySchema = (v) => {
|
|
198
|
-
return isObject(v) && isNonArraySchemaObject(v) && 'x-weclapp' in v && isObject(v['x-weclapp']);
|
|
199
|
-
};
|
|
200
194
|
|
|
201
|
-
const generateEnums = (
|
|
195
|
+
const generateEnums = (context) => {
|
|
202
196
|
const enums = new Map();
|
|
203
|
-
for (const [
|
|
197
|
+
for (const [schemaName, schema] of context.schemas) {
|
|
204
198
|
if (isEnumSchemaObject(schema)) {
|
|
205
|
-
const
|
|
206
|
-
if (!enums.has(
|
|
207
|
-
enums.set(
|
|
199
|
+
const enumName = loosePascalCase(schemaName);
|
|
200
|
+
if (!enums.has(enumName)) {
|
|
201
|
+
enums.set(enumName, {
|
|
202
|
+
name: enumName,
|
|
208
203
|
properties: schema.enum,
|
|
209
|
-
source: generateEnum(
|
|
204
|
+
source: generateEnum(enumName, schema.enum)
|
|
210
205
|
});
|
|
211
206
|
}
|
|
212
207
|
}
|
|
@@ -218,16 +213,30 @@ const concat = (strings, separator = ', ', maxLength = 80) => {
|
|
|
218
213
|
const joined = strings.join(separator);
|
|
219
214
|
if (joined.length > maxLength) {
|
|
220
215
|
const length = strings.length - 1;
|
|
221
|
-
return `\n${indent(strings
|
|
222
|
-
.map((value, index) => index === length ? value : `${(value + separator).trim()}\n`)
|
|
223
|
-
.join(''))}\n`;
|
|
216
|
+
return `\n${indent(strings.map((value, index) => (index === length ? value : `${(value + separator).trim()}\n`)).join(''))}\n`;
|
|
224
217
|
}
|
|
225
218
|
else {
|
|
226
219
|
return joined;
|
|
227
220
|
}
|
|
228
221
|
};
|
|
229
222
|
|
|
230
|
-
|
|
223
|
+
const convertParametersToSchemaObject = (parameters) => {
|
|
224
|
+
const properties = [];
|
|
225
|
+
const required = [];
|
|
226
|
+
for (const param of parameters) {
|
|
227
|
+
if (param.in === 'query' && param.schema) {
|
|
228
|
+
properties.push([param.name, param.schema]);
|
|
229
|
+
if (param.required)
|
|
230
|
+
required.push(param.name);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
type: 'object',
|
|
235
|
+
properties: Object.fromEntries(properties),
|
|
236
|
+
required
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
|
|
231
240
|
const createReferenceType = (value) => ({
|
|
232
241
|
type: 'reference',
|
|
233
242
|
toString: () => loosePascalCase(value)
|
|
@@ -242,22 +251,25 @@ const createArrayType = (value) => ({
|
|
|
242
251
|
});
|
|
243
252
|
const createTupleType = (value) => ({
|
|
244
253
|
type: 'tuple',
|
|
245
|
-
toString: () => concat([...new Set(value.map(v => typeof v === 'string' ? `'${v}'` : v.toString()))], ' | ')
|
|
254
|
+
toString: () => concat([...new Set(value.map((v) => (typeof v === 'string' ? `'${v}'` : v.toString())))], ' | ')
|
|
246
255
|
});
|
|
247
256
|
const createObjectType = (value, required = []) => ({
|
|
248
257
|
type: 'object',
|
|
249
258
|
isFullyOptional: () => {
|
|
250
|
-
return !required.length &&
|
|
251
|
-
.
|
|
252
|
-
|
|
259
|
+
return (!required.length &&
|
|
260
|
+
Object.values(value)
|
|
261
|
+
.filter((v) => v?.type === 'object')
|
|
262
|
+
.every((v) => v.isFullyOptional()));
|
|
253
263
|
},
|
|
254
|
-
toString: (
|
|
264
|
+
toString: (propertyPropagationOption = 'ignore') => {
|
|
255
265
|
const properties = Object.entries(value)
|
|
256
|
-
.filter(v => v[1])
|
|
257
|
-
.map(v => {
|
|
266
|
+
.filter((v) => v[1])
|
|
267
|
+
.map((v) => {
|
|
258
268
|
const name = v[0];
|
|
259
269
|
const value = v[1];
|
|
260
|
-
const isRequired = required.includes(name) ||
|
|
270
|
+
const isRequired = required.includes(name) ||
|
|
271
|
+
propertyPropagationOption === 'force' ||
|
|
272
|
+
(value.type === 'object' && !value.isFullyOptional() && propertyPropagationOption === 'propagate');
|
|
261
273
|
return `${name + (isRequired ? '' : '?')}: ${value.toString()};`;
|
|
262
274
|
});
|
|
263
275
|
return properties.length ? `{\n${indent(properties.join('\n'))}\n}` : '{}';
|
|
@@ -266,8 +278,11 @@ const createObjectType = (value, required = []) => ({
|
|
|
266
278
|
const getRefName = (obj) => {
|
|
267
279
|
return obj.$ref.replace(/.*\//, '');
|
|
268
280
|
};
|
|
269
|
-
const convertToTypeScriptType = (schema
|
|
270
|
-
if (
|
|
281
|
+
const convertToTypeScriptType = (schema) => {
|
|
282
|
+
if (Array.isArray(schema)) {
|
|
283
|
+
return convertToTypeScriptType(convertParametersToSchemaObject(schema));
|
|
284
|
+
}
|
|
285
|
+
else if (isReferenceObject(schema)) {
|
|
271
286
|
return createReferenceType(getRefName(schema));
|
|
272
287
|
}
|
|
273
288
|
else {
|
|
@@ -277,7 +292,7 @@ const convertToTypeScriptType = (schema, property) => {
|
|
|
277
292
|
return createRawType('number');
|
|
278
293
|
case 'string':
|
|
279
294
|
if (schema.enum) {
|
|
280
|
-
return
|
|
295
|
+
return createTupleType(schema.enum);
|
|
281
296
|
}
|
|
282
297
|
else {
|
|
283
298
|
return schema.format === 'binary' ? createRawType('binary') : createRawType('string');
|
|
@@ -286,46 +301,53 @@ const convertToTypeScriptType = (schema, property) => {
|
|
|
286
301
|
return createRawType('boolean');
|
|
287
302
|
case 'object': {
|
|
288
303
|
const { properties = {}, required = [] } = schema;
|
|
289
|
-
return createObjectType(Object.fromEntries(Object.entries(properties)
|
|
290
|
-
.map(v => [v[0], convertToTypeScriptType(v[1])])), required);
|
|
304
|
+
return createObjectType(Object.fromEntries(Object.entries(properties).map(([prop, propSchema]) => [prop, convertToTypeScriptType(propSchema)])), required);
|
|
291
305
|
}
|
|
292
306
|
case 'array':
|
|
293
|
-
return createArrayType(convertToTypeScriptType(schema.items
|
|
307
|
+
return createArrayType(convertToTypeScriptType(schema.items));
|
|
294
308
|
default:
|
|
295
309
|
return createRawType('unknown');
|
|
296
310
|
}
|
|
297
311
|
}
|
|
298
312
|
};
|
|
299
313
|
|
|
300
|
-
const
|
|
314
|
+
const setReferenceMeta = (prop, metaData, context) => {
|
|
301
315
|
const referenceName = getRefName(prop);
|
|
302
|
-
const
|
|
303
|
-
if (
|
|
304
|
-
|
|
316
|
+
const referenceSchema = context.schemas.get(referenceName);
|
|
317
|
+
if (isEnumSchemaObject(referenceSchema)) {
|
|
318
|
+
metaData.enum = loosePascalCase(referenceName);
|
|
305
319
|
}
|
|
306
320
|
else {
|
|
307
|
-
|
|
321
|
+
metaData.entity = referenceName;
|
|
308
322
|
}
|
|
309
323
|
};
|
|
310
|
-
const extractPropertyMetaData = (
|
|
311
|
-
const
|
|
324
|
+
const extractPropertyMetaData = (prop, context) => {
|
|
325
|
+
const metaData = {};
|
|
326
|
+
const weclappExtension = prop['x-weclapp'];
|
|
327
|
+
if (weclappExtension) {
|
|
328
|
+
metaData.service = weclappExtension.service;
|
|
329
|
+
metaData.entity = weclappExtension.entity;
|
|
330
|
+
}
|
|
312
331
|
if (isReferenceObject(prop)) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
332
|
+
metaData.type = 'reference';
|
|
333
|
+
setReferenceMeta(prop, metaData, context);
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
metaData.type = prop.type;
|
|
337
|
+
metaData.format = prop.format;
|
|
338
|
+
metaData.maxLength = prop.maxLength;
|
|
339
|
+
metaData.pattern = prop.pattern;
|
|
340
|
+
if (isArraySchemaObject(prop)) {
|
|
341
|
+
if (isReferenceObject(prop.items)) {
|
|
342
|
+
metaData.format = 'reference';
|
|
343
|
+
setReferenceMeta(prop.items, metaData, context);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
metaData.format = 'string';
|
|
347
|
+
}
|
|
326
348
|
}
|
|
327
349
|
}
|
|
328
|
-
return
|
|
350
|
+
return metaData;
|
|
329
351
|
};
|
|
330
352
|
|
|
331
353
|
const generateInlineComment = (comment) => `/** ${comment} */`;
|
|
@@ -335,12 +357,12 @@ const generateType = (name, value) => {
|
|
|
335
357
|
return `export type ${name} = ${value.trim()};`;
|
|
336
358
|
};
|
|
337
359
|
|
|
338
|
-
const arrayify = (v) => Array.isArray(v) ? v : [v];
|
|
360
|
+
const arrayify = (v) => (Array.isArray(v) ? v : [v]);
|
|
339
361
|
|
|
340
362
|
const generateInterfaceProperties = (entries) => {
|
|
341
363
|
const properties = entries
|
|
342
|
-
.filter(v => v.type !== undefined)
|
|
343
|
-
.filter((value, index, array) => array.findIndex(v => v.name === value.name) === index)
|
|
364
|
+
.filter((v) => v.type !== undefined)
|
|
365
|
+
.filter((value, index, array) => array.findIndex((v) => v.name === value.name) === index)
|
|
344
366
|
.map(({ name, type, required, readonly, comment }) => {
|
|
345
367
|
const cmd = comment ? `${generateInlineComment(comment)}\n` : '';
|
|
346
368
|
const req = required ? '' : '?';
|
|
@@ -350,7 +372,7 @@ const generateInterfaceProperties = (entries) => {
|
|
|
350
372
|
.join('\n');
|
|
351
373
|
return properties.length ? `{\n${indent(properties)}\n}` : `{}`;
|
|
352
374
|
};
|
|
353
|
-
const generateInterfaceFromObject = (name, obj,
|
|
375
|
+
const generateInterfaceFromObject = (name, obj, propertyPropagationOption) => `export interface ${name} ${obj.toString(propertyPropagationOption)}`;
|
|
354
376
|
const generateInterface = (name, entries, extend) => {
|
|
355
377
|
const signature = `${name} ${extend ? `extends ${arrayify(extend).join(', ')}` : ''}`.trim();
|
|
356
378
|
const body = generateInterfaceProperties(entries);
|
|
@@ -359,69 +381,17 @@ const generateInterface = (name, entries, extend) => {
|
|
|
359
381
|
const generateInterfaceType = (name, entries, extend) => {
|
|
360
382
|
const body = generateInterfaceProperties(entries);
|
|
361
383
|
const bases = extend ? arrayify(extend).join(' & ') : undefined;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const generateEntities = (schemas, enums) => {
|
|
366
|
-
const entities = new Map();
|
|
367
|
-
for (const [schemaName, schema] of schemas) {
|
|
368
|
-
// Enums are generated separately
|
|
369
|
-
if (isEnumSchemaObject(schema)) {
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
const entity = pascalCase(schemaName);
|
|
373
|
-
// Entity and filter
|
|
374
|
-
const entityInterface = [];
|
|
375
|
-
const filterInterface = [];
|
|
376
|
-
// Referenced entities and property-to-referenced-entity mapping
|
|
377
|
-
const referenceInterface = [];
|
|
378
|
-
const referenceMappingsInterface = [];
|
|
379
|
-
const properties = new Map();
|
|
380
|
-
// The parent entity
|
|
381
|
-
let extend = undefined;
|
|
382
|
-
const processProperties = (props = {}) => {
|
|
383
|
-
for (const [name, property] of Object.entries(props)) {
|
|
384
|
-
const meta = isRelatedEntitySchema(property) ? property['x-weclapp'] : {};
|
|
385
|
-
if (meta.entity) {
|
|
386
|
-
const type = `${pascalCase(meta.entity)}[]`;
|
|
387
|
-
referenceInterface.push({ name, type, required: true });
|
|
388
|
-
filterInterface.push({ name: meta.entity, type, required: true });
|
|
389
|
-
}
|
|
390
|
-
if (meta.service) {
|
|
391
|
-
referenceMappingsInterface.push({ name, type: generateString(meta.service), required: true });
|
|
392
|
-
}
|
|
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
|
-
}
|
|
405
|
-
};
|
|
406
|
-
if (schema.allOf?.length) {
|
|
407
|
-
for (const item of schema.allOf) {
|
|
408
|
-
if (isReferenceObject(item)) {
|
|
409
|
-
extend = convertToTypeScriptType(item).toString();
|
|
410
|
-
}
|
|
411
|
-
else if (isObjectSchemaObject(item)) {
|
|
412
|
-
processProperties(item.properties);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
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
|
-
entities.set(schemaName, {
|
|
419
|
-
extends: extend ? camelCase(extend) : extend,
|
|
420
|
-
properties,
|
|
421
|
-
source
|
|
422
|
-
});
|
|
384
|
+
let typeDefinition = '';
|
|
385
|
+
if (bases) {
|
|
386
|
+
typeDefinition = bases;
|
|
423
387
|
}
|
|
424
|
-
|
|
388
|
+
else {
|
|
389
|
+
typeDefinition = body;
|
|
390
|
+
}
|
|
391
|
+
if (bases && body !== '{}') {
|
|
392
|
+
typeDefinition += ` & ${body}`;
|
|
393
|
+
}
|
|
394
|
+
return generateType(name, typeDefinition);
|
|
425
395
|
};
|
|
426
396
|
|
|
427
397
|
/**
|
|
@@ -429,13 +399,10 @@ const generateEntities = (schemas, enums) => {
|
|
|
429
399
|
* @param s String to pluralize.
|
|
430
400
|
*/
|
|
431
401
|
const pluralize = (s) => {
|
|
432
|
-
return s.endsWith('s') ? s :
|
|
433
|
-
s.endsWith('y') ? `${s.slice(0, -1)}ies` :
|
|
434
|
-
`${s}s`;
|
|
402
|
+
return s.endsWith('s') ? s : s.endsWith('y') ? `${s.slice(0, -1)}ies` : `${s}s`;
|
|
435
403
|
};
|
|
436
404
|
|
|
437
|
-
|
|
438
|
-
const logger = new class {
|
|
405
|
+
const logger = new (class {
|
|
439
406
|
active = true;
|
|
440
407
|
warnings = 0;
|
|
441
408
|
errors = 0;
|
|
@@ -483,21 +450,126 @@ const logger = new class {
|
|
|
483
450
|
printSummary() {
|
|
484
451
|
const format = (v, name, fail, ok) => {
|
|
485
452
|
const color = v ? fail : ok;
|
|
486
|
-
return v === 0
|
|
487
|
-
|
|
453
|
+
return v === 0
|
|
454
|
+
? `${color('zero')} ${pluralize(name)}`
|
|
455
|
+
: v === 1
|
|
456
|
+
? `${color('one')} ${name}`
|
|
457
|
+
: `${color(v)} ${pluralize(name)}`;
|
|
488
458
|
};
|
|
489
459
|
const warnings = format(this.warnings, 'warning', chalk.yellowBright, chalk.greenBright);
|
|
490
460
|
const errors = format(this.errors, 'error', chalk.redBright, chalk.greenBright);
|
|
491
461
|
const info = `Finished with ${warnings} and ${errors}.`;
|
|
492
462
|
this[this.errors ? 'errorLn' : this.warnings ? 'warnLn' : 'successLn'](info);
|
|
493
463
|
}
|
|
464
|
+
})();
|
|
465
|
+
|
|
466
|
+
const FILTER_PROPS_SUFFIX = 'Filter_Props';
|
|
467
|
+
const generateEntities = (context) => {
|
|
468
|
+
const entities = new Map();
|
|
469
|
+
for (const [schemaName, schema] of context.schemas) {
|
|
470
|
+
// Enums are generated separately
|
|
471
|
+
if (isEnumSchemaObject(schema)) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const entityName = schemaName;
|
|
475
|
+
const entityInterfaceName = loosePascalCase(entityName);
|
|
476
|
+
const entityInterfaceProperties = [];
|
|
477
|
+
const properties = new Map();
|
|
478
|
+
let parentEntityName = undefined;
|
|
479
|
+
let parentEntityInterfaceName = undefined;
|
|
480
|
+
const entityFilterInterfaceName = `${entityInterfaceName}_${FILTER_PROPS_SUFFIX}`;
|
|
481
|
+
const entityFilterInterfaceProperties = [];
|
|
482
|
+
let parentEntityFilterInterfaceName = undefined;
|
|
483
|
+
const processProperties = (props = {}) => {
|
|
484
|
+
for (const [propertyName, propertySchema] of Object.entries(props)) {
|
|
485
|
+
const weclappExtension = propertySchema['x-weclapp'];
|
|
486
|
+
properties.set(propertyName, extractPropertyMetaData(propertySchema, context));
|
|
487
|
+
const type = convertToTypeScriptType(propertySchema).toString();
|
|
488
|
+
// cast to SchemaObject to access deprecated and readOnly properties (ReferenceObject can also include these props in OpenAPI 3.1)
|
|
489
|
+
const castedSchema = propertySchema;
|
|
490
|
+
const comment = castedSchema.deprecated
|
|
491
|
+
? '@deprecated will be removed.'
|
|
492
|
+
: castedSchema.format
|
|
493
|
+
? `format: ${castedSchema.format}`
|
|
494
|
+
: undefined;
|
|
495
|
+
if (weclappExtension?.filterable !== false) {
|
|
496
|
+
entityFilterInterfaceProperties.push({ name: propertyName, type, comment });
|
|
497
|
+
}
|
|
498
|
+
entityInterfaceProperties.push({
|
|
499
|
+
name: propertyName,
|
|
500
|
+
type,
|
|
501
|
+
required: weclappExtension?.required,
|
|
502
|
+
readonly: castedSchema.readOnly,
|
|
503
|
+
comment
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
const processExtraFilterProperties = (props = {}) => {
|
|
508
|
+
for (const [propertyName, propertySchema] of Object.entries(props)) {
|
|
509
|
+
if (isReferenceObject(propertySchema))
|
|
510
|
+
continue;
|
|
511
|
+
const type = convertToTypeScriptType(propertySchema).toString();
|
|
512
|
+
const comment = propertySchema.deprecated
|
|
513
|
+
? '@deprecated will be removed.'
|
|
514
|
+
: propertySchema.format
|
|
515
|
+
? `format: ${propertySchema.format}`
|
|
516
|
+
: undefined;
|
|
517
|
+
entityFilterInterfaceProperties.push({ name: propertyName, type, comment });
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
const processFilterPaths = (filterPaths = {}) => {
|
|
521
|
+
for (const [filterProp, entityName] of Object.entries(filterPaths)) {
|
|
522
|
+
if (!filterProp.includes('.') && context.schemas.get(entityName)) {
|
|
523
|
+
entityFilterInterfaceProperties.push({
|
|
524
|
+
name: filterProp,
|
|
525
|
+
type: `${loosePascalCase(entityName)}_${FILTER_PROPS_SUFFIX}`
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
if (schema.allOf?.length) {
|
|
531
|
+
if (schema.allOf.length > 2) {
|
|
532
|
+
logger.errorLn(`Failed to process schema for ${schemaName}: invalid allOf length`);
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
for (const item of schema.allOf) {
|
|
536
|
+
if (isReferenceObject(item)) {
|
|
537
|
+
parentEntityName = getRefName(item);
|
|
538
|
+
parentEntityInterfaceName = createReferenceType(parentEntityName).toString();
|
|
539
|
+
parentEntityFilterInterfaceName = `${parentEntityInterfaceName}_${FILTER_PROPS_SUFFIX}`;
|
|
540
|
+
}
|
|
541
|
+
else if (item.type === 'object') {
|
|
542
|
+
processProperties(item.properties);
|
|
543
|
+
processExtraFilterProperties(item['x-weclapp-filterProperties']);
|
|
544
|
+
processFilterPaths(item['x-weclapp-filterPaths']);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
logger.errorLn(`Failed to process schema for ${schemaName}: invalid schema type in allOf`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
processProperties(schema.properties);
|
|
553
|
+
}
|
|
554
|
+
entities.set(entityName, {
|
|
555
|
+
name: entityName,
|
|
556
|
+
interfaceName: entityInterfaceName,
|
|
557
|
+
properties,
|
|
558
|
+
source: generateStatements(generateInterface(entityInterfaceName, entityInterfaceProperties, parentEntityInterfaceName)),
|
|
559
|
+
filterInterfaceName: entityFilterInterfaceName,
|
|
560
|
+
filterSource: generateStatements(generateInterface(entityFilterInterfaceName, entityFilterInterfaceProperties, parentEntityFilterInterfaceName)),
|
|
561
|
+
parentName: parentEntityName,
|
|
562
|
+
parentInterfaceName: parentEntityInterfaceName
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
return entities;
|
|
494
566
|
};
|
|
495
567
|
|
|
496
568
|
/**
|
|
497
|
-
* ROOT
|
|
498
|
-
* COUNT
|
|
499
|
-
* ENTITY
|
|
500
|
-
* SPECIAL_ROOT
|
|
569
|
+
* ROOT => /article
|
|
570
|
+
* COUNT => /article/count
|
|
571
|
+
* ENTITY => /article/{id}
|
|
572
|
+
* SPECIAL_ROOT => /article/generateImage
|
|
501
573
|
* SPECIAL_ENTITY => /article/id/{id}/generateImag
|
|
502
574
|
*/
|
|
503
575
|
var WeclappEndpointType;
|
|
@@ -509,26 +581,61 @@ var WeclappEndpointType;
|
|
|
509
581
|
WeclappEndpointType["GENERIC_ENTITY"] = "GENERIC_ENTITY";
|
|
510
582
|
})(WeclappEndpointType || (WeclappEndpointType = {}));
|
|
511
583
|
const parseEndpointPath = (path) => {
|
|
512
|
-
const [,
|
|
513
|
-
if (!
|
|
584
|
+
const [, service, ...rest] = path.split('/');
|
|
585
|
+
if (!service) {
|
|
514
586
|
return undefined;
|
|
515
587
|
}
|
|
516
588
|
if (!rest.length) {
|
|
517
|
-
return { path,
|
|
589
|
+
return { path, service, type: WeclappEndpointType.ROOT };
|
|
518
590
|
}
|
|
519
591
|
else if (rest[0] === 'count') {
|
|
520
|
-
return { path,
|
|
592
|
+
return { path, service, type: WeclappEndpointType.COUNT };
|
|
521
593
|
}
|
|
522
594
|
else if (rest[0] === 'id') {
|
|
523
|
-
return rest.length === 2
|
|
524
|
-
{ path,
|
|
525
|
-
|
|
595
|
+
return rest.length === 2
|
|
596
|
+
? { path, service, type: WeclappEndpointType.ENTITY }
|
|
597
|
+
: {
|
|
598
|
+
path,
|
|
599
|
+
service,
|
|
600
|
+
method: rest[2],
|
|
601
|
+
type: WeclappEndpointType.GENERIC_ENTITY
|
|
602
|
+
};
|
|
526
603
|
}
|
|
527
604
|
else if (rest.length === 1) {
|
|
528
|
-
return {
|
|
605
|
+
return {
|
|
606
|
+
path,
|
|
607
|
+
service,
|
|
608
|
+
method: rest[1],
|
|
609
|
+
type: WeclappEndpointType.GENERIC_ROOT
|
|
610
|
+
};
|
|
529
611
|
}
|
|
530
612
|
return undefined;
|
|
531
613
|
};
|
|
614
|
+
const isMultiPartUploadPath = (path) => {
|
|
615
|
+
const [, entity, ...rest] = path.split('/');
|
|
616
|
+
return entity && rest.length === 2 && rest[1] === 'multipartUpload';
|
|
617
|
+
};
|
|
618
|
+
const parseEndpointsPaths = (paths) => {
|
|
619
|
+
const endpoints = new Map();
|
|
620
|
+
for (const [rawPath, path] of Object.entries(paths)) {
|
|
621
|
+
const endpoint = parseEndpointPath(rawPath);
|
|
622
|
+
if (!endpoint || !path) {
|
|
623
|
+
// Todo: Should be removed if sdk supports multi part upload.
|
|
624
|
+
if (isMultiPartUploadPath(rawPath)) {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
logger.errorLn(`Failed to parse ${rawPath}`);
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (endpoints.has(endpoint.service)) {
|
|
631
|
+
endpoints.get(endpoint.service)?.push({ endpoint, path });
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
endpoints.set(endpoint.service, [{ endpoint, path }]);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return endpoints;
|
|
638
|
+
};
|
|
532
639
|
|
|
533
640
|
const generateArrowFunction = ({ name, signature, returns, params }) => {
|
|
534
641
|
return `const ${name}: ${signature} = (${params?.join(', ') ?? ''}) =>\n${indent(returns)};`;
|
|
@@ -540,109 +647,109 @@ const generateArrowFunctionType = ({ type, returns = 'void', generics, params })
|
|
|
540
647
|
return generateType(type, `${genericsString + paramsString} =>\n${indent(returns)}`);
|
|
541
648
|
};
|
|
542
649
|
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
param.required && required.push(param.name);
|
|
551
|
-
}
|
|
650
|
+
const resolveParameters = (resolvableParameters = [], parameters) => {
|
|
651
|
+
if (!resolvableParameters)
|
|
652
|
+
return [];
|
|
653
|
+
return resolvableParameters.flatMap((param) => {
|
|
654
|
+
if (isReferenceObject(param)) {
|
|
655
|
+
const resolved = parameters.get(getRefName(param));
|
|
656
|
+
return resolved ? [resolved] : [];
|
|
552
657
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
type: 'object', required,
|
|
556
|
-
properties: Object.fromEntries(properties)
|
|
557
|
-
};
|
|
658
|
+
return [param];
|
|
659
|
+
});
|
|
558
660
|
};
|
|
559
661
|
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
const
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const
|
|
569
|
-
|
|
662
|
+
const generateCountEndpoint = ({ endpoint, operationObject, entities, context, options }) => {
|
|
663
|
+
const functionName = 'count';
|
|
664
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
665
|
+
const relatedEntityName = context.aliases.get(endpoint.service);
|
|
666
|
+
const relatedEntity = !!relatedEntityName && entities.get(relatedEntityName);
|
|
667
|
+
if (!relatedEntity) {
|
|
668
|
+
throw Error(`Related entity schema for service ${endpoint.service} not found`);
|
|
669
|
+
}
|
|
670
|
+
const parametersTypeName = `${functionTypeName}_Parameters`;
|
|
671
|
+
const parametersType = createObjectType({
|
|
672
|
+
params: convertToTypeScriptType(resolveParameters(operationObject.parameters, context.parameters))
|
|
570
673
|
});
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
674
|
+
const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
|
|
675
|
+
const filterTypeName = `${functionTypeName}_Filter`;
|
|
676
|
+
const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${relatedEntity.filterInterfaceName}`]);
|
|
677
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
678
|
+
type: functionTypeName,
|
|
679
|
+
params: [
|
|
680
|
+
`query${parametersType.isFullyOptional() ? '?' : ''}: CountQuery<${filterTypeName}>${operationObject.parameters?.length ? ' & ' + parametersTypeName : ''}`,
|
|
681
|
+
'requestOptions?: RequestOptions'
|
|
682
|
+
],
|
|
683
|
+
returns: `${resolveResponseType(options.target)}<number>`
|
|
576
684
|
});
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
returns:
|
|
685
|
+
const functionSource = generateArrowFunction({
|
|
686
|
+
name: functionName,
|
|
687
|
+
signature: functionTypeName,
|
|
688
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
|
|
689
|
+
params: ['query', 'requestOptions?: RequestOptions']
|
|
581
690
|
});
|
|
582
691
|
return {
|
|
583
|
-
|
|
584
|
-
name:
|
|
585
|
-
|
|
586
|
-
func: { name: functionName$5, source: functionSource },
|
|
692
|
+
name: functionName,
|
|
693
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
694
|
+
func: { name: functionName, source: functionSource },
|
|
587
695
|
interfaces: [
|
|
588
|
-
{
|
|
589
|
-
|
|
590
|
-
source: generateInterfaceFromObject(entityParameters, parameters, true)
|
|
591
|
-
}
|
|
696
|
+
...(operationObject.parameters?.length ? [{ name: parametersTypeName, source: parametersTypeSource }] : []),
|
|
697
|
+
{ name: filterTypeName, source: filterTypeSource }
|
|
592
698
|
]
|
|
593
699
|
};
|
|
594
700
|
};
|
|
595
701
|
|
|
596
|
-
const
|
|
597
|
-
if (
|
|
598
|
-
return
|
|
599
|
-
}
|
|
702
|
+
const generateContentType = (body, fallback = 'unknown') => {
|
|
703
|
+
if (!body?.content)
|
|
704
|
+
return createRawType(fallback);
|
|
600
705
|
const types = [];
|
|
601
|
-
for (const { schema } of Object.values(body
|
|
706
|
+
for (const { schema } of Object.values(body.content)) {
|
|
602
707
|
if (schema) {
|
|
603
708
|
types.push(convertToTypeScriptType(schema));
|
|
604
709
|
}
|
|
605
710
|
}
|
|
606
|
-
return types.length ? createTupleType(types) :
|
|
711
|
+
return (types.length > 1 ? createTupleType(types) : types[0]) ?? createRawType(fallback);
|
|
607
712
|
};
|
|
608
713
|
|
|
609
|
-
const generateRequestBodyType = ({ requestBody }) => {
|
|
610
|
-
|
|
714
|
+
const generateRequestBodyType = ({ requestBody }, requestBodies) => {
|
|
715
|
+
const requestBodyObject = requestBody && isReferenceObject(requestBody) ? requestBodies.get(getRefName(requestBody)) : requestBody;
|
|
716
|
+
return generateContentType(requestBodyObject);
|
|
611
717
|
};
|
|
612
718
|
|
|
613
|
-
const
|
|
614
|
-
.filter(v => v[0].startsWith('2'))[0]?.[1];
|
|
615
|
-
const generateResponseBodyType = (object) => generateBodyType(resolveBodyType(object)) ?? createRawType('void');
|
|
719
|
+
const resolveResponsesObject = (responses) => Object.entries(responses).find(([statusCode]) => statusCode.startsWith('2'))?.[1];
|
|
616
720
|
|
|
617
|
-
const
|
|
618
|
-
const
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
721
|
+
const generateResponseType = (operationObject, responses) => {
|
|
722
|
+
const response = resolveResponsesObject(operationObject.responses);
|
|
723
|
+
const responseObject = response && isReferenceObject(response) ? responses.get(getRefName(response)) : response;
|
|
724
|
+
return generateContentType(responseObject, 'void');
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const generateCreateEndpoint = ({ endpoint, operationObject, context, options }) => {
|
|
728
|
+
const functionName = 'create';
|
|
729
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
730
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
731
|
+
type: functionTypeName,
|
|
732
|
+
params: [
|
|
733
|
+
`data: DeepPartial<${generateRequestBodyType(operationObject, context.requestBodies).toString()}>`,
|
|
734
|
+
'requestOptions?: RequestOptions'
|
|
735
|
+
],
|
|
736
|
+
returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
|
|
626
737
|
});
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
returns:
|
|
738
|
+
const functionSource = generateArrowFunction({
|
|
739
|
+
name: functionName,
|
|
740
|
+
signature: functionTypeName,
|
|
741
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, data, requestOptions)`,
|
|
742
|
+
params: ['data', 'requestOptions?: RequestOptions']
|
|
631
743
|
});
|
|
632
744
|
return {
|
|
633
|
-
|
|
634
|
-
name:
|
|
635
|
-
|
|
636
|
-
func: { name: functionName$4, source: functionSource }
|
|
745
|
+
name: functionName,
|
|
746
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
747
|
+
func: { name: functionName, source: functionSource }
|
|
637
748
|
};
|
|
638
749
|
};
|
|
639
750
|
|
|
640
751
|
const generateGenericFunctionName = (path, suffix = '', prefix = '') => {
|
|
641
|
-
return camelCase(`${prefix}_` +
|
|
642
|
-
path
|
|
643
|
-
.replace(/.*\//, '')
|
|
644
|
-
.replace(/\W+/, '_')
|
|
645
|
-
.replace(/[_]+/, '_') + `_${suffix}`);
|
|
752
|
+
return camelCase(`${prefix}_` + path.replace(/.*\//, '').replace(/\W+/, '_').replace(/[_]+/, '_') + `_${suffix}`);
|
|
646
753
|
};
|
|
647
754
|
|
|
648
755
|
const insertPathPlaceholder = (path, record) => {
|
|
@@ -650,77 +757,81 @@ const insertPathPlaceholder = (path, record) => {
|
|
|
650
757
|
};
|
|
651
758
|
|
|
652
759
|
const wrapBody = (type, target) => {
|
|
653
|
-
return type.toString() === 'binary' ?
|
|
654
|
-
createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') :
|
|
655
|
-
type; // node-fetch returns a Blob as well
|
|
760
|
+
return type.toString() === 'binary' ? createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') : type; // node-fetch returns a Blob as well
|
|
656
761
|
};
|
|
657
|
-
const generateGenericEndpoint = (suffix) => ({
|
|
762
|
+
const generateGenericEndpoint = (suffix) => ({ method, endpoint, operationObject, context, options }) => {
|
|
658
763
|
const functionName = generateGenericFunctionName(endpoint.path, suffix, method);
|
|
659
|
-
const
|
|
660
|
-
const
|
|
661
|
-
const entityQuery = `${interfaceName}_Query`;
|
|
764
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
765
|
+
const entityQuery = `${functionTypeName}_Query`;
|
|
662
766
|
const hasId = endpoint.path.includes('{id}');
|
|
663
767
|
const params = createObjectType({
|
|
664
|
-
params:
|
|
665
|
-
|
|
768
|
+
params: operationObject.parameters &&
|
|
769
|
+
convertToTypeScriptType(resolveParameters(operationObject.parameters, context.parameters)),
|
|
770
|
+
body: method === 'get'
|
|
771
|
+
? undefined
|
|
772
|
+
: wrapBody(generateRequestBodyType(operationObject, context.requestBodies), options.target)
|
|
773
|
+
});
|
|
774
|
+
const responseBody = generateResponseType(operationObject, context.responses);
|
|
775
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
776
|
+
type: functionTypeName,
|
|
777
|
+
params: [
|
|
778
|
+
...(hasId ? ['id: string'] : []),
|
|
779
|
+
`query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`,
|
|
780
|
+
'requestOptions?: RequestOptions'
|
|
781
|
+
],
|
|
782
|
+
returns: `${resolveResponseType(options.target)}<${wrapBody(responseBody, options.target).toString('force')}>`
|
|
666
783
|
});
|
|
667
|
-
const responseBody = generateResponseBodyType(path);
|
|
668
|
-
const forceBlobResponse = String(responseBody.toString() === 'binary');
|
|
669
784
|
const functionSource = generateArrowFunction({
|
|
670
785
|
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()}>`
|
|
786
|
+
signature: functionTypeName,
|
|
787
|
+
params: hasId ? ['id', 'query', 'requestOptions?: RequestOptions'] : ['query', 'requestOptions?: RequestOptions'],
|
|
788
|
+
returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${String(responseBody.toString() === 'binary')}, requestOptions)`
|
|
679
789
|
});
|
|
680
790
|
return {
|
|
681
|
-
entity,
|
|
682
791
|
name: functionName,
|
|
683
|
-
type: { name:
|
|
792
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
684
793
|
func: { name: functionName, source: functionSource },
|
|
685
794
|
interfaces: [
|
|
686
795
|
{
|
|
687
796
|
name: entityQuery,
|
|
688
|
-
source: generateInterfaceFromObject(entityQuery, params,
|
|
797
|
+
source: generateInterfaceFromObject(entityQuery, params, 'propagate')
|
|
689
798
|
}
|
|
690
799
|
]
|
|
691
800
|
};
|
|
692
801
|
};
|
|
693
802
|
|
|
694
|
-
const
|
|
695
|
-
const
|
|
696
|
-
const
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
returns: `_${functionName$3}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options)`,
|
|
702
|
-
params: ['id', 'options?: RemoveQuery']
|
|
803
|
+
const generateRemoveEndpoint = ({ endpoint, options }) => {
|
|
804
|
+
const functionName = 'remove';
|
|
805
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
806
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
807
|
+
type: functionTypeName,
|
|
808
|
+
params: ['id: string', 'options?: RemoveQuery', 'requestOptions?: RequestOptions'],
|
|
809
|
+
returns: `${resolveResponseType(options.target)}<void>`
|
|
703
810
|
});
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
returns:
|
|
811
|
+
const functionSource = generateArrowFunction({
|
|
812
|
+
name: functionName,
|
|
813
|
+
signature: functionTypeName,
|
|
814
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options, requestOptions)`,
|
|
815
|
+
params: ['id', 'options?: RemoveQuery', 'requestOptions?: RequestOptions']
|
|
708
816
|
});
|
|
709
817
|
return {
|
|
710
|
-
|
|
711
|
-
name:
|
|
712
|
-
|
|
713
|
-
func: { name: functionName$3, source: functionSource }
|
|
818
|
+
name: functionName,
|
|
819
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
820
|
+
func: { name: functionName, source: functionSource }
|
|
714
821
|
};
|
|
715
822
|
};
|
|
716
823
|
|
|
717
|
-
const functionName$2 = 'some';
|
|
718
824
|
const excludedParameters = [
|
|
719
|
-
'page',
|
|
720
|
-
'
|
|
825
|
+
'page',
|
|
826
|
+
'pageSize',
|
|
827
|
+
'sort',
|
|
828
|
+
'serializeNulls',
|
|
829
|
+
'properties',
|
|
830
|
+
'includeReferencedEntities',
|
|
831
|
+
'additionalProperties'
|
|
721
832
|
];
|
|
722
|
-
const
|
|
723
|
-
const body =
|
|
833
|
+
const resolveAdditionalPropertiesSchema = ({ responses }) => {
|
|
834
|
+
const body = resolveResponsesObject(responses);
|
|
724
835
|
if (isResponseObject(body)) {
|
|
725
836
|
const schema = body?.content?.['application/json']?.schema;
|
|
726
837
|
if (isObjectSchemaObject(schema)) {
|
|
@@ -732,276 +843,311 @@ const resolveAdditionalProperties = (path) => {
|
|
|
732
843
|
}
|
|
733
844
|
return undefined;
|
|
734
845
|
};
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
846
|
+
const resolveReferences = (entity, entities) => {
|
|
847
|
+
const references = [];
|
|
848
|
+
const generatedEntity = entities.get(entity);
|
|
849
|
+
if (generatedEntity) {
|
|
850
|
+
for (const [property, propertyMetaData] of generatedEntity.properties) {
|
|
851
|
+
if (propertyMetaData.service) {
|
|
852
|
+
references.push({
|
|
853
|
+
name: property,
|
|
854
|
+
type: generateString(propertyMetaData.service),
|
|
855
|
+
required: true
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (generatedEntity.parentName) {
|
|
860
|
+
references.push(...resolveReferences(generatedEntity.parentName, entities));
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return references;
|
|
864
|
+
};
|
|
865
|
+
const resolveReferencedEntities = (entity, entities) => {
|
|
866
|
+
const referencedEntities = [];
|
|
867
|
+
const generatedEntity = entities.get(entity);
|
|
868
|
+
if (generatedEntity) {
|
|
869
|
+
for (const [, propertyMetaData] of generatedEntity.properties) {
|
|
870
|
+
if (propertyMetaData.service && propertyMetaData.entity) {
|
|
871
|
+
const referencedEntity = entities.get(propertyMetaData.entity);
|
|
872
|
+
if (referencedEntity)
|
|
873
|
+
referencedEntities.push({
|
|
874
|
+
name: propertyMetaData.service,
|
|
875
|
+
type: `${referencedEntity.interfaceName}[]`,
|
|
876
|
+
required: true
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if (generatedEntity.parentName) {
|
|
881
|
+
referencedEntities.push(...resolveReferencedEntities(generatedEntity.parentName, entities));
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return referencedEntities;
|
|
885
|
+
};
|
|
886
|
+
const generateSomeEndpoint = ({ endpoint, operationObject, entities, context, options }) => {
|
|
887
|
+
const functionName = 'some';
|
|
888
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
889
|
+
const relatedEntityName = context.aliases.get(endpoint.service);
|
|
890
|
+
const relatedEntity = !!relatedEntityName && entities.get(relatedEntityName);
|
|
891
|
+
if (!relatedEntity) {
|
|
892
|
+
throw Error(`Related entity schema for service ${endpoint.service} not found`);
|
|
893
|
+
}
|
|
894
|
+
const parametersTypeName = `${functionTypeName}_Parameters`;
|
|
895
|
+
const parameters = operationObject.parameters?.filter((v) => isParameterObject(v) ? !excludedParameters.includes(v.name) : false);
|
|
896
|
+
const parametersType = createObjectType({
|
|
897
|
+
params: parameters && convertToTypeScriptType(resolveParameters(parameters, context.parameters))
|
|
753
898
|
});
|
|
754
|
-
const
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
899
|
+
const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
|
|
900
|
+
const filterTypeName = `${functionTypeName}_Filter`;
|
|
901
|
+
const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${relatedEntity.filterInterfaceName}`]);
|
|
902
|
+
const referencesTypeName = `${functionTypeName}_References`;
|
|
903
|
+
const referencesTypeSource = generateInterfaceType(referencesTypeName, resolveReferences(endpoint.service, entities));
|
|
904
|
+
const additionalPropertyTypeName = `${functionTypeName}_AdditionalProperty`;
|
|
905
|
+
const additionalPropertyTypeSource = generateType(additionalPropertyTypeName, 'string');
|
|
906
|
+
const queryTypeName = `${functionTypeName}_Query`;
|
|
907
|
+
const queryTypeSource = generateType(queryTypeName, `SomeQuery<${relatedEntity.interfaceName}, ${filterTypeName}, ${referencesTypeName}, ${additionalPropertyTypeName}> & ${parametersTypeName}`);
|
|
908
|
+
const referencedEntitiesTypeName = `${functionTypeName}_ReferencedEntities`;
|
|
909
|
+
const referencedEntitiesTypeSource = generateInterfaceType(referencedEntitiesTypeName, resolveReferencedEntities(endpoint.service, entities));
|
|
910
|
+
const additionalPropertiesTypeName = `${functionTypeName}_AdditionalProperties`;
|
|
911
|
+
const additionalPropertiesSchema = resolveAdditionalPropertiesSchema(operationObject);
|
|
912
|
+
const additionalPropertiesTypeSource = generateType(additionalPropertiesTypeName, additionalPropertiesSchema ? convertToTypeScriptType(additionalPropertiesSchema).toString() : '{}');
|
|
913
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
914
|
+
type: functionTypeName,
|
|
915
|
+
params: [`query${parametersType.isFullyOptional() ? '?' : ''}: ${queryTypeName}, requestOptions?: RequestOptions`],
|
|
916
|
+
returns: `${resolveResponseType(options.target)}<SomeQueryReturn<${relatedEntity.interfaceName}, ${referencedEntitiesTypeName}, ${additionalPropertiesTypeName}>>`
|
|
763
917
|
});
|
|
764
918
|
const functionSource = generateArrowFunction({
|
|
765
|
-
name: functionName
|
|
766
|
-
signature:
|
|
767
|
-
returns: `_${functionName
|
|
768
|
-
params: ['query']
|
|
919
|
+
name: functionName,
|
|
920
|
+
signature: functionTypeName,
|
|
921
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
|
|
922
|
+
params: ['query', 'requestOptions?: RequestOptions']
|
|
769
923
|
});
|
|
770
924
|
return {
|
|
771
|
-
|
|
772
|
-
name:
|
|
773
|
-
|
|
774
|
-
func: { name: functionName$2, source: functionSource },
|
|
925
|
+
name: functionName,
|
|
926
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
927
|
+
func: { name: functionName, source: functionSource },
|
|
775
928
|
interfaces: [
|
|
776
|
-
{
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
929
|
+
{ name: parametersTypeName, source: parametersTypeSource },
|
|
930
|
+
{ name: filterTypeName, source: filterTypeSource },
|
|
931
|
+
{ name: referencesTypeName, source: referencesTypeSource },
|
|
932
|
+
{ name: additionalPropertyTypeName, source: additionalPropertyTypeSource },
|
|
933
|
+
{ name: queryTypeName, source: queryTypeSource },
|
|
934
|
+
{ name: referencedEntitiesTypeName, source: referencedEntitiesTypeSource },
|
|
935
|
+
{ name: additionalPropertiesTypeName, source: additionalPropertiesTypeSource }
|
|
780
936
|
]
|
|
781
937
|
};
|
|
782
938
|
};
|
|
783
939
|
|
|
784
|
-
const
|
|
785
|
-
const
|
|
786
|
-
const
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
signature: interfaceName,
|
|
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'],
|
|
940
|
+
const generateUniqueEndpoint = ({ operationObject, endpoint, context, options }) => {
|
|
941
|
+
const functionName = 'unique';
|
|
942
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
943
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
944
|
+
type: functionTypeName,
|
|
945
|
+
params: ['id: string', 'query?: Q', 'requestOptions?: RequestOptions'],
|
|
797
946
|
generics: ['Q extends UniqueQuery'],
|
|
798
|
-
returns: `${resolveResponseType(target)}<${
|
|
947
|
+
returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
|
|
948
|
+
});
|
|
949
|
+
const functionSource = generateArrowFunction({
|
|
950
|
+
name: functionName,
|
|
951
|
+
signature: functionTypeName,
|
|
952
|
+
params: ['id', 'query', 'requestOptions?: RequestOptions'],
|
|
953
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, requestOptions)`
|
|
799
954
|
});
|
|
800
955
|
return {
|
|
801
|
-
|
|
802
|
-
name:
|
|
803
|
-
|
|
804
|
-
func: { name: functionName$1, source: functionSource }
|
|
956
|
+
name: functionName,
|
|
957
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
958
|
+
func: { name: functionName, source: functionSource }
|
|
805
959
|
};
|
|
806
960
|
};
|
|
807
961
|
|
|
808
|
-
const
|
|
809
|
-
const
|
|
810
|
-
const
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
962
|
+
const generateUpdateEndpoint = ({ endpoint, operationObject, context, options }) => {
|
|
963
|
+
const functionName = 'update';
|
|
964
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
965
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
966
|
+
type: functionTypeName,
|
|
967
|
+
params: [
|
|
968
|
+
'id: string',
|
|
969
|
+
`data: DeepPartial<${generateRequestBodyType(operationObject, context.requestBodies).toString()}>`,
|
|
970
|
+
'options?: UpdateQuery',
|
|
971
|
+
'requestOptions?: RequestOptions'
|
|
972
|
+
],
|
|
973
|
+
returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
|
|
816
974
|
});
|
|
817
975
|
const functionSource = generateArrowFunction({
|
|
818
976
|
name: functionName,
|
|
819
|
-
signature:
|
|
820
|
-
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options)`,
|
|
821
|
-
params: ['id', 'data', 'options']
|
|
977
|
+
signature: functionTypeName,
|
|
978
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options, requestOptions)`,
|
|
979
|
+
params: ['id', 'data', 'options', 'requestOptions?: RequestOptions']
|
|
822
980
|
});
|
|
823
981
|
return {
|
|
824
|
-
entity,
|
|
825
982
|
name: functionName,
|
|
826
|
-
type: { name:
|
|
983
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
827
984
|
func: { name: functionName, source: functionSource }
|
|
828
985
|
};
|
|
829
986
|
};
|
|
830
987
|
|
|
831
|
-
const groupEndpointsByEntity = (paths) => {
|
|
832
|
-
const endpoints = new Map();
|
|
833
|
-
for (const [rawPath, path] of Object.entries(paths)) {
|
|
834
|
-
const endpoint = parseEndpointPath(rawPath);
|
|
835
|
-
if (!endpoint || !path) {
|
|
836
|
-
logger.errorLn(`Failed to parse ${rawPath}`);
|
|
837
|
-
continue;
|
|
838
|
-
}
|
|
839
|
-
if (endpoints.has(endpoint.entity)) {
|
|
840
|
-
endpoints.get(endpoint.entity)?.push({ endpoint, path });
|
|
841
|
-
}
|
|
842
|
-
else {
|
|
843
|
-
endpoints.set(endpoint.entity, [{ endpoint, path }]);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
return endpoints;
|
|
847
|
-
};
|
|
848
|
-
|
|
849
988
|
const generators = {
|
|
850
989
|
/* /article */
|
|
851
990
|
[WeclappEndpointType.ROOT]: {
|
|
852
|
-
|
|
853
|
-
|
|
991
|
+
[OpenAPIV3.HttpMethods.GET]: generateSomeEndpoint,
|
|
992
|
+
[OpenAPIV3.HttpMethods.POST]: generateCreateEndpoint
|
|
854
993
|
},
|
|
855
994
|
/* /article/count */
|
|
856
995
|
[WeclappEndpointType.COUNT]: {
|
|
857
|
-
|
|
996
|
+
[OpenAPIV3.HttpMethods.GET]: generateCountEndpoint
|
|
858
997
|
},
|
|
859
998
|
/* /article/:id */
|
|
860
999
|
[WeclappEndpointType.ENTITY]: {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1000
|
+
[OpenAPIV3.HttpMethods.GET]: generateUniqueEndpoint,
|
|
1001
|
+
[OpenAPIV3.HttpMethods.PUT]: generateUpdateEndpoint,
|
|
1002
|
+
[OpenAPIV3.HttpMethods.DELETE]: generateRemoveEndpoint
|
|
864
1003
|
},
|
|
865
1004
|
/* /article/:id/method */
|
|
866
1005
|
[WeclappEndpointType.GENERIC_ENTITY]: {
|
|
867
|
-
|
|
868
|
-
|
|
1006
|
+
[OpenAPIV3.HttpMethods.GET]: generateGenericEndpoint('ById'),
|
|
1007
|
+
[OpenAPIV3.HttpMethods.POST]: generateGenericEndpoint('ById')
|
|
869
1008
|
},
|
|
870
1009
|
/* /article/method */
|
|
871
1010
|
[WeclappEndpointType.GENERIC_ROOT]: {
|
|
872
|
-
|
|
873
|
-
|
|
1011
|
+
[OpenAPIV3.HttpMethods.GET]: generateGenericEndpoint(),
|
|
1012
|
+
[OpenAPIV3.HttpMethods.POST]: generateGenericEndpoint()
|
|
874
1013
|
}
|
|
875
1014
|
};
|
|
876
|
-
const generateServices = (
|
|
1015
|
+
const generateServices = (entities, context, options) => {
|
|
877
1016
|
const services = new Map();
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
const
|
|
881
|
-
const serviceTypeName = pascalCase(`${endpoint}Service`);
|
|
882
|
-
// Service functions
|
|
1017
|
+
for (const [serviceName, serviceEndpoints] of context.endpoints) {
|
|
1018
|
+
const serviceFnName = camelCase(`${serviceName}Service`);
|
|
1019
|
+
const serviceTypeName = pascalCase(`${serviceName}Service`);
|
|
883
1020
|
const functions = [];
|
|
884
|
-
for (const { path, endpoint } of
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
1021
|
+
for (const { path, endpoint } of serviceEndpoints) {
|
|
1022
|
+
for (const method of [
|
|
1023
|
+
OpenAPIV3.HttpMethods.GET,
|
|
1024
|
+
OpenAPIV3.HttpMethods.POST,
|
|
1025
|
+
OpenAPIV3.HttpMethods.PUT,
|
|
1026
|
+
OpenAPIV3.HttpMethods.DELETE
|
|
1027
|
+
]) {
|
|
1028
|
+
if ((method === OpenAPIV3.HttpMethods.GET &&
|
|
1029
|
+
endpoint.type === WeclappEndpointType.ENTITY &&
|
|
1030
|
+
!options.generateUnique) ||
|
|
1031
|
+
(method === OpenAPIV3.HttpMethods.POST &&
|
|
1032
|
+
(endpoint.type === WeclappEndpointType.COUNT || endpoint.path.endsWith('query')))) {
|
|
1033
|
+
// Skip unique endpoints if generateUnique option is not set or if POST is used for filter queries
|
|
888
1034
|
continue;
|
|
889
1035
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
if (!
|
|
1036
|
+
const operationObject = path[method];
|
|
1037
|
+
const generatorFn = generators[endpoint.type][method];
|
|
1038
|
+
if (operationObject && generatorFn) {
|
|
1039
|
+
if (!operationObject.deprecated || options.deprecated) {
|
|
894
1040
|
functions.push({
|
|
895
|
-
...
|
|
896
|
-
|
|
1041
|
+
...generatorFn({
|
|
1042
|
+
method,
|
|
1043
|
+
endpoint,
|
|
1044
|
+
operationObject,
|
|
1045
|
+
entities,
|
|
1046
|
+
context,
|
|
1047
|
+
options
|
|
1048
|
+
}),
|
|
1049
|
+
path: operationObject
|
|
897
1050
|
});
|
|
898
1051
|
}
|
|
899
1052
|
}
|
|
900
|
-
else {
|
|
901
|
-
logger.errorLn(`Failed to generate a function for ${method.toUpperCase()}:${endpoint.type} ${endpoint.path}`);
|
|
902
|
-
}
|
|
903
1053
|
}
|
|
904
1054
|
}
|
|
905
1055
|
if (!functions.length) {
|
|
906
1056
|
continue;
|
|
907
1057
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
...functions.map(v => ({
|
|
1058
|
+
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, [
|
|
1059
|
+
...functions.map((v) => ({
|
|
911
1060
|
required: true,
|
|
912
1061
|
comment: v.path.deprecated ? '@deprecated' : undefined,
|
|
913
1062
|
name: v.func.name,
|
|
914
1063
|
type: v.type.name
|
|
915
1064
|
}))
|
|
916
|
-
]));
|
|
917
|
-
|
|
918
|
-
const
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1065
|
+
])));
|
|
1066
|
+
const serviceFn = `export const ${serviceFnName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${generateBlockStatements(...functions.map((v) => v.func.source), `return {${concat(functions.map((v) => v.func.name))}};`)};`;
|
|
1067
|
+
const relatedEntityName = context.aliases.get(serviceName);
|
|
1068
|
+
const relatedEntity = relatedEntityName ? entities.get(relatedEntityName) : undefined;
|
|
1069
|
+
services.set(serviceName, {
|
|
1070
|
+
name: serviceName,
|
|
1071
|
+
serviceFnName,
|
|
1072
|
+
functions,
|
|
1073
|
+
source: generateStatements(serviceTypes, serviceFn),
|
|
1074
|
+
deprecated: functions.every((v) => v.path.deprecated),
|
|
1075
|
+
relatedEntity
|
|
1076
|
+
});
|
|
923
1077
|
}
|
|
924
1078
|
return services;
|
|
925
1079
|
};
|
|
926
1080
|
|
|
927
|
-
const
|
|
928
|
-
const customValueEntity = entities.get('customValue');
|
|
1081
|
+
const generateCustomValueServices = (services) => {
|
|
929
1082
|
const customValueEntities = [];
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
serviceLoop: for (const service of services) {
|
|
935
|
-
const someFunction = service.functions.find(v => v.name === 'some');
|
|
936
|
-
if (!someFunction) {
|
|
937
|
-
continue;
|
|
938
|
-
}
|
|
939
|
-
const entity = entities.get(camelCase(someFunction.entity));
|
|
940
|
-
if (entity?.properties.size !== customValueEntity.properties.size) {
|
|
941
|
-
continue;
|
|
942
|
-
}
|
|
943
|
-
for (const [prop, { type }] of entity.properties) {
|
|
944
|
-
if (customValueEntity.properties.get(prop)?.type !== type) {
|
|
945
|
-
continue serviceLoop;
|
|
946
|
-
}
|
|
1083
|
+
for (const service of services) {
|
|
1084
|
+
const relatedEntity = service.relatedEntity;
|
|
1085
|
+
if (relatedEntity?.name === 'customValue') {
|
|
1086
|
+
customValueEntities.push(service.name);
|
|
947
1087
|
}
|
|
948
|
-
customValueEntities.push(service.entity);
|
|
949
1088
|
}
|
|
950
|
-
return
|
|
1089
|
+
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
1090
|
};
|
|
952
1091
|
|
|
953
1092
|
const generateObject = (properties) => {
|
|
954
1093
|
const body = [];
|
|
955
|
-
for (const { key, value } of properties) {
|
|
1094
|
+
for (const { key, value, comment } of properties) {
|
|
956
1095
|
if (value === undefined) {
|
|
957
1096
|
continue;
|
|
958
1097
|
}
|
|
959
1098
|
if (Array.isArray(value)) {
|
|
960
1099
|
const str = generateObject(value);
|
|
961
1100
|
if (str.length > 2) {
|
|
962
|
-
body.push(`${key}: ${str}`);
|
|
1101
|
+
body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${str}`);
|
|
963
1102
|
}
|
|
964
1103
|
}
|
|
965
1104
|
else {
|
|
966
|
-
body.push(`${key}: ${String(value)}`);
|
|
1105
|
+
body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${String(value)}`);
|
|
967
1106
|
}
|
|
968
1107
|
}
|
|
969
1108
|
return body.length ? `{\n${indent(body.join(',\n'))}\n}` : `{}`;
|
|
970
1109
|
};
|
|
971
1110
|
|
|
972
1111
|
const resolveInheritedEntities = (root, entities) => {
|
|
973
|
-
const parent = root.
|
|
1112
|
+
const parent = root.parentName ? entities.get(root.parentName) : undefined;
|
|
974
1113
|
return parent ? [parent, ...resolveInheritedEntities(parent, entities)] : [];
|
|
975
1114
|
};
|
|
976
|
-
const generatePropertyDescriptors = (entity, entities, services, options) => [
|
|
977
|
-
|
|
978
|
-
...entity.properties
|
|
979
|
-
].filter(([, meta]) => {
|
|
1115
|
+
const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
|
|
1116
|
+
.filter(([, meta]) => {
|
|
980
1117
|
// If we generate deprecated things we can skip the filtering
|
|
981
1118
|
if (options.deprecated) {
|
|
982
1119
|
return true;
|
|
983
1120
|
}
|
|
984
1121
|
// Check if corresponding service is deprecated and can be removed
|
|
985
|
-
const service = services.find(v => v.
|
|
1122
|
+
const service = services.find((v) => v.name === meta.service);
|
|
986
1123
|
return !meta.service || (service && !service.deprecated);
|
|
987
|
-
})
|
|
1124
|
+
})
|
|
1125
|
+
.map(([property, meta]) => ({
|
|
988
1126
|
key: property,
|
|
989
1127
|
value: Object.entries(meta).map(([key, value]) => ({
|
|
990
1128
|
key,
|
|
991
|
-
value: value ? generateString(value) : undefined
|
|
1129
|
+
value: value !== undefined ? (typeof value === 'number' ? value : generateString(value)) : undefined
|
|
992
1130
|
}))
|
|
993
1131
|
}));
|
|
994
|
-
const
|
|
1132
|
+
const generateEntityProperties = (entities, services, options) => {
|
|
995
1133
|
const typeName = 'WEntityProperties';
|
|
996
|
-
const propertyMap = [
|
|
997
|
-
|
|
998
|
-
|
|
1134
|
+
const propertyMap = [
|
|
1135
|
+
...entities.entries(),
|
|
1136
|
+
...services
|
|
1137
|
+
.filter(({ relatedEntity }) => !!relatedEntity)
|
|
1138
|
+
.filter(({ name }) => !entities.get(name))
|
|
1139
|
+
.map(({ name, relatedEntity }) => {
|
|
1140
|
+
return [name, relatedEntity];
|
|
1141
|
+
})
|
|
1142
|
+
].map(([entityName, entity]) => ({
|
|
1143
|
+
key: entityName,
|
|
1144
|
+
value: generatePropertyDescriptors(entity, entities, services, options)
|
|
999
1145
|
}));
|
|
1000
1146
|
return generateStatements(`export type ${typeName} = Partial<Record<WEntity, Partial<Record<string, WEntityPropertyMeta>>>>;`, `export const wEntityProperties: ${typeName} = ${generateObject(propertyMap)};`);
|
|
1001
1147
|
};
|
|
1002
1148
|
|
|
1003
1149
|
const generateArray = (values) => {
|
|
1004
|
-
return `[${concat(values.map(v => generateString(String(v))))}]`;
|
|
1150
|
+
return `[${concat(values.map((v) => generateString(String(v))))}]`;
|
|
1005
1151
|
};
|
|
1006
1152
|
|
|
1007
1153
|
// Only functions matching this regex are included in the generation.
|
|
@@ -1014,16 +1160,17 @@ const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
|
|
|
1014
1160
|
*/
|
|
1015
1161
|
const generateGroupedServices = (services) => {
|
|
1016
1162
|
const entityDescriptors = new Map();
|
|
1017
|
-
for (const
|
|
1018
|
-
for (const
|
|
1019
|
-
if (!FILTER_REGEX.test(name)) {
|
|
1163
|
+
for (const service of services) {
|
|
1164
|
+
for (const fn of service.functions) {
|
|
1165
|
+
if (!FILTER_REGEX.test(fn.name)) {
|
|
1020
1166
|
continue;
|
|
1021
1167
|
}
|
|
1022
|
-
entityDescriptors.set(name, [
|
|
1023
|
-
...(entityDescriptors.get(name) ?? []),
|
|
1024
|
-
|
|
1168
|
+
entityDescriptors.set(fn.name, [
|
|
1169
|
+
...(entityDescriptors.get(fn.name) ?? []),
|
|
1170
|
+
{
|
|
1171
|
+
name: service.name,
|
|
1025
1172
|
required: true,
|
|
1026
|
-
type: `${pascalCase(
|
|
1173
|
+
type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}`
|
|
1027
1174
|
}
|
|
1028
1175
|
]);
|
|
1029
1176
|
}
|
|
@@ -1036,138 +1183,138 @@ const generateGroupedServices = (services) => {
|
|
|
1036
1183
|
const guard = `(service: string | undefined): service is ${service} =>\n${indent(`${constant}.includes(service as ${service});`)}`;
|
|
1037
1184
|
typeGuards.push(`export const is${service} = ${guard}`);
|
|
1038
1185
|
}
|
|
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
|
-
];
|
|
1186
|
+
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]) => {
|
|
1187
|
+
const constant = camelCase(`wServiceWith_${name}_Names`);
|
|
1188
|
+
const type = pascalCase(`WServiceWith_${name}`);
|
|
1189
|
+
const value = generateArray(props.map((v) => v.name));
|
|
1190
|
+
return `export const ${constant}: ${type}[] = ${value};`;
|
|
1191
|
+
}), ...typeGuards);
|
|
1050
1192
|
};
|
|
1051
1193
|
|
|
1052
|
-
const
|
|
1053
|
-
const
|
|
1054
|
-
const
|
|
1055
|
-
const
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1194
|
+
const generateMaps = (enums, entities, services, context, options) => {
|
|
1195
|
+
const enumInstances = `export const wEnums = ${generateObject([...enums.keys()].map((v) => ({ key: v, value: v })))};`;
|
|
1196
|
+
const entityNames = `export const wEntityNames: WEntity[] = ${generateArray([...entities.keys()])};`;
|
|
1197
|
+
const generatedServices = [...services.values()];
|
|
1198
|
+
const serviceInstances = `export const wServices = ${generateObject(generatedServices.map((v) => ({
|
|
1199
|
+
key: v.name,
|
|
1200
|
+
value: `${v.serviceFnName}()`,
|
|
1201
|
+
comment: v.deprecated ? '@deprecated' : undefined
|
|
1202
|
+
})))};`;
|
|
1203
|
+
const serviceFactories = `export const wServiceFactories = ${generateObject(generatedServices.map((v) => ({
|
|
1204
|
+
key: v.name,
|
|
1205
|
+
value: v.serviceFnName,
|
|
1206
|
+
comment: v.deprecated ? '@deprecated' : undefined
|
|
1207
|
+
})))};`;
|
|
1208
|
+
return generateStatements(
|
|
1209
|
+
/* Enums */
|
|
1210
|
+
generateInterface('WEnums', [...enums.keys()].map((name) => ({ name, type: name, required: true }))), generateType('WEnum', 'keyof WEnums'), enumInstances,
|
|
1211
|
+
/* Entities */
|
|
1212
|
+
generateInterface('WEntities', [
|
|
1213
|
+
...[...entities.entries()].map(([name, entity]) => ({
|
|
1214
|
+
name,
|
|
1215
|
+
type: entity.interfaceName,
|
|
1068
1216
|
required: true
|
|
1069
1217
|
})),
|
|
1070
|
-
...
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
return {
|
|
1086
|
-
source: generateStatements(
|
|
1087
|
-
/* JS Values */
|
|
1088
|
-
serviceValues, serviceInstanceValues, entityNames, serviceNames, enumsArray, generateEntityPropertyMap(entities, services, options),
|
|
1089
|
-
/* Map of entity to references / mappings and filters*/
|
|
1090
|
-
entityReferences, entityMappings, entityFilter,
|
|
1091
|
-
/* List of all entities with their corresponding service */
|
|
1092
|
-
generateBlockComment(`
|
|
1093
|
-
This interfaces merges two maps:
|
|
1094
|
-
- Map<[entityName], [entityInterfaceName]>
|
|
1095
|
-
- Map<[serviceName], [entityInterfaceName]>
|
|
1096
|
-
|
|
1097
|
-
Where [entityName] is
|
|
1098
|
-
- the name of a nested entity (e.g. 'address' from Party)
|
|
1099
|
-
- the name of an entity (e.g. 'party', 'article' etc.)
|
|
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))
|
|
1113
|
-
};
|
|
1218
|
+
...generatedServices
|
|
1219
|
+
.filter(({ relatedEntity }) => !!relatedEntity)
|
|
1220
|
+
.filter(({ name }) => !entities.get(name))
|
|
1221
|
+
.map(({ name, relatedEntity }) => ({
|
|
1222
|
+
name,
|
|
1223
|
+
type: relatedEntity.interfaceName,
|
|
1224
|
+
required: true
|
|
1225
|
+
}))
|
|
1226
|
+
].sort((a, b) => (a.name > b.name ? 1 : -1))), generateType('WEntity', 'keyof WEntities'), entityNames,
|
|
1227
|
+
/* Services */
|
|
1228
|
+
serviceInstances, generateType('WServices', 'typeof wServices'), generateType('WService', 'keyof WServices'), serviceFactories, generateType('WServiceFactories', 'typeof wServiceFactories'),
|
|
1229
|
+
/* Service Utils */
|
|
1230
|
+
generateGroupedServices(generatedServices), generateCustomValueServices(generatedServices),
|
|
1231
|
+
/* Entity Properties (Runtime Meta Infos) */
|
|
1232
|
+
generateEntityProperties(entities, generatedServices, options));
|
|
1114
1233
|
};
|
|
1115
1234
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1235
|
+
function extractRelatedEntityName(serviceEndpoints, responses) {
|
|
1236
|
+
const rootEndpoint = serviceEndpoints.find((v) => v.endpoint.type === WeclappEndpointType.ROOT);
|
|
1237
|
+
if (!rootEndpoint)
|
|
1238
|
+
return;
|
|
1239
|
+
const response = rootEndpoint?.path.get?.responses['200'];
|
|
1240
|
+
if (!response)
|
|
1241
|
+
return;
|
|
1242
|
+
let responseObject;
|
|
1243
|
+
if (isReferenceObject(response)) {
|
|
1244
|
+
const refName = getRefName(response);
|
|
1245
|
+
responseObject = responses.get(refName);
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
responseObject = response;
|
|
1249
|
+
}
|
|
1250
|
+
const responseSchema = responseObject?.content?.['application/json'].schema;
|
|
1251
|
+
if (responseSchema) {
|
|
1252
|
+
if (isReferenceObject(responseSchema)) {
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
const resultSchema = responseSchema.properties?.result;
|
|
1256
|
+
if (!resultSchema) {
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
if (isReferenceObject(resultSchema)) {
|
|
1260
|
+
return getRefName(resultSchema);
|
|
1261
|
+
}
|
|
1262
|
+
else if (isArraySchemaObject(resultSchema)) {
|
|
1263
|
+
const resultItemSchema = resultSchema.items;
|
|
1264
|
+
if (isReferenceObject(resultItemSchema)) {
|
|
1265
|
+
return getRefName(resultItemSchema);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
const extractServiceAliases = (endpoints, responses) => {
|
|
1120
1272
|
const aliases = new Map();
|
|
1273
|
+
for (const [serviceName, serviceEndpoints] of endpoints) {
|
|
1274
|
+
const relatedEntityName = extractRelatedEntityName(serviceEndpoints, responses);
|
|
1275
|
+
if (relatedEntityName)
|
|
1276
|
+
aliases.set(serviceName, relatedEntityName);
|
|
1277
|
+
}
|
|
1278
|
+
return aliases;
|
|
1279
|
+
};
|
|
1280
|
+
const extractContext = (doc) => {
|
|
1281
|
+
const endpoints = parseEndpointsPaths(doc.paths);
|
|
1282
|
+
const schemas = new Map();
|
|
1121
1283
|
for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
|
|
1122
1284
|
if (!isReferenceObject(schema)) {
|
|
1123
1285
|
schemas.set(name, schema);
|
|
1124
1286
|
}
|
|
1125
1287
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
for (const [path, methods] of Object.entries(doc.paths)) {
|
|
1131
|
-
const parsed = parseEndpointPath(path);
|
|
1132
|
-
if (!parsed || schemas.has(parsed.entity)) {
|
|
1133
|
-
continue;
|
|
1288
|
+
const responses = new Map();
|
|
1289
|
+
for (const [name, response] of Object.entries(doc.components?.responses ?? {})) {
|
|
1290
|
+
if (!isReferenceObject(response)) {
|
|
1291
|
+
responses.set(name, response);
|
|
1134
1292
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
}
|
|
1147
|
-
if (isArraySchemaObject(itemsSchema)) {
|
|
1148
|
-
const { items } = itemsSchema;
|
|
1149
|
-
if (isReferenceObject(items)) {
|
|
1150
|
-
aliases.set(parsed.entity, parseReferencedEntity(items));
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1293
|
+
}
|
|
1294
|
+
const parameters = new Map();
|
|
1295
|
+
for (const [name, parameter] of Object.entries(doc.components?.parameters ?? {})) {
|
|
1296
|
+
if (!isReferenceObject(parameter)) {
|
|
1297
|
+
parameters.set(name, parameter);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
const requestBodies = new Map();
|
|
1301
|
+
for (const [name, requestBody] of Object.entries(doc.components?.requestBodies ?? {})) {
|
|
1302
|
+
if (!isReferenceObject(requestBody)) {
|
|
1303
|
+
requestBodies.set(name, requestBody);
|
|
1154
1304
|
}
|
|
1155
1305
|
}
|
|
1156
|
-
|
|
1306
|
+
const aliases = extractServiceAliases(endpoints, responses);
|
|
1307
|
+
return { endpoints, schemas, responses, parameters, requestBodies, aliases };
|
|
1157
1308
|
};
|
|
1158
1309
|
|
|
1159
1310
|
const generate = (doc, options) => {
|
|
1160
|
-
const
|
|
1161
|
-
const
|
|
1162
|
-
const
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
options,
|
|
1168
|
-
entities,
|
|
1169
|
-
aliases
|
|
1170
|
-
}).source));
|
|
1311
|
+
const context = extractContext(doc);
|
|
1312
|
+
const base = generateBase(doc.info.version, options);
|
|
1313
|
+
const enums = generateEnums(context);
|
|
1314
|
+
const entities = generateEntities(context);
|
|
1315
|
+
const services = generateServices(entities, context, options);
|
|
1316
|
+
const maps = generateMaps(enums, entities, services, context, options);
|
|
1317
|
+
return generateStatements(generateBlockComment('BASE', base), generateBlockComment('ENUMS', generateStatements(...[...enums.values()].map((v) => v.source))), generateBlockComment('ENTITIES', generateStatements(...[...entities.values()].map((v) => v.source))), generateBlockComment('FILTERS', generateStatements(...[...entities.values()].map((v) => v.filterSource))), generateBlockComment('SERVICES', generateStatements(...[...services.values()].map((v) => v.source))), generateBlockComment('MAPS', maps));
|
|
1171
1318
|
};
|
|
1172
1319
|
|
|
1173
1320
|
const hash = (content, algorithm = 'sha256') => {
|
|
@@ -1183,12 +1330,13 @@ const hash = (content, algorithm = 'sha256') => {
|
|
|
1183
1330
|
|
|
1184
1331
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
1185
1332
|
const cli = async () => {
|
|
1186
|
-
const
|
|
1333
|
+
const version = pkg.version;
|
|
1187
1334
|
const { argv } = yargs(hideBin(process.argv))
|
|
1188
1335
|
.scriptName('build-weclapp-sdk')
|
|
1189
1336
|
.usage('Usage: $0 <source> [flags]')
|
|
1190
1337
|
.version(version)
|
|
1191
1338
|
.example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
|
|
1339
|
+
.example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
|
|
1192
1340
|
.example('$0 xxx.weclapp.com --key ...', 'Generate the SDK based on the openapi file from the given weclapp instance')
|
|
1193
1341
|
.help('h')
|
|
1194
1342
|
.alias('v', 'version')
|
|
@@ -1228,36 +1376,47 @@ const cli = async () => {
|
|
|
1228
1376
|
type: 'string',
|
|
1229
1377
|
choices: ['browser', 'browser.rx', 'node', 'node.rx']
|
|
1230
1378
|
})
|
|
1231
|
-
.option('
|
|
1232
|
-
|
|
1233
|
-
describe: 'Include deprecated functions and services',
|
|
1379
|
+
.option('use-query-language', {
|
|
1380
|
+
describe: 'Generate the new where property for some and count queries',
|
|
1234
1381
|
type: 'boolean'
|
|
1382
|
+
})
|
|
1383
|
+
.option('apiVersion', {
|
|
1384
|
+
describe: 'Specify the api version (only needed when not using a local file)',
|
|
1385
|
+
type: 'string'
|
|
1235
1386
|
})
|
|
1236
1387
|
.epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
|
|
1237
1388
|
if (argv.fromEnv) {
|
|
1238
1389
|
config();
|
|
1239
1390
|
}
|
|
1240
1391
|
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;
|
|
1392
|
+
const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, apiVersion, _: [src = WECLAPP_BACKEND_URL] } = argv;
|
|
1242
1393
|
const options = {
|
|
1243
1394
|
deprecated,
|
|
1244
1395
|
generateUnique: argv.generateUnique ?? false,
|
|
1245
|
-
target: argv.target ?? Target.BROWSER_PROMISES
|
|
1396
|
+
target: argv.target ?? Target.BROWSER_PROMISES,
|
|
1397
|
+
useQueryLanguage: argv.useQueryLanguage ?? false
|
|
1246
1398
|
};
|
|
1247
|
-
if (typeof src === 'number') {
|
|
1248
|
-
return Promise.reject('Expected string as command');
|
|
1399
|
+
if (!src || typeof src === 'number') {
|
|
1400
|
+
return Promise.reject(new Error('Expected string as command'));
|
|
1249
1401
|
}
|
|
1250
1402
|
if (!Object.values(Target).includes(options.target)) {
|
|
1251
1403
|
logger.errorLn(`Unknown target: ${options.target}. Possible values are ${Object.values(Target).join(', ')}`);
|
|
1252
|
-
return Promise.reject();
|
|
1404
|
+
return Promise.reject(new Error());
|
|
1253
1405
|
}
|
|
1254
1406
|
if (await stat(src).catch(() => false)) {
|
|
1255
|
-
logger.infoLn(`Source is a file
|
|
1407
|
+
logger.infoLn(`Source is a file`);
|
|
1256
1408
|
const content = JSON.parse(await readFile(src, 'utf-8'));
|
|
1257
1409
|
return { cache, content, options };
|
|
1258
1410
|
}
|
|
1411
|
+
logger.infoLn(`Source is a URL`);
|
|
1412
|
+
if (!key) {
|
|
1413
|
+
return Promise.reject(new Error('API key is missing'));
|
|
1414
|
+
}
|
|
1415
|
+
if (!apiVersion) {
|
|
1416
|
+
return Promise.reject(new Error('API version is missing'));
|
|
1417
|
+
}
|
|
1259
1418
|
const url = new URL(src.startsWith('http') ? src : `https://${src}`);
|
|
1260
|
-
url.pathname =
|
|
1419
|
+
url.pathname = `/webapp/api/${apiVersion}/meta/openapi.json`;
|
|
1261
1420
|
if (query?.length) {
|
|
1262
1421
|
for (const param of query.split(',')) {
|
|
1263
1422
|
const [name, value] = param.split('=');
|
|
@@ -1265,11 +1424,11 @@ const cli = async () => {
|
|
|
1265
1424
|
}
|
|
1266
1425
|
}
|
|
1267
1426
|
const content = await fetch(url.toString(), {
|
|
1268
|
-
headers: {
|
|
1269
|
-
}).then(res => res.ok ? res.json() : undefined);
|
|
1427
|
+
headers: { Accept: 'application/json', AuthenticationToken: key }
|
|
1428
|
+
}).then((res) => (res.ok ? res.json() : undefined));
|
|
1270
1429
|
if (!content) {
|
|
1271
1430
|
logger.errorLn(`Couldn't fetch file ${url.toString()} `);
|
|
1272
|
-
return Promise.reject();
|
|
1431
|
+
return Promise.reject(new Error());
|
|
1273
1432
|
}
|
|
1274
1433
|
else {
|
|
1275
1434
|
logger.infoLn(`Use remote file: ${url.toString()}`);
|
|
@@ -1277,50 +1436,59 @@ const cli = async () => {
|
|
|
1277
1436
|
return { cache, content, options };
|
|
1278
1437
|
};
|
|
1279
1438
|
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1439
|
+
const workingDir = resolve(currentDirname(), './sdk');
|
|
1440
|
+
const cacheDir = resolve(currentDirname(), './.cache');
|
|
1282
1441
|
void (async () => {
|
|
1283
1442
|
const start = process.hrtime.bigint();
|
|
1284
|
-
const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
|
|
1285
1443
|
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);
|
|
1444
|
+
const workingDirPath = async (...paths) => {
|
|
1445
|
+
const fullPath = resolve(workingDir, ...paths);
|
|
1446
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
1293
1447
|
return fullPath;
|
|
1294
1448
|
};
|
|
1449
|
+
// Resolve cache dir and key
|
|
1450
|
+
const cacheKey = hash([pkg.version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
|
|
1451
|
+
const cachedSdkDir = resolve(cacheDir, cacheKey);
|
|
1452
|
+
// Remove old SDK
|
|
1453
|
+
await rm(workingDir, { recursive: true, force: true });
|
|
1295
1454
|
if (useCache) {
|
|
1296
1455
|
logger.infoLn(`Cache ID: ${cacheKey}`);
|
|
1297
1456
|
}
|
|
1298
|
-
if (useCache && await stat(
|
|
1299
|
-
|
|
1457
|
+
if (useCache && (await stat(cachedSdkDir).catch(() => false))) {
|
|
1458
|
+
// Copy cached SDK to working dir
|
|
1459
|
+
logger.successLn(`Cache match! (${cachedSdkDir})`);
|
|
1460
|
+
await cp(cachedSdkDir, workingDir, { recursive: true });
|
|
1300
1461
|
}
|
|
1301
1462
|
else {
|
|
1302
|
-
//
|
|
1303
|
-
await writeFile(await
|
|
1463
|
+
// Write openapi.json file
|
|
1464
|
+
await writeFile(await workingDirPath('openapi.json'), JSON.stringify(doc, null, 2));
|
|
1304
1465
|
logger.infoLn(`Generate sdk (target: ${options.target})`);
|
|
1305
|
-
// Generate
|
|
1466
|
+
// Generate and write SDK (index.ts)
|
|
1306
1467
|
const sdk = generate(doc, options);
|
|
1307
|
-
await writeFile(await
|
|
1308
|
-
// Bundle
|
|
1468
|
+
await writeFile(await workingDirPath('src', 'index.ts'), sdk.trim() + '\n');
|
|
1469
|
+
// Bundle and write SDK
|
|
1309
1470
|
logger.infoLn('Bundle... (this may take some time)');
|
|
1310
|
-
await bundle(
|
|
1311
|
-
// Remove
|
|
1312
|
-
await
|
|
1471
|
+
await bundle(workingDir, options.target);
|
|
1472
|
+
// Remove index.ts (only bundle is required)
|
|
1473
|
+
await rm(await workingDirPath('src'), { recursive: true, force: true });
|
|
1474
|
+
if (useCache) {
|
|
1475
|
+
// Copy SDK to cache
|
|
1476
|
+
logger.successLn(`Caching SDK: (${cachedSdkDir})`);
|
|
1477
|
+
await mkdir(cachedSdkDir, { recursive: true });
|
|
1478
|
+
await cp(workingDir, cachedSdkDir, { recursive: true });
|
|
1479
|
+
}
|
|
1313
1480
|
}
|
|
1314
|
-
// Copy bundled SDK
|
|
1315
|
-
await cp(cacheDir, workingDirectory, { recursive: true });
|
|
1316
1481
|
// Print job summary
|
|
1317
1482
|
const duration = (process.hrtime.bigint() - start) / 1000000n;
|
|
1318
1483
|
logger.successLn(`SDK built in ${prettyMs(Number(duration))}`);
|
|
1319
1484
|
logger.printSummary();
|
|
1320
|
-
})()
|
|
1485
|
+
})()
|
|
1486
|
+
.catch((error) => {
|
|
1321
1487
|
logger.errorLn(`Fatal error:`);
|
|
1322
1488
|
/* eslint-disable no-console */
|
|
1323
1489
|
console.error(error);
|
|
1324
|
-
})
|
|
1325
|
-
|
|
1490
|
+
})
|
|
1491
|
+
.finally(() => {
|
|
1492
|
+
if (logger.errors)
|
|
1493
|
+
process.exit(1);
|
|
1326
1494
|
});
|