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

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,102 @@ 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 || enums.has(prop.type) || prop.type === 'string' || prop.type === 'number' || prop.type === 'boolean' || prop.type.endsWith('[]')) {
462
+ return prop;
463
+ }
464
+ return { ...prop, type: `${pascalCase(prop.type)}_${FILTER_PROPS_SUFFIX}` };
465
+ });
466
+ };
467
+ entities.forEach((entity, name) => {
468
+ const entityFilterName = `${pascalCase(name)}_${FILTER_PROPS_SUFFIX}`;
469
+ const parentName = entity.parentName ? `${pascalCase(entity.parentName)}_${FILTER_PROPS_SUFFIX}` : undefined;
470
+ const filterableInterfaceProperties = transformFilterProps(entity.filterableInterfaceProperties);
471
+ entityFilterProps.set(entityFilterName, {
472
+ name: entityFilterName,
473
+ parentName,
474
+ source: generateStatements(generateInterface(entityFilterName, filterableInterfaceProperties, parentName))
475
+ });
476
+ });
477
+ return entityFilterProps;
478
+ };
426
479
 
427
480
  /**
428
481
  * Pluralizes a word, most of the time correct.
429
482
  * @param s String to pluralize.
430
483
  */
431
484
  const pluralize = (s) => {
432
- return s.endsWith('s') ? s :
433
- s.endsWith('y') ? `${s.slice(0, -1)}ies` :
434
- `${s}s`;
485
+ return s.endsWith('s') ? s : s.endsWith('y') ? `${s.slice(0, -1)}ies` : `${s}s`;
435
486
  };
436
487
 
437
- /* eslint-disable no-console */
438
- const logger = new class {
488
+ const logger = new (class {
439
489
  active = true;
440
490
  warnings = 0;
441
491
  errors = 0;
@@ -483,21 +533,24 @@ const logger = new class {
483
533
  printSummary() {
484
534
  const format = (v, name, fail, ok) => {
485
535
  const color = v ? fail : ok;
486
- return v === 0 ? `${color('zero')} ${pluralize(name)}` :
487
- v === 1 ? `${color('one')} ${name}` : `${color(v)} ${pluralize(name)}`;
536
+ return v === 0
537
+ ? `${color('zero')} ${pluralize(name)}`
538
+ : v === 1
539
+ ? `${color('one')} ${name}`
540
+ : `${color(v)} ${pluralize(name)}`;
488
541
  };
489
542
  const warnings = format(this.warnings, 'warning', chalk.yellowBright, chalk.greenBright);
490
543
  const errors = format(this.errors, 'error', chalk.redBright, chalk.greenBright);
491
544
  const info = `Finished with ${warnings} and ${errors}.`;
492
545
  this[this.errors ? 'errorLn' : this.warnings ? 'warnLn' : 'successLn'](info);
493
546
  }
494
- };
547
+ })();
495
548
 
496
549
  /**
497
- * ROOT => /article
498
- * COUNT => /article/count
499
- * ENTITY => /article/{id}
500
- * SPECIAL_ROOT => /article/generateImage
550
+ * ROOT => /article
551
+ * COUNT => /article/count
552
+ * ENTITY => /article/{id}
553
+ * SPECIAL_ROOT => /article/generateImage
501
554
  * SPECIAL_ENTITY => /article/id/{id}/generateImag
502
555
  */
503
556
  var WeclappEndpointType;
@@ -509,23 +562,33 @@ var WeclappEndpointType;
509
562
  WeclappEndpointType["GENERIC_ENTITY"] = "GENERIC_ENTITY";
510
563
  })(WeclappEndpointType || (WeclappEndpointType = {}));
511
564
  const parseEndpointPath = (path) => {
512
- const [, entity, ...rest] = path.split('/');
513
- if (!entity) {
565
+ const [, service, ...rest] = path.split('/');
566
+ if (!service) {
514
567
  return undefined;
515
568
  }
516
569
  if (!rest.length) {
517
- return { path, entity, type: WeclappEndpointType.ROOT };
570
+ return { path, service, type: WeclappEndpointType.ROOT };
518
571
  }
519
572
  else if (rest[0] === 'count') {
520
- return { path, entity, type: WeclappEndpointType.COUNT };
573
+ return { path, service, type: WeclappEndpointType.COUNT };
521
574
  }
522
575
  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 };
576
+ return rest.length === 2
577
+ ? { path, service, type: WeclappEndpointType.ENTITY }
578
+ : {
579
+ path,
580
+ service,
581
+ method: rest[2],
582
+ type: WeclappEndpointType.GENERIC_ENTITY
583
+ };
526
584
  }
527
585
  else if (rest.length === 1) {
528
- return { path, entity, method: rest[1], type: WeclappEndpointType.GENERIC_ROOT };
586
+ return {
587
+ path,
588
+ service,
589
+ method: rest[1],
590
+ type: WeclappEndpointType.GENERIC_ROOT
591
+ };
529
592
  }
530
593
  return undefined;
531
594
  };
@@ -547,48 +610,50 @@ const convertParametersToSchema = (parameters = []) => {
547
610
  if (isParameterObject(param) && param.in === 'query') {
548
611
  if (param.schema) {
549
612
  properties.push([param.name, param.schema]);
550
- param.required && required.push(param.name);
613
+ if (param.required)
614
+ required.push(param.name);
551
615
  }
552
616
  }
553
617
  }
554
618
  return {
555
- type: 'object', required,
619
+ type: 'object',
620
+ required,
556
621
  properties: Object.fromEntries(properties)
557
622
  };
558
623
  };
559
624
 
560
- const functionName$5 = 'count';
561
625
  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)
