@weclapp/sdk 2.0.0-dev.3 → 2.0.0-dev.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +371 -19
- package/dist/cli.js +216 -162
- package/package.json +37 -32
- package/tsconfig.sdk.json +14 -0
- package/tsconfig.lib.json +0 -17
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
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
|
|
|
17
18
|
var Target;
|
|
@@ -39,8 +40,8 @@ const currentDirname = () => {
|
|
|
39
40
|
return fileURLToPath(new URL('..', import.meta.url));
|
|
40
41
|
};
|
|
41
42
|
|
|
42
|
-
const tsconfig = resolve(currentDirname(), './tsconfig.
|
|
43
|
-
const resolveGlobals = (...globals) => Object.fromEntries(globals.map(v => [v, '*']));
|
|
43
|
+
const tsconfig = resolve(currentDirname(), './tsconfig.sdk.json');
|
|
44
|
+
const resolveGlobals = (...globals) => Object.fromEntries(globals.map((v) => [v, '*']));
|
|
44
45
|
const generateOutput = (config) => ({
|
|
45
46
|
sourcemap: true,
|
|
46
47
|
banner: `/* weclapp sdk */`,
|
|
@@ -61,35 +62,22 @@ const bundle = async (workingDirectory, target) => {
|
|
|
61
62
|
globals: resolveGlobals('node-fetch', 'url')
|
|
62
63
|
})
|
|
63
64
|
];
|
|
64
|
-
// Remove build dir
|
|
65
|
-
await rmdir(dist()).catch(() => void 0);
|
|
66
65
|
const bundles = {
|
|
67
66
|
[Target.BROWSER_PROMISES]: () => ({
|
|
68
|
-
input: src('
|
|
67
|
+
input: src('index.ts'),
|
|
68
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
69
69
|
output: [
|
|
70
|
-
generateOutput({
|
|
71
|
-
file: dist('index.cjs'),
|
|
72
|
-
name: 'Weclapp',
|
|
73
|
-
format: 'umd'
|
|
74
|
-
}),
|
|
75
70
|
generateOutput({
|
|
76
71
|
file: dist('index.js'),
|
|
77
72
|
format: 'es'
|
|
78
73
|
})
|
|
79
|
-
]
|
|
80
|
-
plugins: [ts({ tsconfig }), terser()]
|
|
74
|
+
]
|
|
81
75
|
}),
|
|
82
76
|
[Target.BROWSER_RX]: () => ({
|
|
77
|
+
input: src('index.ts'),
|
|
78
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
83
79
|
external: ['rxjs'],
|
|
84
|
-
input: src('browser.rx.ts'),
|
|
85
|
-
plugins: [ts({ tsconfig }), terser()],
|
|
86
80
|
output: [
|
|
87
|
-
generateOutput({
|
|
88
|
-
file: dist('index.cjs'),
|
|
89
|
-
name: 'Weclapp',
|
|
90
|
-
format: 'umd',
|
|
91
|
-
globals: resolveGlobals('rxjs')
|
|
92
|
-
}),
|
|
93
81
|
generateOutput({
|
|
94
82
|
file: dist('index.js'),
|
|
95
83
|
format: 'es',
|
|
@@ -98,16 +86,16 @@ const bundle = async (workingDirectory, target) => {
|
|
|
98
86
|
]
|
|
99
87
|
}),
|
|
100
88
|
[Target.NODE_PROMISES]: () => ({
|
|
101
|
-
input: src('
|
|
102
|
-
|
|
89
|
+
input: src('index.ts'),
|
|
90
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
103
91
|
external: ['node-fetch', 'url'],
|
|
104
|
-
|
|
92
|
+
output: generateNodeOutput()
|
|
105
93
|
}),
|
|
106
94
|
[Target.NODE_RX]: () => ({
|
|
107
|
-
input: src('
|
|
108
|
-
|
|
95
|
+
input: src('index.ts'),
|
|
96
|
+
plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
|
|
109
97
|
external: ['node-fetch', 'url', 'rxjs'],
|
|
110
|
-
|
|
98
|
+
output: generateNodeOutput()
|
|
111
99
|
})
|
|
112
100
|
};
|
|
113
101
|
const config = bundles[target]();
|
|
@@ -139,14 +127,22 @@ const indent = (s, level = 1) => {
|
|
|
139
127
|
};
|
|
140
128
|
|
|
141
129
|
const generateStatements = (...statements) => statements
|
|
142
|
-
.map(v => v.trim())
|
|
143
|
-
.filter(v => v.length)
|
|
130
|
+
.map((v) => v.trim())
|
|
131
|
+
.filter((v) => v.length)
|
|
144
132
|
.join('\n\n');
|
|
145
133
|
const generateBlockStatements = (...statements) => `{\n${indent(generateStatements(...statements))}\n}`;
|
|
146
134
|
|
|
147
|
-
var
|
|
135
|
+
var globalConfig = "export type RequestPayloadMethod =\n | 'GET'\n | 'HEAD'\n | 'POST'\n | 'PUT'\n | 'DELETE'\n | 'CONNECT'\n | 'OPTIONS'\n | 'TRACE'\n | 'PATCH';\n\nexport interface RequestPayload {\n method?: RequestPayloadMethod;\n query?: Record<string, any>;\n body?: any;\n unwrap?: boolean;\n forceBlob?: boolean;\n}\n\nexport interface ServiceConfig {\n // Your API-Key, this is optional in the sense of if you omit this, and you're in a browser, the\n // cookie-authentication (include-credentials) will be used.\n key?: string;\n\n // Your domain, if omitted location.host will be used (browser env).\n host?: string;\n\n // If you want to use https, defaults to location.protocol (browser env).\n secure?: boolean;\n\n // If you want that some and count requests are bundled into multi requests.\n multiRequest?: boolean;\n\n // If you want that the ignoreMissingProperties parameter to be set to true for every post request.\n ignoreMissingProperties?: boolean;\n\n // Optional request/response interceptors.\n interceptors?: {\n // Takes the generated request, you can either return a new request,\n // a response (which will be taken as \"the\" response) or nothing.\n // The payload contains the raw input generated by the SDK.\n request?: (\n request: Request,\n payload: RequestPayload\n ) => Request | Response | void | Promise<Request | Response | void>;\n\n // Takes the response. This can either be the one from the server or an\n // artificially-crafted one by the request interceptor.\n response?: (response: Response) => Response | void | Promise<Response | void>;\n };\n}\n\ntype ServiceConfigWithoutMultiRequest = Omit<ServiceConfig, 'multiRequest'>;\n\nlet globalConfig: ServiceConfig | undefined;\nexport const getGlobalConfig = (): ServiceConfig | undefined => globalConfig;\nexport const setGlobalConfig = (cfg?: ServiceConfig) => (globalConfig = cfg);\n\nexport const getHost = (cfg: ServiceConfig) => {\n let host = cfg.host?.replace(/^https?:\\/\\//, '');\n if (!host && typeof location !== 'undefined') {\n host = location.host;\n }\n\n if (!host) {\n throw new Error('Please specify a host');\n }\n\n return host;\n};\n\nexport const getProtocol = (cfg: ServiceConfig) => {\n const protocol =\n cfg.secure !== undefined\n ? cfg.secure\n ? 'https:'\n : 'http:'\n : typeof location !== 'undefined'\n ? location.protocol\n : undefined;\n\n if (!protocol) {\n throw new Error('Please specify a protocol (secure)');\n }\n\n return protocol;\n};\n";
|
|
136
|
+
|
|
137
|
+
var multiRequest = "type RequestTask = {\n uri: string;\n resolve: (result: unknown) => void;\n reject: (error: unknown) => void;\n};\n\ntype BatchRequestTask = RequestTask & {\n settled: boolean;\n};\n\ntype MultiRequestResponse = {\n status: number;\n body: object;\n};\n\nlet microtaskQueued: boolean = false;\nconst tasksSet: Set<RequestTask> = new Set<RequestTask>();\n\nconst SQUARE_BRACKET_OPEN = '['.charCodeAt(0);\nconst COMMA = ','.charCodeAt(0);\nconst DECODER = new TextDecoder();\n\nconst readNextResponse = (bytes: Uint8Array) => {\n let headerStart: number | undefined = undefined;\n let commasSeen = 0;\n\n for (let i = 0; i < bytes.length; i++) {\n const byte = bytes[i];\n if (headerStart === undefined) {\n if (byte === SQUARE_BRACKET_OPEN || byte === COMMA) {\n headerStart = i + 1;\n }\n } else {\n if (byte === COMMA) {\n commasSeen++;\n }\n if (commasSeen === 2) {\n const headerArrayString = `[${DECODER.decode(bytes.subarray(headerStart, i))}]`;\n const [index, jsonLength] = JSON.parse(headerArrayString);\n if (!(typeof index === 'number') || !(typeof jsonLength === 'number')) {\n throw new Error(`unexpected header: ${headerArrayString}`);\n }\n\n const endIndex = i + 1 + jsonLength;\n if (endIndex > bytes.length) {\n // not all bytes available yet\n return undefined;\n }\n const jsonString = DECODER.decode(bytes.subarray(i + 1, endIndex));\n const data = JSON.parse(jsonString) as MultiRequestResponse;\n return {\n index,\n data,\n remainingBytes: bytes.subarray(endIndex)\n };\n }\n }\n }\n return undefined;\n};\n\nconst fetchMultiRequest = async (requests: string[]) => {\n const cfg = getGlobalConfig();\n\n if (!cfg) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const host = getHost(cfg);\n const protocol = getProtocol(cfg);\n\n return await fetch(`${protocol}//${host}/webapp/api/v2/batch/query`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(cfg.key && { AuthenticationToken: cfg.key })\n },\n body: JSON.stringify({ requests })\n });\n};\n\nconst rejectTasks = (tasks: BatchRequestTask[], error: unknown) => {\n for (const task of tasks) {\n if (!task.settled) {\n task.reject(error);\n }\n }\n};\n\nconst processStream = (\n { value: chunk, done }: ReadableStreamReadResult<Uint8Array>,\n remainingBytes: Uint8Array,\n reader: ReadableStreamDefaultReader<Uint8Array>,\n tasks: BatchRequestTask[]\n) => {\n if (done) {\n return;\n }\n if (chunk) {\n let bytes = new Uint8Array(remainingBytes.length + chunk.length);\n bytes.set(remainingBytes);\n bytes.set(chunk, remainingBytes.length);\n\n while (bytes.length) {\n const result = readNextResponse(bytes);\n if (!result) {\n break;\n }\n const task = tasks[result.index];\n if (result.data.status >= 100 && result.data.status < 400) {\n task.resolve({\n ...result.data.body\n });\n } else {\n task.reject({\n ...result.data.body\n });\n }\n task.settled = true;\n bytes = result.remainingBytes;\n }\n reader\n .read()\n .then((readResult) => processStream(readResult, bytes, reader, tasks))\n .catch((error) => rejectTasks(tasks, error));\n }\n};\n\nconst batch = async (tasks: BatchRequestTask[]) => {\n try {\n const requests = tasks.map(({ uri }) => uri);\n const resp = await fetchMultiRequest(requests);\n const reader = resp.body?.getReader();\n\n if (!reader) {\n throw new Error('Stream reader is undefined');\n }\n reader\n .read()\n .then((readResult) => processStream(readResult, new Uint8Array(0), reader, tasks))\n .catch((error) => rejectTasks(tasks, error));\n } catch (e) {\n rejectTasks(tasks, e);\n throw e;\n }\n};\n\nconst addTask = (task: RequestTask) => {\n tasksSet.add(task);\n\n if (!microtaskQueued) {\n queueMicrotask(() => {\n microtaskQueued = false;\n if (tasksSet.size > 0) {\n const batchTasks = Array.from(tasksSet).map((task) => ({ ...task, settled: false }));\n 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 = "// Filter properties\nexport type EqualityOperators = 'EQ' | 'NE';\nexport type ComparisonOperators =\n | 'LT'\n | 'GT'\n | 'LE'\n | 'GE'\n | 'LIKE'\n | 'ILIKE'\n | 'NOT_LIKE'\n | 'NOT_ILIKE'\n | 'IEQ'\n | 'NOT_IEQ';\nexport type ArrayOperators = 'IN' | 'NOT_IN';\nexport type Operator = EqualityOperators | ComparisonOperators | ArrayOperators;\n\nexport type MapOperators<T> = { [K in EqualityOperators]?: T | null } & { [K in ComparisonOperators]?: T } & {\n [K in ArrayOperators]?: 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\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\n// Endpoint configurations\nexport type CountQuery<F> = {\n filter?: QueryFilter<F>;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\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\nconst _count = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: CountQuery<any> & { params?: Record<any, any> }\n) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n unwrap: true,\n query: {\n ...flattenFilter(query?.filter),\n ...flattenOrFilter(query?.or),\n ...query?.params\n }\n })\n );\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any, any> & { params?: Record<any, any> }\n) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n query: {\n serializeNulls: query?.serializeNulls,\n additionalProperties: query?.properties?.join(','),\n properties: query?.select ? flattenSelect(query.select).join(',') : undefined,\n includeReferencedEntities: query?.include ? Object.keys(query.include).join(',') : undefined,\n ...flattenOrFilter(query?.or),\n ...flattenFilter(query?.filter),\n ...flattenSort(query?.sort),\n ...query?.params,\n ...query?.pagination\n }\n }).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n );\n\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";
|
|
140
|
+
|
|
141
|
+
var queriesWithQueryLanguage = "// New filter language\n\nexport type ComparisonOperators = 'EQ' | 'NE' | 'LT' | 'GT' | 'LE' | 'GE' | 'LIKE';\nexport type ArrayOperators = 'IN';\nexport type NullOperator = 'NULL';\nexport type Operator = ComparisonOperators | ArrayOperators | NullOperator;\n\nconst comparisonOperatorsList: ComparisonOperators[] = ['EQ', 'NE', 'LT', 'GT', 'LE', 'GE', 'LIKE'];\n\nconst filterMap: Record<Operator, string> = {\n EQ: '=',\n NE: '!=',\n LT: '<',\n GT: '>',\n LE: '<=',\n GE: '>=',\n LIKE: '~',\n IN: 'in',\n NULL: 'null'\n};\n\n// Maybe we need more in the future, hence the type.\nexport type ModifierFunction = 'lower';\n\nexport type MapOperators<T> =\n | ({ [K in ComparisonOperators]?: T } & { [K in ArrayOperators]?: T[] } & { [K in NullOperator]?: never } & {\n [K in ModifierFunction]?: boolean;\n })\n | ({ [K in ComparisonOperators]?: T } & { [K in ArrayOperators]?: T[] } & { [K in NullOperator]?: boolean } & {\n [K in ModifierFunction]?: never;\n });\n\nexport type SingleFilterExpr<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? U extends Record<any, any>\n ? SingleFilterExpr<U> | { NOT?: SingleFilterExpr<U> }\n : MapOperators<U>\n : T[P] extends Record<any, any> | undefined\n ? SingleFilterExpr<T[P]> | { NOT?: SingleFilterExpr<T[P]> }\n : MapOperators<T[P]>;\n};\n\nexport type QueryFilter<T> = SingleFilterExpr<T> & {\n OR?: QueryFilter<T>[];\n AND?: QueryFilter<T>[];\n NOT?: QueryFilter<T>;\n};\n\n// Endpoint configurations\nexport type CountQuery<F> = {\n where?: QueryFilter<F> & CustomAttributeFilter;\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 where?: QueryFilter<F> & CustomAttributeFilter;\n select?: S;\n sort?: Sort<E>[];\n pagination?: Pagination;\n};\n\nconst possibleModifierFunctions: ModifierFunction[] = ['lower'];\n\nconst flattenWhere = (obj: QueryFilter<any> = {}, nestedPaths: string[]): string[] => {\n const entries: string[] = [];\n for (const [prop, propValue] of Object.entries(obj)) {\n const setModifiers = findAllModifierFunctions(propValue ?? {}, possibleModifierFunctions).filter(\n (modifier) => modifier[1]\n );\n if (prop === 'OR') {\n const flattedOr: string[][] = [];\n for (let i = 0; i < (obj.OR?.length ?? 0); i++) {\n flattedOr.push(flattenWhere(obj.OR?.[i], nestedPaths));\n }\n entries.push(\n `(${flattedOr\n .map((x) => {\n const joined = x.join(' and ');\n\n if (x.length > 1) {\n return `(${joined})`;\n } else {\n return joined;\n }\n })\n .join(' or ')})`\n );\n } else if (prop === 'AND') {\n const flattedAnd: string[][] = [];\n for (let i = 0; i < (obj.AND?.length ?? 0); i++) {\n flattedAnd.push(flattenWhere(obj.AND?.[i], nestedPaths));\n }\n entries.push(\n `(${flattedAnd\n .map((x) => {\n const joined = x.join(' and ');\n\n if (x.length > 1) {\n return `(${joined})`;\n } else {\n return joined;\n }\n })\n .join(' and ')})`\n );\n } else if (prop === 'NOT') {\n const flattedNot = flattenWhere(obj.NOT, nestedPaths);\n entries.push(\n `not ${flattedNot.length > 1 ? '(' : ''}${flattedNot.join(' and ')}${flattedNot.length > 1 ? ')' : ''}`\n );\n } else if (propValue) {\n for (const [operator, value] of Object.entries(propValue)) {\n if (value === undefined) continue;\n if (comparisonOperatorsList.includes(operator as ComparisonOperators)) {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${filterMap[operator as Operator]} ${\n typeof value === 'string'\n ? setModifiers.reduce((acc, [first]) => `${first}(${acc})`, JSON.stringify(value))\n : value\n }`\n );\n } else if ((operator as Operator) === 'NULL') {\n entries.push(\n `${!value ? 'not ' : ''}${nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')} ${filterMap[operator as Operator]}`\n );\n } else if ((operator as Operator) === 'IN') {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${filterMap[operator as Operator]} [${value.map((v: string | number) =>\n typeof v === 'string' ? setModifiers.reduce((acc, [first]) => `${first}(${acc})`, JSON.stringify(v)) : v\n )}]`\n );\n } else if (!possibleModifierFunctions.includes(operator as ModifierFunction)) {\n entries.push(...flattenWhere(propValue as QueryFilter<any>, [...nestedPaths, prop]));\n break;\n }\n }\n }\n }\n return entries;\n};\n\nconst assembleFilterParam = (obj: QueryFilter<any> = {}): Record<string, string> => {\n const flattedFilter = flattenWhere(obj, []);\n return flattedFilter.length ? { filter: flattedFilter.join(' and ') } : {};\n};\n\nconst findAllModifierFunctions = (obj: Record<string, any>, types: ModifierFunction[]) => {\n const result: Record<string, any> = {};\n for (const key in obj) {\n if (types.includes(key as ModifierFunction)) {\n result[key] = obj[key];\n }\n }\n return Object.entries(result);\n};\n\nconst _count = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: CountQuery<any> & { params?: Record<any, any> }\n) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n unwrap: true,\n query: {\n ...assembleFilterParam(query?.where),\n ...query?.params\n }\n })\n );\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any, any> & { params?: Record<any, any> }\n) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n query: {\n serializeNulls: query?.serializeNulls,\n additionalProperties: query?.properties?.join(','),\n properties: query?.select ? flattenSelect(query.select).join(',') : undefined,\n includeReferencedEntities: query?.include ? Object.keys(query.include).join(',') : undefined,\n ...assembleFilterParam(query?.where),\n ...flattenSort(query?.sort),\n ...query?.params,\n ...query?.pagination\n }\n }).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n );\n";
|
|
142
|
+
|
|
143
|
+
var root = "export const raw = async (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n payload: RequestPayload = {}\n): Promise<any> => {\n const globalConfig = getGlobalConfig();\n if (!cfg && !globalConfig) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const localCfg = {\n ...globalConfig,\n ...cfg,\n interceptors: { ...globalConfig?.interceptors, ...cfg?.interceptors }\n };\n\n const isBinaryData = payload.body instanceof resolveBinaryObject();\n const params = new URLSearchParams(\n Object.entries(payload.query ?? {})\n .filter((v) => v[1] !== undefined)\n .map(([key, value]) => [key, typeof value === 'string' ? value : JSON.stringify(value)])\n );\n\n const protocol = getProtocol(localCfg);\n\n const interceptRequest = localCfg.interceptors?.request ?? ((v) => v);\n const interceptResponse = localCfg.interceptors?.response ?? ((v) => v);\n\n const host = getHost(localCfg);\n\n let data: any = undefined;\n if (!cfg && localCfg.multiRequest) {\n let ep = endpoint;\n if (endpoint.startsWith('/')) {\n ep = endpoint.replace('/', '');\n }\n data = await addRequest(`${ep}?${params}`);\n } else {\n const request = new Request(`${protocol}//${host}/webapp/api/v${apiVersion}${endpoint}?${params}`, {\n ...(payload.body && {\n body: isBinaryData\n ? payload.body\n : JSON.stringify(payload.body, (key, value) => (value === undefined ? null : value))\n }),\n ...(!localCfg.key && { credentials: 'same-origin' }),\n method: payload.method ?? 'get',\n headers: {\n Accept: 'application/json',\n ...(localCfg.key && { AuthenticationToken: localCfg.key }),\n ...(!isBinaryData && { 'Content-Type': 'application/json' })\n }\n });\n let res = (await interceptRequest(request, payload)) ?? request;\n if (!(res instanceof Response)) {\n res = await fetch(res);\n }\n res = (await interceptResponse(res)) ?? res;\n data =\n (!payload.forceBlob || !res.ok) && res.headers?.get('content-type')?.includes('application/json')\n ? await res.json()\n : await res.blob();\n\n // Check if response was successful\n if (!res.ok) {\n return Promise.reject(data);\n }\n }\n\n return payload.unwrap ? data.result : data;\n};\n\nconst _remove = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n { dryRun = false }: RemoveQuery = {}\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'DELETE',\n query: { dryRun }\n }).then(() => undefined)\n );\n\nconst _create = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, data: any) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'POST',\n body: data\n })\n );\n\nconst _update = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n data: any,\n { ignoreMissingProperties, dryRun = false }: UpdateQuery = {}\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'PUT',\n body: data,\n query: {\n ignoreMissingProperties:\n ignoreMissingProperties ?? cfg?.ignoreMissingProperties ?? globalConfig?.ignoreMissingProperties,\n dryRun\n }\n })\n );\n\nconst _unique = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, query?: UniqueQuery) =>\n wrapResponse(() => raw({ ...cfg, multiRequest: false }, endpoint, { query }));\n\nconst _generic = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n method: RequestPayloadMethod,\n endpoint: string,\n payload?: GenericQuery<any, any>,\n forceBlob?: boolean\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method,\n forceBlob,\n body: payload?.body,\n query: payload?.params\n })\n );\n";
|
|
148
144
|
|
|
149
|
-
var types = "export type DeepPartial<T> = T extends object
|
|
145
|
+
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\n// Select properties\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 Pagination = {\n page: number;\n pageSize: number;\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 | {\n type: 'string';\n format?: 'decimal' | 'html' | 'email' | 'password';\n maxLength?: number;\n pattern?: string;\n entity?: WEntity;\n service?: WService;\n }\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// Utils\nconst equality: string[] = ['EQ', 'NE', 'IEQ', 'NOT_IEQ'];\nconst simple: string[] = [...equality, 'LT', 'GT', 'LE', 'GE', 'LIKE', 'NOT_LIKE', 'ILIKE', 'NOT_ILIKE'];\nconst array: string[] = ['IN', 'NOT_IN'];\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";
|
|
150
146
|
|
|
151
147
|
const resolveImports = (target) => {
|
|
152
148
|
const imports = [];
|
|
@@ -157,13 +153,13 @@ const resolveImports = (target) => {
|
|
|
157
153
|
};
|
|
158
154
|
const resolveMappings = (target) => `const wrapResponse = ${isRXTarget(target) ? 'defer' : '(v: (...args: any[]) => any) => v()'};`;
|
|
159
155
|
const resolveBinaryClass = (target) => `const resolveBinaryObject = () => ${resolveBinaryType(target)};`;
|
|
160
|
-
const generateBase = (target) => {
|
|
161
|
-
return generateStatements(resolveImports(target), resolveMappings(target), resolveBinaryClass(target), types, root);
|
|
156
|
+
const generateBase = (target, apiVersion, useQueryLanguage) => {
|
|
157
|
+
return generateStatements(resolveImports(target), resolveMappings(target), resolveBinaryClass(target), `const apiVersion = ${apiVersion}`, globalConfig, multiRequest, useQueryLanguage ? queriesWithQueryLanguage : queriesWithFilter, types, root);
|
|
162
158
|
};
|
|
163
159
|
|
|
164
160
|
const transformKey = (s) => snakeCase(s).toUpperCase();
|
|
165
161
|
const generateEnum = (name, values) => {
|
|
166
|
-
const props = indent(values.map(v => `${transformKey(v)} = ${generateString(v)}`).join(',\n'));
|
|
162
|
+
const props = indent(values.map((v) => `${transformKey(v)} = ${generateString(v)}`).join(',\n'));
|
|
167
163
|
return `export enum ${name} {\n${props}\n}`;
|
|
168
164
|
};
|
|
169
165
|
|
|
@@ -218,16 +214,13 @@ const concat = (strings, separator = ', ', maxLength = 80) => {
|
|
|
218
214
|
const joined = strings.join(separator);
|
|
219
215
|
if (joined.length > maxLength) {
|
|
220
216
|
const length = strings.length - 1;
|
|
221
|
-
return `\n${indent(strings
|
|
222
|
-
.map((value, index) => index === length ? value : `${(value + separator).trim()}\n`)
|
|
223
|
-
.join(''))}\n`;
|
|
217
|
+
return `\n${indent(strings.map((value, index) => (index === length ? value : `${(value + separator).trim()}\n`)).join(''))}\n`;
|
|
224
218
|
}
|
|
225
219
|
else {
|
|
226
220
|
return joined;
|
|
227
221
|
}
|
|
228
222
|
};
|
|
229
223
|
|
|
230
|
-
/* eslint-disable no-use-before-define */
|
|
231
224
|
const createReferenceType = (value) => ({
|
|
232
225
|
type: 'reference',
|
|
233
226
|
toString: () => loosePascalCase(value)
|
|
@@ -238,26 +231,28 @@ const createRawType = (value) => ({
|
|
|
238
231
|
});
|
|
239
232
|
const createArrayType = (value) => ({
|
|
240
233
|
type: 'array',
|
|
241
|
-
toString: () =>
|
|
234
|
+
toString: () => `(${value.toString()})[]`
|
|
242
235
|
});
|
|
243
236
|
const createTupleType = (value) => ({
|
|
244
237
|
type: 'tuple',
|
|
245
|
-
toString: () => concat([...new Set(value.map(v => typeof v === 'string' ? `'${v}'` : v.toString()))], ' | ')
|
|
238
|
+
toString: () => concat([...new Set(value.map((v) => (typeof v === 'string' ? `'${v}'` : v.toString())))], ' | ')
|
|
246
239
|
});
|
|
247
240
|
const createObjectType = (value, required = []) => ({
|
|
248
241
|
type: 'object',
|
|
249
242
|
isFullyOptional: () => {
|
|
250
|
-
return !required.length &&
|
|
251
|
-
.
|
|
252
|
-
|
|
243
|
+
return (!required.length &&
|
|
244
|
+
Object.values(value)
|
|
245
|
+
.filter((v) => v?.type === 'object')
|
|
246
|
+
.every((v) => v.isFullyOptional()));
|
|
253
247
|
},
|
|
254
248
|
toString: (propagateOptionalProperties = false) => {
|
|
255
249
|
const properties = Object.entries(value)
|
|
256
|
-
.filter(v => v[1])
|
|
257
|
-
.map(v => {
|
|
250
|
+
.filter((v) => v[1])
|
|
251
|
+
.map((v) => {
|
|
258
252
|
const name = v[0];
|
|
259
253
|
const value = v[1];
|
|
260
|
-
const isRequired = required.includes(name) ||
|
|
254
|
+
const isRequired = required.includes(name) ||
|
|
255
|
+
(value.type === 'object' && !value.isFullyOptional() && propagateOptionalProperties);
|
|
261
256
|
return `${name + (isRequired ? '' : '?')}: ${value.toString()};`;
|
|
262
257
|
});
|
|
263
258
|
return properties.length ? `{\n${indent(properties.join('\n'))}\n}` : '{}';
|
|
@@ -286,8 +281,7 @@ const convertToTypeScriptType = (schema, property) => {
|
|
|
286
281
|
return createRawType('boolean');
|
|
287
282
|
case 'object': {
|
|
288
283
|
const { properties = {}, required = [] } = schema;
|
|
289
|
-
return createObjectType(Object.fromEntries(Object.entries(properties)
|
|
290
|
-
.map(v => [v[0], convertToTypeScriptType(v[1])])), required);
|
|
284
|
+
return createObjectType(Object.fromEntries(Object.entries(properties).map((v) => [v[0], convertToTypeScriptType(v[1])])), required);
|
|
291
285
|
}
|
|
292
286
|
case 'array':
|
|
293
287
|
return createArrayType(convertToTypeScriptType(schema.items, property));
|
|
@@ -308,14 +302,19 @@ const setEntityEnumProperty = (enums, prop, meta) => {
|
|
|
308
302
|
}
|
|
309
303
|
};
|
|
310
304
|
const extractPropertyMetaData = (enums, meta, prop) => {
|
|
311
|
-
const result = {
|
|
305
|
+
const result = {
|
|
306
|
+
service: meta.service,
|
|
307
|
+
entity: meta.entity
|
|
308
|
+
};
|
|
312
309
|
if (isReferenceObject(prop)) {
|
|
313
310
|
setEntityEnumProperty(enums, prop, result);
|
|
314
311
|
result.type = 'reference';
|
|
315
312
|
return result;
|
|
316
313
|
}
|
|
317
|
-
result.format = prop.format;
|
|
318
314
|
result.type = prop.type;
|
|
315
|
+
result.format = prop.format;
|
|
316
|
+
result.maxLength = prop.maxLength;
|
|
317
|
+
result.pattern = prop.pattern;
|
|
319
318
|
if (isArraySchemaObject(prop)) {
|
|
320
319
|
if (isReferenceObject(prop.items)) {
|
|
321
320
|
setEntityEnumProperty(enums, prop.items, result);
|
|
@@ -335,12 +334,12 @@ const generateType = (name, value) => {
|
|
|
335
334
|
return `export type ${name} = ${value.trim()};`;
|
|
336
335
|
};
|
|
337
336
|
|
|
338
|
-
const arrayify = (v) => Array.isArray(v) ? v : [v];
|
|
337
|
+
const arrayify = (v) => (Array.isArray(v) ? v : [v]);
|
|
339
338
|
|
|
340
339
|
const generateInterfaceProperties = (entries) => {
|
|
341
340
|
const properties = entries
|
|
342
|
-
.filter(v => v.type !== undefined)
|
|
343
|
-
.filter((value, index, array) => array.findIndex(v => v.name === value.name) === index)
|
|
341
|
+
.filter((v) => v.type !== undefined)
|
|
342
|
+
.filter((value, index, array) => array.findIndex((v) => v.name === value.name) === index)
|
|
344
343
|
.map(({ name, type, required, readonly, comment }) => {
|
|
345
344
|
const cmd = comment ? `${generateInlineComment(comment)}\n` : '';
|
|
346
345
|
const req = required ? '' : '?';
|
|
@@ -384,19 +383,32 @@ const generateEntities = (schemas, enums) => {
|
|
|
384
383
|
const meta = isRelatedEntitySchema(property) ? property['x-weclapp'] : {};
|
|
385
384
|
if (meta.entity) {
|
|
386
385
|
const type = `${pascalCase(meta.entity)}[]`;
|
|
387
|
-
|
|
388
|
-
|
|
386
|
+
if (schemas.has(meta.entity)) {
|
|
387
|
+
referenceInterface.push({ name, type, required: true });
|
|
388
|
+
filterInterface.push({ name: meta.entity, type, required: true });
|
|
389
|
+
}
|
|
389
390
|
}
|
|
390
391
|
if (meta.service) {
|
|
391
|
-
|
|
392
|
+
if (schemas.has(meta.service)) {
|
|
393
|
+
referenceMappingsInterface.push({
|
|
394
|
+
name,
|
|
395
|
+
type: generateString(meta.service),
|
|
396
|
+
required: true
|
|
397
|
+
});
|
|
398
|
+
}
|
|
392
399
|
}
|
|
393
400
|
const type = convertToTypeScriptType(property, name).toString();
|
|
394
|
-
const comment = isNonArraySchemaObject(property)
|
|
395
|
-
property.deprecated
|
|
396
|
-
|
|
397
|
-
|
|
401
|
+
const comment = isNonArraySchemaObject(property)
|
|
402
|
+
? property.deprecated
|
|
403
|
+
? '@deprecated will be removed.'
|
|
404
|
+
: property.format
|
|
405
|
+
? `format: ${property.format}`
|
|
406
|
+
: undefined
|
|
407
|
+
: undefined;
|
|
398
408
|
entityInterface.push({
|
|
399
|
-
name,
|
|
409
|
+
name,
|
|
410
|
+
type,
|
|
411
|
+
comment,
|
|
400
412
|
required: meta.required,
|
|
401
413
|
readonly: !isReferenceObject(property) && property.readOnly
|
|
402
414
|
});
|
|
@@ -429,13 +441,10 @@ const generateEntities = (schemas, enums) => {
|
|
|
429
441
|
* @param s String to pluralize.
|
|
430
442
|
*/
|
|
431
443
|
const pluralize = (s) => {
|
|
432
|
-
return s.endsWith('s') ? s :
|
|
433
|
-
s.endsWith('y') ? `${s.slice(0, -1)}ies` :
|
|
434
|
-
`${s}s`;
|
|
444
|
+
return s.endsWith('s') ? s : s.endsWith('y') ? `${s.slice(0, -1)}ies` : `${s}s`;
|
|
435
445
|
};
|
|
436
446
|
|
|
437
|
-
|
|
438
|
-
const logger = new class {
|
|
447
|
+
const logger = new (class {
|
|
439
448
|
active = true;
|
|
440
449
|
warnings = 0;
|
|
441
450
|
errors = 0;
|
|
@@ -483,15 +492,18 @@ const logger = new class {
|
|
|
483
492
|
printSummary() {
|
|
484
493
|
const format = (v, name, fail, ok) => {
|
|
485
494
|
const color = v ? fail : ok;
|
|
486
|
-
return v === 0
|
|
487
|
-
|
|
495
|
+
return v === 0
|
|
496
|
+
? `${color('zero')} ${pluralize(name)}`
|
|
497
|
+
: v === 1
|
|
498
|
+
? `${color('one')} ${name}`
|
|
499
|
+
: `${color(v)} ${pluralize(name)}`;
|
|
488
500
|
};
|
|
489
501
|
const warnings = format(this.warnings, 'warning', chalk.yellowBright, chalk.greenBright);
|
|
490
502
|
const errors = format(this.errors, 'error', chalk.redBright, chalk.greenBright);
|
|
491
503
|
const info = `Finished with ${warnings} and ${errors}.`;
|
|
492
504
|
this[this.errors ? 'errorLn' : this.warnings ? 'warnLn' : 'successLn'](info);
|
|
493
505
|
}
|
|
494
|
-
};
|
|
506
|
+
})();
|
|
495
507
|
|
|
496
508
|
/**
|
|
497
509
|
* ROOT => /article
|
|
@@ -520,12 +532,22 @@ const parseEndpointPath = (path) => {
|
|
|
520
532
|
return { path, entity, type: WeclappEndpointType.COUNT };
|
|
521
533
|
}
|
|
522
534
|
else if (rest[0] === 'id') {
|
|
523
|
-
return rest.length === 2
|
|
524
|
-
{ path, entity, type: WeclappEndpointType.ENTITY }
|
|
525
|
-
|
|
535
|
+
return rest.length === 2
|
|
536
|
+
? { path, entity, type: WeclappEndpointType.ENTITY }
|
|
537
|
+
: {
|
|
538
|
+
path,
|
|
539
|
+
entity,
|
|
540
|
+
method: rest[2],
|
|
541
|
+
type: WeclappEndpointType.GENERIC_ENTITY
|
|
542
|
+
};
|
|
526
543
|
}
|
|
527
544
|
else if (rest.length === 1) {
|
|
528
|
-
return {
|
|
545
|
+
return {
|
|
546
|
+
path,
|
|
547
|
+
entity,
|
|
548
|
+
method: rest[1],
|
|
549
|
+
type: WeclappEndpointType.GENERIC_ROOT
|
|
550
|
+
};
|
|
529
551
|
}
|
|
530
552
|
return undefined;
|
|
531
553
|
};
|
|
@@ -547,12 +569,14 @@ const convertParametersToSchema = (parameters = []) => {
|
|
|
547
569
|
if (isParameterObject(param) && param.in === 'query') {
|
|
548
570
|
if (param.schema) {
|
|
549
571
|
properties.push([param.name, param.schema]);
|
|
550
|
-
|
|
572
|
+
if (param.required)
|
|
573
|
+
required.push(param.name);
|
|
551
574
|
}
|
|
552
575
|
}
|
|
553
576
|
}
|
|
554
577
|
return {
|
|
555
|
-
type: 'object',
|
|
578
|
+
type: 'object',
|
|
579
|
+
required,
|
|
556
580
|
properties: Object.fromEntries(properties)
|
|
557
581
|
};
|
|
558
582
|
};
|
|
@@ -610,8 +634,7 @@ const generateRequestBodyType = ({ requestBody }) => {
|
|
|
610
634
|
return generateBodyType(requestBody) ?? createRawType('unknown');
|
|
611
635
|
};
|
|
612
636
|
|
|
613
|
-
const resolveBodyType = ({ responses }) => Object.entries(responses)
|
|
614
|
-
.filter(v => v[0].startsWith('2'))[0]?.[1];
|
|
637
|
+
const resolveBodyType = ({ responses }) => Object.entries(responses).filter((v) => v[0].startsWith('2'))[0]?.[1];
|
|
615
638
|
const generateResponseBodyType = (object) => generateBodyType(resolveBodyType(object)) ?? createRawType('void');
|
|
616
639
|
|
|
617
640
|
const functionName$4 = 'create';
|
|
@@ -638,11 +661,7 @@ const generateCreateEndpoint = ({ target, path, endpoint }) => {
|
|
|
638
661
|
};
|
|
639
662
|
|
|
640
663
|
const generateGenericFunctionName = (path, suffix = '', prefix = '') => {
|
|
641
|
-
return camelCase(`${prefix}_` +
|
|
642
|
-
path
|
|
643
|
-
.replace(/.*\//, '')
|
|
644
|
-
.replace(/\W+/, '_')
|
|
645
|
-
.replace(/[_]+/, '_') + `_${suffix}`);
|
|
664
|
+
return camelCase(`${prefix}_` + path.replace(/.*\//, '').replace(/\W+/, '_').replace(/[_]+/, '_') + `_${suffix}`);
|
|
646
665
|
};
|
|
647
666
|
|
|
648
667
|
const insertPathPlaceholder = (path, record) => {
|
|
@@ -650,9 +669,7 @@ const insertPathPlaceholder = (path, record) => {
|
|
|
650
669
|
};
|
|
651
670
|
|
|
652
671
|
const wrapBody = (type, target) => {
|
|
653
|
-
return type.toString() === 'binary' ?
|
|
654
|
-
createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') :
|
|
655
|
-
type; // node-fetch returns a Blob as well
|
|
672
|
+
return type.toString() === 'binary' ? createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') : type; // node-fetch returns a Blob as well
|
|
656
673
|
};
|
|
657
674
|
const generateGenericEndpoint = (suffix) => ({ target, method, path, endpoint }) => {
|
|
658
675
|
const functionName = generateGenericFunctionName(endpoint.path, suffix, method);
|
|
@@ -698,12 +715,12 @@ const generateRemoveEndpoint = ({ target, endpoint }) => {
|
|
|
698
715
|
const functionSource = generateArrowFunction({
|
|
699
716
|
name: functionName$3,
|
|
700
717
|
signature: interfaceName,
|
|
701
|
-
returns: `_${functionName$3}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}
|
|
702
|
-
params: ['id']
|
|
718
|
+
returns: `_${functionName$3}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options)`,
|
|
719
|
+
params: ['id', 'options?: RemoveQuery']
|
|
703
720
|
});
|
|
704
721
|
const interfaceSource = generateArrowFunctionType({
|
|
705
722
|
type: interfaceName,
|
|
706
|
-
params: ['id: string'],
|
|
723
|
+
params: ['id: string', 'options?: RemoveQuery'],
|
|
707
724
|
returns: `${resolveResponseType(target)}<void>`
|
|
708
725
|
});
|
|
709
726
|
return {
|
|
@@ -715,10 +732,7 @@ const generateRemoveEndpoint = ({ target, endpoint }) => {
|
|
|
715
732
|
};
|
|
716
733
|
|
|
717
734
|
const functionName$2 = 'some';
|
|
718
|
-
const excludedParameters = [
|
|
719
|
-
'page', 'pageSize', 'sort',
|
|
720
|
-
'serializeNulls', 'properties', 'includeReferencedEntities'
|
|
721
|
-
];
|
|
735
|
+
const excludedParameters = ['page', 'pageSize', 'sort', 'serializeNulls', 'properties', 'includeReferencedEntities'];
|
|
722
736
|
const resolveAdditionalProperties = (path) => {
|
|
723
737
|
const body = resolveBodyType(path);
|
|
724
738
|
if (isResponseObject(body)) {
|
|
@@ -744,10 +758,11 @@ const generateSomeEndpoint = ({ aliases, target, path, endpoint }) => {
|
|
|
744
758
|
const parameterSchema = convertParametersToSchema(path.parameters);
|
|
745
759
|
const additionalProperties = resolveAdditionalProperties(path);
|
|
746
760
|
const additionalPropertyNames = generateStrings(Object.keys(additionalProperties?.properties ?? {}));
|
|
747
|
-
const additionalPropertyNamesType = additionalPropertyNames.length
|
|
761
|
+
const additionalPropertyNamesType = additionalPropertyNames.length
|
|
762
|
+
? `(${concat(additionalPropertyNames, ' | ')})[]`
|
|
763
|
+
: '[]';
|
|
748
764
|
// We already cover some properties
|
|
749
|
-
parameterSchema.properties = Object.fromEntries(Object.entries(parameterSchema.properties ?? {})
|
|
750
|
-
.filter(v => !excludedParameters.includes(v[0])));
|
|
765
|
+
parameterSchema.properties = Object.fromEntries(Object.entries(parameterSchema.properties ?? {}).filter((v) => !excludedParameters.includes(v[0])));
|
|
751
766
|
const parameters = createObjectType({
|
|
752
767
|
params: convertToTypeScriptType(parameterSchema)
|
|
753
768
|
});
|
|
@@ -758,7 +773,9 @@ const generateSomeEndpoint = ({ aliases, target, path, endpoint }) => {
|
|
|
758
773
|
`S extends (QuerySelect<${entity}> | undefined) = undefined`,
|
|
759
774
|
`I extends (QuerySelect<${entityMappings}> | undefined) = undefined`
|
|
760
775
|
],
|
|
761
|
-
params: [
|
|
776
|
+
params: [
|
|
777
|
+
`query${parameters.isFullyOptional() ? '?' : ''}: SomeQuery<${entity}, ${entityFilter}, I, S, ${additionalPropertyNamesType}> & ${entityParameters}`
|
|
778
|
+
],
|
|
762
779
|
returns: `${resolveResponseType(target)}<SomeQueryReturn<${entity}, ${entityReferences}, ${entityMappings}, I, S, ${properties}>>`
|
|
763
780
|
});
|
|
764
781
|
const functionSource = generateArrowFunction({
|
|
@@ -828,11 +845,19 @@ const generateUpdateEndpoint = ({ target, path, endpoint }) => {
|
|
|
828
845
|
};
|
|
829
846
|
};
|
|
830
847
|
|
|
848
|
+
const isMultiPartUploadPath = (path) => {
|
|
849
|
+
const [, entity, ...rest] = path.split('/');
|
|
850
|
+
return entity && rest.length === 2 && rest[1] === 'multipartUpload';
|
|
851
|
+
};
|
|
831
852
|
const groupEndpointsByEntity = (paths) => {
|
|
832
853
|
const endpoints = new Map();
|
|
833
854
|
for (const [rawPath, path] of Object.entries(paths)) {
|
|
834
855
|
const endpoint = parseEndpointPath(rawPath);
|
|
835
856
|
if (!endpoint || !path) {
|
|
857
|
+
// Todo: Should be removed if sdk supports multi part upload.
|
|
858
|
+
if (isMultiPartUploadPath(rawPath)) {
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
836
861
|
logger.errorLn(`Failed to parse ${rawPath}`);
|
|
837
862
|
continue;
|
|
838
863
|
}
|
|
@@ -906,8 +931,8 @@ const generateServices = (doc, aliases, options) => {
|
|
|
906
931
|
continue;
|
|
907
932
|
}
|
|
908
933
|
// Construct service type
|
|
909
|
-
const types = generateStatements(...functions.flatMap(v => v.interfaces?.map(v => v.source) ?? []), ...functions.map(v => v.type.source), generateInterface(serviceTypeName, [
|
|
910
|
-
...functions.map(v => ({
|
|
934
|
+
const types = generateStatements(...functions.flatMap((v) => v.interfaces?.map((v) => v.source) ?? []), ...functions.map((v) => v.type.source), generateInterface(serviceTypeName, [
|
|
935
|
+
...functions.map((v) => ({
|
|
911
936
|
required: true,
|
|
912
937
|
comment: v.path.deprecated ? '@deprecated' : undefined,
|
|
913
938
|
name: v.func.name,
|
|
@@ -915,11 +940,18 @@ const generateServices = (doc, aliases, options) => {
|
|
|
915
940
|
}))
|
|
916
941
|
]));
|
|
917
942
|
// Construct service value
|
|
918
|
-
const funcBody = generateBlockStatements(...functions.map(v => v.func.source), `return {${concat(functions.map(v => v.func.name))}};`);
|
|
943
|
+
const funcBody = generateBlockStatements(...functions.map((v) => v.func.source), `return {${concat(functions.map((v) => v.func.name))}};`);
|
|
919
944
|
const func = `export const ${serviceName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${funcBody};`;
|
|
920
945
|
const source = generateBlockComment(`${pascalCase(endpoint)} service`, generateStatements(types, func));
|
|
921
|
-
const deprecated = functions.every(v => v.path.deprecated);
|
|
922
|
-
services.set(endpoint, {
|
|
946
|
+
const deprecated = functions.every((v) => v.path.deprecated);
|
|
947
|
+
services.set(endpoint, {
|
|
948
|
+
entity: endpoint,
|
|
949
|
+
deprecated,
|
|
950
|
+
serviceName,
|
|
951
|
+
serviceTypeName,
|
|
952
|
+
source,
|
|
953
|
+
functions
|
|
954
|
+
});
|
|
923
955
|
}
|
|
924
956
|
return services;
|
|
925
957
|
};
|
|
@@ -932,7 +964,7 @@ const generateCustomValueUtilities = (entities, services) => {
|
|
|
932
964
|
return '';
|
|
933
965
|
}
|
|
934
966
|
serviceLoop: for (const service of services) {
|
|
935
|
-
const someFunction = service.functions.find(v => v.name === 'some');
|
|
967
|
+
const someFunction = service.functions.find((v) => v.name === 'some');
|
|
936
968
|
if (!someFunction) {
|
|
937
969
|
continue;
|
|
938
970
|
}
|
|
@@ -973,22 +1005,21 @@ const resolveInheritedEntities = (root, entities) => {
|
|
|
973
1005
|
const parent = root.extends ? entities.get(root.extends) : undefined;
|
|
974
1006
|
return parent ? [parent, ...resolveInheritedEntities(parent, entities)] : [];
|
|
975
1007
|
};
|
|
976
|
-
const generatePropertyDescriptors = (entity, entities, services, options) => [
|
|
977
|
-
|
|
978
|
-
...entity.properties
|
|
979
|
-
].filter(([, meta]) => {
|
|
1008
|
+
const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
|
|
1009
|
+
.filter(([, meta]) => {
|
|
980
1010
|
// If we generate deprecated things we can skip the filtering
|
|
981
1011
|
if (options.deprecated) {
|
|
982
1012
|
return true;
|
|
983
1013
|
}
|
|
984
1014
|
// Check if corresponding service is deprecated and can be removed
|
|
985
|
-
const service = services.find(v => v.entity === meta.service);
|
|
1015
|
+
const service = services.find((v) => v.entity === meta.service);
|
|
986
1016
|
return !meta.service || (service && !service.deprecated);
|
|
987
|
-
})
|
|
1017
|
+
})
|
|
1018
|
+
.map(([property, meta]) => ({
|
|
988
1019
|
key: property,
|
|
989
1020
|
value: Object.entries(meta).map(([key, value]) => ({
|
|
990
1021
|
key,
|
|
991
|
-
value: value ? generateString(value) : undefined
|
|
1022
|
+
value: value !== undefined ? (typeof value === 'number' ? value : generateString(value)) : undefined
|
|
992
1023
|
}))
|
|
993
1024
|
}));
|
|
994
1025
|
const generateEntityPropertyMap = (entities, services, options) => {
|
|
@@ -1001,7 +1032,7 @@ const generateEntityPropertyMap = (entities, services, options) => {
|
|
|
1001
1032
|
};
|
|
1002
1033
|
|
|
1003
1034
|
const generateArray = (values) => {
|
|
1004
|
-
return `[${concat(values.map(v => generateString(String(v))))}]`;
|
|
1035
|
+
return `[${concat(values.map((v) => generateString(String(v))))}]`;
|
|
1005
1036
|
};
|
|
1006
1037
|
|
|
1007
1038
|
// Only functions matching this regex are included in the generation.
|
|
@@ -1020,7 +1051,8 @@ const generateGroupedServices = (services) => {
|
|
|
1020
1051
|
continue;
|
|
1021
1052
|
}
|
|
1022
1053
|
entityDescriptors.set(name, [
|
|
1023
|
-
...(entityDescriptors.get(name) ?? []),
|
|
1054
|
+
...(entityDescriptors.get(name) ?? []),
|
|
1055
|
+
{
|
|
1024
1056
|
name: entity,
|
|
1025
1057
|
required: true,
|
|
1026
1058
|
type: `${pascalCase(entity)}Service_${pascalCase(name)}`
|
|
@@ -1042,7 +1074,7 @@ const generateGroupedServices = (services) => {
|
|
|
1042
1074
|
...descriptors.map(([name, props]) => {
|
|
1043
1075
|
const constant = camelCase(`wServiceWith_${name}_Names`);
|
|
1044
1076
|
const type = pascalCase(`WServiceWith_${name}`);
|
|
1045
|
-
const value = generateArray(props.map(v => v.name));
|
|
1077
|
+
const value = generateArray(props.map((v) => v.name));
|
|
1046
1078
|
return `export const ${constant}: ${type}[] = ${value};`;
|
|
1047
1079
|
}),
|
|
1048
1080
|
generateBlockComment('Type guards for service classes.', generateStatements(...typeGuards))
|
|
@@ -1054,20 +1086,20 @@ const arr = (list) => `[\n${indent(list.join(',\n'))}\n]`;
|
|
|
1054
1086
|
const generateMaps = ({ services, entities, aliases, enums, options }) => {
|
|
1055
1087
|
const entitiesKeys = [...entities.keys()];
|
|
1056
1088
|
const enumsArray = `export const wEnums = ${obj(enums)};`;
|
|
1057
|
-
const entityNames = `export const wEntityNames: WEntity[] = ${arr(entitiesKeys.map(v => `'${v}'`))};`;
|
|
1058
|
-
const serviceNames = `export const wServiceNames: WService[] = ${arr(services.map(v => `'${v.entity}'`))};`;
|
|
1059
|
-
const serviceValues = `export const wServiceFactories = ${obj(services.map(v => `${v.entity}: ${v.serviceName}`))};`;
|
|
1060
|
-
const serviceInstanceValues = `export const wServices = ${obj(services.map(v => {
|
|
1089
|
+
const entityNames = `export const wEntityNames: WEntity[] = ${arr(entitiesKeys.map((v) => `'${v}'`))};`;
|
|
1090
|
+
const serviceNames = `export const wServiceNames: WService[] = ${arr(services.map((v) => `'${v.entity}'`))};`;
|
|
1091
|
+
const serviceValues = `export const wServiceFactories = ${obj(services.map((v) => `${v.entity}: ${v.serviceName}`))};`;
|
|
1092
|
+
const serviceInstanceValues = `export const wServices = ${obj(services.map((v) => {
|
|
1061
1093
|
const src = `${v.entity}: ${v.serviceName}()`;
|
|
1062
1094
|
return v.deprecated ? generateInlineComment('@deprecated') + `\n${src}` : src;
|
|
1063
1095
|
}))};`;
|
|
1064
1096
|
const entityInterfaces = [
|
|
1065
|
-
...entitiesKeys.map(entity => ({
|
|
1097
|
+
...entitiesKeys.map((entity) => ({
|
|
1066
1098
|
name: entity,
|
|
1067
1099
|
type: pascalCase(entity),
|
|
1068
1100
|
required: true
|
|
1069
1101
|
})),
|
|
1070
|
-
...services.map(service => {
|
|
1102
|
+
...services.map((service) => {
|
|
1071
1103
|
const alias = aliases.get(service.entity);
|
|
1072
1104
|
return {
|
|
1073
1105
|
name: service.entity,
|
|
@@ -1077,11 +1109,20 @@ const generateMaps = ({ services, entities, aliases, enums, options }) => {
|
|
|
1077
1109
|
};
|
|
1078
1110
|
})
|
|
1079
1111
|
];
|
|
1080
|
-
const createMappingType = (type, prefix) => type !== 'never' ? `${type}_${prefix}` : type;
|
|
1112
|
+
const createMappingType = (type, prefix) => (type !== 'never' ? `${type}_${prefix}` : type);
|
|
1081
1113
|
const entitiesList = generateInterface('WEntities', entityInterfaces);
|
|
1082
|
-
const entityReferences = generateInterface('WEntityReferences', entityInterfaces.map(v => ({
|
|
1083
|
-
|
|
1084
|
-
|
|
1114
|
+
const entityReferences = generateInterface('WEntityReferences', entityInterfaces.map((v) => ({
|
|
1115
|
+
...v,
|
|
1116
|
+
type: createMappingType(v.type, 'References')
|
|
1117
|
+
})));
|
|
1118
|
+
const entityMappings = generateInterface('WEntityMappings', entityInterfaces.map((v) => ({
|
|
1119
|
+
...v,
|
|
1120
|
+
type: createMappingType(v.type, 'Mappings')
|
|
1121
|
+
})));
|
|
1122
|
+
const entityFilter = generateInterface('WEntityFilters', entityInterfaces.map((v) => ({
|
|
1123
|
+
...v,
|
|
1124
|
+
type: createMappingType(v.type, 'Filter')
|
|
1125
|
+
})));
|
|
1085
1126
|
return {
|
|
1086
1127
|
source: generateStatements(
|
|
1087
1128
|
/* JS Values */
|
|
@@ -1114,7 +1155,6 @@ const generateMaps = ({ services, entities, aliases, enums, options }) => {
|
|
|
1114
1155
|
};
|
|
1115
1156
|
|
|
1116
1157
|
const parseReferencedEntity = (obj) => pascalCase(obj.$ref.replace(/.*\//, ''));
|
|
1117
|
-
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
1118
1158
|
const extractSchemas = (doc) => {
|
|
1119
1159
|
const schemas = new Map();
|
|
1120
1160
|
const aliases = new Map();
|
|
@@ -1161,7 +1201,7 @@ const generate = (doc, options) => {
|
|
|
1161
1201
|
const enums = generateEnums(schemas);
|
|
1162
1202
|
const entities = generateEntities(schemas, enums);
|
|
1163
1203
|
const services = generateServices(doc, aliases, options);
|
|
1164
|
-
return generateStatements(generateBase(options.target), generateBlockComment('ENUMS', generateStatements(...[...enums.values()].map(v => v.source))), generateBlockComment('ENTITIES', generateStatements(...[...entities.values()].map(v => v.source))), generateBlockComment('SERVICES', generateStatements(...[...services.values()].map(v => v.source))), generateBlockComment('MAPS', generateMaps({
|
|
1204
|
+
return generateStatements(generateBase(options.target, doc.info.version, options.useQueryLanguage), generateBlockComment('ENUMS', generateStatements(...[...enums.values()].map((v) => v.source))), generateBlockComment('ENTITIES', generateStatements(...[...entities.values()].map((v) => v.source))), generateBlockComment('SERVICES', generateStatements(...[...services.values()].map((v) => v.source))), generateBlockComment('MAPS', generateMaps({
|
|
1165
1205
|
services: [...services.values()],
|
|
1166
1206
|
enums: [...enums.keys()],
|
|
1167
1207
|
options,
|
|
@@ -1183,7 +1223,7 @@ const hash = (content, algorithm = 'sha256') => {
|
|
|
1183
1223
|
|
|
1184
1224
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
1185
1225
|
const cli = async () => {
|
|
1186
|
-
const
|
|
1226
|
+
const version = pkg.version;
|
|
1187
1227
|
const { argv } = yargs(hideBin(process.argv))
|
|
1188
1228
|
.scriptName('build-weclapp-sdk')
|
|
1189
1229
|
.usage('Usage: $0 <source> [flags]')
|
|
@@ -1228,9 +1268,8 @@ const cli = async () => {
|
|
|
1228
1268
|
type: 'string',
|
|
1229
1269
|
choices: ['browser', 'browser.rx', 'node', 'node.rx']
|
|
1230
1270
|
})
|
|
1231
|
-
.option('
|
|
1232
|
-
|
|
1233
|
-
describe: 'Include deprecated functions and services',
|
|
1271
|
+
.option('use-query-language', {
|
|
1272
|
+
describe: 'Generate the new where property for some and count queries',
|
|
1234
1273
|
type: 'boolean'
|
|
1235
1274
|
})
|
|
1236
1275
|
.epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
|
|
@@ -1242,21 +1281,27 @@ const cli = async () => {
|
|
|
1242
1281
|
const options = {
|
|
1243
1282
|
deprecated,
|
|
1244
1283
|
generateUnique: argv.generateUnique ?? false,
|
|
1245
|
-
target: argv.target ?? Target.BROWSER_PROMISES
|
|
1284
|
+
target: argv.target ?? Target.BROWSER_PROMISES,
|
|
1285
|
+
useQueryLanguage: argv.useQueryLanguage ?? false
|
|
1246
1286
|
};
|
|
1247
|
-
if (typeof src === 'number') {
|
|
1248
|
-
return Promise.reject('Expected string as command');
|
|
1287
|
+
if (!src || typeof src === 'number') {
|
|
1288
|
+
return Promise.reject(new Error('Expected string as command'));
|
|
1249
1289
|
}
|
|
1250
1290
|
if (!Object.values(Target).includes(options.target)) {
|
|
1251
1291
|
logger.errorLn(`Unknown target: ${options.target}. Possible values are ${Object.values(Target).join(', ')}`);
|
|
1252
|
-
return Promise.reject();
|
|
1292
|
+
return Promise.reject(new Error());
|
|
1253
1293
|
}
|
|
1254
1294
|
if (await stat(src).catch(() => false)) {
|
|
1255
|
-
logger.infoLn(`Source is a file
|
|
1295
|
+
logger.infoLn(`Source is a file`);
|
|
1256
1296
|
const content = JSON.parse(await readFile(src, 'utf-8'));
|
|
1257
1297
|
return { cache, content, options };
|
|
1258
1298
|
}
|
|
1299
|
+
logger.infoLn(`Source is a URL`);
|
|
1300
|
+
if (!key) {
|
|
1301
|
+
return Promise.reject(new Error('API key is missing'));
|
|
1302
|
+
}
|
|
1259
1303
|
const url = new URL(src.startsWith('http') ? src : `https://${src}`);
|
|
1304
|
+
// At the moment just v1
|
|
1260
1305
|
url.pathname = '/webapp/api/v1/meta/openapi.json';
|
|
1261
1306
|
if (query?.length) {
|
|
1262
1307
|
for (const param of query.split(',')) {
|
|
@@ -1265,11 +1310,11 @@ const cli = async () => {
|
|
|
1265
1310
|
}
|
|
1266
1311
|
}
|
|
1267
1312
|
const content = await fetch(url.toString(), {
|
|
1268
|
-
headers: {
|
|
1269
|
-
}).then(res => res.ok ? res.json() : undefined);
|
|
1313
|
+
headers: { Accept: 'application/json', AuthenticationToken: key }
|
|
1314
|
+
}).then((res) => (res.ok ? res.json() : undefined));
|
|
1270
1315
|
if (!content) {
|
|
1271
1316
|
logger.errorLn(`Couldn't fetch file ${url.toString()} `);
|
|
1272
|
-
return Promise.reject();
|
|
1317
|
+
return Promise.reject(new Error());
|
|
1273
1318
|
}
|
|
1274
1319
|
else {
|
|
1275
1320
|
logger.infoLn(`Use remote file: ${url.toString()}`);
|
|
@@ -1277,50 +1322,59 @@ const cli = async () => {
|
|
|
1277
1322
|
return { cache, content, options };
|
|
1278
1323
|
};
|
|
1279
1324
|
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1325
|
+
const workingDir = resolve(currentDirname(), './sdk');
|
|
1326
|
+
const cacheDir = resolve(currentDirname(), './.cache');
|
|
1282
1327
|
void (async () => {
|
|
1283
1328
|
const start = process.hrtime.bigint();
|
|
1284
|
-
const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
|
|
1285
1329
|
const { content: doc, cache: useCache, options } = await cli();
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
const dist = (...paths) => resolve(workingDirectory, ...paths);
|
|
1290
|
-
const tmp = async (...paths) => {
|
|
1291
|
-
const fullPath = resolve(cacheDir, ...paths);
|
|
1292
|
-
await mkdir(dirname(fullPath), { recursive: true }).catch(() => null);
|
|
1330
|
+
const workingDirPath = async (...paths) => {
|
|
1331
|
+
const fullPath = resolve(workingDir, ...paths);
|
|
1332
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
1293
1333
|
return fullPath;
|
|
1294
1334
|
};
|
|
1335
|
+
// Resolve cache dir and key
|
|
1336
|
+
const cacheKey = hash([pkg.version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
|
|
1337
|
+
const cachedSdkDir = resolve(cacheDir, cacheKey);
|
|
1338
|
+
// Remove old SDK
|
|
1339
|
+
await rm(workingDir, { recursive: true, force: true });
|
|
1295
1340
|
if (useCache) {
|
|
1296
1341
|
logger.infoLn(`Cache ID: ${cacheKey}`);
|
|
1297
1342
|
}
|
|
1298
|
-
if (useCache && await stat(
|
|
1299
|
-
|
|
1343
|
+
if (useCache && (await stat(cachedSdkDir).catch(() => false))) {
|
|
1344
|
+
// Copy cached SDK to working dir
|
|
1345
|
+
logger.successLn(`Cache match! (${cachedSdkDir})`);
|
|
1346
|
+
await cp(cachedSdkDir, workingDir, { recursive: true });
|
|
1300
1347
|
}
|
|
1301
1348
|
else {
|
|
1302
|
-
//
|
|
1303
|
-
await writeFile(await
|
|
1349
|
+
// Write openapi.json file
|
|
1350
|
+
await writeFile(await workingDirPath('openapi.json'), JSON.stringify(doc, null, 2));
|
|
1304
1351
|
logger.infoLn(`Generate sdk (target: ${options.target})`);
|
|
1305
|
-
// Generate
|
|
1352
|
+
// Generate and write SDK (index.ts)
|
|
1306
1353
|
const sdk = generate(doc, options);
|
|
1307
|
-
await writeFile(await
|
|
1308
|
-
// Bundle
|
|
1354
|
+
await writeFile(await workingDirPath('src', 'index.ts'), sdk.trim() + '\n');
|
|
1355
|
+
// Bundle and write SDK
|
|
1309
1356
|
logger.infoLn('Bundle... (this may take some time)');
|
|
1310
|
-
await bundle(
|
|
1311
|
-
// Remove
|
|
1312
|
-
await
|
|
1357
|
+
await bundle(workingDir, options.target);
|
|
1358
|
+
// Remove index.ts (only bundle is required)
|
|
1359
|
+
await rm(await workingDirPath('src'), { recursive: true, force: true });
|
|
1360
|
+
if (useCache) {
|
|
1361
|
+
// Copy SDK to cache
|
|
1362
|
+
logger.successLn(`Caching SDK: (${cachedSdkDir})`);
|
|
1363
|
+
await mkdir(cachedSdkDir, { recursive: true });
|
|
1364
|
+
await cp(workingDir, cachedSdkDir, { recursive: true });
|
|
1365
|
+
}
|
|
1313
1366
|
}
|
|
1314
|
-
// Copy bundled SDK
|
|
1315
|
-
await cp(cacheDir, workingDirectory, { recursive: true });
|
|
1316
1367
|
// Print job summary
|
|
1317
1368
|
const duration = (process.hrtime.bigint() - start) / 1000000n;
|
|
1318
1369
|
logger.successLn(`SDK built in ${prettyMs(Number(duration))}`);
|
|
1319
1370
|
logger.printSummary();
|
|
1320
|
-
})()
|
|
1371
|
+
})()
|
|
1372
|
+
.catch((error) => {
|
|
1321
1373
|
logger.errorLn(`Fatal error:`);
|
|
1322
1374
|
/* eslint-disable no-console */
|
|
1323
1375
|
console.error(error);
|
|
1324
|
-
})
|
|
1325
|
-
|
|
1376
|
+
})
|
|
1377
|
+
.finally(() => {
|
|
1378
|
+
if (logger.errors)
|
|
1379
|
+
process.exit(1);
|
|
1326
1380
|
});
|