@weclapp/sdk 2.0.0-dev.3 → 2.0.0-dev.31
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 +371 -19
- package/dist/cli.js +497 -450
- package/package.json +37 -32
- package/tsconfig.sdk.json +14 -0
- package/tsconfig.lib.json +0 -17
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import { fileURLToPath } from 'url';
|
|
2
2
|
import { resolve, dirname } from 'path';
|
|
3
|
-
import { rmdir, stat, readFile, writeFile, rm, cp, mkdir } from 'fs/promises';
|
|
4
3
|
import { rollup } from 'rollup';
|
|
5
4
|
import terser from '@rollup/plugin-terser';
|
|
6
|
-
import ts from 'rollup
|
|
5
|
+
import ts from '@rollup/plugin-typescript';
|
|
7
6
|
import indentString from 'indent-string';
|
|
8
|
-
import { snakeCase,
|
|
7
|
+
import { snakeCase, camelCase, pascalCase } from 'change-case';
|
|
9
8
|
import chalk from 'chalk';
|
|
10
9
|
import { OpenAPIV3 } from 'openapi-types';
|
|
11
10
|
import { createHash } from 'crypto';
|
|
11
|
+
import { stat, readFile, rm, cp, writeFile, mkdir } from 'fs/promises';
|
|
12
12
|
import { config } from 'dotenv';
|
|
13
13
|
import yargs from 'yargs';
|
|
14
14
|
import { hideBin } from 'yargs/helpers';
|
|
15
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
15
16
|
import prettyMs from 'pretty-ms';
|
|
16
17
|
|
|
18
|
+
const currentDirname = () => {
|
|
19
|
+
// Go one level up as the CLI is inside a folder
|
|
20
|
+
return fileURLToPath(new URL('..', import.meta.url));
|
|
21
|
+
};
|
|
22
|
+
|
|
17
23
|
var Target;
|
|
18
24
|
(function (Target) {
|
|
19
25
|
Target["BROWSER_PROMISES"] = "browser";
|
|
@@ -34,13 +40,8 @@ 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
46
|
sourcemap: true,
|
|
46
47
|
banner: `/* weclapp sdk */`,
|
|
@@ -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\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) => {\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) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n unwrap: true,\n query: {\n ...flattenFilter(query?.filter),\n ...flattenOrFilter(query?.or),\n ...query?.params\n }\n })\n );\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> }\n) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n query: {\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 }).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n );\n";
|
|
140
|
+
|
|
141
|
+
var queriesWithQueryLanguage = "export type ComparisonOperator =\n | 'EQ'\n | 'NE'\n | 'LT'\n | 'GT'\n | 'LE'\n | 'GE'\n | 'LIKE';\n\nexport type ArrayOperator = 'IN';\n\nexport type NullOperator = 'NULL';\n\nexport type Operator = ComparisonOperator | ArrayOperator | NullOperator;\n\nexport type ModifierFunction = 'lower';\n\nexport type MapOperators<T> =\n | ({ [K in ComparisonOperator]?: T } & { [K in ArrayOperator]?: T[] } & {\n [K in NullOperator]?: never } & {\n [K in ModifierFunction]?: boolean\n })\n | ({ [K in ComparisonOperator]?: T } & { [K in ArrayOperator]?: T[] } & {\n [K in NullOperator]?: boolean } & {\n [K in ModifierFunction]?: never\n });\n\nexport type SingleFilterExpr<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? U extends Record<any, any>\n ? SingleFilterExpr<U> | { NOT?: SingleFilterExpr<U> }\n : MapOperators<U>\n : T[P] extends Record<any, any> | undefined\n ? SingleFilterExpr<T[P]> | { NOT?: SingleFilterExpr<T[P]> }\n : MapOperators<T[P]>;\n};\n\nexport type QueryFilter<T> = SingleFilterExpr<T> & {\n OR?: QueryFilter<T>[];\n AND?: QueryFilter<T>[];\n NOT?: QueryFilter<T>;\n};\n\nexport type CountQuery<F> = {\n where?: QueryFilter<F> & CustomAttributeFilter;\n};\n\nexport type SomeQuery<E, F, I, P> = {\n serializeNulls?: boolean;\n include?: QuerySelect<I>;\n properties?: P;\n where?: QueryFilter<F> & CustomAttributeFilter;\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 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 } 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) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n unwrap: true,\n query: {\n ...assembleFilterParam(query?.where),\n ...query?.params\n }\n })\n );\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> }\n) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n query: {\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 }).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n );\n";
|
|
142
|
+
|
|
143
|
+
var root = "export const raw = async (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n payload: RequestPayload = {}\n): Promise<any> => {\n const globalConfig = getGlobalConfig();\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(\n Object.entries(payload.query ?? {})\n .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 = await fetch(res);\n }\n res = (await interceptResponse(res)) ?? res;\n data =\n (!payload.forceBlob || !res.ok) && res.headers?.get('content-type')?.includes('application/json')\n ? await res.json()\n : await res.blob();\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) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'DELETE',\n query: { dryRun }\n }).then(() => undefined)\n );\n\nconst _create = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, data: any) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'POST',\n body: data\n })\n );\n\nconst _update = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n data: any,\n { ignoreMissingProperties, dryRun = false }: UpdateQuery = {}\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 })\n );\n\nconst _generic = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n method: RequestPayloadMethod,\n endpoint: string,\n payload?: GenericQuery<any, any>,\n forceBlob?: boolean\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method,\n forceBlob,\n body: payload?.body,\n query: payload?.params\n })\n );\n";
|
|
148
144
|
|
|
149
|
-
var
|
|
145
|
+
var unique = "const _unique = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, query?: UniqueQuery) =>\n wrapResponse(() => raw({ ...cfg, multiRequest: false }, endpoint, { query }));\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 = (target, apiVersion, options) => {
|
|
161
|
+
return generateStatements(resolveImports(target), `const apiVersion = ${apiVersion}`, resolveMappings(target), resolveBinaryClass(target), globalConfig, types, utils, root, options.useQueryLanguage ? queriesWithQueryLanguage : queriesWithFilter, options.generateUnique ? unique : '', multiRequest);
|
|
162
162
|
};
|
|
163
163
|
|
|
164
164
|
const transformKey = (s) => snakeCase(s).toUpperCase();
|
|
165
165
|
const generateEnum = (name, values) => {
|
|
166
|
-
const props = indent(values.map(v => `${transformKey(v)} = ${generateString(v)}`).join(',\n'));
|
|
166
|
+
const props = indent(values.map((v) => `${transformKey(v)} = ${generateString(v)}`).join(',\n'));
|
|
167
167
|
return `export enum ${name} {\n${props}\n}`;
|
|
168
168
|
};
|
|
169
169
|
|
|
@@ -200,13 +200,14 @@ const isRelatedEntitySchema = (v) => {
|
|
|
200
200
|
|
|
201
201
|
const generateEnums = (schemas) => {
|
|
202
202
|
const enums = new Map();
|
|
203
|
-
for (const [
|
|
203
|
+
for (const [schemaName, schema] of schemas) {
|
|
204
204
|
if (isEnumSchemaObject(schema)) {
|
|
205
|
-
const
|
|
206
|
-
if (!enums.has(
|
|
207
|
-
enums.set(
|
|
205
|
+
const enumName = loosePascalCase(schemaName);
|
|
206
|
+
if (!enums.has(enumName)) {
|
|
207
|
+
enums.set(enumName, {
|
|
208
|
+
name: enumName,
|
|
208
209
|
properties: schema.enum,
|
|
209
|
-
source: generateEnum(
|
|
210
|
+
source: generateEnum(enumName, schema.enum)
|
|
210
211
|
});
|
|
211
212
|
}
|
|
212
213
|
}
|
|
@@ -218,16 +219,13 @@ const concat = (strings, separator = ', ', maxLength = 80) => {
|
|
|
218
219
|
const joined = strings.join(separator);
|
|
219
220
|
if (joined.length > maxLength) {
|
|
220
221
|
const length = strings.length - 1;
|
|
221
|
-
return `\n${indent(strings
|
|
222
|
-
.map((value, index) => index === length ? value : `${(value + separator).trim()}\n`)
|
|
223
|
-
.join(''))}\n`;
|
|
222
|
+
return `\n${indent(strings.map((value, index) => (index === length ? value : `${(value + separator).trim()}\n`)).join(''))}\n`;
|
|
224
223
|
}
|
|
225
224
|
else {
|
|
226
225
|
return joined;
|
|
227
226
|
}
|
|
228
227
|
};
|
|
229
228
|
|
|
230
|
-
/* eslint-disable no-use-before-define */
|
|
231
229
|
const createReferenceType = (value) => ({
|
|
232
230
|
type: 'reference',
|
|
233
231
|
toString: () => loosePascalCase(value)
|
|
@@ -238,26 +236,28 @@ const createRawType = (value) => ({
|
|
|
238
236
|
});
|
|
239
237
|
const createArrayType = (value) => ({
|
|
240
238
|
type: 'array',
|
|
241
|
-
toString: () =>
|
|
239
|
+
toString: () => `(${value.toString()})[]`
|
|
242
240
|
});
|
|
243
241
|
const createTupleType = (value) => ({
|
|
244
242
|
type: 'tuple',
|
|
245
|
-
toString: () => concat([...new Set(value.map(v => typeof v === 'string' ? `'${v}'` : v.toString()))], ' | ')
|
|
243
|
+
toString: () => concat([...new Set(value.map((v) => (typeof v === 'string' ? `'${v}'` : v.toString())))], ' | ')
|
|
246
244
|
});
|
|
247
245
|
const createObjectType = (value, required = []) => ({
|
|
248
246
|
type: 'object',
|
|
249
247
|
isFullyOptional: () => {
|
|
250
|
-
return !required.length &&
|
|
251
|
-
.
|
|
252
|
-
|
|
248
|
+
return (!required.length &&
|
|
249
|
+
Object.values(value)
|
|
250
|
+
.filter((v) => v?.type === 'object')
|
|
251
|
+
.every((v) => v.isFullyOptional()));
|
|
253
252
|
},
|
|
254
253
|
toString: (propagateOptionalProperties = false) => {
|
|
255
254
|
const properties = Object.entries(value)
|
|
256
|
-
.filter(v => v[1])
|
|
257
|
-
.map(v => {
|
|
255
|
+
.filter((v) => v[1])
|
|
256
|
+
.map((v) => {
|
|
258
257
|
const name = v[0];
|
|
259
258
|
const value = v[1];
|
|
260
|
-
const isRequired = required.includes(name) ||
|
|
259
|
+
const isRequired = required.includes(name) ||
|
|
260
|
+
(value.type === 'object' && !value.isFullyOptional() && propagateOptionalProperties);
|
|
261
261
|
return `${name + (isRequired ? '' : '?')}: ${value.toString()};`;
|
|
262
262
|
});
|
|
263
263
|
return properties.length ? `{\n${indent(properties.join('\n'))}\n}` : '{}';
|
|
@@ -286,8 +286,7 @@ const convertToTypeScriptType = (schema, property) => {
|
|
|
286
286
|
return createRawType('boolean');
|
|
287
287
|
case 'object': {
|
|
288
288
|
const { properties = {}, required = [] } = schema;
|
|
289
|
-
return createObjectType(Object.fromEntries(Object.entries(properties)
|
|
290
|
-
.map(v => [v[0], convertToTypeScriptType(v[1])])), required);
|
|
289
|
+
return createObjectType(Object.fromEntries(Object.entries(properties).map((v) => [v[0], convertToTypeScriptType(v[1])])), required);
|
|
291
290
|
}
|
|
292
291
|
case 'array':
|
|
293
292
|
return createArrayType(convertToTypeScriptType(schema.items, property));
|
|
@@ -308,14 +307,19 @@ const setEntityEnumProperty = (enums, prop, meta) => {
|
|
|
308
307
|
}
|
|
309
308
|
};
|
|
310
309
|
const extractPropertyMetaData = (enums, meta, prop) => {
|
|
311
|
-
const result = {
|
|
310
|
+
const result = {
|
|
311
|
+
service: meta.service,
|
|
312
|
+
entity: meta.entity
|
|
313
|
+
};
|
|
312
314
|
if (isReferenceObject(prop)) {
|
|
313
315
|
setEntityEnumProperty(enums, prop, result);
|
|
314
316
|
result.type = 'reference';
|
|
315
317
|
return result;
|
|
316
318
|
}
|
|
317
|
-
result.format = prop.format;
|
|
318
319
|
result.type = prop.type;
|
|
320
|
+
result.format = prop.format;
|
|
321
|
+
result.maxLength = prop.maxLength;
|
|
322
|
+
result.pattern = prop.pattern;
|
|
319
323
|
if (isArraySchemaObject(prop)) {
|
|
320
324
|
if (isReferenceObject(prop.items)) {
|
|
321
325
|
setEntityEnumProperty(enums, prop.items, result);
|
|
@@ -335,12 +339,12 @@ const generateType = (name, value) => {
|
|
|
335
339
|
return `export type ${name} = ${value.trim()};`;
|
|
336
340
|
};
|
|
337
341
|
|
|
338
|
-
const arrayify = (v) => Array.isArray(v) ? v : [v];
|
|
342
|
+
const arrayify = (v) => (Array.isArray(v) ? v : [v]);
|
|
339
343
|
|
|
340
344
|
const generateInterfaceProperties = (entries) => {
|
|
341
345
|
const properties = entries
|
|
342
|
-
.filter(v => v.type !== undefined)
|
|
343
|
-
.filter((value, index, array) => array.findIndex(v => v.name === value.name) === index)
|
|
346
|
+
.filter((v) => v.type !== undefined)
|
|
347
|
+
.filter((value, index, array) => array.findIndex((v) => v.name === value.name) === index)
|
|
344
348
|
.map(({ name, type, required, readonly, comment }) => {
|
|
345
349
|
const cmd = comment ? `${generateInlineComment(comment)}\n` : '';
|
|
346
350
|
const req = required ? '' : '?';
|
|
@@ -369,34 +373,25 @@ const generateEntities = (schemas, enums) => {
|
|
|
369
373
|
if (isEnumSchemaObject(schema)) {
|
|
370
374
|
continue;
|
|
371
375
|
}
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
const filterInterface = [];
|
|
376
|
-
// Referenced entities and property-to-referenced-entity mapping
|
|
377
|
-
const referenceInterface = [];
|
|
378
|
-
const referenceMappingsInterface = [];
|
|
376
|
+
const entityInterfaceName = loosePascalCase(schemaName);
|
|
377
|
+
let parentEntityInterfaceName = undefined;
|
|
378
|
+
const entityInterfaceProperties = [];
|
|
379
379
|
const properties = new Map();
|
|
380
|
-
// The parent entity
|
|
381
|
-
let extend = undefined;
|
|
382
380
|
const processProperties = (props = {}) => {
|
|
383
381
|
for (const [name, property] of Object.entries(props)) {
|
|
384
382
|
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
383
|
const type = convertToTypeScriptType(property, name).toString();
|
|
394
|
-
const comment = isNonArraySchemaObject(property)
|
|
395
|
-
property.deprecated
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
384
|
+
const comment = isNonArraySchemaObject(property)
|
|
385
|
+
? property.deprecated
|
|
386
|
+
? '@deprecated will be removed.'
|
|
387
|
+
: property.format
|
|
388
|
+
? `format: ${property.format}`
|
|
389
|
+
: undefined
|
|
390
|
+
: undefined;
|
|
391
|
+
entityInterfaceProperties.push({
|
|
392
|
+
name,
|
|
393
|
+
type,
|
|
394
|
+
comment,
|
|
400
395
|
required: meta.required,
|
|
401
396
|
readonly: !isReferenceObject(property) && property.readOnly
|
|
402
397
|
});
|
|
@@ -406,7 +401,7 @@ const generateEntities = (schemas, enums) => {
|
|
|
406
401
|
if (schema.allOf?.length) {
|
|
407
402
|
for (const item of schema.allOf) {
|
|
408
403
|
if (isReferenceObject(item)) {
|
|
409
|
-
|
|
404
|
+
parentEntityInterfaceName = convertToTypeScriptType(item).toString();
|
|
410
405
|
}
|
|
411
406
|
else if (isObjectSchemaObject(item)) {
|
|
412
407
|
processProperties(item.properties);
|
|
@@ -414,11 +409,11 @@ const generateEntities = (schemas, enums) => {
|
|
|
414
409
|
}
|
|
415
410
|
}
|
|
416
411
|
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
412
|
entities.set(schemaName, {
|
|
419
|
-
|
|
413
|
+
name: schemaName,
|
|
420
414
|
properties,
|
|
421
|
-
|
|
415
|
+
parentName: parentEntityInterfaceName ? camelCase(parentEntityInterfaceName) : undefined,
|
|
416
|
+
source: generateStatements(generateInterface(entityInterfaceName, entityInterfaceProperties, parentEntityInterfaceName))
|
|
422
417
|
});
|
|
423
418
|
}
|
|
424
419
|
return entities;
|
|
@@ -429,13 +424,10 @@ const generateEntities = (schemas, enums) => {
|
|
|
429
424
|
* @param s String to pluralize.
|
|
430
425
|
*/
|
|
431
426
|
const pluralize = (s) => {
|
|
432
|
-
return s.endsWith('s') ? s :
|
|
433
|
-
s.endsWith('y') ? `${s.slice(0, -1)}ies` :
|
|
434
|
-
`${s}s`;
|
|
427
|
+
return s.endsWith('s') ? s : s.endsWith('y') ? `${s.slice(0, -1)}ies` : `${s}s`;
|
|
435
428
|
};
|
|
436
429
|
|
|
437
|
-
|
|
438
|
-
const logger = new class {
|
|
430
|
+
const logger = new (class {
|
|
439
431
|
active = true;
|
|
440
432
|
warnings = 0;
|
|
441
433
|
errors = 0;
|
|
@@ -483,21 +475,24 @@ const logger = new class {
|
|
|
483
475
|
printSummary() {
|
|
484
476
|
const format = (v, name, fail, ok) => {
|
|
485
477
|
const color = v ? fail : ok;
|
|
486
|
-
return v === 0
|
|
487
|
-
|
|
478
|
+
return v === 0
|
|
479
|
+
? `${color('zero')} ${pluralize(name)}`
|
|
480
|
+
: v === 1
|
|
481
|
+
? `${color('one')} ${name}`
|
|
482
|
+
: `${color(v)} ${pluralize(name)}`;
|
|
488
483
|
};
|
|
489
484
|
const warnings = format(this.warnings, 'warning', chalk.yellowBright, chalk.greenBright);
|
|
490
485
|
const errors = format(this.errors, 'error', chalk.redBright, chalk.greenBright);
|
|
491
486
|
const info = `Finished with ${warnings} and ${errors}.`;
|
|
492
487
|
this[this.errors ? 'errorLn' : this.warnings ? 'warnLn' : 'successLn'](info);
|
|
493
488
|
}
|
|
494
|
-
};
|
|
489
|
+
})();
|
|
495
490
|
|
|
496
491
|
/**
|
|
497
|
-
* ROOT
|
|
498
|
-
* COUNT
|
|
499
|
-
* ENTITY
|
|
500
|
-
* SPECIAL_ROOT
|
|
492
|
+
* ROOT => /article
|
|
493
|
+
* COUNT => /article/count
|
|
494
|
+
* ENTITY => /article/{id}
|
|
495
|
+
* SPECIAL_ROOT => /article/generateImage
|
|
501
496
|
* SPECIAL_ENTITY => /article/id/{id}/generateImag
|
|
502
497
|
*/
|
|
503
498
|
var WeclappEndpointType;
|
|
@@ -509,23 +504,33 @@ var WeclappEndpointType;
|
|
|
509
504
|
WeclappEndpointType["GENERIC_ENTITY"] = "GENERIC_ENTITY";
|
|
510
505
|
})(WeclappEndpointType || (WeclappEndpointType = {}));
|
|
511
506
|
const parseEndpointPath = (path) => {
|
|
512
|
-
const [,
|
|
513
|
-
if (!
|
|
507
|
+
const [, service, ...rest] = path.split('/');
|
|
508
|
+
if (!service) {
|
|
514
509
|
return undefined;
|
|
515
510
|
}
|
|
516
511
|
if (!rest.length) {
|
|
517
|
-
return { path,
|
|
512
|
+
return { path, service, type: WeclappEndpointType.ROOT };
|
|
518
513
|
}
|
|
519
514
|
else if (rest[0] === 'count') {
|
|
520
|
-
return { path,
|
|
515
|
+
return { path, service, type: WeclappEndpointType.COUNT };
|
|
521
516
|
}
|
|
522
517
|
else if (rest[0] === 'id') {
|
|
523
|
-
return rest.length === 2
|
|
524
|
-
{ path,
|
|
525
|
-
|
|
518
|
+
return rest.length === 2
|
|
519
|
+
? { path, service, type: WeclappEndpointType.ENTITY }
|
|
520
|
+
: {
|
|
521
|
+
path,
|
|
522
|
+
service,
|
|
523
|
+
method: rest[2],
|
|
524
|
+
type: WeclappEndpointType.GENERIC_ENTITY
|
|
525
|
+
};
|
|
526
526
|
}
|
|
527
527
|
else if (rest.length === 1) {
|
|
528
|
-
return {
|
|
528
|
+
return {
|
|
529
|
+
path,
|
|
530
|
+
service,
|
|
531
|
+
method: rest[1],
|
|
532
|
+
type: WeclappEndpointType.GENERIC_ROOT
|
|
533
|
+
};
|
|
529
534
|
}
|
|
530
535
|
return undefined;
|
|
531
536
|
};
|
|
@@ -547,48 +552,50 @@ const convertParametersToSchema = (parameters = []) => {
|
|
|
547
552
|
if (isParameterObject(param) && param.in === 'query') {
|
|
548
553
|
if (param.schema) {
|
|
549
554
|
properties.push([param.name, param.schema]);
|
|
550
|
-
|
|
555
|
+
if (param.required)
|
|
556
|
+
required.push(param.name);
|
|
551
557
|
}
|
|
552
558
|
}
|
|
553
559
|
}
|
|
554
560
|
return {
|
|
555
|
-
type: 'object',
|
|
561
|
+
type: 'object',
|
|
562
|
+
required,
|
|
556
563
|
properties: Object.fromEntries(properties)
|
|
557
564
|
};
|
|
558
565
|
};
|
|
559
566
|
|
|
560
|
-
const functionName$5 = 'count';
|
|
561
567
|
const generateCountEndpoint = ({ aliases, path, target, endpoint }) => {
|
|
562
|
-
const
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
const
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
568
|
+
const functionName = 'count';
|
|
569
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
570
|
+
const entity = aliases.get(endpoint.service) ?? pascalCase(endpoint.service);
|
|
571
|
+
const parametersTypeName = `${functionTypeName}_Parameters`;
|
|
572
|
+
const parametersType = createObjectType({
|
|
573
|
+
params: convertToTypeScriptType(convertParametersToSchema(path.parameters))
|
|
574
|
+
});
|
|
575
|
+
const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, true);
|
|
576
|
+
const filterTypeName = `${functionTypeName}_Filter`;
|
|
577
|
+
const filterTypeSource = generateInterfaceType(filterTypeName, [], [entity]);
|
|
578
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
579
|
+
type: functionTypeName,
|
|
580
|
+
params: [
|
|
581
|
+
`query${parametersType.isFullyOptional() ? '?' : ''}: CountQuery<${filterTypeName}>${path.parameters?.length ? ' & ' + parametersTypeName : ''}`
|
|
582
|
+
],
|
|
583
|
+
returns: `${resolveResponseType(target)}<number>`
|
|
570
584
|
});
|
|
571
585
|
const functionSource = generateArrowFunction({
|
|
572
|
-
name: functionName
|
|
573
|
-
signature:
|
|
574
|
-
returns: `_${functionName
|
|
586
|
+
name: functionName,
|
|
587
|
+
signature: functionTypeName,
|
|
588
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query)`,
|
|
575
589
|
params: ['query']
|
|
576
590
|
});
|
|
577
|
-
const interfaceSource = generateArrowFunctionType({
|
|
578
|
-
type: interfaceName,
|
|
579
|
-
params: [`query${parameters.isFullyOptional() ? '?' : ''}: CountQuery<${entityFilter}> & ${entityParameters}`],
|
|
580
|
-
returns: `${resolveResponseType(target)}<number>`
|
|
581
|
-
});
|
|
582
591
|
return {
|
|
583
592
|
entity,
|
|
584
|
-
name: functionName
|
|
585
|
-
type: { name:
|
|
586
|
-
func: { name: functionName
|
|
593
|
+
name: functionName,
|
|
594
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
595
|
+
func: { name: functionName, source: functionSource },
|
|
587
596
|
interfaces: [
|
|
588
|
-
{
|
|
589
|
-
|
|
590
|
-
source: generateInterfaceFromObject(entityParameters, parameters, true)
|
|
591
|
-
}
|
|
597
|
+
...(path.parameters?.length ? [{ name: parametersTypeName, source: parametersTypeSource }] : []),
|
|
598
|
+
{ name: filterTypeName, source: filterTypeSource }
|
|
592
599
|
]
|
|
593
600
|
};
|
|
594
601
|
};
|
|
@@ -610,39 +617,33 @@ const generateRequestBodyType = ({ requestBody }) => {
|
|
|
610
617
|
return generateBodyType(requestBody) ?? createRawType('unknown');
|
|
611
618
|
};
|
|
612
619
|
|
|
613
|
-
const resolveBodyType = ({ responses }) => Object.entries(responses)
|
|
614
|
-
.filter(v => v[0].startsWith('2'))[0]?.[1];
|
|
620
|
+
const resolveBodyType = ({ responses }) => Object.entries(responses).filter((v) => v[0].startsWith('2'))[0]?.[1];
|
|
615
621
|
const generateResponseBodyType = (object) => generateBodyType(resolveBodyType(object)) ?? createRawType('void');
|
|
616
622
|
|
|
617
|
-
const functionName$4 = 'create';
|
|
618
623
|
const generateCreateEndpoint = ({ target, path, endpoint }) => {
|
|
619
|
-
const
|
|
620
|
-
const
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
signature: interfaceName,
|
|
624
|
-
returns: `_${functionName$4}(cfg, ${generateString(endpoint.path)}, data)`,
|
|
625
|
-
params: ['data']
|
|
626
|
-
});
|
|
627
|
-
const interfaceSource = generateArrowFunctionType({
|
|
628
|
-
type: interfaceName,
|
|
624
|
+
const functionName = 'create';
|
|
625
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
626
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
627
|
+
type: functionTypeName,
|
|
629
628
|
params: [`data: DeepPartial<${generateRequestBodyType(path).toString()}>`],
|
|
630
629
|
returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
|
|
631
630
|
});
|
|
631
|
+
const functionSource = generateArrowFunction({
|
|
632
|
+
name: functionName,
|
|
633
|
+
signature: functionTypeName,
|
|
634
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, data)`,
|
|
635
|
+
params: ['data']
|
|
636
|
+
});
|
|
632
637
|
return {
|
|
633
|
-
entity,
|
|
634
|
-
name: functionName
|
|
635
|
-
type: { name:
|
|
636
|
-
func: { name: functionName
|
|
638
|
+
entity: pascalCase(endpoint.service),
|
|
639
|
+
name: functionName,
|
|
640
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
641
|
+
func: { name: functionName, source: functionSource }
|
|
637
642
|
};
|
|
638
643
|
};
|
|
639
644
|
|
|
640
645
|
const generateGenericFunctionName = (path, suffix = '', prefix = '') => {
|
|
641
|
-
return camelCase(`${prefix}_` +
|
|
642
|
-
path
|
|
643
|
-
.replace(/.*\//, '')
|
|
644
|
-
.replace(/\W+/, '_')
|
|
645
|
-
.replace(/[_]+/, '_') + `_${suffix}`);
|
|
646
|
+
return camelCase(`${prefix}_` + path.replace(/.*\//, '').replace(/\W+/, '_').replace(/[_]+/, '_') + `_${suffix}`);
|
|
646
647
|
};
|
|
647
648
|
|
|
648
649
|
const insertPathPlaceholder = (path, record) => {
|
|
@@ -650,37 +651,33 @@ const insertPathPlaceholder = (path, record) => {
|
|
|
650
651
|
};
|
|
651
652
|
|
|
652
653
|
const wrapBody = (type, target) => {
|
|
653
|
-
return type.toString() === 'binary' ?
|
|
654
|
-
createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') :
|
|
655
|
-
type; // node-fetch returns a Blob as well
|
|
654
|
+
return type.toString() === 'binary' ? createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') : type; // node-fetch returns a Blob as well
|
|
656
655
|
};
|
|
657
656
|
const generateGenericEndpoint = (suffix) => ({ target, method, path, endpoint }) => {
|
|
658
657
|
const functionName = generateGenericFunctionName(endpoint.path, suffix, method);
|
|
659
|
-
const
|
|
660
|
-
const
|
|
661
|
-
const entityQuery = `${interfaceName}_Query`;
|
|
658
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
659
|
+
const entityQuery = `${functionTypeName}_Query`;
|
|
662
660
|
const hasId = endpoint.path.includes('{id}');
|
|
663
661
|
const params = createObjectType({
|
|
664
662
|
params: convertToTypeScriptType(convertParametersToSchema(path.parameters)),
|
|
665
663
|
body: method === 'get' ? undefined : wrapBody(generateRequestBodyType(path), target)
|
|
666
664
|
});
|
|
667
665
|
const responseBody = generateResponseBodyType(path);
|
|
668
|
-
const
|
|
666
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
667
|
+
type: functionTypeName,
|
|
668
|
+
params: [...(hasId ? ['id: string'] : []), `query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`],
|
|
669
|
+
returns: `${resolveResponseType(target)}<${wrapBody(responseBody, target).toString()}>`
|
|
670
|
+
});
|
|
669
671
|
const functionSource = generateArrowFunction({
|
|
670
672
|
name: functionName,
|
|
671
|
-
signature:
|
|
673
|
+
signature: functionTypeName,
|
|
672
674
|
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()}>`
|
|
675
|
+
returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${String(responseBody.toString() === 'binary')})`
|
|
679
676
|
});
|
|
680
677
|
return {
|
|
681
|
-
entity,
|
|
678
|
+
entity: pascalCase(endpoint.service),
|
|
682
679
|
name: functionName,
|
|
683
|
-
type: { name:
|
|
680
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
684
681
|
func: { name: functionName, source: functionSource },
|
|
685
682
|
interfaces: [
|
|
686
683
|
{
|
|
@@ -691,35 +688,38 @@ const generateGenericEndpoint = (suffix) => ({ target, method, path, endpoint })
|
|
|
691
688
|
};
|
|
692
689
|
};
|
|
693
690
|
|
|
694
|
-
const functionName$3 = 'remove';
|
|
695
691
|
const generateRemoveEndpoint = ({ target, endpoint }) => {
|
|
696
|
-
const
|
|
697
|
-
const
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
returns: `_${functionName$3}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`)`,
|
|
702
|
-
params: ['id']
|
|
703
|
-
});
|
|
704
|
-
const interfaceSource = generateArrowFunctionType({
|
|
705
|
-
type: interfaceName,
|
|
706
|
-
params: ['id: string'],
|
|
692
|
+
const functionName = 'remove';
|
|
693
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
694
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
695
|
+
type: functionTypeName,
|
|
696
|
+
params: ['id: string', 'options?: RemoveQuery'],
|
|
707
697
|
returns: `${resolveResponseType(target)}<void>`
|
|
708
698
|
});
|
|
699
|
+
const functionSource = generateArrowFunction({
|
|
700
|
+
name: functionName,
|
|
701
|
+
signature: functionTypeName,
|
|
702
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options)`,
|
|
703
|
+
params: ['id', 'options?: RemoveQuery']
|
|
704
|
+
});
|
|
709
705
|
return {
|
|
710
|
-
entity,
|
|
711
|
-
name: functionName
|
|
712
|
-
type: { name:
|
|
713
|
-
func: { name: functionName
|
|
706
|
+
entity: pascalCase(endpoint.service),
|
|
707
|
+
name: functionName,
|
|
708
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
709
|
+
func: { name: functionName, source: functionSource }
|
|
714
710
|
};
|
|
715
711
|
};
|
|
716
712
|
|
|
717
|
-
const functionName$2 = 'some';
|
|
718
713
|
const excludedParameters = [
|
|
719
|
-
'page',
|
|
720
|
-
'
|
|
714
|
+
'page',
|
|
715
|
+
'pageSize',
|
|
716
|
+
'sort',
|
|
717
|
+
'serializeNulls',
|
|
718
|
+
'properties',
|
|
719
|
+
'includeReferencedEntities',
|
|
720
|
+
'additionalProperties'
|
|
721
721
|
];
|
|
722
|
-
const
|
|
722
|
+
const resolveAdditionalPropertiesSchema = (path) => {
|
|
723
723
|
const body = resolveBodyType(path);
|
|
724
724
|
if (isResponseObject(body)) {
|
|
725
725
|
const schema = body?.content?.['application/json']?.schema;
|
|
@@ -732,115 +732,161 @@ const resolveAdditionalProperties = (path) => {
|
|
|
732
732
|
}
|
|
733
733
|
return undefined;
|
|
734
734
|
};
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
735
|
+
const resolveReferences = (entity, entities) => {
|
|
736
|
+
const references = [];
|
|
737
|
+
const generatedEntity = entities.get(entity);
|
|
738
|
+
if (generatedEntity) {
|
|
739
|
+
for (const [property, propertyMetaData] of generatedEntity.properties) {
|
|
740
|
+
if (propertyMetaData.service) {
|
|
741
|
+
references.push({
|
|
742
|
+
name: property,
|
|
743
|
+
type: generateString(propertyMetaData.service),
|
|
744
|
+
required: true
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (generatedEntity.parentName) {
|
|
749
|
+
references.push(...resolveReferences(generatedEntity.parentName, entities));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return references;
|
|
753
|
+
};
|
|
754
|
+
const resolveReferencedEntities = (entity, entities) => {
|
|
755
|
+
const referencedEntities = [];
|
|
756
|
+
const generatedEntity = entities.get(entity);
|
|
757
|
+
if (generatedEntity) {
|
|
758
|
+
for (const [, propertyMetaData] of generatedEntity.properties) {
|
|
759
|
+
if (propertyMetaData.entity && propertyMetaData.service) {
|
|
760
|
+
referencedEntities.push({
|
|
761
|
+
name: propertyMetaData.service,
|
|
762
|
+
type: `${pascalCase(propertyMetaData.entity)}[]`,
|
|
763
|
+
required: true
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (generatedEntity.parentName) {
|
|
768
|
+
referencedEntities.push(...resolveReferencedEntities(generatedEntity.parentName, entities));
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return referencedEntities;
|
|
772
|
+
};
|
|
773
|
+
const generateSomeEndpoint = ({ endpoint, target, path, entities, aliases }) => {
|
|
774
|
+
const functionName = 'some';
|
|
775
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
776
|
+
const entity = aliases.get(endpoint.service) ?? pascalCase(endpoint.service);
|
|
777
|
+
const parametersTypeName = `${functionTypeName}_Parameters`;
|
|
778
|
+
const parameters = path.parameters?.filter((v) => (isParameterObject(v) ? !excludedParameters.includes(v.name) : false)) ?? [];
|
|
779
|
+
const parametersType = createObjectType({
|
|
780
|
+
params: convertToTypeScriptType(convertParametersToSchema(parameters))
|
|
753
781
|
});
|
|
754
|
-
const
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
782
|
+
const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, true);
|
|
783
|
+
const filterTypeName = `${functionTypeName}_Filter`;
|
|
784
|
+
const filterTypeSource = generateInterfaceType(filterTypeName, [], [entity]);
|
|
785
|
+
const referencesTypeName = `${functionTypeName}_References`;
|
|
786
|
+
const referencesTypeSource = generateInterfaceType(referencesTypeName, resolveReferences(endpoint.service, entities));
|
|
787
|
+
const additionalPropertyTypeName = `${functionTypeName}_AdditionalProperty`;
|
|
788
|
+
const additionalPropertyTypeSource = generateType(additionalPropertyTypeName, 'string');
|
|
789
|
+
const queryTypeName = `${functionTypeName}_Query`;
|
|
790
|
+
const queryTypeSource = generateType(queryTypeName, `SomeQuery<${entity}, ${filterTypeName}, ${referencesTypeName}, ${additionalPropertyTypeName}> & ${parametersTypeName}`);
|
|
791
|
+
const referencedEntitiesTypeName = `${functionTypeName}_ReferencedEntities`;
|
|
792
|
+
const referencedEntitiesTypeSource = generateInterfaceType(referencedEntitiesTypeName, resolveReferencedEntities(endpoint.service, entities));
|
|
793
|
+
const additionalPropertiesTypeName = `${functionTypeName}_AdditionalProperties`;
|
|
794
|
+
const additionalPropertiesSchema = resolveAdditionalPropertiesSchema(path);
|
|
795
|
+
const additionalPropertiesTypeSource = generateType(additionalPropertiesTypeName, additionalPropertiesSchema ? convertToTypeScriptType(additionalPropertiesSchema).toString() : '{}');
|
|
796
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
797
|
+
type: functionTypeName,
|
|
798
|
+
params: [`query${parametersType.isFullyOptional() ? '?' : ''}: ${queryTypeName}`],
|
|
799
|
+
returns: `${resolveResponseType(target)}<SomeQueryReturn<${entity}, ${referencedEntitiesTypeName}, ${additionalPropertiesTypeName}>>`
|
|
763
800
|
});
|
|
764
801
|
const functionSource = generateArrowFunction({
|
|
765
|
-
name: functionName
|
|
766
|
-
signature:
|
|
767
|
-
returns: `_${functionName
|
|
802
|
+
name: functionName,
|
|
803
|
+
signature: functionTypeName,
|
|
804
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query)`,
|
|
768
805
|
params: ['query']
|
|
769
806
|
});
|
|
770
807
|
return {
|
|
771
808
|
entity,
|
|
772
|
-
name: functionName
|
|
773
|
-
type: { name:
|
|
774
|
-
func: { name: functionName
|
|
809
|
+
name: functionName,
|
|
810
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
811
|
+
func: { name: functionName, source: functionSource },
|
|
775
812
|
interfaces: [
|
|
776
|
-
{
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
813
|
+
{ name: parametersTypeName, source: parametersTypeSource },
|
|
814
|
+
{ name: filterTypeName, source: filterTypeSource },
|
|
815
|
+
{ name: referencesTypeName, source: referencesTypeSource },
|
|
816
|
+
{ name: additionalPropertyTypeName, source: additionalPropertyTypeSource },
|
|
817
|
+
{ name: queryTypeName, source: queryTypeSource },
|
|
818
|
+
{ name: referencedEntitiesTypeName, source: referencedEntitiesTypeSource },
|
|
819
|
+
{ name: additionalPropertiesTypeName, source: additionalPropertiesTypeSource }
|
|
780
820
|
]
|
|
781
821
|
};
|
|
782
822
|
};
|
|
783
823
|
|
|
784
|
-
const functionName$1 = 'unique';
|
|
785
824
|
const generateUniqueEndpoint = ({ target, path, endpoint }) => {
|
|
786
|
-
const
|
|
787
|
-
const
|
|
788
|
-
const
|
|
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,
|
|
825
|
+
const functionName = 'unique';
|
|
826
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
827
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
828
|
+
type: functionTypeName,
|
|
796
829
|
params: ['id: string', 'query?: Q'],
|
|
797
830
|
generics: ['Q extends UniqueQuery'],
|
|
798
831
|
returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
|
|
799
832
|
});
|
|
833
|
+
const functionSource = generateArrowFunction({
|
|
834
|
+
name: functionName,
|
|
835
|
+
signature: functionTypeName,
|
|
836
|
+
params: ['id', 'query'],
|
|
837
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query)`
|
|
838
|
+
});
|
|
800
839
|
return {
|
|
801
|
-
entity,
|
|
802
|
-
name: functionName
|
|
803
|
-
type: { name:
|
|
804
|
-
func: { name: functionName
|
|
840
|
+
entity: pascalCase(endpoint.service),
|
|
841
|
+
name: functionName,
|
|
842
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
843
|
+
func: { name: functionName, source: functionSource }
|
|
805
844
|
};
|
|
806
845
|
};
|
|
807
846
|
|
|
808
|
-
const functionName = 'update';
|
|
809
847
|
const generateUpdateEndpoint = ({ target, path, endpoint }) => {
|
|
810
|
-
const
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
type:
|
|
848
|
+
const functionName = 'update';
|
|
849
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
850
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
851
|
+
type: functionTypeName,
|
|
814
852
|
params: ['id: string', `data: DeepPartial<${generateRequestBodyType(path).toString()}>`, 'options?: UpdateQuery'],
|
|
815
853
|
returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
|
|
816
854
|
});
|
|
817
855
|
const functionSource = generateArrowFunction({
|
|
818
856
|
name: functionName,
|
|
819
|
-
signature:
|
|
857
|
+
signature: functionTypeName,
|
|
820
858
|
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options)`,
|
|
821
859
|
params: ['id', 'data', 'options']
|
|
822
860
|
});
|
|
823
861
|
return {
|
|
824
|
-
entity,
|
|
862
|
+
entity: pascalCase(endpoint.service),
|
|
825
863
|
name: functionName,
|
|
826
|
-
type: { name:
|
|
864
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
827
865
|
func: { name: functionName, source: functionSource }
|
|
828
866
|
};
|
|
829
867
|
};
|
|
830
868
|
|
|
831
|
-
const
|
|
869
|
+
const isMultiPartUploadPath = (path) => {
|
|
870
|
+
const [, entity, ...rest] = path.split('/');
|
|
871
|
+
return entity && rest.length === 2 && rest[1] === 'multipartUpload';
|
|
872
|
+
};
|
|
873
|
+
const parseEndpointsAndGroupByEntity = (paths) => {
|
|
832
874
|
const endpoints = new Map();
|
|
833
875
|
for (const [rawPath, path] of Object.entries(paths)) {
|
|
834
876
|
const endpoint = parseEndpointPath(rawPath);
|
|
835
877
|
if (!endpoint || !path) {
|
|
878
|
+
// Todo: Should be removed if sdk supports multi part upload.
|
|
879
|
+
if (isMultiPartUploadPath(rawPath)) {
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
836
882
|
logger.errorLn(`Failed to parse ${rawPath}`);
|
|
837
883
|
continue;
|
|
838
884
|
}
|
|
839
|
-
if (endpoints.has(endpoint.
|
|
840
|
-
endpoints.get(endpoint.
|
|
885
|
+
if (endpoints.has(endpoint.service)) {
|
|
886
|
+
endpoints.get(endpoint.service)?.push({ endpoint, path });
|
|
841
887
|
}
|
|
842
888
|
else {
|
|
843
|
-
endpoints.set(endpoint.
|
|
889
|
+
endpoints.set(endpoint.service, [{ endpoint, path }]);
|
|
844
890
|
}
|
|
845
891
|
}
|
|
846
892
|
return endpoints;
|
|
@@ -873,26 +919,34 @@ const generators = {
|
|
|
873
919
|
post: generateGenericEndpoint()
|
|
874
920
|
}
|
|
875
921
|
};
|
|
876
|
-
const generateServices = (
|
|
922
|
+
const generateServices = (paths, entities, aliases, options) => {
|
|
877
923
|
const services = new Map();
|
|
878
|
-
const
|
|
879
|
-
for (const [
|
|
880
|
-
const
|
|
881
|
-
const serviceTypeName = pascalCase(`${
|
|
882
|
-
// Service functions
|
|
924
|
+
const endpoints = parseEndpointsAndGroupByEntity(paths);
|
|
925
|
+
for (const [serviceName, paths] of endpoints) {
|
|
926
|
+
const serviceFnName = camelCase(`${serviceName}Service`);
|
|
927
|
+
const serviceTypeName = pascalCase(`${serviceName}Service`);
|
|
883
928
|
const functions = [];
|
|
884
929
|
for (const { path, endpoint } of paths) {
|
|
885
|
-
const
|
|
930
|
+
const generator = generators[endpoint.type];
|
|
886
931
|
for (const [method, config] of Object.entries(path)) {
|
|
887
932
|
if (method === 'get' && endpoint.type === WeclappEndpointType.ENTITY && !options.generateUnique) {
|
|
933
|
+
// Skip unique endpoints if generateUnique option is not set
|
|
888
934
|
continue;
|
|
889
935
|
}
|
|
890
|
-
|
|
936
|
+
const generatorFn = generator[method];
|
|
937
|
+
if (generatorFn) {
|
|
891
938
|
const path = config;
|
|
892
939
|
const target = options.target;
|
|
893
940
|
if (!path.deprecated || options.deprecated) {
|
|
894
941
|
functions.push({
|
|
895
|
-
...
|
|
942
|
+
...generatorFn({
|
|
943
|
+
endpoint,
|
|
944
|
+
method,
|
|
945
|
+
target,
|
|
946
|
+
path,
|
|
947
|
+
entities,
|
|
948
|
+
aliases
|
|
949
|
+
}),
|
|
896
950
|
path
|
|
897
951
|
});
|
|
898
952
|
}
|
|
@@ -905,26 +959,28 @@ const generateServices = (doc, aliases, options) => {
|
|
|
905
959
|
if (!functions.length) {
|
|
906
960
|
continue;
|
|
907
961
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
...functions.map(v => ({
|
|
962
|
+
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, [
|
|
963
|
+
...functions.map((v) => ({
|
|
911
964
|
required: true,
|
|
912
965
|
comment: v.path.deprecated ? '@deprecated' : undefined,
|
|
913
966
|
name: v.func.name,
|
|
914
967
|
type: v.type.name
|
|
915
968
|
}))
|
|
916
|
-
]));
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
969
|
+
])));
|
|
970
|
+
const serviceFn = `export const ${serviceFnName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${generateBlockStatements(...functions.map((v) => v.func.source), `return {${concat(functions.map((v) => v.func.name))}};`)};`;
|
|
971
|
+
services.set(serviceName, {
|
|
972
|
+
name: serviceName,
|
|
973
|
+
serviceFnName,
|
|
974
|
+
serviceTypeName,
|
|
975
|
+
functions,
|
|
976
|
+
source: generateStatements(serviceTypes, serviceFn),
|
|
977
|
+
deprecated: functions.every((v) => v.path.deprecated)
|
|
978
|
+
});
|
|
923
979
|
}
|
|
924
980
|
return services;
|
|
925
981
|
};
|
|
926
982
|
|
|
927
|
-
const
|
|
983
|
+
const generateCustomValueServices = (entities, services) => {
|
|
928
984
|
const customValueEntity = entities.get('customValue');
|
|
929
985
|
const customValueEntities = [];
|
|
930
986
|
if (!customValueEntity) {
|
|
@@ -932,7 +988,7 @@ const generateCustomValueUtilities = (entities, services) => {
|
|
|
932
988
|
return '';
|
|
933
989
|
}
|
|
934
990
|
serviceLoop: for (const service of services) {
|
|
935
|
-
const someFunction = service.functions.find(v => v.name === 'some');
|
|
991
|
+
const someFunction = service.functions.find((v) => v.name === 'some');
|
|
936
992
|
if (!someFunction) {
|
|
937
993
|
continue;
|
|
938
994
|
}
|
|
@@ -945,55 +1001,57 @@ const generateCustomValueUtilities = (entities, services) => {
|
|
|
945
1001
|
continue serviceLoop;
|
|
946
1002
|
}
|
|
947
1003
|
}
|
|
948
|
-
customValueEntities.push(service.
|
|
1004
|
+
customValueEntities.push(service.name);
|
|
949
1005
|
}
|
|
950
|
-
return
|
|
1006
|
+
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
1007
|
};
|
|
952
1008
|
|
|
953
1009
|
const generateObject = (properties) => {
|
|
954
1010
|
const body = [];
|
|
955
|
-
for (const { key, value } of properties) {
|
|
1011
|
+
for (const { key, value, comment } of properties) {
|
|
956
1012
|
if (value === undefined) {
|
|
957
1013
|
continue;
|
|
958
1014
|
}
|
|
959
1015
|
if (Array.isArray(value)) {
|
|
960
1016
|
const str = generateObject(value);
|
|
961
1017
|
if (str.length > 2) {
|
|
962
|
-
body.push(`${key}: ${str}`);
|
|
1018
|
+
body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${str}`);
|
|
963
1019
|
}
|
|
964
1020
|
}
|
|
965
1021
|
else {
|
|
966
|
-
body.push(`${key}: ${String(value)}`);
|
|
1022
|
+
body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${String(value)}`);
|
|
967
1023
|
}
|
|
968
1024
|
}
|
|
969
1025
|
return body.length ? `{\n${indent(body.join(',\n'))}\n}` : `{}`;
|
|
970
1026
|
};
|
|
971
1027
|
|
|
972
1028
|
const resolveInheritedEntities = (root, entities) => {
|
|
973
|
-
const parent = root.
|
|
1029
|
+
const parent = root.parentName ? entities.get(root.parentName) : undefined;
|
|
974
1030
|
return parent ? [parent, ...resolveInheritedEntities(parent, entities)] : [];
|
|
975
1031
|
};
|
|
976
|
-
const generatePropertyDescriptors = (entity, entities, services, options) => [
|
|
977
|
-
|
|
978
|
-
...entity.properties
|
|
979
|
-
].filter(([, meta]) => {
|
|
1032
|
+
const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
|
|
1033
|
+
.filter(([, meta]) => {
|
|
980
1034
|
// If we generate deprecated things we can skip the filtering
|
|
981
1035
|
if (options.deprecated) {
|
|
982
1036
|
return true;
|
|
983
1037
|
}
|
|
984
1038
|
// Check if corresponding service is deprecated and can be removed
|
|
985
|
-
const service = services.find(v => v.
|
|
1039
|
+
const service = services.find((v) => v.name === meta.service);
|
|
986
1040
|
return !meta.service || (service && !service.deprecated);
|
|
987
|
-
})
|
|
1041
|
+
})
|
|
1042
|
+
.map(([property, meta]) => ({
|
|
988
1043
|
key: property,
|
|
989
1044
|
value: Object.entries(meta).map(([key, value]) => ({
|
|
990
1045
|
key,
|
|
991
|
-
value: value ? generateString(value) : undefined
|
|
1046
|
+
value: value !== undefined ? (typeof value === 'number' ? value : generateString(value)) : undefined
|
|
992
1047
|
}))
|
|
993
1048
|
}));
|
|
994
|
-
const
|
|
1049
|
+
const generateEntityProperties = (entities, aliases, services, options) => {
|
|
995
1050
|
const typeName = 'WEntityProperties';
|
|
996
|
-
const propertyMap = [
|
|
1051
|
+
const propertyMap = [
|
|
1052
|
+
...entities.entries(),
|
|
1053
|
+
...aliases.entries().map(([service, type]) => [service, entities.get(camelCase(type))])
|
|
1054
|
+
].map(([entity, data]) => ({
|
|
997
1055
|
key: entity,
|
|
998
1056
|
value: generatePropertyDescriptors(data, entities, services, options)
|
|
999
1057
|
}));
|
|
@@ -1001,7 +1059,7 @@ const generateEntityPropertyMap = (entities, services, options) => {
|
|
|
1001
1059
|
};
|
|
1002
1060
|
|
|
1003
1061
|
const generateArray = (values) => {
|
|
1004
|
-
return `[${concat(values.map(v => generateString(String(v))))}]`;
|
|
1062
|
+
return `[${concat(values.map((v) => generateString(String(v))))}]`;
|
|
1005
1063
|
};
|
|
1006
1064
|
|
|
1007
1065
|
// Only functions matching this regex are included in the generation.
|
|
@@ -1014,16 +1072,17 @@ const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
|
|
|
1014
1072
|
*/
|
|
1015
1073
|
const generateGroupedServices = (services) => {
|
|
1016
1074
|
const entityDescriptors = new Map();
|
|
1017
|
-
for (const
|
|
1018
|
-
for (const
|
|
1019
|
-
if (!FILTER_REGEX.test(name)) {
|
|
1075
|
+
for (const service of services) {
|
|
1076
|
+
for (const fn of service.functions) {
|
|
1077
|
+
if (!FILTER_REGEX.test(fn.name)) {
|
|
1020
1078
|
continue;
|
|
1021
1079
|
}
|
|
1022
|
-
entityDescriptors.set(name, [
|
|
1023
|
-
...(entityDescriptors.get(name) ?? []),
|
|
1024
|
-
|
|
1080
|
+
entityDescriptors.set(fn.name, [
|
|
1081
|
+
...(entityDescriptors.get(fn.name) ?? []),
|
|
1082
|
+
{
|
|
1083
|
+
name: service.name,
|
|
1025
1084
|
required: true,
|
|
1026
|
-
type: `${pascalCase(
|
|
1085
|
+
type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}`
|
|
1027
1086
|
}
|
|
1028
1087
|
]);
|
|
1029
1088
|
}
|
|
@@ -1036,123 +1095,92 @@ const generateGroupedServices = (services) => {
|
|
|
1036
1095
|
const guard = `(service: string | undefined): service is ${service} =>\n${indent(`${constant}.includes(service as ${service});`)}`;
|
|
1037
1096
|
typeGuards.push(`export const is${service} = ${guard}`);
|
|
1038
1097
|
}
|
|
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
|
-
];
|
|
1098
|
+
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]) => {
|
|
1099
|
+
const constant = camelCase(`wServiceWith_${name}_Names`);
|
|
1100
|
+
const type = pascalCase(`WServiceWith_${name}`);
|
|
1101
|
+
const value = generateArray(props.map((v) => v.name));
|
|
1102
|
+
return `export const ${constant}: ${type}[] = ${value};`;
|
|
1103
|
+
}), ...typeGuards);
|
|
1050
1104
|
};
|
|
1051
1105
|
|
|
1052
|
-
const
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
const entityNames = `export const wEntityNames: WEntity[] = ${
|
|
1058
|
-
const
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
}))};`;
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
})),
|
|
1070
|
-
...services.map(service => {
|
|
1071
|
-
const alias = aliases.get(service.entity);
|
|
1072
|
-
return {
|
|
1073
|
-
name: service.entity,
|
|
1074
|
-
type: alias ?? 'never',
|
|
1075
|
-
required: true,
|
|
1076
|
-
comment: alias ? undefined : 'no response defined or inlined'
|
|
1077
|
-
};
|
|
1078
|
-
})
|
|
1079
|
-
];
|
|
1080
|
-
const createMappingType = (type, prefix) => type !== 'never' ? `${type}_${prefix}` : type;
|
|
1081
|
-
const entitiesList = generateInterface('WEntities', entityInterfaces);
|
|
1082
|
-
const entityReferences = generateInterface('WEntityReferences', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'References') })));
|
|
1083
|
-
const entityMappings = generateInterface('WEntityMappings', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'Mappings') })));
|
|
1084
|
-
const entityFilter = generateInterface('WEntityFilters', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'Filter') })));
|
|
1106
|
+
const generateMaps = (enums, entities, services, aliases, options) => {
|
|
1107
|
+
const enumInstances = `export const wEnums = ${generateObject(enums
|
|
1108
|
+
.keys()
|
|
1109
|
+
.toArray()
|
|
1110
|
+
.map((v) => ({ key: v, value: v })))};`;
|
|
1111
|
+
const entityNames = `export const wEntityNames: WEntity[] = ${generateArray(entities.keys().toArray())};`;
|
|
1112
|
+
const generatedServices = services.values().toArray();
|
|
1113
|
+
const serviceInstances = `export const wServices = ${generateObject(generatedServices.map((v) => ({
|
|
1114
|
+
key: v.name,
|
|
1115
|
+
value: `${v.serviceFnName}()`,
|
|
1116
|
+
comment: v.deprecated ? '@deprecated' : undefined
|
|
1117
|
+
})))};`;
|
|
1118
|
+
const serviceFactories = `export const wServiceFactories = ${generateObject(generatedServices.map((v) => ({
|
|
1119
|
+
key: v.name,
|
|
1120
|
+
value: v.serviceFnName,
|
|
1121
|
+
comment: v.deprecated ? '@deprecated' : undefined
|
|
1122
|
+
})))};`;
|
|
1085
1123
|
return {
|
|
1086
1124
|
source: generateStatements(
|
|
1087
|
-
/*
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
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))
|
|
1125
|
+
/* Enums */
|
|
1126
|
+
generateInterface('WEnums', enums
|
|
1127
|
+
.keys()
|
|
1128
|
+
.toArray()
|
|
1129
|
+
.map((name) => ({ name, type: name, required: true }))), generateType('WEnum', 'keyof WEnums'), enumInstances,
|
|
1130
|
+
/* Entities */
|
|
1131
|
+
generateInterface('WEntities', [
|
|
1132
|
+
...entities.keys().map((name) => ({ name, type: loosePascalCase(name), required: true })),
|
|
1133
|
+
...aliases.entries().map(([name, type]) => ({ name, type, required: true }))
|
|
1134
|
+
].sort((a, b) => (a.name > b.name ? 1 : -1))), generateType('WEntity', 'keyof WEntities'), entityNames,
|
|
1135
|
+
/* Services */
|
|
1136
|
+
serviceInstances, generateType('WServices', 'typeof wServices'), generateType('WService', 'keyof WServices'), serviceFactories, generateType('WServiceFactories', 'typeof wServiceFactories'),
|
|
1137
|
+
/* Service Utils */
|
|
1138
|
+
generateGroupedServices(generatedServices), generateCustomValueServices(entities, generatedServices),
|
|
1139
|
+
/* Entity Properties (Runtime Meta Infos) */
|
|
1140
|
+
generateEntityProperties(entities, aliases, generatedServices, options))
|
|
1113
1141
|
};
|
|
1114
1142
|
};
|
|
1115
1143
|
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
const extractSchemas = (doc) => {
|
|
1119
|
-
const schemas = new Map();
|
|
1144
|
+
const parseReferencedEntityType = (obj) => pascalCase(obj.$ref.replace(/.*\//, ''));
|
|
1145
|
+
const extractServiceAliases = (doc, schemas) => {
|
|
1120
1146
|
const aliases = new Map();
|
|
1121
|
-
for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
|
|
1122
|
-
if (!isReferenceObject(schema)) {
|
|
1123
|
-
schemas.set(name, schema);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
/**
|
|
1127
|
-
* Referenced schemas in responses, in some case the response from the root endpoint
|
|
1128
|
-
* refers to a schema with a different name
|
|
1129
|
-
*/
|
|
1130
1147
|
for (const [path, methods] of Object.entries(doc.paths)) {
|
|
1131
1148
|
const parsed = parseEndpointPath(path);
|
|
1132
|
-
if (!parsed || schemas.has(parsed.
|
|
1149
|
+
if (!parsed || !methods || parsed.type !== WeclappEndpointType.ROOT || schemas.has(parsed.service)) {
|
|
1133
1150
|
continue;
|
|
1134
1151
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
+
const body = methods[OpenAPIV3.HttpMethods.GET]?.responses['200'];
|
|
1153
|
+
if (isResponseObject(body) && body.content?.['application/json']) {
|
|
1154
|
+
const responseSchema = body.content['application/json'].schema;
|
|
1155
|
+
if (!responseSchema || isReferenceObject(responseSchema)) {
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
const resultSchema = responseSchema.properties?.result;
|
|
1159
|
+
if (!resultSchema) {
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
if (isReferenceObject(resultSchema)) {
|
|
1163
|
+
aliases.set(parsed.service, parseReferencedEntityType(resultSchema));
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
if (isArraySchemaObject(resultSchema)) {
|
|
1167
|
+
const resultItemSchema = resultSchema.items;
|
|
1168
|
+
if (isReferenceObject(resultItemSchema)) {
|
|
1169
|
+
aliases.set(parsed.service, parseReferencedEntityType(resultItemSchema));
|
|
1152
1170
|
}
|
|
1153
1171
|
}
|
|
1154
1172
|
}
|
|
1155
1173
|
}
|
|
1174
|
+
return aliases;
|
|
1175
|
+
};
|
|
1176
|
+
const extractSchemas = (doc) => {
|
|
1177
|
+
const schemas = new Map();
|
|
1178
|
+
for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
|
|
1179
|
+
if (!isReferenceObject(schema)) {
|
|
1180
|
+
schemas.set(name, schema);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
const aliases = extractServiceAliases(doc, schemas);
|
|
1156
1184
|
return { schemas, aliases };
|
|
1157
1185
|
};
|
|
1158
1186
|
|
|
@@ -1160,14 +1188,18 @@ const generate = (doc, options) => {
|
|
|
1160
1188
|
const { schemas, aliases } = extractSchemas(doc);
|
|
1161
1189
|
const enums = generateEnums(schemas);
|
|
1162
1190
|
const entities = generateEntities(schemas, enums);
|
|
1163
|
-
const services = generateServices(doc, aliases, options);
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
entities
|
|
1169
|
-
|
|
1170
|
-
|
|
1191
|
+
const services = generateServices(doc.paths, entities, aliases, options);
|
|
1192
|
+
const maps = generateMaps(enums, entities, services, aliases, options);
|
|
1193
|
+
return generateStatements(generateBase(options.target, doc.info.version, options), generateBlockComment('ENUMS', generateStatements(...enums
|
|
1194
|
+
.values()
|
|
1195
|
+
.map((v) => v.source)
|
|
1196
|
+
.toArray())), generateBlockComment('ENTITIES', generateStatements(...entities
|
|
1197
|
+
.values()
|
|
1198
|
+
.map((v) => v.source)
|
|
1199
|
+
.toArray())), generateBlockComment('SERVICES', generateStatements(...services
|
|
1200
|
+
.values()
|
|
1201
|
+
.map((v) => v.source)
|
|
1202
|
+
.toArray())), generateBlockComment('MAPS', generateStatements(maps.source)));
|
|
1171
1203
|
};
|
|
1172
1204
|
|
|
1173
1205
|
const hash = (content, algorithm = 'sha256') => {
|
|
@@ -1183,12 +1215,13 @@ const hash = (content, algorithm = 'sha256') => {
|
|
|
1183
1215
|
|
|
1184
1216
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
1185
1217
|
const cli = async () => {
|
|
1186
|
-
const
|
|
1218
|
+
const version = pkg.version;
|
|
1187
1219
|
const { argv } = yargs(hideBin(process.argv))
|
|
1188
1220
|
.scriptName('build-weclapp-sdk')
|
|
1189
1221
|
.usage('Usage: $0 <source> [flags]')
|
|
1190
1222
|
.version(version)
|
|
1191
1223
|
.example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
|
|
1224
|
+
.example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
|
|
1192
1225
|
.example('$0 xxx.weclapp.com --key ...', 'Generate the SDK based on the openapi file from the given weclapp instance')
|
|
1193
1226
|
.help('h')
|
|
1194
1227
|
.alias('v', 'version')
|
|
@@ -1228,9 +1261,8 @@ const cli = async () => {
|
|
|
1228
1261
|
type: 'string',
|
|
1229
1262
|
choices: ['browser', 'browser.rx', 'node', 'node.rx']
|
|
1230
1263
|
})
|
|
1231
|
-
.option('
|
|
1232
|
-
|
|
1233
|
-
describe: 'Include deprecated functions and services',
|
|
1264
|
+
.option('use-query-language', {
|
|
1265
|
+
describe: 'Generate the new where property for some and count queries',
|
|
1234
1266
|
type: 'boolean'
|
|
1235
1267
|
})
|
|
1236
1268
|
.epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
|
|
@@ -1242,21 +1274,27 @@ const cli = async () => {
|
|
|
1242
1274
|
const options = {
|
|
1243
1275
|
deprecated,
|
|
1244
1276
|
generateUnique: argv.generateUnique ?? false,
|
|
1245
|
-
target: argv.target ?? Target.BROWSER_PROMISES
|
|
1277
|
+
target: argv.target ?? Target.BROWSER_PROMISES,
|
|
1278
|
+
useQueryLanguage: argv.useQueryLanguage ?? false
|
|
1246
1279
|
};
|
|
1247
|
-
if (typeof src === 'number') {
|
|
1248
|
-
return Promise.reject('Expected string as command');
|
|
1280
|
+
if (!src || typeof src === 'number') {
|
|
1281
|
+
return Promise.reject(new Error('Expected string as command'));
|
|
1249
1282
|
}
|
|
1250
1283
|
if (!Object.values(Target).includes(options.target)) {
|
|
1251
1284
|
logger.errorLn(`Unknown target: ${options.target}. Possible values are ${Object.values(Target).join(', ')}`);
|
|
1252
|
-
return Promise.reject();
|
|
1285
|
+
return Promise.reject(new Error());
|
|
1253
1286
|
}
|
|
1254
1287
|
if (await stat(src).catch(() => false)) {
|
|
1255
|
-
logger.infoLn(`Source is a file
|
|
1288
|
+
logger.infoLn(`Source is a file`);
|
|
1256
1289
|
const content = JSON.parse(await readFile(src, 'utf-8'));
|
|
1257
1290
|
return { cache, content, options };
|
|
1258
1291
|
}
|
|
1292
|
+
logger.infoLn(`Source is a URL`);
|
|
1293
|
+
if (!key) {
|
|
1294
|
+
return Promise.reject(new Error('API key is missing'));
|
|
1295
|
+
}
|
|
1259
1296
|
const url = new URL(src.startsWith('http') ? src : `https://${src}`);
|
|
1297
|
+
// At the moment just v1
|
|
1260
1298
|
url.pathname = '/webapp/api/v1/meta/openapi.json';
|
|
1261
1299
|
if (query?.length) {
|
|
1262
1300
|
for (const param of query.split(',')) {
|
|
@@ -1265,11 +1303,11 @@ const cli = async () => {
|
|
|
1265
1303
|
}
|
|
1266
1304
|
}
|
|
1267
1305
|
const content = await fetch(url.toString(), {
|
|
1268
|
-
headers: {
|
|
1269
|
-
}).then(res => res.ok ? res.json() : undefined);
|
|
1306
|
+
headers: { Accept: 'application/json', AuthenticationToken: key }
|
|
1307
|
+
}).then((res) => (res.ok ? res.json() : undefined));
|
|
1270
1308
|
if (!content) {
|
|
1271
1309
|
logger.errorLn(`Couldn't fetch file ${url.toString()} `);
|
|
1272
|
-
return Promise.reject();
|
|
1310
|
+
return Promise.reject(new Error());
|
|
1273
1311
|
}
|
|
1274
1312
|
else {
|
|
1275
1313
|
logger.infoLn(`Use remote file: ${url.toString()}`);
|
|
@@ -1277,50 +1315,59 @@ const cli = async () => {
|
|
|
1277
1315
|
return { cache, content, options };
|
|
1278
1316
|
};
|
|
1279
1317
|
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1318
|
+
const workingDir = resolve(currentDirname(), './sdk');
|
|
1319
|
+
const cacheDir = resolve(currentDirname(), './.cache');
|
|
1282
1320
|
void (async () => {
|
|
1283
1321
|
const start = process.hrtime.bigint();
|
|
1284
|
-
const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
|
|
1285
1322
|
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);
|
|
1323
|
+
const workingDirPath = async (...paths) => {
|
|
1324
|
+
const fullPath = resolve(workingDir, ...paths);
|
|
1325
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
1293
1326
|
return fullPath;
|
|
1294
1327
|
};
|
|
1328
|
+
// Resolve cache dir and key
|
|
1329
|
+
const cacheKey = hash([pkg.version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
|
|
1330
|
+
const cachedSdkDir = resolve(cacheDir, cacheKey);
|
|
1331
|
+
// Remove old SDK
|
|
1332
|
+
await rm(workingDir, { recursive: true, force: true });
|
|
1295
1333
|
if (useCache) {
|
|
1296
1334
|
logger.infoLn(`Cache ID: ${cacheKey}`);
|
|
1297
1335
|
}
|
|
1298
|
-
if (useCache && await stat(
|
|
1299
|
-
|
|
1336
|
+
if (useCache && (await stat(cachedSdkDir).catch(() => false))) {
|
|
1337
|
+
// Copy cached SDK to working dir
|
|
1338
|
+
logger.successLn(`Cache match! (${cachedSdkDir})`);
|
|
1339
|
+
await cp(cachedSdkDir, workingDir, { recursive: true });
|
|
1300
1340
|
}
|
|
1301
1341
|
else {
|
|
1302
|
-
//
|
|
1303
|
-
await writeFile(await
|
|
1342
|
+
// Write openapi.json file
|
|
1343
|
+
await writeFile(await workingDirPath('openapi.json'), JSON.stringify(doc, null, 2));
|
|
1304
1344
|
logger.infoLn(`Generate sdk (target: ${options.target})`);
|
|
1305
|
-
// Generate
|
|
1345
|
+
// Generate and write SDK (index.ts)
|
|
1306
1346
|
const sdk = generate(doc, options);
|
|
1307
|
-
await writeFile(await
|
|
1308
|
-
// Bundle
|
|
1347
|
+
await writeFile(await workingDirPath('src', 'index.ts'), sdk.trim() + '\n');
|
|
1348
|
+
// Bundle and write SDK
|
|
1309
1349
|
logger.infoLn('Bundle... (this may take some time)');
|
|
1310
|
-
await bundle(
|
|
1311
|
-
// Remove
|
|
1312
|
-
await
|
|
1350
|
+
await bundle(workingDir, options.target);
|
|
1351
|
+
// Remove index.ts (only bundle is required)
|
|
1352
|
+
// await rm(await workingDirPath('src'), { recursive: true, force: true });
|
|
1353
|
+
if (useCache) {
|
|
1354
|
+
// Copy SDK to cache
|
|
1355
|
+
logger.successLn(`Caching SDK: (${cachedSdkDir})`);
|
|
1356
|
+
await mkdir(cachedSdkDir, { recursive: true });
|
|
1357
|
+
await cp(workingDir, cachedSdkDir, { recursive: true });
|
|
1358
|
+
}
|
|
1313
1359
|
}
|
|
1314
|
-
// Copy bundled SDK
|
|
1315
|
-
await cp(cacheDir, workingDirectory, { recursive: true });
|
|
1316
1360
|
// Print job summary
|
|
1317
1361
|
const duration = (process.hrtime.bigint() - start) / 1000000n;
|
|
1318
1362
|
logger.successLn(`SDK built in ${prettyMs(Number(duration))}`);
|
|
1319
1363
|
logger.printSummary();
|
|
1320
|
-
})()
|
|
1364
|
+
})()
|
|
1365
|
+
.catch((error) => {
|
|
1321
1366
|
logger.errorLn(`Fatal error:`);
|
|
1322
1367
|
/* eslint-disable no-console */
|
|
1323
1368
|
console.error(error);
|
|
1324
|
-
})
|
|
1325
|
-
|
|
1369
|
+
})
|
|
1370
|
+
.finally(() => {
|
|
1371
|
+
if (logger.errors)
|
|
1372
|
+
process.exit(1);
|
|
1326
1373
|
});
|