626
+ const functionName = 'count';
627
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
628
+ const entity = aliases.get(endpoint.service) ?? pascalCase(endpoint.service);
629
+ const parametersTypeName = `${functionTypeName}_Parameters`;
630
+ const parametersType = createObjectType({
631
+ params: convertToTypeScriptType(convertParametersToSchema(path.parameters))
570
632
  });
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}`],
633
+ const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, true);
634
+ const filterTypeName = `${functionTypeName}_Filter`;
635
+ const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${entity}_${FILTER_PROPS_SUFFIX}`]);
636
+ const functionTypeSource = generateArrowFunctionType({
637
+ type: functionTypeName,
638
+ params: [
639
+ `query${parametersType.isFullyOptional() ? '?' : ''}: CountQuery<${filterTypeName}>${path.parameters?.length ? ' & ' + parametersTypeName : ''}`, 'requestOptions?: RequestOptions'
640
+ ],
580
641
  returns: `${resolveResponseType(target)}<number>`
581
642
  });
643
+ const functionSource = generateArrowFunction({
644
+ name: functionName,
645
+ signature: functionTypeName,
646
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
647
+ params: ['query', 'requestOptions?: RequestOptions']
648
+ });
582
649
  return {
583
650
  entity,
584
- name: functionName$5,
585
- type: { name: interfaceName, source: interfaceSource },
586
- func: { name: functionName$5, source: functionSource },
651
+ name: functionName,
652
+ type: { name: functionTypeName, source: functionTypeSource },
653
+ func: { name: functionName, source: functionSource },
587
654
  interfaces: [
588
- {
589
- name: entityParameters,
590
- source: generateInterfaceFromObject(entityParameters, parameters, true)
591
- }
655
+ ...(path.parameters?.length ? [{ name: parametersTypeName, source: parametersTypeSource }] : []),
656
+ { name: filterTypeName, source: filterTypeSource }
592
657
  ]
593
658
  };
594
659
  };
@@ -610,39 +675,33 @@ const generateRequestBodyType = ({ requestBody }) => {
610
675
  return generateBodyType(requestBody) ?? createRawType('unknown');
611
676
  };
612
677
 
613
- const resolveBodyType = ({ responses }) => Object.entries(responses)
614
- .filter(v => v[0].startsWith('2'))[0]?.[1];
678
+ const resolveBodyType = ({ responses }) => Object.entries(responses).filter((v) => v[0].startsWith('2'))[0]?.[1];
615
679
  const generateResponseBodyType = (object) => generateBodyType(resolveBodyType(object)) ?? createRawType('void');
616
680
 
617
- const functionName$4 = 'create';
618
681
  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()}>`],
682
+ const functionName = 'create';
683
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
684
+ const functionTypeSource = generateArrowFunctionType({
685
+ type: functionTypeName,
686
+ params: [`data: DeepPartial<${generateRequestBodyType(path).toString()}>`, 'requestOptions?: RequestOptions'],
630
687
  returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
631
688
  });
689
+ const functionSource = generateArrowFunction({
690
+ name: functionName,
691
+ signature: functionTypeName,
692
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, data, requestOptions)`,
693
+ params: ['data', 'requestOptions?: RequestOptions']
694
+ });
632
695
  return {
633
- entity,
634
- name: functionName$4,
635
- type: { name: interfaceName, source: interfaceSource },
636
- func: { name: functionName$4, source: functionSource }
696
+ entity: pascalCase(endpoint.service),
697
+ name: functionName,
698
+ type: { name: functionTypeName, source: functionTypeSource },
699
+ func: { name: functionName, source: functionSource }
637
700
  };
638
701
  };
639
702
 
