@weclapp/sdk 2.0.0-dev.4 → 2.0.0-dev.42

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