@weclapp/sdk 2.0.0-dev.6 → 2.0.0-dev.60
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 +522 -23
- package/dist/cli.js +805 -623
- package/package.json +53 -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: usePost ? query?.properties : query?.properties?.join(','),\n properties: query?.select\n ? usePost\n ? flattenSelect(query.select)\n : flattenSelect(query.select).join(',')\n : undefined,\n includeReferencedEntities: query?.include\n ? usePost\n ? Object.keys(query.include)\n : Object.keys(query.include).join(',')\n : undefined,\n ...flattenOrFilter(query?.or),\n ...flattenFilter(query?.filter),\n ...flattenSort(query?.sort, usePost),\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 types = "export type DeepPartial<T> = T extends object ? {\n [P in keyof T]?: DeepPartial<T[P]>;\n} : T;\n\n// Filter properties\nexport type EqualityOperators = 'EQ' | 'NE';\nexport type ComparisonOperators = 'LT' | 'GT' | 'LE' | 'GE' | 'LIKE' | 'ILIKE' | 'NOT_LIKE' | 'NOT_ILIKE';\nexport type ArrayOperators = 'IN' | 'NOT_IN';\nexport type Operator = EqualityOperators | ComparisonOperators | ArrayOperators;\n\nexport type MapOperators<T> = { [K in EqualityOperators]?: T | null; } &\n { [K in ComparisonOperators]?: T; } &\n { [K in ArrayOperators]?: T[]; };\n\nexport type QueryFilter<T> = {\n [P in keyof T]?:\n T[P] extends Array<infer U> | undefined ?\n U extends Record<any, any> ? QueryFilter<U> : MapOperators<U> :\n T[P] extends Record<any, any> | undefined ? QueryFilter<T[P]> : MapOperators<T[P]>;\n};\n\nexport type Sort<T> = {\n [K in keyof T]?: {\n [V in keyof T]?:\n V extends K ?\n T[V] extends Array<infer U> | undefined ?\n U extends object ?\n Sort<U> : never :\n T[V] extends object | undefined ?\n Sort<T[V]> : 'asc' | 'desc' : never;\n };\n}[keyof T];\n\n// Select properties\nexport type CustomAttributeFilter = {\n [K in number]: string | number | boolean |\n {id: string;} |\n {entityName: string; entityId: string;};\n}\n\nexport type QuerySelect<T> = {\n [P in keyof T]?:\n T[P] extends Array<infer U> | undefined ? (QuerySelect<U> | boolean) :\n T[P] extends Record<any, any> | undefined ? (QuerySelect<T[P]> | boolean) : boolean;\n}\n\nexport type Select<T, Q extends (QuerySelect<T> | undefined)> = 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]:\n\n // Property\n Q[P] extends true ? T[P] :\n\n // Array\n T[P] extends Array<infer U> ? Select<U, Q[P] & QuerySelect<any>>[] :\n\n // Object\n T[P] extends Record<any, any> ? Select<T[P], Q[P] & QuerySelect<any>> : never\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\n// Endpoint configurations\nexport type CountQuery<F> = {\n filter?: QueryFilter<F>;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\n};\n\nexport type Pagination = {\n page: number;\n pageSize: number;\n};\n\nexport type SomeQuery<\n E, // Entity\n F, // Entity filter\n I extends (QuerySelect<any> | undefined), // Select for referenced entities\n S extends (QuerySelect<any> | undefined), // Select for entity properties\n P extends string[] // Select for additional properties\n> = {\n serializeNulls?: boolean;\n include?: I;\n properties?: P\n filter?: QueryFilter<F> & CustomAttributeFilter;\n select?: S;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\n sort?: Sort<E>[];\n pagination?: Pagination;\n};\n\nexport type UniqueQuery = {\n serializeNulls?: boolean;\n}\n\nexport type SomeQueryReturn<\n E, // Entity\n R, // Map of referenced-entity names to the type\n M, // Map of referenced-entity-id names to their entity name\n I extends (QuerySelect<any> | undefined), // Select for referenced entities\n S extends (QuerySelect<any> | undefined), // Select for entity properties\n P // Additional properties\n> = {\n entities: (S extends QuerySelect<E> ? Select<E, S> : E)[];\n references: I extends QuerySelect<R> ? Partial<MapKeys<Select<R, I>, M & Record<any, any>>> : {};\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\n// Entity meta types\nexport type WEntityPropertyMeta = (\n { type: 'string'; format?: 'decimal' | 'html' | 'email' | 'password'; maxLength?: number; pattern?: string; entity?: WEntity; service?: WService; } |\n { type: 'integer'; format: 'int32' | 'int64' | 'duration' | 'date' | 'timestamp'; } |\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);\n\n// Utils\nconst equality: string[] = ['EQ', 'NE'];\nconst simple: string[] = [...equality, 'LT', 'GT', 'LE', 'GE', 'LIKE', 'NOT_LIKE', 'ILIKE', 'NOT_ILIKE'];\nconst array: string[] = ['IN', 'NOT_IN'];\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};\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)) {\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 if (array.includes(filter)) {\n entries.push([\n `${prop}-${filterMap[filter as Operator]}`,\n `[${value.map((v: string | number) => typeof v === 'string' ? `\"${v}\"` : v)}]`\n ]);\n } else {\n entries.push(\n ...flatten(propValue as QueryFilter<any>)\n .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][] = [], 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(\n ...flatten(obj[i])\n .map(v => [`or${i || ''}-${v[0]}`, v[1]]) as [string, string][]\n );\n }\n\n return Object.fromEntries(entries);\n};\n\nconst 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";
|
|
141
|
+
var queriesWithQueryLanguage = "const flattenOrderBy = (orderBy: OrderBy<any>[] = []): string[] => {\n\n const applyModifiers = (property: string, modifier: { TRIM?: boolean; LOWER?: boolean; LENGTH?: boolean }) => {\n let result = property;\n if (modifier.TRIM) {\n result = `trim(${result})`;\n }\n if (modifier.LOWER) {\n result = `lower(${result})`;\n }\n if (modifier.LENGTH) {\n result = `length(${result})`;\n }\n return result;\n };\n\n const isConditionalOrderBy = (\n orderByItem: OrderBy<any>\n ): orderByItem is ConditionalOrderBy<any> =>\n !!orderByItem && typeof orderByItem === 'object' && 'CASE' in orderByItem;\n\n const flattenValue = (value: number | FieldOrderBy<any>): string => {\n if (typeof value === 'number') {\n return value.toString();\n }\n return applyModifiers(value.FIELD, value);\n };\n\n const flattenConditional = (item: ConditionalOrderBy<any>): string => {\n const cases = item.CASE.map((c) => {\n const whenStr = flattenWhere(c.WHEN as QueryFilter<any>, []).join(' and ');\n return `(${whenStr}) ? ${flattenValue(c.THEN)}`;\n });\n\n return cases.reduceRight((acc, caseStr) => `${caseStr} : ${acc}`, flattenValue(item.ELSE));\n };\n\n const flattenSingle = (orderByItem: OrderBy<any>): string => {\n if (isConditionalOrderBy(orderByItem)) {\n const expr = flattenConditional(orderByItem);\n const sort = orderByItem.SORT === 'desc' ? 'desc' : 'asc';\n return `${expr} ${sort}`;\n }\n\n const field = orderByItem as FieldOrderBy<any>;\n const property = applyModifiers(field.FIELD, field);\n const sort = field.SORT === 'desc' ? 'desc' : 'asc';\n return `${property} ${sort}`;\n };\n\n return orderBy.map(flattenSingle);\n}\n\nexport type ComparisonOperator =\n | 'EQ'\n | 'NE'\n | 'LT'\n | 'GT'\n | 'LE'\n | 'GE'\n | 'LIKE';\n\nexport type LengthOperator = 'LENGTH';\n\nexport type ArrayOperator = 'IN';\n\nexport type CaseOperator = 'CASE';\n\nexport type CaseNode<Entity, PropType> = {\n [K in CaseOperator]: CaseExpression<Entity, PropType>;\n};\n\ntype InCaseNode<Case> = Case extends CaseNode<infer Entity, infer PropType> ? CaseNode<Entity, PropType[]> : never;\n\nexport type CaseExpression<Entity, PropType> = {\n IF: SingleFilterExpr<Entity>;\n THEN: PropType | CaseNode<Entity, PropType>;\n ELSE: PropType | CaseNode<Entity, PropType>;\n};\n\nexport type NullOperator = 'NULL';\n\nexport type Operator = ComparisonOperator | ArrayOperator | CaseOperator | NullOperator;\n\nexport type BinaryOperator = Exclude<Operator, CaseOperator>;\n\nexport type ModifierFunction = 'LOWER' | 'TRIM';\n\n// use globalThis to use typescript's 'Pick', since the SDK contains an interface which is also called 'Pick'\nexport type RequireAtLeastOne<T> = {\n [K in keyof T]-?: Required<globalThis.Pick<T, K>> & Partial<Omit<T, K>>;\n}[keyof T];\n\nexport type LengthExpr = {\n [K in LengthOperator]: {\n [K in Exclude<ComparisonOperator, 'LIKE'>]?: number\n }\n};\n\nexport type LengthExprWithModifier =\n LengthExpr &\n { [K in ComparisonOperator]?: never } &\n { [K in ArrayOperator]?: never } &\n { [K in NullOperator]?: never } &\n { [K in ModifierFunction]?: boolean };\n\nexport type FilterExpr<T, Case = never> =\n { [K in ComparisonOperator]?: T | Case } &\n { [K in ArrayOperator]?: T[] | InCaseNode<Case> } &\n { [K in keyof LengthExpr]?: never };\n\nexport type FilterExprWithoutNull<T, Case = never> =\n RequireAtLeastOne<FilterExpr<T, Case>> &\n { [K in NullOperator]?: never } &\n { [K in ModifierFunction]?: boolean };\n\nexport type FilterExprWithNull<T, Case = never> =\n FilterExpr<T, Case> &\n { [K in NullOperator]?: boolean} &\n { [K in ModifierFunction]?: never };\n\nexport type MapOperators<T, Case = never> = LengthExprWithModifier | FilterExprWithoutNull<T, Case> | FilterExprWithNull<T, Case>;\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, CaseNode<T, T[P]>>\n : T[P] extends Record<any, any> | undefined\n ? SingleFilterExpr<T[P]> | { NOT?: SingleFilterExpr<T[P]> }\n : MapOperators<T[P], CaseNode<T, 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 ConditionalOrderByCase<E> = {\n WHEN: QueryFilter<E>;\n THEN: number | FieldOrderBy<E>;\n};\n\nexport type ConditionalOrderBy<E> = {\n CASE: ConditionalOrderByCase<E>[];\n ELSE: number | FieldOrderBy<E>;\n SORT?: 'asc' | 'desc';\n};\n\nexport type FieldPath<T> = {\n [K in keyof T & string]: NonNullable<T[K]> extends Array<infer U>\n ? U extends Record<any, any>\n ? `${K}.${FieldPath<U>}`\n : never\n : NonNullable<T[K]> extends Record<any, any>\n ? K | `${K}.${FieldPath<NonNullable<T[K]>>}`\n : K;\n}[keyof T & string];\n\nexport type FieldOrderBy<E> = {\n FIELD: FieldPath<E>;\n SORT?: 'asc' | 'desc';\n LOWER?: boolean;\n TRIM?: boolean;\n LENGTH?: boolean;\n};\n\nexport type OrderBy<E> = FieldOrderBy<E> | ConditionalOrderBy<E>;\n\nexport type CountQuery<F> = {\n where?: QueryFilter<F>;\n};\n\ntype SomeQueryBase<E, F, I, P> = {\n serializeNulls?: boolean;\n include?: QuerySelect<I>;\n properties?: P;\n where?: QueryFilter<F>;\n select?: QuerySelect<E>;\n pagination?: Pagination;\n};\n\nexport type SomeQuery<E, F, I, P> = SomeQueryBase<E, F, I, P> &\n ({ sort?: Sort<E>[]; orderBy?: never } | { sort?: never; orderBy?: OrderBy<E>[] });\n\nconst comparisonOperatorList: ComparisonOperator[] = [\n 'EQ',\n 'NE',\n 'LT',\n 'GT',\n 'LE',\n 'GE',\n 'LIKE'\n];\n\nconst comparisonOperatorMap: Record<BinaryOperator, string> = {\n EQ: '=',\n NE: '!=',\n LT: '<',\n GT: '>',\n LE: '<=',\n GE: '>=',\n LIKE: '~',\n IN: 'in',\n NULL: 'null'\n};\n\nconst modifierFunctionList: ModifierFunction[] = ['LOWER', 'TRIM'];\n\nconst flattenWhere = (\n obj: QueryFilter<any> = {},\n nestedPaths: string[]\n): string[] => {\n const entries: string[] = [];\n for (const [prop, propValue] of Object.entries(obj)) {\n const setModifiers = findAllModifierFunctions(propValue ?? {}, modifierFunctionList).filter(\n (modifier) => modifier[1]\n );\n if (prop === 'OR') {\n const flattedOr: string[][] = [];\n for (let i = 0; i < (obj.OR?.length ?? 0); i++) {\n flattedOr.push(flattenWhere(obj.OR?.[i], nestedPaths));\n }\n entries.push(\n `(${flattedOr\n .map((x) => {\n const joined = x.join(' and ');\n\n if (x.length > 1) {\n return `(${joined})`;\n } else {\n return joined;\n }\n })\n .join(' or ')})`\n );\n } else if (prop === 'AND') {\n const flattedAnd: string[][] = [];\n for (let i = 0; i < (obj.AND?.length ?? 0); i++) {\n flattedAnd.push(flattenWhere(obj.AND?.[i], nestedPaths));\n }\n entries.push(\n `(${flattedAnd\n .map((x) => {\n const joined = x.join(' and ');\n\n if (x.length > 1) {\n return `(${joined})`;\n } else {\n return joined;\n }\n })\n .join(' and ')})`\n );\n } else if (prop === 'NOT') {\n const flattedNot = flattenWhere(obj.NOT, nestedPaths);\n entries.push(\n `not ${flattedNot.length > 1 ? '(' : ''}${flattedNot.join(' and ')}${flattedNot.length > 1 ? ')' : ''}`\n );\n } else if (prop === 'CASE') {\n entries.push(evaluateCaseExpression(propValue as CaseExpression<any, any>, setModifiers, nestedPaths));\n } else if (propValue) {\n for (const [operator, value] of Object.entries(propValue)) {\n if (value === undefined) continue;\n if (comparisonOperatorList.includes(operator as ComparisonOperator)) {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first.toLowerCase()}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${comparisonOperatorMap[operator as BinaryOperator]} ${\n typeof value === 'object'\n ? flattenWhere(value, nestedPaths)\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 BinaryOperator]}`\n );\n } else if ((operator as Operator) === 'IN') {\n if (typeof value === 'object' && !Array.isArray(value) && value.CASE) {\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 BinaryOperator]} ${evaluateCaseExpression(value.CASE as CaseExpression<any, any>, setModifiers, nestedPaths)}`\n );\n } else if(value.length === 0) {\n entries.push('1 = 0')\n } else {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first.toLowerCase()}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${comparisonOperatorMap[operator as BinaryOperator]} [${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 ((operator as Operator) === 'CASE') {\n entries.push(evaluateCaseExpression(value as CaseExpression<any, any>, setModifiers, nestedPaths));\n } else if ((operator as LengthOperator) === 'LENGTH') {\n const lengthProp = `length(${setModifiers.reduce(\n (acc, [first]) => `${first.toLowerCase()}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )})`;\n entries.push(...flattenWhere({ [lengthProp]: value } as QueryFilter<any>, []));\n } else if (\n !modifierFunctionList.includes(operator as ModifierFunction)\n && typeof value === 'object'\n ) {\n entries.push(\n ...flattenWhere(propValue as QueryFilter<any>, [\n ...nestedPaths,\n prop\n ])\n );\n break;\n }\n }\n }\n }\n return entries;\n};\n\nconst evaluateCaseExpression = <FilterProps, PropType>(\n exp: CaseExpression<FilterProps, PropType>,\n setModifiers: [string, any][],\n nestedPaths: string[]\n): string =>\n `(${flattenWhere(exp.IF, nestedPaths)} ? ${\n typeof exp.THEN === 'string' || Array.isArray(exp.THEN)\n ? setModifiers.reduce((acc, [first]) => `${first}(${acc})`, JSON.stringify(exp.THEN))\n : typeof exp.THEN === 'number' || typeof exp.THEN === 'boolean'\n ? exp.THEN\n : evaluateCaseExpression((exp.THEN as CaseNode<FilterProps, PropType>).CASE, setModifiers, nestedPaths)\n } : ${\n typeof exp.ELSE === 'string' || Array.isArray(exp.ELSE)\n ? setModifiers.reduce((acc, [first]) => `${first}(${acc})`, JSON.stringify(exp.ELSE))\n : typeof exp.ELSE === 'number' || typeof exp.ELSE === 'boolean'\n ? exp.ELSE\n : evaluateCaseExpression((exp.ELSE as CaseNode<FilterProps, PropType>).CASE, setModifiers, nestedPaths)\n })`;\n\nconst assembleOrderBy = (orderBy: OrderBy<any>[] = [], usePost?: boolean): Record<string, string | string[]> => {\n if(!orderBy.length) {\n return {}\n }\n const flattedOrderByList = flattenOrderBy(orderBy);\n\n if(!flattedOrderByList.length) {\n return {};\n }\n\n if(usePost) {\n return { orderBy: flattedOrderByList };\n }\n\n const flattedOrderBy = flattedOrderByList.join(',');\n return { orderBy: flattedOrderBy };\n}\n\nconst assembleFilterParam = (\n obj: QueryFilter<any> = {}\n): Record<string, string> => {\n const flattedFilter = flattenWhere(obj, []);\n return flattedFilter.length ? { filter: flattedFilter.join(' and ') } : {};\n};\n\nconst findAllModifierFunctions = (\n obj: Record<string, any>,\n types: ModifierFunction[]\n) => {\n const result: [string, any][] = [];\n for (const key in obj) {\n if (types.includes(key as ModifierFunction)) {\n result[types.indexOf(key as ModifierFunction)] = [key, obj[key]];\n }\n }\n return result.filter((modifierTuple) => modifierTuple);\n};\n\nconst _count = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: CountQuery<any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n {\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n ...assembleFilterParam(query?.where),\n ...query?.params\n }\n\n return wrapResponse(() =>\n raw(cfg, endpoint, {\n unwrap: true,\n method: usePost ? 'POST' : 'GET',\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions)\n )\n };\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) => {\n const usePost = cfg?.usePost ?? globalConfig?.usePost;\n const payload = {\n serializeNulls: query?.serializeNulls,\n additionalProperties: usePost ? query?.properties : query?.properties?.join(','),\n properties: query?.select\n ? usePost\n ? flattenSelect(query.select)\n : flattenSelect(query.select).join(',')\n : undefined,\n includeReferencedEntities: query?.include\n ? usePost\n ? Object.keys(query.include)\n : Object.keys(query.include).join(',')\n : undefined,\n ...assembleFilterParam(query?.where),\n ...flattenSort(query?.sort, usePost),\n ...assembleOrderBy(query?.orderBy, usePost),\n ...query?.params,\n ...query?.pagination\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>[] = [], usePost?: boolean):\n{ sort?: string } | { orderBy?: string[] } | Record<string, never> => {\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 `${!usePost && value === 'desc' ? '-' : ''}${path}${usePost && value === 'desc' ? ' desc' : ''}`;\n }\n }\n\n return undefined;\n };\n\n\n const sorts = obj.map((v) => flatten(v)).filter((x): x is string => typeof x === 'string');\n\n if (!sorts.length) {\n return {};\n }\n\n if (usePost) {\n return { orderBy: sorts };\n } else {\n return { sort: sorts.join(',') };\n }\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,48 +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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
+
}
|
|
328
348
|
}
|
|
329
349
|
}
|
|
330
|
-
return
|
|
350
|
+
return metaData;
|
|
331
351
|
};
|
|
332
352
|
|
|
333
353
|
const generateInlineComment = (comment) => `/** ${comment} */`;
|
|
@@ -337,12 +357,12 @@ const generateType = (name, value) => {
|
|
|
337
357
|
return `export type ${name} = ${value.trim()};`;
|
|
338
358
|
};
|
|
339
359
|
|
|
340
|
-
const arrayify = (v) => Array.isArray(v) ? v : [v];
|
|
360
|
+
const arrayify = (v) => (Array.isArray(v) ? v : [v]);
|
|
341
361
|
|
|
342
362
|
const generateInterfaceProperties = (entries) => {
|
|
343
363
|
const properties = entries
|
|
344
|
-
.filter(v => v.type !== undefined)
|
|
345
|
-
.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)
|
|
346
366
|
.map(({ name, type, required, readonly, comment }) => {
|
|
347
367
|
const cmd = comment ? `${generateInlineComment(comment)}\n` : '';
|
|
348
368
|
const req = required ? '' : '?';
|
|
@@ -352,7 +372,7 @@ const generateInterfaceProperties = (entries) => {
|
|
|
352
372
|
.join('\n');
|
|
353
373
|
return properties.length ? `{\n${indent(properties)}\n}` : `{}`;
|
|
354
374
|
};
|
|
355
|
-
const generateInterfaceFromObject = (name, obj,
|
|
375
|
+
const generateInterfaceFromObject = (name, obj, propertyPropagationOption) => `export interface ${name} ${obj.toString(propertyPropagationOption)}`;
|
|
356
376
|
const generateInterface = (name, entries, extend) => {
|
|
357
377
|
const signature = `${name} ${extend ? `extends ${arrayify(extend).join(', ')}` : ''}`.trim();
|
|
358
378
|
const body = generateInterfaceProperties(entries);
|
|
@@ -361,69 +381,17 @@ const generateInterface = (name, entries, extend) => {
|
|
|
361
381
|
const generateInterfaceType = (name, entries, extend) => {
|
|
362
382
|
const body = generateInterfaceProperties(entries);
|
|
363
383
|
const bases = extend ? arrayify(extend).join(' & ') : undefined;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const generateEntities = (schemas, enums) => {
|
|
368
|
-
const entities = new Map();
|
|
369
|
-
for (const [schemaName, schema] of schemas) {
|
|
370
|
-
// Enums are generated separately
|
|
371
|
-
if (isEnumSchemaObject(schema)) {
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
const entity = pascalCase(schemaName);
|
|
375
|
-
// Entity and filter
|
|
376
|
-
const entityInterface = [];
|
|
377
|
-
const filterInterface = [];
|
|
378
|
-
// Referenced entities and property-to-referenced-entity mapping
|
|
379
|
-
const referenceInterface = [];
|
|
380
|
-
const referenceMappingsInterface = [];
|
|
381
|
-
const properties = new Map();
|
|
382
|
-
// The parent entity
|
|
383
|
-
let extend = undefined;
|
|
384
|
-
const processProperties = (props = {}) => {
|
|
385
|
-
for (const [name, property] of Object.entries(props)) {
|
|
386
|
-
const meta = isRelatedEntitySchema(property) ? property['x-weclapp'] : {};
|
|
387
|
-
if (meta.entity) {
|
|
388
|
-
const type = `${pascalCase(meta.entity)}[]`;
|
|
389
|
-
referenceInterface.push({ name, type, required: true });
|
|
390
|
-
filterInterface.push({ name: meta.entity, type, required: true });
|
|
391
|
-
}
|
|
392
|
-
if (meta.service) {
|
|
393
|
-
referenceMappingsInterface.push({ name, type: generateString(meta.service), required: true });
|
|
394
|
-
}
|
|
395
|
-
const type = convertToTypeScriptType(property, name).toString();
|
|
396
|
-
const comment = isNonArraySchemaObject(property) ?
|
|
397
|
-
property.deprecated ? '@deprecated will be removed.' :
|
|
398
|
-
property.format ? `format: ${property.format}` :
|
|
399
|
-
undefined : undefined;
|
|
400
|
-
entityInterface.push({
|
|
401
|
-
name, type, comment,
|
|
402
|
-
required: meta.required,
|
|
403
|
-
readonly: !isReferenceObject(property) && property.readOnly
|
|
404
|
-
});
|
|
405
|
-
properties.set(name, extractPropertyMetaData(enums, meta, property));
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
|
-
if (schema.allOf?.length) {
|
|
409
|
-
for (const item of schema.allOf) {
|
|
410
|
-
if (isReferenceObject(item)) {
|
|
411
|
-
extend = convertToTypeScriptType(item).toString();
|
|
412
|
-
}
|
|
413
|
-
else if (isObjectSchemaObject(item)) {
|
|
414
|
-
processProperties(item.properties);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
processProperties(schema.properties);
|
|
419
|
-
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));
|
|
420
|
-
entities.set(schemaName, {
|
|
421
|
-
extends: extend ? camelCase(extend) : extend,
|
|
422
|
-
properties,
|
|
423
|
-
source
|
|
424
|
-
});
|
|
384
|
+
let typeDefinition = '';
|
|
385
|
+
if (bases) {
|
|
386
|
+
typeDefinition = bases;
|
|
425
387
|
}
|
|
426
|
-
|
|
388
|
+
else {
|
|
389
|
+
typeDefinition = body;
|
|
390
|
+
}
|
|
391
|
+
if (bases && body !== '{}') {
|
|
392
|
+
typeDefinition += ` & ${body}`;
|
|
393
|
+
}
|
|
394
|
+
return generateType(name, typeDefinition);
|
|
427
395
|
};
|
|
428
396
|
|
|
429
397
|
/**
|
|
@@ -431,13 +399,10 @@ const generateEntities = (schemas, enums) => {
|
|
|
431
399
|
* @param s String to pluralize.
|
|
432
400
|
*/
|
|
433
401
|
const pluralize = (s) => {
|
|
434
|
-
return s.endsWith('s') ? s :
|
|
435
|
-
s.endsWith('y') ? `${s.slice(0, -1)}ies` :
|
|
436
|
-
`${s}s`;
|
|
402
|
+
return s.endsWith('s') ? s : s.endsWith('y') ? `${s.slice(0, -1)}ies` : `${s}s`;
|
|
437
403
|
};
|
|
438
404
|
|
|
439
|
-
|
|
440
|
-
const logger = new class {
|
|
405
|
+
const logger = new (class {
|
|
441
406
|
active = true;
|
|
442
407
|
warnings = 0;
|
|
443
408
|
errors = 0;
|
|
@@ -485,21 +450,126 @@ const logger = new class {
|
|
|
485
450
|
printSummary() {
|
|
486
451
|
const format = (v, name, fail, ok) => {
|
|
487
452
|
const color = v ? fail : ok;
|
|
488
|
-
return v === 0
|
|
489
|
-
|
|
453
|
+
return v === 0
|
|
454
|
+
? `${color('zero')} ${pluralize(name)}`
|
|
455
|
+
: v === 1
|
|
456
|
+
? `${color('one')} ${name}`
|
|
457
|
+
: `${color(v)} ${pluralize(name)}`;
|
|
490
458
|
};
|
|
491
459
|
const warnings = format(this.warnings, 'warning', chalk.yellowBright, chalk.greenBright);
|
|
492
460
|
const errors = format(this.errors, 'error', chalk.redBright, chalk.greenBright);
|
|
493
461
|
const info = `Finished with ${warnings} and ${errors}.`;
|
|
494
462
|
this[this.errors ? 'errorLn' : this.warnings ? 'warnLn' : 'successLn'](info);
|
|
495
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;
|
|
496
566
|
};
|
|
497
567
|
|
|
498
568
|
/**
|
|
499
|
-
* ROOT
|
|
500
|
-
* COUNT
|
|
501
|
-
* ENTITY
|
|
502
|
-
* SPECIAL_ROOT
|
|
569
|
+
* ROOT => /article
|
|
570
|
+
* COUNT => /article/count
|
|
571
|
+
* ENTITY => /article/{id}
|
|
572
|
+
* SPECIAL_ROOT => /article/generateImage
|
|
503
573
|
* SPECIAL_ENTITY => /article/id/{id}/generateImag
|
|
504
574
|
*/
|
|
505
575
|
var WeclappEndpointType;
|
|
@@ -511,26 +581,61 @@ var WeclappEndpointType;
|
|
|
511
581
|
WeclappEndpointType["GENERIC_ENTITY"] = "GENERIC_ENTITY";
|
|
512
582
|
})(WeclappEndpointType || (WeclappEndpointType = {}));
|
|
513
583
|
const parseEndpointPath = (path) => {
|
|
514
|
-
const [,
|
|
515
|
-
if (!
|
|
584
|
+
const [, service, ...rest] = path.split('/');
|
|
585
|
+
if (!service) {
|
|
516
586
|
return undefined;
|
|
517
587
|
}
|
|
518
588
|
if (!rest.length) {
|
|
519
|
-
return { path,
|
|
589
|
+
return { path, service, type: WeclappEndpointType.ROOT };
|
|
520
590
|
}
|
|
521
591
|
else if (rest[0] === 'count') {
|
|
522
|
-
return { path,
|
|
592
|
+
return { path, service, type: WeclappEndpointType.COUNT };
|
|
523
593
|
}
|
|
524
594
|
else if (rest[0] === 'id') {
|
|
525
|
-
return rest.length === 2
|
|
526
|
-
{ path,
|
|
527
|
-
|
|
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
|
+
};
|
|
528
603
|
}
|
|
529
604
|
else if (rest.length === 1) {
|
|
530
|
-
return {
|
|
605
|
+
return {
|
|
606
|
+
path,
|
|
607
|
+
service,
|
|
608
|
+
method: rest[1],
|
|
609
|
+
type: WeclappEndpointType.GENERIC_ROOT
|
|
610
|
+
};
|
|
531
611
|
}
|
|
532
612
|
return undefined;
|
|
533
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
|
+
};
|
|
534
639
|
|
|
535
640
|
const generateArrowFunction = ({ name, signature, returns, params }) => {
|
|
536
641
|
return `const ${name}: ${signature} = (${params?.join(', ') ?? ''}) =>\n${indent(returns)};`;
|
|
@@ -542,109 +647,109 @@ const generateArrowFunctionType = ({ type, returns = 'void', generics, params })
|
|
|
542
647
|
return generateType(type, `${genericsString + paramsString} =>\n${indent(returns)}`);
|
|
543
648
|
};
|
|
544
649
|
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
if (
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
param.required && required.push(param.name);
|
|
553
|
-
}
|
|
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] : [];
|
|
554
657
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
type: 'object', required,
|
|
558
|
-
properties: Object.fromEntries(properties)
|
|
559
|
-
};
|
|
658
|
+
return [param];
|
|
659
|
+
});
|
|
560
660
|
};
|
|
561
661
|
|
|
562
|
-
const
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
const
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
|
|
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))
|
|
572
673
|
});
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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>`
|
|
578
684
|
});
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
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']
|
|
583
690
|
});
|
|
584
691
|
return {
|
|
585
|
-
|
|
586
|
-
name:
|
|
587
|
-
|
|
588
|
-
func: { name: functionName$5, source: functionSource },
|
|
692
|
+
name: functionName,
|
|
693
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
694
|
+
func: { name: functionName, source: functionSource },
|
|
589
695
|
interfaces: [
|
|
590
|
-
{
|
|
591
|
-
|
|
592
|
-
source: generateInterfaceFromObject(entityParameters, parameters, true)
|
|
593
|
-
}
|
|
696
|
+
...(operationObject.parameters?.length ? [{ name: parametersTypeName, source: parametersTypeSource }] : []),
|
|
697
|
+
{ name: filterTypeName, source: filterTypeSource }
|
|
594
698
|
]
|
|
595
699
|
};
|
|
596
700
|
};
|
|
597
701
|
|
|
598
|
-
const
|
|
599
|
-
if (
|
|
600
|
-
return
|
|
601
|
-
}
|
|
702
|
+
const generateContentType = (body, fallback = 'unknown') => {
|
|
703
|
+
if (!body?.content)
|
|
704
|
+
return createRawType(fallback);
|
|
602
705
|
const types = [];
|
|
603
|
-
for (const { schema } of Object.values(body
|
|
706
|
+
for (const { schema } of Object.values(body.content)) {
|
|
604
707
|
if (schema) {
|
|
605
708
|
types.push(convertToTypeScriptType(schema));
|
|
606
709
|
}
|
|
607
710
|
}
|
|
608
|
-
return types.length ? createTupleType(types) :
|
|
711
|
+
return (types.length > 1 ? createTupleType(types) : types[0]) ?? createRawType(fallback);
|
|
609
712
|
};
|
|
610
713
|
|
|
611
|
-
const generateRequestBodyType = ({ requestBody }) => {
|
|
612
|
-
|
|
714
|
+
const generateRequestBodyType = ({ requestBody }, requestBodies) => {
|
|
715
|
+
const requestBodyObject = requestBody && isReferenceObject(requestBody) ? requestBodies.get(getRefName(requestBody)) : requestBody;
|
|
716
|
+
return generateContentType(requestBodyObject);
|
|
613
717
|
};
|
|
614
718
|
|
|
615
|
-
const
|
|
616
|
-
.filter(v => v[0].startsWith('2'))[0]?.[1];
|
|
617
|
-
const generateResponseBodyType = (object) => generateBodyType(resolveBodyType(object)) ?? createRawType('void');
|
|
719
|
+
const resolveResponsesObject = (responses) => Object.entries(responses).find(([statusCode]) => statusCode.startsWith('2'))?.[1];
|
|
618
720
|
|
|
619
|
-
const
|
|
620
|
-
const
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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()}>`
|
|
628
737
|
});
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
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']
|
|
633
743
|
});
|
|
634
744
|
return {
|
|
635
|
-
|
|
636
|
-
name:
|
|
637
|
-
|
|
638
|
-
func: { name: functionName$4, source: functionSource }
|
|
745
|
+
name: functionName,
|
|
746
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
747
|
+
func: { name: functionName, source: functionSource }
|
|
639
748
|
};
|
|
640
749
|
};
|
|
641
750
|
|
|
642
751
|
const generateGenericFunctionName = (path, suffix = '', prefix = '') => {
|
|
643
|
-
return camelCase(`${prefix}_` +
|
|
644
|
-
path
|
|
645
|
-
.replace(/.*\//, '')
|
|
646
|
-
.replace(/\W+/, '_')
|
|
647
|
-
.replace(/[_]+/, '_') + `_${suffix}`);
|
|
752
|
+
return camelCase(`${prefix}_` + path.replace(/.*\//, '').replace(/\W+/, '_').replace(/[_]+/, '_') + `_${suffix}`);
|
|
648
753
|
};
|
|
649
754
|
|
|
650
755
|
const insertPathPlaceholder = (path, record) => {
|
|
@@ -652,77 +757,84 @@ const insertPathPlaceholder = (path, record) => {
|
|
|
652
757
|
};
|
|
653
758
|
|
|
654
759
|
const wrapBody = (type, target) => {
|
|
655
|
-
return type.toString() === 'binary' ?
|
|
656
|
-
createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') :
|
|
657
|
-
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
|
|
658
761
|
};
|
|
659
|
-
const generateGenericEndpoint = (suffix) => ({
|
|
762
|
+
const generateGenericEndpoint = (suffix) => ({ method, endpoint, operationObject, context, options }) => {
|
|
660
763
|
const functionName = generateGenericFunctionName(endpoint.path, suffix, method);
|
|
661
|
-
const
|
|
662
|
-
const
|
|
663
|
-
const entityQuery = `${interfaceName}_Query`;
|
|
764
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
765
|
+
const entityQuery = `${functionTypeName}_Query`;
|
|
664
766
|
const hasId = endpoint.path.includes('{id}');
|
|
665
767
|
const params = createObjectType({
|
|
666
|
-
params:
|
|
667
|
-
|
|
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')}>`
|
|
668
783
|
});
|
|
669
|
-
const responseBody = generateResponseBodyType(path);
|
|
670
|
-
const forceBlobResponse = String(responseBody.toString() === 'binary');
|
|
671
784
|
const functionSource = generateArrowFunction({
|
|
672
785
|
name: functionName,
|
|
673
|
-
signature:
|
|
674
|
-
params: hasId ? ['id', 'query'] : ['query'],
|
|
675
|
-
returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${
|
|
676
|
-
});
|
|
677
|
-
const interfaceSource = generateArrowFunctionType({
|
|
678
|
-
type: interfaceName,
|
|
679
|
-
params: [...(hasId ? ['id: string'] : []), `query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`],
|
|
680
|
-
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)`
|
|
681
789
|
});
|
|
682
790
|
return {
|
|
683
|
-
entity,
|
|
684
791
|
name: functionName,
|
|
685
|
-
type: { name:
|
|
792
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
686
793
|
func: { name: functionName, source: functionSource },
|
|
687
794
|
interfaces: [
|
|
688
795
|
{
|
|
689
796
|
name: entityQuery,
|
|
690
|
-
source: generateInterfaceFromObject(entityQuery, params,
|
|
797
|
+
source: generateInterfaceFromObject(entityQuery, params, 'propagate')
|
|
691
798
|
}
|
|
692
799
|
]
|
|
693
800
|
};
|
|
694
801
|
};
|
|
695
802
|
|
|
696
|
-
const
|
|
697
|
-
const
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
returns: `_${functionName$3}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options)`,
|
|
704
|
-
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>`
|
|
705
810
|
});
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
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']
|
|
710
816
|
});
|
|
711
817
|
return {
|
|
712
|
-
|
|
713
|
-
name:
|
|
714
|
-
|
|
715
|
-
func: { name: functionName$3, source: functionSource }
|
|
818
|
+
name: functionName,
|
|
819
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
820
|
+
func: { name: functionName, source: functionSource }
|
|
716
821
|
};
|
|
717
822
|
};
|
|
718
823
|
|
|
719
|
-
const
|
|
824
|
+
const generateTupleArray = (values) => `(${concat(values.map(generateString), ' | ')})[]`;
|
|
825
|
+
|
|
720
826
|
const excludedParameters = [
|
|
721
|
-
'page',
|
|
722
|
-
'
|
|
827
|
+
'page',
|
|
828
|
+
'pageSize',
|
|
829
|
+
'sort',
|
|
830
|
+
'serializeNulls',
|
|
831
|
+
'properties',
|
|
832
|
+
'includeReferencedEntities',
|
|
833
|
+
'additionalProperties'
|
|
723
834
|
];
|
|
724
|
-
const
|
|
725
|
-
const
|
|
835
|
+
const resolveAdditionalPropertiesSchema = ({ responses }, contextResponses) => {
|
|
836
|
+
const response = resolveResponsesObject(responses);
|
|
837
|
+
const body = response && isReferenceObject(response) ? contextResponses.get(getRefName(response)) : response;
|
|
726
838
|
if (isResponseObject(body)) {
|
|
727
839
|
const schema = body?.content?.['application/json']?.schema;
|
|
728
840
|
if (isObjectSchemaObject(schema)) {
|
|
@@ -734,280 +846,316 @@ const resolveAdditionalProperties = (path) => {
|
|
|
734
846
|
}
|
|
735
847
|
return undefined;
|
|
736
848
|
};
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
849
|
+
const resolveReferences = (entity, entities) => {
|
|
850
|
+
const references = [];
|
|
851
|
+
const generatedEntity = entities.get(entity);
|
|
852
|
+
if (generatedEntity) {
|
|
853
|
+
for (const [property, propertyMetaData] of generatedEntity.properties) {
|
|
854
|
+
if (propertyMetaData.service) {
|
|
855
|
+
references.push({
|
|
856
|
+
name: property,
|
|
857
|
+
type: generateString(propertyMetaData.service),
|
|
858
|
+
required: true
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (generatedEntity.parentName) {
|
|
863
|
+
references.push(...resolveReferences(generatedEntity.parentName, entities));
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return references;
|
|
867
|
+
};
|
|
868
|
+
const resolveReferencedEntities = (entity, entities) => {
|
|
869
|
+
const referencedEntities = [];
|
|
870
|
+
const generatedEntity = entities.get(entity);
|
|
871
|
+
if (generatedEntity) {
|
|
872
|
+
for (const [, propertyMetaData] of generatedEntity.properties) {
|
|
873
|
+
if (propertyMetaData.service && propertyMetaData.entity) {
|
|
874
|
+
const referencedEntity = entities.get(propertyMetaData.entity);
|
|
875
|
+
if (referencedEntity)
|
|
876
|
+
referencedEntities.push({
|
|
877
|
+
name: propertyMetaData.service,
|
|
878
|
+
type: `${referencedEntity.interfaceName}[]`,
|
|
879
|
+
required: true
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (generatedEntity.parentName) {
|
|
884
|
+
referencedEntities.push(...resolveReferencedEntities(generatedEntity.parentName, entities));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return referencedEntities;
|
|
888
|
+
};
|
|
889
|
+
const generateSomeEndpoint = ({ endpoint, operationObject, entities, context, options }) => {
|
|
890
|
+
const functionName = 'some';
|
|
891
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
892
|
+
const relatedEntityName = context.aliases.get(endpoint.service);
|
|
893
|
+
const relatedEntity = !!relatedEntityName && entities.get(relatedEntityName);
|
|
894
|
+
if (!relatedEntity) {
|
|
895
|
+
throw Error(`Related entity schema for service ${endpoint.service} not found`);
|
|
896
|
+
}
|
|
897
|
+
const parametersTypeName = `${functionTypeName}_Parameters`;
|
|
898
|
+
const parameters = operationObject.parameters?.filter((v) => isParameterObject(v) ? !excludedParameters.includes(v.name) : false);
|
|
899
|
+
const parametersType = createObjectType({
|
|
900
|
+
params: parameters && convertToTypeScriptType(resolveParameters(parameters, context.parameters))
|
|
755
901
|
});
|
|
756
|
-
const
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
902
|
+
const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
|
|
903
|
+
const filterTypeName = `${functionTypeName}_Filter`;
|
|
904
|
+
const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${relatedEntity.filterInterfaceName}`]);
|
|
905
|
+
const referencesTypeName = `${functionTypeName}_References`;
|
|
906
|
+
const referencesTypeSource = generateInterfaceType(referencesTypeName, resolveReferences(endpoint.service, entities));
|
|
907
|
+
const additionalPropertiesSchema = resolveAdditionalPropertiesSchema(operationObject, context.responses);
|
|
908
|
+
const additionalPropertyTypeName = `${functionTypeName}_AdditionalPropertyNames`;
|
|
909
|
+
const additionalPropertyTypeSource = generateType(additionalPropertyTypeName, additionalPropertiesSchema ? generateTupleArray(Object.keys(additionalPropertiesSchema?.properties)) : '[]');
|
|
910
|
+
const queryTypeName = `${functionTypeName}_Query`;
|
|
911
|
+
const queryTypeSource = generateType(queryTypeName, `SomeQuery<${relatedEntity.interfaceName}, ${filterTypeName}, ${referencesTypeName}, ${additionalPropertyTypeName}> & ${parametersTypeName}`);
|
|
912
|
+
const referencedEntitiesTypeName = `${functionTypeName}_ReferencedEntities`;
|
|
913
|
+
const referencedEntitiesTypeSource = generateInterfaceType(referencedEntitiesTypeName, resolveReferencedEntities(endpoint.service, entities));
|
|
914
|
+
const additionalPropertiesTypeName = `${functionTypeName}_AdditionalProperties`;
|
|
915
|
+
const additionalPropertiesTypeSource = generateType(additionalPropertiesTypeName, additionalPropertiesSchema ? convertToTypeScriptType(additionalPropertiesSchema).toString() : '{}');
|
|
916
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
917
|
+
type: functionTypeName,
|
|
918
|
+
params: [`query${parametersType.isFullyOptional() ? '?' : ''}: ${queryTypeName}, requestOptions?: RequestOptions`],
|
|
919
|
+
returns: `${resolveResponseType(options.target)}<SomeQueryReturn<${relatedEntity.interfaceName}, ${referencedEntitiesTypeName}, ${additionalPropertiesTypeName}>>`
|
|
765
920
|
});
|
|
766
921
|
const functionSource = generateArrowFunction({
|
|
767
|
-
name: functionName
|
|
768
|
-
signature:
|
|
769
|
-
returns: `_${functionName
|
|
770
|
-
params: ['query']
|
|
922
|
+
name: functionName,
|
|
923
|
+
signature: functionTypeName,
|
|
924
|
+
returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
|
|
925
|
+
params: ['query', 'requestOptions?: RequestOptions']
|
|
771
926
|
});
|
|
772
927
|
return {
|
|
773
|
-
|
|
774
|
-
name:
|
|
775
|
-
|
|
776
|
-
func: { name: functionName$2, source: functionSource },
|
|
928
|
+
name: functionName,
|
|
929
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
930
|
+
func: { name: functionName, source: functionSource },
|
|
777
931
|
interfaces: [
|
|
778
|
-
{
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
932
|
+
{ name: parametersTypeName, source: parametersTypeSource },
|
|
933
|
+
{ name: filterTypeName, source: filterTypeSource },
|
|
934
|
+
{ name: referencesTypeName, source: referencesTypeSource },
|
|
935
|
+
{ name: additionalPropertyTypeName, source: additionalPropertyTypeSource },
|
|
936
|
+
{ name: queryTypeName, source: queryTypeSource },
|
|
937
|
+
{ name: referencedEntitiesTypeName, source: referencedEntitiesTypeSource },
|
|
938
|
+
{ name: additionalPropertiesTypeName, source: additionalPropertiesTypeSource }
|
|
782
939
|
]
|
|
783
940
|
};
|
|
784
941
|
};
|
|
785
942
|
|
|
786
|
-
const
|
|
787
|
-
const
|
|
788
|
-
const
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
signature: interfaceName,
|
|
793
|
-
params: ['id', 'query'],
|
|
794
|
-
returns: `_${functionName$1}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query)`
|
|
795
|
-
});
|
|
796
|
-
const interfaceSource = generateArrowFunctionType({
|
|
797
|
-
type: interfaceName,
|
|
798
|
-
params: ['id: string', 'query?: Q'],
|
|
943
|
+
const generateUniqueEndpoint = ({ operationObject, endpoint, context, options }) => {
|
|
944
|
+
const functionName = 'unique';
|
|
945
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
946
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
947
|
+
type: functionTypeName,
|
|
948
|
+
params: ['id: string', 'query?: Q', 'requestOptions?: RequestOptions'],
|
|
799
949
|
generics: ['Q extends UniqueQuery'],
|
|
800
|
-
returns: `${resolveResponseType(target)}<${
|
|
950
|
+
returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
|
|
951
|
+
});
|
|
952
|
+
const functionSource = generateArrowFunction({
|
|
953
|
+
name: functionName,
|
|
954
|
+
signature: functionTypeName,
|
|
955
|
+
params: ['id', 'query', 'requestOptions?: RequestOptions'],
|
|
956
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, requestOptions)`
|
|
801
957
|
});
|
|
802
958
|
return {
|
|
803
|
-
|
|
804
|
-
name:
|
|
805
|
-
|
|
806
|
-
func: { name: functionName$1, source: functionSource }
|
|
959
|
+
name: functionName,
|
|
960
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
961
|
+
func: { name: functionName, source: functionSource }
|
|
807
962
|
};
|
|
808
963
|
};
|
|
809
964
|
|
|
810
|
-
const
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
965
|
+
const generateUpdateEndpoint = ({ endpoint, operationObject, context, options }) => {
|
|
966
|
+
const functionName = 'update';
|
|
967
|
+
const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
|
|
968
|
+
const functionTypeSource = generateArrowFunctionType({
|
|
969
|
+
type: functionTypeName,
|
|
970
|
+
params: [
|
|
971
|
+
'id: string',
|
|
972
|
+
`data: DeepPartial<${generateRequestBodyType(operationObject, context.requestBodies).toString()}>`,
|
|
973
|
+
'options?: UpdateQuery',
|
|
974
|
+
'requestOptions?: RequestOptions'
|
|
975
|
+
],
|
|
976
|
+
returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
|
|
818
977
|
});
|
|
819
978
|
const functionSource = generateArrowFunction({
|
|
820
979
|
name: functionName,
|
|
821
|
-
signature:
|
|
822
|
-
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options)`,
|
|
823
|
-
params: ['id', 'data', 'options']
|
|
980
|
+
signature: functionTypeName,
|
|
981
|
+
returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options, requestOptions)`,
|
|
982
|
+
params: ['id', 'data', 'options', 'requestOptions?: RequestOptions']
|
|
824
983
|
});
|
|
825
984
|
return {
|
|
826
|
-
entity,
|
|
827
985
|
name: functionName,
|
|
828
|
-
type: { name:
|
|
986
|
+
type: { name: functionTypeName, source: functionTypeSource },
|
|
829
987
|
func: { name: functionName, source: functionSource }
|
|
830
988
|
};
|
|
831
989
|
};
|
|
832
990
|
|
|
833
|
-
const groupEndpointsByEntity = (paths) => {
|
|
834
|
-
const endpoints = new Map();
|
|
835
|
-
for (const [rawPath, path] of Object.entries(paths)) {
|
|
836
|
-
const endpoint = parseEndpointPath(rawPath);
|
|
837
|
-
if (!endpoint || !path) {
|
|
838
|
-
logger.errorLn(`Failed to parse ${rawPath}`);
|
|
839
|
-
continue;
|
|
840
|
-
}
|
|
841
|
-
if (endpoints.has(endpoint.entity)) {
|
|
842
|
-
endpoints.get(endpoint.entity)?.push({ endpoint, path });
|
|
843
|
-
}
|
|
844
|
-
else {
|
|
845
|
-
endpoints.set(endpoint.entity, [{ endpoint, path }]);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
return endpoints;
|
|
849
|
-
};
|
|
850
|
-
|
|
851
991
|
const generators = {
|
|
852
992
|
/* /article */
|
|
853
993
|
[WeclappEndpointType.ROOT]: {
|
|
854
|
-
|
|
855
|
-
|
|
994
|
+
[OpenAPIV3.HttpMethods.GET]: generateSomeEndpoint,
|
|
995
|
+
[OpenAPIV3.HttpMethods.POST]: generateCreateEndpoint
|
|
856
996
|
},
|
|
857
997
|
/* /article/count */
|
|
858
998
|
[WeclappEndpointType.COUNT]: {
|
|
859
|
-
|
|
999
|
+
[OpenAPIV3.HttpMethods.GET]: generateCountEndpoint
|
|
860
1000
|
},
|
|
861
1001
|
/* /article/:id */
|
|
862
1002
|
[WeclappEndpointType.ENTITY]: {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1003
|
+
[OpenAPIV3.HttpMethods.GET]: generateUniqueEndpoint,
|
|
1004
|
+
[OpenAPIV3.HttpMethods.PUT]: generateUpdateEndpoint,
|
|
1005
|
+
[OpenAPIV3.HttpMethods.DELETE]: generateRemoveEndpoint
|
|
866
1006
|
},
|
|
867
1007
|
/* /article/:id/method */
|
|
868
1008
|
[WeclappEndpointType.GENERIC_ENTITY]: {
|
|
869
|
-
|
|
870
|
-
|
|
1009
|
+
[OpenAPIV3.HttpMethods.GET]: generateGenericEndpoint('ById'),
|
|
1010
|
+
[OpenAPIV3.HttpMethods.POST]: generateGenericEndpoint('ById')
|
|
871
1011
|
},
|
|
872
1012
|
/* /article/method */
|
|
873
1013
|
[WeclappEndpointType.GENERIC_ROOT]: {
|
|
874
|
-
|
|
875
|
-
|
|
1014
|
+
[OpenAPIV3.HttpMethods.GET]: generateGenericEndpoint(),
|
|
1015
|
+
[OpenAPIV3.HttpMethods.POST]: generateGenericEndpoint()
|
|
876
1016
|
}
|
|
877
1017
|
};
|
|
878
|
-
const generateServices = (
|
|
1018
|
+
const generateServices = (entities, context, options) => {
|
|
879
1019
|
const services = new Map();
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
const
|
|
883
|
-
const serviceTypeName = pascalCase(`${endpoint}Service`);
|
|
884
|
-
// Service functions
|
|
1020
|
+
for (const [serviceName, serviceEndpoints] of context.endpoints) {
|
|
1021
|
+
const serviceFnName = camelCase(`${serviceName}Service`);
|
|
1022
|
+
const serviceTypeName = pascalCase(`${serviceName}Service`);
|
|
885
1023
|
const functions = [];
|
|
886
|
-
for (const { path, endpoint } of
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
1024
|
+
for (const { path, endpoint } of serviceEndpoints) {
|
|
1025
|
+
for (const method of [
|
|
1026
|
+
OpenAPIV3.HttpMethods.GET,
|
|
1027
|
+
OpenAPIV3.HttpMethods.POST,
|
|
1028
|
+
OpenAPIV3.HttpMethods.PUT,
|
|
1029
|
+
OpenAPIV3.HttpMethods.DELETE
|
|
1030
|
+
]) {
|
|
1031
|
+
if ((method === OpenAPIV3.HttpMethods.GET &&
|
|
1032
|
+
endpoint.type === WeclappEndpointType.ENTITY &&
|
|
1033
|
+
!options.generateUnique) ||
|
|
1034
|
+
(method === OpenAPIV3.HttpMethods.POST &&
|
|
1035
|
+
(endpoint.type === WeclappEndpointType.COUNT || endpoint.path.endsWith('query')))) {
|
|
1036
|
+
// Skip unique endpoints if generateUnique option is not set or if POST is used for filter queries
|
|
890
1037
|
continue;
|
|
891
1038
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
if (!
|
|
1039
|
+
const operationObject = path[method];
|
|
1040
|
+
const generatorFn = generators[endpoint.type][method];
|
|
1041
|
+
if (operationObject && generatorFn) {
|
|
1042
|
+
if (!operationObject.deprecated || options.deprecated) {
|
|
896
1043
|
functions.push({
|
|
897
|
-
...
|
|
898
|
-
|
|
1044
|
+
...generatorFn({
|
|
1045
|
+
method,
|
|
1046
|
+
endpoint,
|
|
1047
|
+
operationObject,
|
|
1048
|
+
entities,
|
|
1049
|
+
context,
|
|
1050
|
+
options
|
|
1051
|
+
}),
|
|
1052
|
+
path: operationObject
|
|
899
1053
|
});
|
|
900
1054
|
}
|
|
901
1055
|
}
|
|
902
|
-
else {
|
|
903
|
-
logger.errorLn(`Failed to generate a function for ${method.toUpperCase()}:${endpoint.type} ${endpoint.path}`);
|
|
904
|
-
}
|
|
905
1056
|
}
|
|
906
1057
|
}
|
|
907
1058
|
if (!functions.length) {
|
|
908
1059
|
continue;
|
|
909
1060
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
...functions.map(v => ({
|
|
1061
|
+
const serviceTypes = generateStatements(...functions.flatMap((v) => generateBlockComment(`${serviceTypeName} - ${pascalCase(v.name)}`, generateStatements(...[...(v.interfaces?.map((v) => v.source) ?? []), v.type.source]))), generateBlockComment(`${serviceTypeName}`, generateInterface(serviceTypeName, [
|
|
1062
|
+
...functions.map((v) => ({
|
|
913
1063
|
required: true,
|
|
914
1064
|
comment: v.path.deprecated ? '@deprecated' : undefined,
|
|
915
1065
|
name: v.func.name,
|
|
916
1066
|
type: v.type.name
|
|
917
1067
|
}))
|
|
918
|
-
]));
|
|
919
|
-
|
|
920
|
-
const
|
|
921
|
-
const
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1068
|
+
])));
|
|
1069
|
+
const serviceFn = `export const ${serviceFnName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${generateBlockStatements(...functions.map((v) => v.func.source), `return {${concat(functions.map((v) => v.func.name))}};`)};`;
|
|
1070
|
+
const relatedEntityName = context.aliases.get(serviceName);
|
|
1071
|
+
const relatedEntity = relatedEntityName ? entities.get(relatedEntityName) : undefined;
|
|
1072
|
+
services.set(serviceName, {
|
|
1073
|
+
name: serviceName,
|
|
1074
|
+
serviceFnName,
|
|
1075
|
+
functions,
|
|
1076
|
+
source: generateStatements(serviceTypes, serviceFn),
|
|
1077
|
+
deprecated: functions.every((v) => v.path.deprecated),
|
|
1078
|
+
relatedEntity
|
|
1079
|
+
});
|
|
925
1080
|
}
|
|
926
1081
|
return services;
|
|
927
1082
|
};
|
|
928
1083
|
|
|
929
|
-
const
|
|
930
|
-
const customValueEntity = entities.get('customValue');
|
|
1084
|
+
const generateCustomValueServices = (services) => {
|
|
931
1085
|
const customValueEntities = [];
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
serviceLoop: for (const service of services) {
|
|
937
|
-
const someFunction = service.functions.find(v => v.name === 'some');
|
|
938
|
-
if (!someFunction) {
|
|
939
|
-
continue;
|
|
940
|
-
}
|
|
941
|
-
const entity = entities.get(camelCase(someFunction.entity));
|
|
942
|
-
if (entity?.properties.size !== customValueEntity.properties.size) {
|
|
943
|
-
continue;
|
|
1086
|
+
for (const service of services) {
|
|
1087
|
+
const relatedEntity = service.relatedEntity;
|
|
1088
|
+
if (relatedEntity?.name === 'customValue') {
|
|
1089
|
+
customValueEntities.push(service.name);
|
|
944
1090
|
}
|
|
945
|
-
for (const [prop, { type }] of entity.properties) {
|
|
946
|
-
if (customValueEntity.properties.get(prop)?.type !== type) {
|
|
947
|
-
continue serviceLoop;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
customValueEntities.push(service.entity);
|
|
951
1091
|
}
|
|
952
|
-
return
|
|
1092
|
+
return generateStatements(generateType('WCustomValueService', concat(generateStrings(customValueEntities), ' | ')), `export const wCustomValueServiceNames: WCustomValueService[] = [${concat(generateStrings(customValueEntities))}];`, `export const isWCustomValueService = (service: string | undefined): service is WCustomValueService =>\n${indent('wCustomValueServiceNames.includes(service as WCustomValueService);')}`);
|
|
953
1093
|
};
|
|
954
1094
|
|
|
955
1095
|
const generateObject = (properties) => {
|
|
956
1096
|
const body = [];
|
|
957
|
-
for (const { key, value } of properties) {
|
|
1097
|
+
for (const { key, value, comment } of properties) {
|
|
958
1098
|
if (value === undefined) {
|
|
959
1099
|
continue;
|
|
960
1100
|
}
|
|
961
1101
|
if (Array.isArray(value)) {
|
|
962
1102
|
const str = generateObject(value);
|
|
963
1103
|
if (str.length > 2) {
|
|
964
|
-
body.push(`${key}: ${str}`);
|
|
1104
|
+
body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${str}`);
|
|
965
1105
|
}
|
|
966
1106
|
}
|
|
967
1107
|
else {
|
|
968
|
-
body.push(`${key}: ${String(value)}`);
|
|
1108
|
+
body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${String(value)}`);
|
|
969
1109
|
}
|
|
970
1110
|
}
|
|
971
1111
|
return body.length ? `{\n${indent(body.join(',\n'))}\n}` : `{}`;
|
|
972
1112
|
};
|
|
973
1113
|
|
|
974
1114
|
const resolveInheritedEntities = (root, entities) => {
|
|
975
|
-
const parent = root.
|
|
1115
|
+
const parent = root.parentName ? entities.get(root.parentName) : undefined;
|
|
976
1116
|
return parent ? [parent, ...resolveInheritedEntities(parent, entities)] : [];
|
|
977
1117
|
};
|
|
978
|
-
const generatePropertyDescriptors = (entity, entities, services, options) => [
|
|
979
|
-
|
|
980
|
-
...entity.properties
|
|
981
|
-
].filter(([, meta]) => {
|
|
1118
|
+
const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
|
|
1119
|
+
.filter(([, meta]) => {
|
|
982
1120
|
// If we generate deprecated things we can skip the filtering
|
|
983
1121
|
if (options.deprecated) {
|
|
984
1122
|
return true;
|
|
985
1123
|
}
|
|
986
1124
|
// Check if corresponding service is deprecated and can be removed
|
|
987
|
-
const service = services.find(v => v.
|
|
1125
|
+
const service = services.find((v) => v.name === meta.service);
|
|
988
1126
|
return !meta.service || (service && !service.deprecated);
|
|
989
|
-
})
|
|
1127
|
+
})
|
|
1128
|
+
.map(([property, meta]) => ({
|
|
990
1129
|
key: property,
|
|
991
1130
|
value: Object.entries(meta).map(([key, value]) => ({
|
|
992
1131
|
key,
|
|
993
|
-
value: value !== undefined ? typeof value === 'number' ? value : generateString(value) : undefined
|
|
1132
|
+
value: value !== undefined ? (typeof value === 'number' ? value : generateString(value)) : undefined
|
|
994
1133
|
}))
|
|
995
1134
|
}));
|
|
996
|
-
const
|
|
1135
|
+
const generateEntityProperties = (entities, services, options) => {
|
|
997
1136
|
const typeName = 'WEntityProperties';
|
|
998
|
-
const propertyMap = [
|
|
999
|
-
|
|
1000
|
-
|
|
1137
|
+
const propertyMap = [
|
|
1138
|
+
...entities.entries(),
|
|
1139
|
+
...services
|
|
1140
|
+
.filter(({ relatedEntity }) => !!relatedEntity)
|
|
1141
|
+
.filter(({ name }) => !entities.get(name))
|
|
1142
|
+
.map(({ name, relatedEntity }) => {
|
|
1143
|
+
return [name, relatedEntity];
|
|
1144
|
+
})
|
|
1145
|
+
].map(([entityName, entity]) => ({
|
|
1146
|
+
key: entityName,
|
|
1147
|
+
value: generatePropertyDescriptors(entity, entities, services, options)
|
|
1001
1148
|
}));
|
|
1002
1149
|
return generateStatements(`export type ${typeName} = Partial<Record<WEntity, Partial<Record<string, WEntityPropertyMeta>>>>;`, `export const wEntityProperties: ${typeName} = ${generateObject(propertyMap)};`);
|
|
1003
1150
|
};
|
|
1004
1151
|
|
|
1005
1152
|
const generateArray = (values) => {
|
|
1006
|
-
return `[${concat(values.map(v => generateString(String(v))))}]`;
|
|
1153
|
+
return `[${concat(values.map((v) => generateString(String(v))))}]`;
|
|
1007
1154
|
};
|
|
1008
1155
|
|
|
1009
1156
|
// Only functions matching this regex are included in the generation.
|
|
1010
1157
|
const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
|
|
1158
|
+
const SOME_FILTER_REGEX = /^(some)$/;
|
|
1011
1159
|
/**
|
|
1012
1160
|
* Generates for each function a map with the entity-name as key and service type as value.
|
|
1013
1161
|
* E.g. WServicesWith[Function] where [Function] may be something like "some" or "create".
|
|
@@ -1016,160 +1164,173 @@ const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
|
|
|
1016
1164
|
*/
|
|
1017
1165
|
const generateGroupedServices = (services) => {
|
|
1018
1166
|
const entityDescriptors = new Map();
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1167
|
+
const someQueryDescriptors = new Map();
|
|
1168
|
+
for (const service of services) {
|
|
1169
|
+
for (const fn of service.functions) {
|
|
1170
|
+
if (!FILTER_REGEX.test(fn.name)) {
|
|
1022
1171
|
continue;
|
|
1023
1172
|
}
|
|
1024
|
-
entityDescriptors.set(name, [
|
|
1025
|
-
...(entityDescriptors.get(name) ?? []),
|
|
1026
|
-
|
|
1173
|
+
entityDescriptors.set(fn.name, [
|
|
1174
|
+
...(entityDescriptors.get(fn.name) ?? []),
|
|
1175
|
+
{
|
|
1176
|
+
name: service.name,
|
|
1027
1177
|
required: true,
|
|
1028
|
-
type: `${pascalCase(
|
|
1178
|
+
type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}`
|
|
1029
1179
|
}
|
|
1030
1180
|
]);
|
|
1181
|
+
if (SOME_FILTER_REGEX.test(fn.name)) {
|
|
1182
|
+
someQueryDescriptors.set(fn.name, [
|
|
1183
|
+
...(someQueryDescriptors.get(fn.name) ?? []),
|
|
1184
|
+
{
|
|
1185
|
+
name: service.name,
|
|
1186
|
+
required: true,
|
|
1187
|
+
type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}_Query`
|
|
1188
|
+
}
|
|
1189
|
+
]);
|
|
1190
|
+
}
|
|
1031
1191
|
}
|
|
1032
1192
|
}
|
|
1033
|
-
const
|
|
1193
|
+
const entityDescriptorEntries = [...entityDescriptors.entries()];
|
|
1194
|
+
const someQueryDescriptorEntries = [...someQueryDescriptors.entries()];
|
|
1034
1195
|
const typeGuards = [];
|
|
1035
|
-
for (const [name] of
|
|
1196
|
+
for (const [name] of entityDescriptorEntries) {
|
|
1036
1197
|
const constant = camelCase(`wServiceWith_${name}_Names`);
|
|
1037
1198
|
const service = pascalCase(`WServiceWith_${name}`);
|
|
1038
1199
|
const guard = `(service: string | undefined): service is ${service} =>\n${indent(`${constant}.includes(service as ${service});`)}`;
|
|
1039
1200
|
typeGuards.push(`export const is${service} = ${guard}`);
|
|
1040
1201
|
}
|
|
1041
|
-
return [
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
const value = generateArray(props.map(v => v.name));
|
|
1048
|
-
return `export const ${constant}: ${type}[] = ${value};`;
|
|
1049
|
-
}),
|
|
1050
|
-
generateBlockComment('Type guards for service classes.', generateStatements(...typeGuards))
|
|
1051
|
-
];
|
|
1202
|
+
return generateStatements(...entityDescriptorEntries.map(([name, props]) => generateInterface(pascalCase(`WServicesWith_${name}`), props)), ...entityDescriptorEntries.map(([name]) => generateType(pascalCase(`WServiceWith_${name}`), `keyof ${pascalCase(`WServicesWith_${name}`)}`)), ...someQueryDescriptorEntries.map(([name, props]) => generateInterface(pascalCase(`WServicesWith_${name}_Query`), props)), ...someQueryDescriptorEntries.map(([name]) => generateType(pascalCase(`WServiceWith_${name}_Query`), `keyof ${pascalCase(`WServicesWith_${name}_Query`)}`)), ...entityDescriptorEntries.map(([name, props]) => {
|
|
1203
|
+
const constant = camelCase(`wServiceWith_${name}_Names`);
|
|
1204
|
+
const type = pascalCase(`WServiceWith_${name}`);
|
|
1205
|
+
const value = generateArray(props.map((v) => v.name));
|
|
1206
|
+
return `export const ${constant}: ${type}[] = ${value};`;
|
|
1207
|
+
}), ...typeGuards);
|
|
1052
1208
|
};
|
|
1053
1209
|
|
|
1054
|
-
const
|
|
1055
|
-
const
|
|
1056
|
-
const
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1210
|
+
const generateMaps = (enums, entities, services, context, options) => {
|
|
1211
|
+
const enumInstances = `export const wEnums = ${generateObject([...enums.keys()].map((v) => ({ key: v, value: v })))};`;
|
|
1212
|
+
const entityNames = `export const wEntityNames: WEntity[] = ${generateArray([...entities.keys()])};`;
|
|
1213
|
+
const generatedServices = [...services.values()];
|
|
1214
|
+
const serviceInstances = `export const wServices = ${generateObject(generatedServices.map((v) => ({
|
|
1215
|
+
key: v.name,
|
|
1216
|
+
value: `${v.serviceFnName}()`,
|
|
1217
|
+
comment: v.deprecated ? '@deprecated' : undefined
|
|
1218
|
+
})))};`;
|
|
1219
|
+
const serviceFactories = `export const wServiceFactories = ${generateObject(generatedServices.map((v) => ({
|
|
1220
|
+
key: v.name,
|
|
1221
|
+
value: v.serviceFnName,
|
|
1222
|
+
comment: v.deprecated ? '@deprecated' : undefined
|
|
1223
|
+
})))};`;
|
|
1224
|
+
return generateStatements(
|
|
1225
|
+
/* Enums */
|
|
1226
|
+
generateInterface('WEnums', [...enums.keys()].map((name) => ({ name, type: name, required: true }))), generateType('WEnum', 'keyof WEnums'), enumInstances,
|
|
1227
|
+
/* Entities */
|
|
1228
|
+
generateInterface('WEntities', [
|
|
1229
|
+
...[...entities.entries()].map(([name, entity]) => ({
|
|
1230
|
+
name,
|
|
1231
|
+
type: entity.interfaceName,
|
|
1070
1232
|
required: true
|
|
1071
1233
|
})),
|
|
1072
|
-
...
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
return {
|
|
1088
|
-
source: generateStatements(
|
|
1089
|
-
/* JS Values */
|
|
1090
|
-
serviceValues, serviceInstanceValues, entityNames, serviceNames, enumsArray, generateEntityPropertyMap(entities, services, options),
|
|
1091
|
-
/* Map of entity to references / mappings and filters*/
|
|
1092
|
-
entityReferences, entityMappings, entityFilter,
|
|
1093
|
-
/* List of all entities with their corresponding service */
|
|
1094
|
-
generateBlockComment(`
|
|
1095
|
-
This interfaces merges two maps:
|
|
1096
|
-
- Map<[entityName], [entityInterfaceName]>
|
|
1097
|
-
- Map<[serviceName], [entityInterfaceName]>
|
|
1098
|
-
|
|
1099
|
-
Where [entityName] is
|
|
1100
|
-
- the name of a nested entity (e.g. 'address' from Party)
|
|
1101
|
-
- the name of an entity (e.g. 'party', 'article' etc.)
|
|
1102
|
-
|
|
1103
|
-
Where [serviceName] is the name of an endpoint (e.g. for /article its 'article')
|
|
1104
|
-
|
|
1105
|
-
Where [entityInterfaceName] is
|
|
1106
|
-
- the underlying type for this entity
|
|
1107
|
-
- the type for what is returned by the api
|
|
1108
|
-
`, entitiesList),
|
|
1109
|
-
/* type-ofs and types */
|
|
1110
|
-
generateType('WServices', 'typeof wServices'), generateType('WServiceFactories', 'typeof wServiceFactories'), generateType('WService', 'keyof WServices'), generateType('WEntity', 'keyof WEntities'), generateType('WEnums', 'typeof wEnums'), generateType('WEnum', 'keyof WEnums'),
|
|
1111
|
-
/* Utilities. */
|
|
1112
|
-
generateCustomValueUtilities(entities, services),
|
|
1113
|
-
/* All functions grouped by service supporting it */
|
|
1114
|
-
...generateGroupedServices(services))
|
|
1115
|
-
};
|
|
1234
|
+
...generatedServices
|
|
1235
|
+
.filter(({ relatedEntity }) => !!relatedEntity)
|
|
1236
|
+
.filter(({ name }) => !entities.get(name))
|
|
1237
|
+
.map(({ name, relatedEntity }) => ({
|
|
1238
|
+
name,
|
|
1239
|
+
type: relatedEntity.interfaceName,
|
|
1240
|
+
required: true
|
|
1241
|
+
}))
|
|
1242
|
+
].sort((a, b) => (a.name > b.name ? 1 : -1))), generateType('WEntity', 'keyof WEntities'), entityNames,
|
|
1243
|
+
/* Services */
|
|
1244
|
+
serviceInstances, generateType('WServices', 'typeof wServices'), generateType('WService', 'keyof WServices'), serviceFactories, generateType('WServiceFactories', 'typeof wServiceFactories'),
|
|
1245
|
+
/* Service Utils */
|
|
1246
|
+
generateGroupedServices(generatedServices), generateCustomValueServices(generatedServices),
|
|
1247
|
+
/* Entity Properties (Runtime Meta Infos) */
|
|
1248
|
+
generateEntityProperties(entities, generatedServices, options));
|
|
1116
1249
|
};
|
|
1117
1250
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1251
|
+
function extractRelatedEntityName(serviceEndpoints, responses) {
|
|
1252
|
+
const rootEndpoint = serviceEndpoints.find((v) => v.endpoint.type === WeclappEndpointType.ROOT);
|
|
1253
|
+
if (!rootEndpoint)
|
|
1254
|
+
return;
|
|
1255
|
+
const response = rootEndpoint?.path.get?.responses['200'];
|
|
1256
|
+
if (!response)
|
|
1257
|
+
return;
|
|
1258
|
+
let responseObject;
|
|
1259
|
+
if (isReferenceObject(response)) {
|
|
1260
|
+
const refName = getRefName(response);
|
|
1261
|
+
responseObject = responses.get(refName);
|
|
1262
|
+
}
|
|
1263
|
+
else {
|
|
1264
|
+
responseObject = response;
|
|
1265
|
+
}
|
|
1266
|
+
const responseSchema = responseObject?.content?.['application/json'].schema;
|
|
1267
|
+
if (responseSchema) {
|
|
1268
|
+
if (isReferenceObject(responseSchema)) {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const resultSchema = responseSchema.properties?.result;
|
|
1272
|
+
if (!resultSchema) {
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
if (isReferenceObject(resultSchema)) {
|
|
1276
|
+
return getRefName(resultSchema);
|
|
1277
|
+
}
|
|
1278
|
+
else if (isArraySchemaObject(resultSchema)) {
|
|
1279
|
+
const resultItemSchema = resultSchema.items;
|
|
1280
|
+
if (isReferenceObject(resultItemSchema)) {
|
|
1281
|
+
return getRefName(resultItemSchema);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
const extractServiceAliases = (endpoints, responses) => {
|
|
1122
1288
|
const aliases = new Map();
|
|
1289
|
+
for (const [serviceName, serviceEndpoints] of endpoints) {
|
|
1290
|
+
const relatedEntityName = extractRelatedEntityName(serviceEndpoints, responses);
|
|
1291
|
+
if (relatedEntityName)
|
|
1292
|
+
aliases.set(serviceName, relatedEntityName);
|
|
1293
|
+
}
|
|
1294
|
+
return aliases;
|
|
1295
|
+
};
|
|
1296
|
+
const extractContext = (doc) => {
|
|
1297
|
+
const endpoints = parseEndpointsPaths(doc.paths);
|
|
1298
|
+
const schemas = new Map();
|
|
1123
1299
|
for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
|
|
1124
1300
|
if (!isReferenceObject(schema)) {
|
|
1125
1301
|
schemas.set(name, schema);
|
|
1126
1302
|
}
|
|
1127
1303
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
for (const [path, methods] of Object.entries(doc.paths)) {
|
|
1133
|
-
const parsed = parseEndpointPath(path);
|
|
1134
|
-
if (!parsed || schemas.has(parsed.entity)) {
|
|
1135
|
-
continue;
|
|
1304
|
+
const responses = new Map();
|
|
1305
|
+
for (const [name, response] of Object.entries(doc.components?.responses ?? {})) {
|
|
1306
|
+
if (!isReferenceObject(response)) {
|
|
1307
|
+
responses.set(name, response);
|
|
1136
1308
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1149
|
-
if (isArraySchemaObject(itemsSchema)) {
|
|
1150
|
-
const { items } = itemsSchema;
|
|
1151
|
-
if (isReferenceObject(items)) {
|
|
1152
|
-
aliases.set(parsed.entity, parseReferencedEntity(items));
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1309
|
+
}
|
|
1310
|
+
const parameters = new Map();
|
|
1311
|
+
for (const [name, parameter] of Object.entries(doc.components?.parameters ?? {})) {
|
|
1312
|
+
if (!isReferenceObject(parameter)) {
|
|
1313
|
+
parameters.set(name, parameter);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
const requestBodies = new Map();
|
|
1317
|
+
for (const [name, requestBody] of Object.entries(doc.components?.requestBodies ?? {})) {
|
|
1318
|
+
if (!isReferenceObject(requestBody)) {
|
|
1319
|
+
requestBodies.set(name, requestBody);
|
|
1156
1320
|
}
|
|
1157
1321
|
}
|
|
1158
|
-
|
|
1322
|
+
const aliases = extractServiceAliases(endpoints, responses);
|
|
1323
|
+
return { endpoints, schemas, responses, parameters, requestBodies, aliases };
|
|
1159
1324
|
};
|
|
1160
1325
|
|
|
1161
1326
|
const generate = (doc, options) => {
|
|
1162
|
-
const
|
|
1163
|
-
const
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
options,
|
|
1170
|
-
entities,
|
|
1171
|
-
aliases
|
|
1172
|
-
}).source));
|
|
1327
|
+
const context = extractContext(doc);
|
|
1328
|
+
const base = generateBase(doc.info.version, options);
|
|
1329
|
+
const enums = generateEnums(context);
|
|
1330
|
+
const entities = generateEntities(context);
|
|
1331
|
+
const services = generateServices(entities, context, options);
|
|
1332
|
+
const maps = generateMaps(enums, entities, services, context, options);
|
|
1333
|
+
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));
|
|
1173
1334
|
};
|
|
1174
1335
|
|
|
1175
1336
|
const hash = (content, algorithm = 'sha256') => {
|
|
@@ -1185,12 +1346,13 @@ const hash = (content, algorithm = 'sha256') => {
|
|
|
1185
1346
|
|
|
1186
1347
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
1187
1348
|
const cli = async () => {
|
|
1188
|
-
const
|
|
1349
|
+
const version = pkg.version;
|
|
1189
1350
|
const { argv } = yargs(hideBin(process.argv))
|
|
1190
1351
|
.scriptName('build-weclapp-sdk')
|
|
1191
1352
|
.usage('Usage: $0 <source> [flags]')
|
|
1192
1353
|
.version(version)
|
|
1193
1354
|
.example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
|
|
1355
|
+
.example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
|
|
1194
1356
|
.example('$0 xxx.weclapp.com --key ...', 'Generate the SDK based on the openapi file from the given weclapp instance')
|
|
1195
1357
|
.help('h')
|
|
1196
1358
|
.alias('v', 'version')
|
|
@@ -1230,36 +1392,47 @@ const cli = async () => {
|
|
|
1230
1392
|
type: 'string',
|
|
1231
1393
|
choices: ['browser', 'browser.rx', 'node', 'node.rx']
|
|
1232
1394
|
})
|
|
1233
|
-
.option('
|
|
1234
|
-
|
|
1235
|
-
describe: 'Include deprecated functions and services',
|
|
1395
|
+
.option('use-query-language', {
|
|
1396
|
+
describe: 'Generate the new where property for some and count queries',
|
|
1236
1397
|
type: 'boolean'
|
|
1398
|
+
})
|
|
1399
|
+
.option('apiVersion', {
|
|
1400
|
+
describe: 'Specify the api version (only needed when not using a local file)',
|
|
1401
|
+
type: 'string'
|
|
1237
1402
|
})
|
|
1238
1403
|
.epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
|
|
1239
1404
|
if (argv.fromEnv) {
|
|
1240
1405
|
config();
|
|
1241
1406
|
}
|
|
1242
1407
|
const { WECLAPP_API_KEY, WECLAPP_BACKEND_URL } = process.env;
|
|
1243
|
-
const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, _: [src = WECLAPP_BACKEND_URL] } = argv;
|
|
1408
|
+
const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, apiVersion, _: [src = WECLAPP_BACKEND_URL] } = argv;
|
|
1244
1409
|
const options = {
|
|
1245
1410
|
deprecated,
|
|
1246
1411
|
generateUnique: argv.generateUnique ?? false,
|
|
1247
|
-
target: argv.target ?? Target.BROWSER_PROMISES
|
|
1412
|
+
target: argv.target ?? Target.BROWSER_PROMISES,
|
|
1413
|
+
useQueryLanguage: argv.useQueryLanguage ?? false
|
|
1248
1414
|
};
|
|
1249
|
-
if (typeof src === 'number') {
|
|
1250
|
-
return Promise.reject('Expected string as command');
|
|
1415
|
+
if (!src || typeof src === 'number') {
|
|
1416
|
+
return Promise.reject(new Error('Expected string as command'));
|
|
1251
1417
|
}
|
|
1252
1418
|
if (!Object.values(Target).includes(options.target)) {
|
|
1253
1419
|
logger.errorLn(`Unknown target: ${options.target}. Possible values are ${Object.values(Target).join(', ')}`);
|
|
1254
|
-
return Promise.reject();
|
|
1420
|
+
return Promise.reject(new Error());
|
|
1255
1421
|
}
|
|
1256
1422
|
if (await stat(src).catch(() => false)) {
|
|
1257
|
-
logger.infoLn(`Source is a file
|
|
1423
|
+
logger.infoLn(`Source is a file`);
|
|
1258
1424
|
const content = JSON.parse(await readFile(src, 'utf-8'));
|
|
1259
1425
|
return { cache, content, options };
|
|
1260
1426
|
}
|
|
1427
|
+
logger.infoLn(`Source is a URL`);
|
|
1428
|
+
if (!key) {
|
|
1429
|
+
return Promise.reject(new Error('API key is missing'));
|
|
1430
|
+
}
|
|
1431
|
+
if (!apiVersion) {
|
|
1432
|
+
return Promise.reject(new Error('API version is missing'));
|
|
1433
|
+
}
|
|
1261
1434
|
const url = new URL(src.startsWith('http') ? src : `https://${src}`);
|
|
1262
|
-
url.pathname =
|
|
1435
|
+
url.pathname = `/webapp/api/${apiVersion}/meta/openapi.json`;
|
|
1263
1436
|
if (query?.length) {
|
|
1264
1437
|
for (const param of query.split(',')) {
|
|
1265
1438
|
const [name, value] = param.split('=');
|
|
@@ -1267,11 +1440,11 @@ const cli = async () => {
|
|
|
1267
1440
|
}
|
|
1268
1441
|
}
|
|
1269
1442
|
const content = await fetch(url.toString(), {
|
|
1270
|
-
headers: {
|
|
1271
|
-
}).then(res => res.ok ? res.json() : undefined);
|
|
1443
|
+
headers: { Accept: 'application/json', AuthenticationToken: key }
|
|
1444
|
+
}).then((res) => (res.ok ? res.json() : undefined));
|
|
1272
1445
|
if (!content) {
|
|
1273
1446
|
logger.errorLn(`Couldn't fetch file ${url.toString()} `);
|
|
1274
|
-
return Promise.reject();
|
|
1447
|
+
return Promise.reject(new Error());
|
|
1275
1448
|
}
|
|
1276
1449
|
else {
|
|
1277
1450
|
logger.infoLn(`Use remote file: ${url.toString()}`);
|
|
@@ -1279,50 +1452,59 @@ const cli = async () => {
|
|
|
1279
1452
|
return { cache, content, options };
|
|
1280
1453
|
};
|
|
1281
1454
|
|
|
1282
|
-
const
|
|
1283
|
-
const
|
|
1455
|
+
const workingDir = resolve(currentDirname(), './sdk');
|
|
1456
|
+
const cacheDir = resolve(currentDirname(), './.cache');
|
|
1284
1457
|
void (async () => {
|
|
1285
1458
|
const start = process.hrtime.bigint();
|
|
1286
|
-
const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
|
|
1287
1459
|
const { content: doc, cache: useCache, options } = await cli();
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
const dist = (...paths) => resolve(workingDirectory, ...paths);
|
|
1292
|
-
const tmp = async (...paths) => {
|
|
1293
|
-
const fullPath = resolve(cacheDir, ...paths);
|
|
1294
|
-
await mkdir(dirname(fullPath), { recursive: true }).catch(() => null);
|
|
1460
|
+
const workingDirPath = async (...paths) => {
|
|
1461
|
+
const fullPath = resolve(workingDir, ...paths);
|
|
1462
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
1295
1463
|
return fullPath;
|
|
1296
1464
|
};
|
|
1465
|
+
// Resolve cache dir and key
|
|
1466
|
+
const cacheKey = hash([pkg.version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
|
|
1467
|
+
const cachedSdkDir = resolve(cacheDir, cacheKey);
|
|
1468
|
+
// Remove old SDK
|
|
1469
|
+
await rm(workingDir, { recursive: true, force: true });
|
|
1297
1470
|
if (useCache) {
|
|
1298
1471
|
logger.infoLn(`Cache ID: ${cacheKey}`);
|
|
1299
1472
|
}
|
|
1300
|
-
if (useCache && await stat(
|
|
1301
|
-
|
|
1473
|
+
if (useCache && (await stat(cachedSdkDir).catch(() => false))) {
|
|
1474
|
+
// Copy cached SDK to working dir
|
|
1475
|
+
logger.successLn(`Cache match! (${cachedSdkDir})`);
|
|
1476
|
+
await cp(cachedSdkDir, workingDir, { recursive: true });
|
|
1302
1477
|
}
|
|
1303
1478
|
else {
|
|
1304
|
-
//
|
|
1305
|
-
await writeFile(await
|
|
1479
|
+
// Write openapi.json file
|
|
1480
|
+
await writeFile(await workingDirPath('openapi.json'), JSON.stringify(doc, null, 2));
|
|
1306
1481
|
logger.infoLn(`Generate sdk (target: ${options.target})`);
|
|
1307
|
-
// Generate
|
|
1482
|
+
// Generate and write SDK (index.ts)
|
|
1308
1483
|
const sdk = generate(doc, options);
|
|
1309
|
-
await writeFile(await
|
|
1310
|
-
// Bundle
|
|
1484
|
+
await writeFile(await workingDirPath('src', 'index.ts'), sdk.trim() + '\n');
|
|
1485
|
+
// Bundle and write SDK
|
|
1311
1486
|
logger.infoLn('Bundle... (this may take some time)');
|
|
1312
|
-
await bundle(
|
|
1313
|
-
// Remove
|
|
1314
|
-
await
|
|
1487
|
+
await bundle(workingDir, options.target);
|
|
1488
|
+
// Remove index.ts (only bundle is required)
|
|
1489
|
+
await rm(await workingDirPath('src'), { recursive: true, force: true });
|
|
1490
|
+
if (useCache) {
|
|
1491
|
+
// Copy SDK to cache
|
|
1492
|
+
logger.successLn(`Caching SDK: (${cachedSdkDir})`);
|
|
1493
|
+
await mkdir(cachedSdkDir, { recursive: true });
|
|
1494
|
+
await cp(workingDir, cachedSdkDir, { recursive: true });
|
|
1495
|
+
}
|
|
1315
1496
|
}
|
|
1316
|
-
// Copy bundled SDK
|
|
1317
|
-
await cp(cacheDir, workingDirectory, { recursive: true });
|
|
1318
1497
|
// Print job summary
|
|
1319
1498
|
const duration = (process.hrtime.bigint() - start) / 1000000n;
|
|
1320
1499
|
logger.successLn(`SDK built in ${prettyMs(Number(duration))}`);
|
|
1321
1500
|
logger.printSummary();
|
|
1322
|
-
})()
|
|
1501
|
+
})()
|
|
1502
|
+
.catch((error) => {
|
|
1323
1503
|
logger.errorLn(`Fatal error:`);
|
|
1324
1504
|
/* eslint-disable no-console */
|
|
1325
1505
|
console.error(error);
|
|
1326
|
-
})
|
|
1327
|
-
|
|
1506
|
+
})
|
|
1507
|
+
.finally(() => {
|
|
1508
|
+
if (logger.errors)
|
|
1509
|
+
process.exit(1);
|
|
1328
1510
|
});
|