640
703
  const generateGenericFunctionName = (path, suffix = '', prefix = '') => {
641
- return camelCase(`${prefix}_` +
642
- path
643
- .replace(/.*\//, '')
644
- .replace(/\W+/, '_')
645
- .replace(/[_]+/, '_') + `_${suffix}`);
704
+ return camelCase(`${prefix}_` + path.replace(/.*\//, '').replace(/\W+/, '_').replace(/[_]+/, '_') + `_${suffix}`);
646
705
  };
647
706
 
648
707
  const insertPathPlaceholder = (path, record) => {
@@ -650,37 +709,33 @@ const insertPathPlaceholder = (path, record) => {
650
709
  };
651
710
 
652
711
  const wrapBody = (type, target) => {
653
- return type.toString() === 'binary' ?
654
- createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') :
655
- type; // node-fetch returns a Blob as well
712
+ return type.toString() === 'binary' ? createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') : type; // node-fetch returns a Blob as well
656
713
  };
657
714
  const generateGenericEndpoint = (suffix) => ({ target, method, path, endpoint }) => {
658
715
  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`;
716
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
717
+ const entityQuery = `${functionTypeName}_Query`;
662
718
  const hasId = endpoint.path.includes('{id}');
663
719
  const params = createObjectType({
664
720
  params: convertToTypeScriptType(convertParametersToSchema(path.parameters)),
665
721
  body: method === 'get' ? undefined : wrapBody(generateRequestBodyType(path), target)
666
722
  });
667
723
  const responseBody = generateResponseBodyType(path);
668
- const forceBlobResponse = String(responseBody.toString() === 'binary');
724
+ const functionTypeSource = generateArrowFunctionType({
725
+ type: functionTypeName,
726
+ params: [...(hasId ? ['id: string'] : []), `query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`, 'requestOptions?: RequestOptions'],
727
+ returns: `${resolveResponseType(target)}<${wrapBody(responseBody, target).toString()}>`
728
+ });
669
729
  const functionSource = generateArrowFunction({
670
730
  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()}>`
731
+ signature: functionTypeName,
732
+ params: hasId ? ['id', 'query', 'requestOptions?: RequestOptions'] : ['query', 'requestOptions?: RequestOptions'],
733
+ returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${String(responseBody.toString() === 'binary')}, requestOptions)`
679
734
  });
680
735
  return {
681
- entity,
736
+ entity: pascalCase(endpoint.service),
682
737
  name: functionName,
683
- type: { name: interfaceName, source: interfaceSource },
738
+ type: { name: functionTypeName, source: functionTypeSource },
684
739
  func: { name: functionName, source: functionSource },
685
740
  interfaces: [
686
741
  {
@@ -691,35 +746,38 @@ const generateGenericEndpoint = (suffix) => ({ target, method, path, endpoint })
691
746
  };
692
747
  };
693
748
 
694
- const functionName$3 = 'remove';
695
749
  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'],
750
+ const functionName = 'remove';
751
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
752
+ const functionTypeSource = generateArrowFunctionType({
753
+ type: functionTypeName,
754
+ params: ['id: string', 'options?: RemoveQuery', 'requestOptions?: RequestOptions'],
707
755
  returns: `${resolveResponseType(target)}<void>`
708
756
  });
757
+ const functionSource = generateArrowFunction({
758
+ name: functionName,
759
+ signature: functionTypeName,
760
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options, requestOptions)`,
761
+ params: ['id', 'options?: RemoveQuery', 'requestOptions?: RequestOptions']
762
+ });
709
763
  return {
710
- entity,
711
- name: functionName$3,
712
- type: { name: interfaceName, source: interfaceSource },
713
- func: { name: functionName$3, source: functionSource }
764
+ entity: pascalCase(endpoint.service),
765
+ name: functionName,
766
+ type: { name: functionTypeName, source: functionTypeSource },
767
+ func: { name: functionName, source: functionSource }
714
768
  };
715
769
  };
716
770
 
717
- const functionName$2 = 'some';
718
771
  const excludedParameters = [
719
- 'page', 'pageSize', 'sort',
720
- 'serializeNulls', 'properties', 'includeReferencedEntities'
772
+ 'page',
773
+ 'pageSize',
774
+ 'sort',
775
+ 'serializeNulls',
776
+ 'properties',
777
+ 'includeReferencedEntities',
778
+ 'additionalProperties'
721
779
  ];
722
- const resolveAdditionalProperties = (path) => {
780
+ const resolveAdditionalPropertiesSchema = (path) => {
723
781
  const body = resolveBodyType(path);
724
782
  if (isResponseObject(body)) {
725
783
  const schema = body?.content?.['application/json']?.schema;
@@ -732,115 +790,161 @@ const resolveAdditionalProperties = (path) => {
732
790
  }
733
791
  return undefined;
734
792
  };
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)
793
+ const resolveReferences = (entity, entities) => {
794
+ const references = [];
795
+ const generatedEntity = entities.get(entity);
796
+ if (generatedEntity) {
797
+ for (const [property, propertyMetaData] of generatedEntity.properties) {
798
+ if (propertyMetaData.service) {
799
+ references.push({
800
+ name: property,
801
+ type: generateString(propertyMetaData.service),
802
+ required: true
803
+ });
804
+ }
805
+ }
806
+ if (generatedEntity.parentName) {
807
+ references.push(...resolveReferences(generatedEntity.parentName, entities));
808
+ }
809
+ }
810
+ return references;
811
+ };
812
+ const resolveReferencedEntities = (entity, entities) => {
813
+ const referencedEntities = [];
814
+ const generatedEntity = entities.get(entity);
815
+ if (generatedEntity) {
816
+ for (const [, propertyMetaData] of generatedEntity.properties) {
817
+ if (propertyMetaData.entity && propertyMetaData.service) {
818
+ referencedEntities.push({
819
+ name: propertyMetaData.service,
820
+ type: `${pascalCase(propertyMetaData.entity)}[]`,
821
+ required: true
822
+ });
823
+ }
824
+ }
825
+ if (generatedEntity.parentName) {
826
+ referencedEntities.push(...resolveReferencedEntities(generatedEntity.parentName, entities));
827
+ }
828
+ }
829
+ return referencedEntities;
830
+ };
831
+ const generateSomeEndpoint = ({ endpoint, target, path, entities, aliases }) => {
832
+ const functionName = 'some';
833
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
834
+ const entity = aliases.get(endpoint.service) ?? pascalCase(endpoint.service);
835
+ const parametersTypeName = `${functionTypeName}_Parameters`;
836
+ const parameters = path.parameters?.filter((v) => (isParameterObject(v) ? !excludedParameters.includes(v.name) : false)) ?? [];
837
+ const parametersType = createObjectType({
838
+ params: convertToTypeScriptType(convertParametersToSchema(parameters))
753
839
  });
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}>>`
840
+ const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, true);
841
+ const filterTypeName = `${functionTypeName}_Filter`;
842
+ const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${entity}_${FILTER_PROPS_SUFFIX}`]);
843
+ const referencesTypeName = `${functionTypeName}_References`;
844
+ const referencesTypeSource = generateInterfaceType(referencesTypeName, resolveReferences(endpoint.service, entities));
845
+ const additionalPropertyTypeName = `${functionTypeName}_AdditionalProperty`;
846
+ const additionalPropertyTypeSource = generateType(additionalPropertyTypeName, 'string');
847
+ const queryTypeName = `${functionTypeName}_Query`;
848
+ const queryTypeSource = generateType(queryTypeName, `SomeQuery<${entity}, ${filterTypeName}, ${referencesTypeName}, ${additionalPropertyTypeName}> & ${parametersTypeName}`);
849
+ const referencedEntitiesTypeName = `${functionTypeName}_ReferencedEntities`;
850
+ const referencedEntitiesTypeSource = generateInterfaceType(referencedEntitiesTypeName, resolveReferencedEntities(endpoint.service, entities));
851
+ const additionalPropertiesTypeName = `${functionTypeName}_AdditionalProperties`;
852
+ const additionalPropertiesSchema = resolveAdditionalPropertiesSchema(path);
853
+ const additionalPropertiesTypeSource = generateType(additionalPropertiesTypeName, additionalPropertiesSchema ? convertToTypeScriptType(additionalPropertiesSchema).toString() : '{}');
854
+ const functionTypeSource = generateArrowFunctionType({
855
+ type: functionTypeName,
856
+ params: [`query${parametersType.isFullyOptional() ? '?' : ''}: ${queryTypeName}, requestOptions?: RequestOptions`],
857
+ returns: `${resolveResponseType(target)}<SomeQueryReturn<${entity}, ${referencedEntitiesTypeName}, ${additionalPropertiesTypeName}>>`
763
858
  });
764
859
  const functionSource = generateArrowFunction({
765
- name: functionName$2,
766
- signature: interfaceName,
767
- returns: `_${functionName$2}(cfg, ${generateString(endpoint.path)}, query)`,
768
- params: ['query']
860
+ name: functionName,
861
+ signature: functionTypeName,
862
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
863
+ params: ['query', 'requestOptions?: RequestOptions']
769
864
  });
770
865
  return {
771
866
  entity,
772
- name: functionName$2,
773
- type: { name: interfaceName, source: interfaceSource },
774
- func: { name: functionName$2, source: functionSource },
867
+ name: functionName,
868
+ type: { name: functionTypeName, source: functionTypeSource },
869
+ func: { name: functionName, source: functionSource },
775
870
  interfaces: [
776
- {
777
- name: entityParameters,
778
- source: generateInterfaceFromObject(entityParameters, parameters, true)
779
- }
871
+ { name: parametersTypeName, source: parametersTypeSource },
872
+ { name: filterTypeName, source: filterTypeSource },
873
+ { name: referencesTypeName, source: referencesTypeSource },
874
+ { name: additionalPropertyTypeName, source: additionalPropertyTypeSource },
875
+ { name: queryTypeName, source: queryTypeSource },
876
+ { name: referencedEntitiesTypeName, source: referencedEntitiesTypeSource },
877
+ { name: additionalPropertiesTypeName, source: additionalPropertiesTypeSource }
780
878
  ]
781
879
  };
782
880
  };
783
881
 
784
- const functionName$1 = 'unique';
785
882
  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'],
883
+ const functionName = 'unique';
884
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
885
+ const functionTypeSource = generateArrowFunctionType({
886
+ type: functionTypeName,
887
+ params: ['id: string', 'query?: Q', 'requestOptions?: RequestOptions'],
797
888
  generics: ['Q extends UniqueQuery'],
798
889
  returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
799
890
  });
891
+ const functionSource = generateArrowFunction({
892
+ name: functionName,
893
+ signature: functionTypeName,
894
+ params: ['id', 'query', 'requestOptions?: RequestOptions'],
895
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, requestOptions?: RequestOptions)`
896
+ });
800
897
  return {
801
- entity,
802
- name: functionName$1,
803
- type: { name: interfaceName, source: interfaceSource },
804
- func: { name: functionName$1, source: functionSource }
898
+ entity: pascalCase(endpoint.service),
899
+ name: functionName,
900
+ type: { name: functionTypeName, source: functionTypeSource },
901
+ func: { name: functionName, source: functionSource }
805
902
  };
806
903
  };
807
904
 
808
- const functionName = 'update';
809
905
  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'],
906
+ const functionName = 'update';
907
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
908
+ const functionTypeSource = generateArrowFunctionType({
909
+ type: functionTypeName,
910
+ params: ['id: string', `data: DeepPartial<${generateRequestBodyType(path).toString()}>`, 'options?: UpdateQuery', 'requestOptions?: RequestOptions'],
815
911
  returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
816
912
  });
817
913
  const functionSource = generateArrowFunction({
818
914
  name: functionName,
819
- signature: interfaceName,
820
- returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options)`,
821
- params: ['id', 'data', 'options']
915
+ signature: functionTypeName,
916
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options, requestOptions)`,
917
+ params: ['id', 'data', 'options', 'requestOptions?: RequestOptions']
822
918
  });
823
919
  return {
824
- entity,
920
+ entity: pascalCase(endpoint.service),
825
921
  name: functionName,
826
- type: { name: interfaceName, source: interfaceSource },
922
+ type: { name: functionTypeName, source: functionTypeSource },
827
923
  func: { name: functionName, source: functionSource }
828
924
  };
829
925
  };
830
926
 
831
- const groupEndpointsByEntity = (paths) => {
927
+ const isMultiPartUploadPath = (path) => {
928
+ const [, entity, ...rest] = path.split('/');
929
+ return entity && rest.length === 2 && rest[1] === 'multipartUpload';
930
+ };
931
+ const parseEndpointsAndGroupByEntity = (paths) => {
832
932
  const endpoints = new Map();
833
933
  for (const [rawPath, path] of Object.entries(paths)) {
834
934
  const endpoint = parseEndpointPath(rawPath);
835
935
  if (!endpoint || !path) {
936
+ // Todo: Should be removed if sdk supports multi part upload.
937
+ if (isMultiPartUploadPath(rawPath)) {
938
+ continue;
939
+ }
836
940
  logger.errorLn(`Failed to parse ${rawPath}`);
837
941
  continue;
838
942
  }
839
- if (endpoints.has(endpoint.entity)) {
840
- endpoints.get(endpoint.entity)?.push({ endpoint, path });
943
+ if (endpoints.has(endpoint.service)) {
944
+ endpoints.get(endpoint.service)?.push({ endpoint, path });
841
945
  }
842
946
  else {
843
- endpoints.set(endpoint.entity, [{ endpoint, path }]);
947
+ endpoints.set(endpoint.service, [{ endpoint, path }]);
844
948
  }
845
949
  }
846
950
  return endpoints;
@@ -873,26 +977,35 @@ const generators = {
873
977
  post: generateGenericEndpoint()
874
978
  }
875
979
  };
876
- const generateServices = (doc, aliases, options) => {
980
+ const generateServices = (paths, entities, aliases, options) => {
877
981
  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
982
+ const endpoints = parseEndpointsAndGroupByEntity(paths);
983
+ for (const [serviceName, paths] of endpoints) {
984
+ const serviceFnName = camelCase(`${serviceName}Service`);
985
+ const serviceTypeName = pascalCase(`${serviceName}Service`);
883
986
  const functions = [];
884
987
  for (const { path, endpoint } of paths) {
885
- const resolver = generators[endpoint.type];
988
+ const generator = generators[endpoint.type];
886
989
  for (const [method, config] of Object.entries(path)) {
887
- if (method === 'get' && endpoint.type === WeclappEndpointType.ENTITY && !options.generateUnique) {
990
+ if ((method === 'get' && endpoint.type === WeclappEndpointType.ENTITY && !options.generateUnique)
991
+ || (method === 'post' && (endpoint.type === WeclappEndpointType.COUNT || endpoint.path.endsWith('query')))) {
992
+ // Skip unique endpoints if generateUnique option is not set or if POST is used for filter queries
888
993
  continue;
889
994
  }
890
- if (resolver[method]) {
995
+ const generatorFn = generator[method];
996
+ if (generatorFn) {
891
997
  const path = config;
892
998
  const target = options.target;
893
999
  if (!path.deprecated || options.deprecated) {
894
1000
  functions.push({
895
- ...resolver[method]({ endpoint, method, target, path, aliases }),
1001
+ ...generatorFn({
1002
+ endpoint,
1003
+ method,
1004
+ target,
1005
+ path,
1006
+ entities,
1007
+ aliases
1008
+ }),
896
1009
  path
897
1010
  });
898
1011
  }
@@ -905,26 +1018,28 @@ const generateServices = (doc, aliases, options) => {
905
1018
  if (!functions.length) {
906
1019
  continue;
907
1020
  }
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 => ({
1021
+ 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, [
1022
+ ...functions.map((v) => ({
911
1023
  required: true,
912
1024
  comment: v.path.deprecated ? '@deprecated' : undefined,
913
1025
  name: v.func.name,
914
1026
  type: v.type.name
915
1027
  }))
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 });
1028
+ ])));
1029
+ const serviceFn = `export const ${serviceFnName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${generateBlockStatements(...functions.map((v) => v.func.source), `return {${concat(functions.map((v) => v.func.name))}};`)};`;
1030
+ services.set(serviceName, {
1031
+ name: serviceName,
1032
+ serviceFnName,
1033
+ serviceTypeName,
1034
+ functions,
1035
+ source: generateStatements(serviceTypes, serviceFn),
1036
+ deprecated: functions.every((v) => v.path.deprecated)
1037
+ });
923
1038
  }
924
1039
  return services;
925
1040
  };
926
1041
 
927
- const generateCustomValueUtilities = (entities, services) => {
1042
+ const generateCustomValueServices = (entities, services) => {
928
1043
  const customValueEntity = entities.get('customValue');
929
1044
  const customValueEntities = [];
930
1045
  if (!customValueEntity) {
@@ -932,7 +1047,7 @@ const generateCustomValueUtilities = (entities, services) => {
932
1047
  return '';
933
1048
  }
934
1049
  serviceLoop: for (const service of services) {
935
- const someFunction = service.functions.find(v => v.name === 'some');
1050
+ const someFunction = service.functions.find((v) => v.name === 'some');
936
1051
  if (!someFunction) {
937
1052
  continue;
938
1053
  }
@@ -945,55 +1060,57 @@ const generateCustomValueUtilities = (entities, services) => {
945
1060
  continue serviceLoop;
946
1061
  }
947
1062
  }
948
- customValueEntities.push(service.entity);
1063
+ customValueEntities.push(service.name);
949
1064
  }
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);')}`));
1065
+ 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
1066
  };
952
1067
 
953
1068
  const generateObject = (properties) => {
954
1069
  const body = [];
955
- for (const { key, value } of properties) {
1070
+ for (const { key, value, comment } of properties) {
956
1071
  if (value === undefined) {
957
1072
  continue;
958
1073
  }
959
1074
  if (Array.isArray(value)) {
960
1075
  const str = generateObject(value);
961
1076
  if (str.length > 2) {
962
- body.push(`${key}: ${str}`);
1077
+ body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${str}`);
963
1078
  }
964
1079
  }
965
1080
  else {
966
- body.push(`${key}: ${String(value)}`);
1081
+ body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${String(value)}`);
967
1082
  }
968
1083
  }
969
1084
  return body.length ? `{\n${indent(body.join(',\n'))}\n}` : `{}`;
970
1085
  };
971
1086
 
972
1087
  const resolveInheritedEntities = (root, entities) => {
973
- const parent = root.extends ? entities.get(root.extends) : undefined;
1088
+ const parent = root.parentName ? entities.get(root.parentName) : undefined;
974
1089
  return parent ? [parent, ...resolveInheritedEntities(parent, entities)] : [];
975
1090
  };
976
- const generatePropertyDescriptors = (entity, entities, services, options) => [
977
- ...resolveInheritedEntities(entity, entities).flatMap(v => [...v.properties]),
978
- ...entity.properties
979
- ].filter(([, meta]) => {
1091
+ const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
1092
+ .filter(([, meta]) => {
980
1093
  // If we generate deprecated things we can skip the filtering
981
1094
  if (options.deprecated) {
982
1095
  return true;
983
1096
  }
984
1097
  // Check if corresponding service is deprecated and can be removed
985
- const service = services.find(v => v.entity === meta.service);
1098
+ const service = services.find((v) => v.name === meta.service);
986
1099
  return !meta.service || (service && !service.deprecated);
987
- }).map(([property, meta]) => ({
1100
+ })
1101
+ .map(([property, meta]) => ({
988
1102
  key: property,
989
1103
  value: Object.entries(meta).map(([key, value]) => ({
990
1104
  key,
991
- value: value ? generateString(value) : undefined
1105
+ value: value !== undefined ? (typeof value === 'number' ? value : generateString(value)) : undefined
992
1106
  }))
993
1107
  }));
994
- const generateEntityPropertyMap = (entities, services, options) => {
1108
+ const generateEntityProperties = (entities, aliases, services, options) => {
995
1109
  const typeName = 'WEntityProperties';
996
- const propertyMap = [...entities].map(([entity, data]) => ({
1110
+ const propertyMap = [
1111
+ ...entities.entries(),
1112
+ ...[...aliases.entries()].map(([service, type]) => [service, entities.get(camelCase(type))])
1113
+ ].map(([entity, data]) => ({
997
1114
  key: entity,
998
1115
  value: generatePropertyDescriptors(data, entities, services, options)
999
1116
  }));
@@ -1001,7 +1118,7 @@ const generateEntityPropertyMap = (entities, services, options) => {
1001
1118
  };
1002
1119
 
1003
1120
  const generateArray = (values) => {
1004
- return `[${concat(values.map(v => generateString(String(v))))}]`;
1121
+ return `[${concat(values.map((v) => generateString(String(v))))}]`;
1005
1122
  };
1006
1123
 
1007
1124
  // Only functions matching this regex are included in the generation.
@@ -1014,16 +1131,17 @@ const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
1014
1131
  */
1015
1132
  const generateGroupedServices = (services) => {
1016
1133
  const entityDescriptors = new Map();
1017
- for (const { entity, functions } of services) {
1018
- for (const { name } of functions) {
1019
- if (!FILTER_REGEX.test(name)) {
1134
+ for (const service of services) {
1135
+ for (const fn of service.functions) {
1136
+ if (!FILTER_REGEX.test(fn.name)) {
1020
1137
  continue;
1021
1138
  }
1022
- entityDescriptors.set(name, [
1023
- ...(entityDescriptors.get(name) ?? []), {
1024
- name: entity,
1139
+ entityDescriptors.set(fn.name, [
1140
+ ...(entityDescriptors.get(fn.name) ?? []),
1141
+ {
1142
+ name: service.name,
1025
1143
  required: true,
1026
- type: `${pascalCase(entity)}Service_${pascalCase(name)}`
1144
+ type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}`
1027
1145
  }
1028
1146
  ]);
1029
1147
  }
@@ -1036,123 +1154,86 @@ const generateGroupedServices = (services) => {
1036
1154
  const guard = `(service: string | undefined): service is ${service} =>\n${indent(`${constant}.includes(service as ${service});`)}`;
1037
1155
  typeGuards.push(`export const is${service} = ${guard}`);
1038
1156
  }
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
- ];
1157
+ 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]) => {
1158
+ const constant = camelCase(`wServiceWith_${name}_Names`);
1159
+ const type = pascalCase(`WServiceWith_${name}`);
1160
+ const value = generateArray(props.map((v) => v.name));
1161
+ return `export const ${constant}: ${type}[] = ${value};`;
1162
+ }), ...typeGuards);
1050
1163
  };
1051
1164
 
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') })));
1165
+ const generateMaps = (enums, entities, services, aliases, options) => {
1166
+ const enumInstances = `export const wEnums = ${generateObject([...enums.keys()].map((v) => ({ key: v, value: v })))};`;
1167
+ const entityNames = `export const wEntityNames: WEntity[] = ${generateArray([...entities.keys()])};`;
1168
+ const generatedServices = [...services.values()];
1169
+ const serviceInstances = `export const wServices = ${generateObject(generatedServices.map((v) => ({
1170
+ key: v.name,
1171
+ value: `${v.serviceFnName}()`,
1172
+ comment: v.deprecated ? '@deprecated' : undefined
1173
+ })))};`;
1174
+ const serviceFactories = `export const wServiceFactories = ${generateObject(generatedServices.map((v) => ({
1175
+ key: v.name,
1176
+ value: v.serviceFnName,
1177
+ comment: v.deprecated ? '@deprecated' : undefined
1178
+ })))};`;
1085
1179
  return {
1086
1180
  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))
1181
+ /* Enums */
1182
+ generateInterface('WEnums', [...enums.keys()].map((name) => ({ name, type: name, required: true }))), generateType('WEnum', 'keyof WEnums'), enumInstances,
1183
+ /* Entities */
1184
+ generateInterface('WEntities', [
1185
+ ...[...entities.keys()].map((name) => ({ name, type: loosePascalCase(name), required: true })),
1186
+ ...[...aliases.entries()].map(([name, type]) => ({ name, type, required: true }))
1187
+ ].sort((a, b) => (a.name > b.name ? 1 : -1))), generateType('WEntity', 'keyof WEntities'), entityNames,
1188
+ /* Services */
1189
+ serviceInstances, generateType('WServices', 'typeof wServices'), generateType('WService', 'keyof WServices'), serviceFactories, generateType('WServiceFactories', 'typeof wServiceFactories'),
1190
+ /* Service Utils */
1191
+ generateGroupedServices(generatedServices), generateCustomValueServices(entities, generatedServices),
1192
+ /* Entity Properties (Runtime Meta Infos) */
1193
+ generateEntityProperties(entities, aliases, generatedServices, options))
1113
1194
  };
1114
1195
  };
1115
1196
 
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();
1197
+ const parseReferencedEntityType = (obj) => pascalCase(obj.$ref.replace(/.*\//, ''));
1198
+ const extractServiceAliases = (doc, schemas) => {
1120
1199
  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
1200
  for (const [path, methods] of Object.entries(doc.paths)) {
1131
1201
  const parsed = parseEndpointPath(path);
1132
- if (!parsed || schemas.has(parsed.entity)) {
1202
+ if (!parsed || !methods || parsed.type !== WeclappEndpointType.ROOT || schemas.has(parsed.service)) {
1133
1203
  continue;
1134
1204
  }
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
- }
1205
+ const body = methods[OpenAPIV3.HttpMethods.GET]?.responses['200'];
1206
+ if (isResponseObject(body) && body.content?.['application/json']) {
1207
+ const responseSchema = body.content['application/json'].schema;
1208
+ if (!responseSchema || isReferenceObject(responseSchema)) {
1209
+ continue;
1210
+ }
1211
+ const resultSchema = responseSchema.properties?.result;
1212
+ if (!resultSchema) {
1213
+ continue;
1214
+ }
1215
+ if (isReferenceObject(resultSchema)) {
1216
+ aliases.set(parsed.service, parseReferencedEntityType(resultSchema));
1217
+ continue;
1218
+ }
1219
+ if (isArraySchemaObject(resultSchema)) {
1220
+ const resultItemSchema = resultSchema.items;
1221
+ if (isReferenceObject(resultItemSchema)) {
1222
+ aliases.set(parsed.service, parseReferencedEntityType(resultItemSchema));
1152
1223
  }
1153
1224
  }
1154
1225
  }
1155
1226
  }
1227
+ return aliases;
1228
+ };
1229
+ const extractSchemas = (doc) => {
1230
+ const schemas = new Map();
1231
+ for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
1232
+ if (!isReferenceObject(schema)) {
1233
+ schemas.set(name, schema);
1234
+ }
1235
+ }
1236
+ const aliases = extractServiceAliases(doc, schemas);
1156
1237
  return { schemas, aliases };
1157
1238
  };
1158
1239
 
@@ -1160,14 +1241,10 @@ const generate = (doc, options) => {
1160
1241
  const { schemas, aliases } = extractSchemas(doc);
1161
1242
  const enums = generateEnums(schemas);
1162
1243
  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));
1244
+ const entityFilterProps = generateEntityFilterProps(entities, enums);
1245
+ const services = generateServices(doc.paths, entities, aliases, options);
1246
+ const maps = generateMaps(enums, entities, services, aliases, options);
1247
+ 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
1248
  };
1172
1249
 
1173
1250
  const hash = (content, algorithm = 'sha256') => {
@@ -1183,12 +1260,13 @@ const hash = (content, algorithm = 'sha256') => {
1183
1260
 
1184
1261
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
1185
1262
  const cli = async () => {
1186
- const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
1263
+ const version = pkg.version;
1187
1264
  const { argv } = yargs(hideBin(process.argv))
1188
1265
  .scriptName('build-weclapp-sdk')
1189
1266
  .usage('Usage: $0 <source> [flags]')
1190
1267
  .version(version)
1191
1268
  .example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
1269
+ .example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
1192
1270
  .example('$0 xxx.weclapp.com --key ...', 'Generate the SDK based on the openapi file from the given weclapp instance')
1193
1271
  .help('h')
1194
1272
  .alias('v', 'version')
@@ -1228,36 +1306,47 @@ const cli = async () => {
1228
1306
  type: 'string',
1229
1307
  choices: ['browser', 'browser.rx', 'node', 'node.rx']
1230
1308
  })
1231
- .option('d', {
1232
- alias: 'deprecated',
1233
- describe: 'Include deprecated functions and services',
1309
+ .option('use-query-language', {
1310
+ describe: 'Generate the new where property for some and count queries',
1234
1311
  type: 'boolean'
1312
+ })
1313
+ .option('apiVersion', {
1314
+ describe: 'Specify the api version (only needed when not using a local file)',
1315
+ type: 'string'
1235
1316
  })
1236
1317
  .epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
1237
1318
  if (argv.fromEnv) {
1238
1319
  config();
1239
1320
  }
1240
1321
  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;
1322
+ const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, apiVersion, _: [src = WECLAPP_BACKEND_URL] } = argv;
1242
1323
  const options = {
1243
1324
  deprecated,
1244
1325
  generateUnique: argv.generateUnique ?? false,
1245
- target: argv.target ?? Target.BROWSER_PROMISES
1326
+ target: argv.target ?? Target.BROWSER_PROMISES,
1327
+ useQueryLanguage: argv.useQueryLanguage ?? false
1246
1328
  };
1247
- if (typeof src === 'number') {
1248
- return Promise.reject('Expected string as command');
1329
+ if (!src || typeof src === 'number') {
1330
+ return Promise.reject(new Error('Expected string as command'));
1249
1331
  }
1250
1332
  if (!Object.values(Target).includes(options.target)) {
1251
1333
  logger.errorLn(`Unknown target: ${options.target}. Possible values are ${Object.values(Target).join(', ')}`);
1252
- return Promise.reject();
1334
+ return Promise.reject(new Error());
1253
1335
  }
1254
1336
  if (await stat(src).catch(() => false)) {
1255
- logger.infoLn(`Source is a file.`);
1337
+ logger.infoLn(`Source is a file`);
1256
1338
  const content = JSON.parse(await readFile(src, 'utf-8'));
1257
1339
  return { cache, content, options };
1258
1340
  }
1341
+ logger.infoLn(`Source is a URL`);
1342
+ if (!key) {
1343
+ return Promise.reject(new Error('API key is missing'));
1344
+ }
1345
+ if (!apiVersion) {
1346
+ return Promise.reject(new Error('API version is missing'));
1347
+ }
1259
1348
  const url = new URL(src.startsWith('http') ? src : `https://${src}`);
1260
- url.pathname = '/webapp/api/v1/meta/openapi.json';
1349
+ url.pathname = `/webapp/api/${apiVersion}/meta/openapi.json`;
1261
1350
  if (query?.length) {
1262
1351
  for (const param of query.split(',')) {
1263
1352
  const [name, value] = param.split('=');
@@ -1265,11 +1354,11 @@ const cli = async () => {
1265
1354
  }
1266
1355
  }
1267
1356
  const content = await fetch(url.toString(), {
1268
- headers: { 'Accept': 'application/json', 'AuthenticationToken': key }
1269
- }).then(res => res.ok ? res.json() : undefined);
1357
+ headers: { Accept: 'application/json', AuthenticationToken: key }
1358
+ }).then((res) => (res.ok ? res.json() : undefined));
1270
1359
  if (!content) {
1271
1360
  logger.errorLn(`Couldn't fetch file ${url.toString()} `);
1272
- return Promise.reject();
1361
+ return Promise.reject(new Error());
1273
1362
  }
1274
1363
  else {
1275
1364
  logger.infoLn(`Use remote file: ${url.toString()}`);
@@ -1277,50 +1366,59 @@ const cli = async () => {
1277
1366
  return { cache, content, options };
1278
1367
  };
1279
1368
 
1280
- const workingDirectory = resolve(currentDirname(), './sdk');
1281
- const folders = ['docs', 'main', 'node', 'raw', 'rx', 'utils'];
1369
+ const workingDir = resolve(currentDirname(), './sdk');
1370
+ const cacheDir = resolve(currentDirname(), './.cache');
1282
1371
  void (async () => {
1283
1372
  const start = process.hrtime.bigint();
1284
- const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
1285
1373
  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);
1374
+ const workingDirPath = async (...paths) => {
1375
+ const fullPath = resolve(workingDir, ...paths);
1376
+ await mkdir(dirname(fullPath), { recursive: true });
1293
1377
  return fullPath;
1294
1378
  };
1379
+ // Resolve cache dir and key
1380
+ const cacheKey = hash([pkg.version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
1381
+ const cachedSdkDir = resolve(cacheDir, cacheKey);
1382
+ // Remove old SDK
1383
+ await rm(workingDir, { recursive: true, force: true });
1295
1384
  if (useCache) {
1296
1385
  logger.infoLn(`Cache ID: ${cacheKey}`);
1297
1386
  }
1298
- if (useCache && await stat(cacheDir).catch(() => false)) {
1299
- logger.successLn(`Cache match! (${cacheDir})`);
1387
+ if (useCache && (await stat(cachedSdkDir).catch(() => false))) {
1388
+ // Copy cached SDK to working dir
1389
+ logger.successLn(`Cache match! (${cachedSdkDir})`);
1390
+ await cp(cachedSdkDir, workingDir, { recursive: true });
1300
1391
  }
1301
1392
  else {
1302
- // Store swagger.json file
1303
- await writeFile(await tmp('openapi.json'), JSON.stringify(doc, null, 2));
1393
+ // Write openapi.json file
1394
+ await writeFile(await workingDirPath('openapi.json'), JSON.stringify(doc, null, 2));
1304
1395
  logger.infoLn(`Generate sdk (target: ${options.target})`);
1305
- // Generate SDKs
1396
+ // Generate and write SDK (index.ts)
1306
1397
  const sdk = generate(doc, options);
1307
- await writeFile(await tmp('src', `${options.target}.ts`), sdk.trim() + '\n');
1308
- // Bundle
1398
+ await writeFile(await workingDirPath('src', 'index.ts'), sdk.trim() + '\n');
1399
+ // Bundle and write SDK
1309
1400
  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)));
1401
+ await bundle(workingDir, options.target);
1402
+ // Remove index.ts (only bundle is required)
1403
+ await rm(await workingDirPath('src'), { recursive: true, force: true });
1404
+ if (useCache) {
1405
+ // Copy SDK to cache
1406
+ logger.successLn(`Caching SDK: (${cachedSdkDir})`);
1407
+ await mkdir(cachedSdkDir, { recursive: true });
1408
+ await cp(workingDir, cachedSdkDir, { recursive: true });
1409
+ }
1313
1410
  }
1314
- // Copy bundled SDK
1315
- await cp(cacheDir, workingDirectory, { recursive: true });
1316
1411
  // Print job summary
1317
1412
  const duration = (process.hrtime.bigint() - start) / 1000000n;
1318
1413
  logger.successLn(`SDK built in ${prettyMs(Number(duration))}`);
1319
1414
  logger.printSummary();
1320
- })().catch((error) => {
1415
+ })()
1416
+ .catch((error) => {
1321
1417
  logger.errorLn(`Fatal error:`);
1322
1418
  /* eslint-disable no-console */
1323
1419
  console.error(error);
1324
- }).finally(() => {
1325
- logger.errors && process.exit(1);
1420
+ })
1421
+ .finally(() => {
1422
+ if (logger.errors)
1423
+ process.exit(1);
1326
1424
  });