@weclapp/sdk 2.0.0-dev.5 → 2.0.0-dev.50

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<ArrayBuffer>) => {\n let headerStart: number | undefined = undefined;\n let commasSeen = 0;\n\n for (let i = 0; i < bytes.length; i++) {\n const byte = bytes[i];\n if (headerStart === undefined) {\n if (byte === SQUARE_BRACKET_OPEN || byte === COMMA) {\n headerStart = i + 1;\n }\n } else {\n if (byte === COMMA) {\n commasSeen++;\n }\n if (commasSeen === 2) {\n const headerArrayString = `[${DECODER.decode(bytes.subarray(headerStart, i))}]`;\n const [index, jsonLength] = JSON.parse(headerArrayString);\n if (!(typeof index === 'number') || !(typeof jsonLength === 'number')) {\n throw new Error(`unexpected header: ${headerArrayString}`);\n }\n\n const endIndex = i + 1 + jsonLength;\n if (endIndex > bytes.length) {\n // not all bytes available yet\n return undefined;\n }\n const jsonString = DECODER.decode(bytes.subarray(i + 1, endIndex));\n const data = JSON.parse(jsonString) as MultiRequestResponse;\n return {\n index,\n data,\n remainingBytes: bytes.subarray(endIndex)\n };\n }\n }\n }\n return undefined;\n};\n\nconst fetchMultiRequest = async (requests: string[]) => {\n const cfg = getGlobalConfig();\n\n if (!cfg) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const host = getHost(cfg);\n const protocol = getProtocol(cfg);\n\n return await fetch(`${protocol}//${host}/webapp/api/v2/batch/query`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(cfg.key && { AuthenticationToken: cfg.key })\n },\n body: JSON.stringify({ requests })\n });\n};\n\nconst rejectTasks = (tasks: BatchRequestTask[], error: unknown) => {\n for (const task of tasks) {\n if (!task.settled) {\n task.reject(error);\n }\n }\n};\n\nconst processStream = (\n { value: chunk, done }: ReadableStreamReadResult<Uint8Array>,\n remainingBytes: Uint8Array,\n reader: ReadableStreamDefaultReader<Uint8Array>,\n tasks: BatchRequestTask[]\n) => {\n if (done) {\n return;\n }\n if (chunk) {\n let bytes = new Uint8Array(remainingBytes.length + chunk.length);\n bytes.set(remainingBytes);\n bytes.set(chunk, remainingBytes.length);\n\n while (bytes.length) {\n const result = readNextResponse(bytes);\n if (!result) {\n break;\n }\n const task = tasks[result.index];\n if (result.data.status >= 100 && result.data.status < 400) {\n task.resolve({\n ...result.data.body\n });\n } else {\n task.reject({\n ...result.data.body\n });\n }\n task.settled = true;\n bytes = result.remainingBytes;\n }\n reader\n .read()\n .then((readResult) => processStream(readResult, bytes, reader, tasks))\n .catch((error) => rejectTasks(tasks, error));\n }\n};\n\nconst batch = async (tasks: BatchRequestTask[]) => {\n try {\n const requests = tasks.map(({ uri }) => uri);\n const resp = await fetchMultiRequest(requests);\n const reader = resp.body?.getReader();\n\n if (!reader) {\n throw new Error('Stream reader is undefined');\n }\n reader\n .read()\n .then((readResult) => processStream(readResult, new Uint8Array(0), reader, tasks))\n .catch((error) => rejectTasks(tasks, error));\n } catch (e) {\n rejectTasks(tasks, e);\n throw e;\n }\n};\n\nconst addTask = (task: RequestTask) => {\n tasksSet.add(task);\n\n if (!microtaskQueued) {\n queueMicrotask(() => {\n microtaskQueued = false;\n if (tasksSet.size > 0) {\n const batchTasks = Array.from(tasksSet).map((task) => ({ ...task, settled: false }));\n void batch(batchTasks);\n tasksSet.clear();\n }\n });\n microtaskQueued = true;\n }\n};\n\nconst addRequest = (uri: string) => new Promise((resolve, reject) => addTask({ uri, resolve, reject }));\n";
138
+
139
+ var queriesWithFilter = "export type EqualityOperator = 'EQ' | 'NE';\n\nexport type ComparisonOperator =\n | 'LT'\n | 'GT'\n | 'LE'\n | 'GE'\n | 'LIKE'\n | 'ILIKE'\n | 'NOT_LIKE'\n | 'NOT_ILIKE'\n | 'IEQ'\n | 'NOT_IEQ';\n\nexport type ArrayOperator = 'IN' | 'NOT_IN';\n\nexport type Operator = EqualityOperator | ComparisonOperator | ArrayOperator;\n\nexport type MapOperators<T> = { [K in EqualityOperator]?: T | null } & { [K in ComparisonOperator]?: T } & {\n [K in ArrayOperator]?: T[];\n};\n\nexport type QueryFilter<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? U extends Record<any, any>\n ? QueryFilter<U>\n : MapOperators<U>\n : T[P] extends Record<any, any> | undefined\n ? QueryFilter<T[P]>\n : MapOperators<T[P]>;\n};\n\nexport type CountQuery<F> = {\n filter?: QueryFilter<F>;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\n};\n\nexport type SomeQuery<E, F, I, P> = {\n serializeNulls?: boolean;\n include?: QuerySelect<I>;\n properties?: P;\n filter?: QueryFilter<F> & CustomAttributeFilter;\n select?: QuerySelect<E>;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\n sort?: Sort<E>[];\n pagination?: Pagination;\n};\n\nconst equality: string[] = ['EQ', 'NE', 'IEQ', 'NOT_IEQ'];\n\nconst simple: string[] = [...equality, 'LT', 'GT', 'LE', 'GE', 'LIKE', 'NOT_LIKE', 'ILIKE', 'NOT_ILIKE'];\n\nconst array: string[] = ['IN', 'NOT_IN'];\n\nconst filterMap: Record<Operator, string> = {\n EQ: 'eq',\n NE: 'ne',\n LT: 'lt',\n GT: 'gt',\n LE: 'le',\n GE: 'ge',\n LIKE: 'like',\n NOT_LIKE: 'notlike',\n ILIKE: 'ilike',\n NOT_ILIKE: 'notilike',\n IN: 'in',\n NOT_IN: 'notin',\n IEQ: 'ieq',\n NOT_IEQ: 'notieq'\n};\n\nconst flattenCustomAttributes = (obj: CustomAttributeFilter = {}): [string, string][] => {\n const entries: [string, string][] = [];\n\n for (const [id, filter] of Object.entries(obj)) {\n const key = `customAttribute${id}`;\n\n if (typeof filter === 'object') {\n for (const [prop, value] of Object.entries(filter)) {\n entries.push([`${key}.${prop}-eq`, String(value)]);\n }\n } else if (filter !== undefined) {\n entries.push([`${key}-eq`, String(filter)]);\n }\n }\n\n return entries;\n};\n\nconst flatten = (obj: QueryFilter<any> = {}): [string, string][] => {\n const entries: [string, string][] = [];\n\n for (const [prop, propValue] of Object.entries(obj)) {\n for (const [filter, value] of Object.entries(propValue as object)) {\n if (value === undefined) continue;\n\n if (simple.includes(filter) || array.includes(filter)) {\n if (value === null && equality.includes(filter)) {\n entries.push([`${prop}-${filter === 'EQ' ? 'null' : 'notnull'}`, '']);\n } else {\n entries.push([`${prop}-${filterMap[filter as Operator]}`, value]);\n }\n } else {\n entries.push(\n ...(flatten(propValue as QueryFilter<any>).map((v) => [`${prop}.${v[0]}`, v[1]]) as [string, string][])\n );\n break;\n }\n }\n }\n\n return entries;\n};\n\nconst flattenFilter = (obj: QueryFilter<any> = {}): Record<string, string> => {\n const filter: [string, any][] = [],\n customAttributes: [string, any][] = [];\n\n Object.entries(obj).forEach((value) => {\n (value[0].match(/^\\d+$/) ? customAttributes : filter).push(value);\n });\n\n return Object.fromEntries([\n ...flatten(Object.fromEntries(filter)),\n ...flattenCustomAttributes(Object.fromEntries(customAttributes) as CustomAttributeFilter)\n ]);\n};\n\nconst flattenOrFilter = (obj: QueryFilter<any>[] = []): Record<string, string> => {\n const entries: [string, any][] = [];\n\n for (let i = 0; i < obj.length; i++) {\n entries.push(...(flatten(obj[i]).map((v) => [`or${i || ''}-${v[0]}`, v[1]]) as [string, string][]));\n }\n\n return Object.fromEntries(entries);\n};\n\nconst _count = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: CountQuery<any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n{\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n ...flattenFilter(query?.filter),\n ...flattenOrFilter(query?.or),\n ...query?.params\n }\n\n return wrapResponse(() =>\n raw(cfg, endpoint, {\n method: usePost ? 'POST' : 'GET',\n unwrap: true,\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions)\n )\n };\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n{\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n serializeNulls: query?.serializeNulls,\n additionalProperties: 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 MapOperators<T> =\n | ({ [K in ComparisonOperator]?: T } & { [K in ArrayOperator]?: T[] } & {\n [K in NullOperator]?: never } & {\n [K in ModifierFunction]?: boolean\n })\n | ({ [K in ComparisonOperator]?: T } & { [K in ArrayOperator]?: T[] } & {\n [K in NullOperator]?: boolean } & {\n [K in ModifierFunction]?: never\n });\n\nexport type SingleFilterExpr<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? U extends Record<any, any>\n ? SingleFilterExpr<U> | { NOT?: SingleFilterExpr<U> }\n : MapOperators<U>\n : T[P] extends Record<any, any> | undefined\n ? SingleFilterExpr<T[P]> | { NOT?: SingleFilterExpr<T[P]> }\n : MapOperators<T[P]>;\n};\n\nexport type QueryFilter<T> = SingleFilterExpr<T> & {\n OR?: QueryFilter<T>[];\n AND?: QueryFilter<T>[];\n NOT?: QueryFilter<T>;\n};\n\nexport type CountQuery<F> = {\n where?: QueryFilter<F>;\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 if(value.length === 0) {\n entries.push('1 = 0')\n } else {\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 }\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\n if (res.ok) {\n data =\n payload.forceBlob || !res.headers?.get('content-type')?.includes('application/json')\n ? await res.blob()\n : await res.json();\n } else {\n data = res.headers?.get('content-type')?.includes('application/json')\n ? await res.json()\n : res;\n }\n\n // Check if response was successful\n if (!res.ok) {\n return Promise.reject(data);\n }\n }\n\n return payload.unwrap ? data.result : data;\n};\n\nconst _remove = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n { dryRun = false }: RemoveQuery = {},\n requestOptions?: RequestOptions\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'DELETE',\n query: { dryRun }\n }, requestOptions).then(() => undefined)\n );\n\nconst _create = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, data: any, requestOptions?: RequestOptions) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'POST',\n body: data\n }, requestOptions)\n );\n\nconst _update = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n data: any,\n { ignoreMissingProperties, dryRun = false }: UpdateQuery = {},\n requestOptions?: RequestOptions\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'PUT',\n body: data,\n query: {\n ignoreMissingProperties:\n ignoreMissingProperties ?? cfg?.ignoreMissingProperties ?? globalConfig?.ignoreMissingProperties,\n dryRun\n }\n }, requestOptions)\n );\n\nconst _generic = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n method: RequestPayloadMethod,\n endpoint: string,\n payload?: GenericQuery<any, any>,\n forceBlob?: boolean,\n requestOptions?: RequestOptions\n) =>\n{\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n return wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: usePost ? 'POST' : method,\n forceBlob,\n ...(usePost && method === 'GET' ?\n { body: { ...payload?.params } }\n :\n { body: payload?.body, query: payload?.params } )\n }, requestOptions))\n};\n";
144
+
145
+ var unique = "const _unique = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, query?: UniqueQuery, requestOptions?: RequestOptions) =>\n wrapResponse(() => raw({ ...cfg, multiRequest: false }, endpoint, { query }, requestOptions));\n";
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)
@@ -242,22 +246,25 @@ const createArrayType = (value) => ({
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
- toString: (propagateOptionalProperties = false) => {
259
+ toString: (propertyPropagationOption = 'ignore') => {
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
+ propertyPropagationOption === 'force' ||
267
+ (value.type === 'object' && !value.isFullyOptional() && propertyPropagationOption === 'propagate');
261
268
  return `${name + (isRequired ? '' : '?')}: ${value.toString()};`;
262
269
  });
263
270
  return properties.length ? `{\n${indent(properties.join('\n'))}\n}` : '{}';
@@ -266,7 +273,7 @@ const createObjectType = (value, required = []) => ({
266
273
  const getRefName = (obj) => {
267
274
  return obj.$ref.replace(/.*\//, '');
268
275
  };
269
- const convertToTypeScriptType = (schema, property) => {
276
+ const convertToTypeScriptType = (schema) => {
270
277
  if (isReferenceObject(schema)) {
271
278
  return createReferenceType(getRefName(schema));
272
279
  }
@@ -277,7 +284,7 @@ const convertToTypeScriptType = (schema, property) => {
277
284
  return createRawType('number');
278
285
  case 'string':
279
286
  if (schema.enum) {
280
- return property ? createReferenceType(property) : createTupleType(schema.enum);
287
+ return createTupleType(schema.enum);
281
288
  }
282
289
  else {
283
290
  return schema.format === 'binary' ? createRawType('binary') : createRawType('string');
@@ -286,11 +293,10 @@ const convertToTypeScriptType = (schema, property) => {
286
293
  return createRawType('boolean');
287
294
  case 'object': {
288
295
  const { properties = {}, required = [] } = schema;
289
- return createObjectType(Object.fromEntries(Object.entries(properties)
290
- .map(v => [v[0], convertToTypeScriptType(v[1])])), required);
296
+ return createObjectType(Object.fromEntries(Object.entries(properties).map((v) => [v[0], convertToTypeScriptType(v[1])])), required);
291
297
  }
292
298
  case 'array':
293
- return createArrayType(convertToTypeScriptType(schema.items, property));
299
+ return createArrayType(convertToTypeScriptType(schema.items));
294
300
  default:
295
301
  return createRawType('unknown');
296
302
  }
@@ -308,14 +314,19 @@ const setEntityEnumProperty = (enums, prop, meta) => {
308
314
  }
309
315
  };
310
316
  const extractPropertyMetaData = (enums, meta, prop) => {
311
- const result = { service: meta.service, entity: meta.entity };
317
+ const result = {
318
+ service: meta.service,
319
+ entity: meta.entity
320
+ };
312
321
  if (isReferenceObject(prop)) {
313
322
  setEntityEnumProperty(enums, prop, result);
314
323
  result.type = 'reference';
315
324
  return result;
316
325
  }
317
- result.format = prop.format;
318
326
  result.type = prop.type;
327
+ result.format = prop.format;
328
+ result.maxLength = prop.maxLength;
329
+ result.pattern = prop.pattern;
319
330
  if (isArraySchemaObject(prop)) {
320
331
  if (isReferenceObject(prop.items)) {
321
332
  setEntityEnumProperty(enums, prop.items, result);
@@ -335,12 +346,12 @@ const generateType = (name, value) => {
335
346
  return `export type ${name} = ${value.trim()};`;
336
347
  };
337
348
 
338
- const arrayify = (v) => Array.isArray(v) ? v : [v];
349
+ const arrayify = (v) => (Array.isArray(v) ? v : [v]);
339
350
 
340
351
  const generateInterfaceProperties = (entries) => {
341
352
  const properties = entries
342
- .filter(v => v.type !== undefined)
343
- .filter((value, index, array) => array.findIndex(v => v.name === value.name) === index)
353
+ .filter((v) => v.type !== undefined)
354
+ .filter((value, index, array) => array.findIndex((v) => v.name === value.name) === index)
344
355
  .map(({ name, type, required, readonly, comment }) => {
345
356
  const cmd = comment ? `${generateInlineComment(comment)}\n` : '';
346
357
  const req = required ? '' : '?';
@@ -350,7 +361,7 @@ const generateInterfaceProperties = (entries) => {
350
361
  .join('\n');
351
362
  return properties.length ? `{\n${indent(properties)}\n}` : `{}`;
352
363
  };
353
- const generateInterfaceFromObject = (name, obj, propagateOptionalProperties) => `export interface ${name} ${obj.toString(propagateOptionalProperties)}`;
364
+ const generateInterfaceFromObject = (name, obj, propertyPropagationOption) => `export interface ${name} ${obj.toString(propertyPropagationOption)}`;
354
365
  const generateInterface = (name, entries, extend) => {
355
366
  const signature = `${name} ${extend ? `extends ${arrayify(extend).join(', ')}` : ''}`.trim();
356
367
  const body = generateInterfaceProperties(entries);
@@ -359,9 +370,20 @@ const generateInterface = (name, entries, extend) => {
359
370
  const generateInterfaceType = (name, entries, extend) => {
360
371
  const body = generateInterfaceProperties(entries);
361
372
  const bases = extend ? arrayify(extend).join(' & ') : undefined;
362
- return generateType(name, `${bases ? `${bases} & ` : ''}${body}`);
373
+ let typeDefinition = '';
374
+ if (bases) {
375
+ typeDefinition = bases;
376
+ }
377
+ else {
378
+ typeDefinition = body;
379
+ }
380
+ if (bases && body !== '{}') {
381
+ typeDefinition += ` & ${body}`;
382
+ }
383
+ return generateType(name, typeDefinition);
363
384
  };
364
385
 
386
+ const FILTER_PROPS_SUFFIX = 'Filter_Props';
365
387
  const generateEntities = (schemas, enums) => {
366
388
  const entities = new Map();
367
389
  for (const [schemaName, schema] of schemas) {
@@ -369,73 +391,109 @@ const generateEntities = (schemas, enums) => {
369
391
  if (isEnumSchemaObject(schema)) {
370
392
  continue;
371
393
  }
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 = [];
394
+ const entityInterfaceName = loosePascalCase(schemaName);
395
+ let parentEntityInterfaceName = undefined;
396
+ const entityInterfaceProperties = [];
397
+ const filterableInterfaceProperties = [];
379
398
  const properties = new Map();
380
- // The parent entity
381
- let extend = undefined;
382
- const processProperties = (props = {}) => {
399
+ const processProperties = (props = {}, isXweclappFilterProp) => {
383
400
  for (const [name, property] of Object.entries(props)) {
384
401
  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 });
402
+ const type = convertToTypeScriptType(property).toString();
403
+ const comment = isNonArraySchemaObject(property)
404
+ ? property.deprecated
405
+ ? '@deprecated will be removed.'
406
+ : property.format
407
+ ? `format: ${property.format}`
408
+ : undefined
409
+ : undefined;
410
+ if (meta.filterable !== false) {
411
+ filterableInterfaceProperties.push({ name, type });
389
412
  }
390
- if (meta.service) {
391
- referenceMappingsInterface.push({ name, type: generateString(meta.service), required: true });
413
+ if (!isXweclappFilterProp) {
414
+ entityInterfaceProperties.push({
415
+ name,
416
+ type,
417
+ comment,
418
+ required: meta.required,
419
+ filterable: meta.filterable ?? true,
420
+ readonly: !isReferenceObject(property) && property.readOnly
421
+ });
422
+ properties.set(name, extractPropertyMetaData(enums, meta, property));
392
423
  }
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
424
  }
405
425
  };
406
426
  if (schema.allOf?.length) {
407
427
  for (const item of schema.allOf) {
408
428
  if (isReferenceObject(item)) {
409
- extend = convertToTypeScriptType(item).toString();
429
+ parentEntityInterfaceName = convertToTypeScriptType(item).toString();
410
430
  }
411
431
  else if (isObjectSchemaObject(item)) {
412
432
  processProperties(item.properties);
433
+ if (isFilterPropertySchemaObject(item)) {
434
+ processProperties(item['x-weclapp-filterProperties'], true);
435
+ }
436
+ if (isFilterPathsSchemaObject(item)) {
437
+ const fPaths = item['x-weclapp-filterPaths'];
438
+ for (const path in fPaths) {
439
+ if (!path.includes('.')) {
440
+ filterableInterfaceProperties.push({ name: path, type: fPaths[path] });
441
+ }
442
+ }
443
+ }
413
444
  }
414
445
  }
415
446
  }
416
447
  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
448
  entities.set(schemaName, {
419
- extends: extend ? camelCase(extend) : extend,
449
+ name: schemaName,
420
450
  properties,
421
- source
451
+ filterableInterfaceProperties: filterableInterfaceProperties.sort((propA, propB) => propA.name.localeCompare(propB.name)),
452
+ parentName: parentEntityInterfaceName ? camelCase(parentEntityInterfaceName) : undefined,
453
+ source: generateStatements(generateInterface(entityInterfaceName, entityInterfaceProperties, parentEntityInterfaceName))
422
454
  });
423
455
  }
424
456
  return entities;
425
457
  };
458
+ const generateEntityFilterProps = (entities, enums) => {
459
+ const entityFilterProps = new Map();
460
+ const transformFilterProps = (props) => {
461
+ return props.map((prop) => {
462
+ if (!prop.type ||
463
+ enums.has(prop.type) ||
464
+ prop.type === 'string' ||
465
+ prop.type === 'number' ||
466
+ prop.type === 'boolean' ||
467
+ prop.type === '{}' ||
468
+ prop.type.endsWith('[]') ||
469
+ prop.type.includes("'")) {
470
+ return prop;
471
+ }
472
+ return { ...prop, type: `${pascalCase(prop.type)}_${FILTER_PROPS_SUFFIX}` };
473
+ });
474
+ };
475
+ entities.forEach((entity, name) => {
476
+ const entityFilterName = `${pascalCase(name)}_${FILTER_PROPS_SUFFIX}`;
477
+ const parentName = entity.parentName ? `${pascalCase(entity.parentName)}_${FILTER_PROPS_SUFFIX}` : undefined;
478
+ const filterableInterfaceProperties = transformFilterProps(entity.filterableInterfaceProperties);
479
+ entityFilterProps.set(entityFilterName, {
480
+ name: entityFilterName,
481
+ parentName,
482
+ source: generateStatements(generateInterface(entityFilterName, filterableInterfaceProperties, parentName))
483
+ });
484
+ });
485
+ return entityFilterProps;
486
+ };
426
487
 
427
488
  /**
428
489
  * Pluralizes a word, most of the time correct.
429
490
  * @param s String to pluralize.
430
491
  */
431
492
  const pluralize = (s) => {
432
- return s.endsWith('s') ? s :
433
- s.endsWith('y') ? `${s.slice(0, -1)}ies` :
434
- `${s}s`;
493
+ return s.endsWith('s') ? s : s.endsWith('y') ? `${s.slice(0, -1)}ies` : `${s}s`;
435
494
  };
436
495
 
437
- /* eslint-disable no-console */
438
- const logger = new class {
496
+ const logger = new (class {
439
497
  active = true;
440
498
  warnings = 0;
441
499
  errors = 0;
@@ -483,21 +541,24 @@ const logger = new class {
483
541
  printSummary() {
484
542
  const format = (v, name, fail, ok) => {
485
543
  const color = v ? fail : ok;
486
- return v === 0 ? `${color('zero')} ${pluralize(name)}` :
487
- v === 1 ? `${color('one')} ${name}` : `${color(v)} ${pluralize(name)}`;
544
+ return v === 0
545
+ ? `${color('zero')} ${pluralize(name)}`
546
+ : v === 1
547
+ ? `${color('one')} ${name}`
548
+ : `${color(v)} ${pluralize(name)}`;
488
549
  };
489
550
  const warnings = format(this.warnings, 'warning', chalk.yellowBright, chalk.greenBright);
490
551
  const errors = format(this.errors, 'error', chalk.redBright, chalk.greenBright);
491
552
  const info = `Finished with ${warnings} and ${errors}.`;
492
553
  this[this.errors ? 'errorLn' : this.warnings ? 'warnLn' : 'successLn'](info);
493
554
  }
494
- };
555
+ })();
495
556
 
496
557
  /**
497
- * ROOT => /article
498
- * COUNT => /article/count
499
- * ENTITY => /article/{id}
500
- * SPECIAL_ROOT => /article/generateImage
558
+ * ROOT => /article
559
+ * COUNT => /article/count
560
+ * ENTITY => /article/{id}
561
+ * SPECIAL_ROOT => /article/generateImage
501
562
  * SPECIAL_ENTITY => /article/id/{id}/generateImag
502
563
  */
503
564
  var WeclappEndpointType;
@@ -509,23 +570,33 @@ var WeclappEndpointType;
509
570
  WeclappEndpointType["GENERIC_ENTITY"] = "GENERIC_ENTITY";
510
571
  })(WeclappEndpointType || (WeclappEndpointType = {}));
511
572
  const parseEndpointPath = (path) => {
512
- const [, entity, ...rest] = path.split('/');
513
- if (!entity) {
573
+ const [, service, ...rest] = path.split('/');
574
+ if (!service) {
514
575
  return undefined;
515
576
  }
516
577
  if (!rest.length) {
517
- return { path, entity, type: WeclappEndpointType.ROOT };
578
+ return { path, service, type: WeclappEndpointType.ROOT };
518
579
  }
519
580
  else if (rest[0] === 'count') {
520
- return { path, entity, type: WeclappEndpointType.COUNT };
581
+ return { path, service, type: WeclappEndpointType.COUNT };
521
582
  }
522
583
  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 };
584
+ return rest.length === 2
585
+ ? { path, service, type: WeclappEndpointType.ENTITY }
586
+ : {
587
+ path,
588
+ service,
589
+ method: rest[2],
590
+ type: WeclappEndpointType.GENERIC_ENTITY
591
+ };
526
592
  }
527
593
  else if (rest.length === 1) {
528
- return { path, entity, method: rest[1], type: WeclappEndpointType.GENERIC_ROOT };
594
+ return {
595
+ path,
596
+ service,
597
+ method: rest[1],
598
+ type: WeclappEndpointType.GENERIC_ROOT
599
+ };
529
600
  }
530
601
  return undefined;
531
602
  };
@@ -547,48 +618,51 @@ const convertParametersToSchema = (parameters = []) => {
547
618
  if (isParameterObject(param) && param.in === 'query') {
548
619
  if (param.schema) {
549
620
  properties.push([param.name, param.schema]);
550
- param.required && required.push(param.name);
621
+ if (param.required)
622
+ required.push(param.name);
551
623
  }
552
624
  }
553
625
  }
554
626
  return {
555
- type: 'object', required,
627
+ type: 'object',
628
+ required,
556
629
  properties: Object.fromEntries(properties)
557
630
  };
558
631
  };
559
632
 
560
- const functionName$5 = 'count';
561
633
  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)
570
- });
571
- const functionSource = generateArrowFunction({
572
- name: functionName$5,
573
- signature: interfaceName,
574
- returns: `_${functionName$5}(cfg, ${generateString(endpoint.path)}, query)`,
575
- params: ['query']
634
+ const functionName = 'count';
635
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
636
+ const entity = aliases.get(endpoint.service) ?? pascalCase(endpoint.service);
637
+ const parametersTypeName = `${functionTypeName}_Parameters`;
638
+ const parametersType = createObjectType({
639
+ params: convertToTypeScriptType(convertParametersToSchema(path.parameters))
576
640
  });
577
- const interfaceSource = generateArrowFunctionType({
578
- type: interfaceName,
579
- params: [`query${parameters.isFullyOptional() ? '?' : ''}: CountQuery<${entityFilter}> & ${entityParameters}`],
641
+ const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
642
+ const filterTypeName = `${functionTypeName}_Filter`;
643
+ const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${entity}_${FILTER_PROPS_SUFFIX}`]);
644
+ const functionTypeSource = generateArrowFunctionType({
645
+ type: functionTypeName,
646
+ params: [
647
+ `query${parametersType.isFullyOptional() ? '?' : ''}: CountQuery<${filterTypeName}>${path.parameters?.length ? ' & ' + parametersTypeName : ''}`,
648
+ 'requestOptions?: RequestOptions'
649
+ ],
580
650
  returns: `${resolveResponseType(target)}<number>`
581
651
  });
652
+ const functionSource = generateArrowFunction({
653
+ name: functionName,
654
+ signature: functionTypeName,
655
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
656
+ params: ['query', 'requestOptions?: RequestOptions']
657
+ });
582
658
  return {
583
659
  entity,
584
- name: functionName$5,
585
- type: { name: interfaceName, source: interfaceSource },
586
- func: { name: functionName$5, source: functionSource },
660
+ name: functionName,
661
+ type: { name: functionTypeName, source: functionTypeSource },
662
+ func: { name: functionName, source: functionSource },
587
663
  interfaces: [
588
- {
589
- name: entityParameters,
590
- source: generateInterfaceFromObject(entityParameters, parameters, true)
591
- }
664
+ ...(path.parameters?.length ? [{ name: parametersTypeName, source: parametersTypeSource }] : []),
665
+ { name: filterTypeName, source: filterTypeSource }
592
666
  ]
593
667
  };
594
668
  };
@@ -603,46 +677,40 @@ const generateBodyType = (body) => {
603
677
  types.push(convertToTypeScriptType(schema));
604
678
  }
605
679
  }
606
- return types.length ? createTupleType(types) : undefined;
680
+ return types.length ? (types.length === 1 ? types[0] : createTupleType(types)) : undefined;
607
681
  };
608
682
 
609
683
  const generateRequestBodyType = ({ requestBody }) => {
610
684
  return generateBodyType(requestBody) ?? createRawType('unknown');
611
685
  };
612
686
 
613
- const resolveBodyType = ({ responses }) => Object.entries(responses)
614
- .filter(v => v[0].startsWith('2'))[0]?.[1];
687
+ const resolveBodyType = ({ responses }) => Object.entries(responses).filter((v) => v[0].startsWith('2'))[0]?.[1];
615
688
  const generateResponseBodyType = (object) => generateBodyType(resolveBodyType(object)) ?? createRawType('void');
616
689
 
617
- const functionName$4 = 'create';
618
690
  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()}>`],
691
+ const functionName = 'create';
692
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
693
+ const functionTypeSource = generateArrowFunctionType({
694
+ type: functionTypeName,
695
+ params: [`data: DeepPartial<${generateRequestBodyType(path).toString()}>`, 'requestOptions?: RequestOptions'],
630
696
  returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
631
697
  });
698
+ const functionSource = generateArrowFunction({
699
+ name: functionName,
700
+ signature: functionTypeName,
701
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, data, requestOptions)`,
702
+ params: ['data', 'requestOptions?: RequestOptions']
703
+ });
632
704
  return {
633
- entity,
634
- name: functionName$4,
635
- type: { name: interfaceName, source: interfaceSource },
636
- func: { name: functionName$4, source: functionSource }
705
+ entity: pascalCase(endpoint.service),
706
+ name: functionName,
707
+ type: { name: functionTypeName, source: functionTypeSource },
708
+ func: { name: functionName, source: functionSource }
637
709
  };
638
710
  };
639
711
 
640
712
  const generateGenericFunctionName = (path, suffix = '', prefix = '') => {
641
- return camelCase(`${prefix}_` +
642
- path
643
- .replace(/.*\//, '')
644
- .replace(/\W+/, '_')
645
- .replace(/[_]+/, '_') + `_${suffix}`);
713
+ return camelCase(`${prefix}_` + path.replace(/.*\//, '').replace(/\W+/, '_').replace(/[_]+/, '_') + `_${suffix}`);
646
714
  };
647
715
 
648
716
  const insertPathPlaceholder = (path, record) => {
@@ -650,76 +718,79 @@ const insertPathPlaceholder = (path, record) => {
650
718
  };
651
719
 
652
720
  const wrapBody = (type, target) => {
653
- return type.toString() === 'binary' ?
654
- createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') :
655
- type; // node-fetch returns a Blob as well
721
+ return type.toString() === 'binary' ? createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') : type; // node-fetch returns a Blob as well
656
722
  };
657
723
  const generateGenericEndpoint = (suffix) => ({ target, method, path, endpoint }) => {
658
724
  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`;
725
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
726
+ const entityQuery = `${functionTypeName}_Query`;
662
727
  const hasId = endpoint.path.includes('{id}');
663
728
  const params = createObjectType({
664
729
  params: convertToTypeScriptType(convertParametersToSchema(path.parameters)),
665
730
  body: method === 'get' ? undefined : wrapBody(generateRequestBodyType(path), target)
666
731
  });
667
732
  const responseBody = generateResponseBodyType(path);
668
- const forceBlobResponse = String(responseBody.toString() === 'binary');
733
+ const functionTypeSource = generateArrowFunctionType({
734
+ type: functionTypeName,
735
+ params: [
736
+ ...(hasId ? ['id: string'] : []),
737
+ `query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`,
738
+ 'requestOptions?: RequestOptions'
739
+ ],
740
+ returns: `${resolveResponseType(target)}<${wrapBody(responseBody, target).toString('force')}>`
741
+ });
669
742
  const functionSource = generateArrowFunction({
670
743
  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()}>`
744
+ signature: functionTypeName,
745
+ params: hasId ? ['id', 'query', 'requestOptions?: RequestOptions'] : ['query', 'requestOptions?: RequestOptions'],
746
+ returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${String(responseBody.toString() === 'binary')}, requestOptions)`
679
747
  });
680
748
  return {
681
- entity,
749
+ entity: pascalCase(endpoint.service),
682
750
  name: functionName,
683
- type: { name: interfaceName, source: interfaceSource },
751
+ type: { name: functionTypeName, source: functionTypeSource },
684
752
  func: { name: functionName, source: functionSource },
685
753
  interfaces: [
686
754
  {
687
755
  name: entityQuery,
688
- source: generateInterfaceFromObject(entityQuery, params, true)
756
+ source: generateInterfaceFromObject(entityQuery, params, 'propagate')
689
757
  }
690
758
  ]
691
759
  };
692
760
  };
693
761
 
694
- const functionName$3 = 'remove';
695
762
  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'],
763
+ const functionName = 'remove';
764
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
765
+ const functionTypeSource = generateArrowFunctionType({
766
+ type: functionTypeName,
767
+ params: ['id: string', 'options?: RemoveQuery', 'requestOptions?: RequestOptions'],
707
768
  returns: `${resolveResponseType(target)}<void>`
708
769
  });
770
+ const functionSource = generateArrowFunction({
771
+ name: functionName,
772
+ signature: functionTypeName,
773
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options, requestOptions)`,
774
+ params: ['id', 'options?: RemoveQuery', 'requestOptions?: RequestOptions']
775
+ });
709
776
  return {
710
- entity,
711
- name: functionName$3,
712
- type: { name: interfaceName, source: interfaceSource },
713
- func: { name: functionName$3, source: functionSource }
777
+ entity: pascalCase(endpoint.service),
778
+ name: functionName,
779
+ type: { name: functionTypeName, source: functionTypeSource },
780
+ func: { name: functionName, source: functionSource }
714
781
  };
715
782
  };
716
783
 
717
- const functionName$2 = 'some';
718
784
  const excludedParameters = [
719
- 'page', 'pageSize', 'sort',
720
- 'serializeNulls', 'properties', 'includeReferencedEntities'
785
+ 'page',
786
+ 'pageSize',
787
+ 'sort',
788
+ 'serializeNulls',
789
+ 'properties',
790
+ 'includeReferencedEntities',
791
+ 'additionalProperties'
721
792
  ];
722
- const resolveAdditionalProperties = (path) => {
793
+ const resolveAdditionalPropertiesSchema = (path) => {
723
794
  const body = resolveBodyType(path);
724
795
  if (isResponseObject(body)) {
725
796
  const schema = body?.content?.['application/json']?.schema;
@@ -732,115 +803,166 @@ const resolveAdditionalProperties = (path) => {
732
803
  }
733
804
  return undefined;
734
805
  };
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)
806
+ const resolveReferences = (entity, entities) => {
807
+ const references = [];
808
+ const generatedEntity = entities.get(entity);
809
+ if (generatedEntity) {
810
+ for (const [property, propertyMetaData] of generatedEntity.properties) {
811
+ if (propertyMetaData.service) {
812
+ references.push({
813
+ name: property,
814
+ type: generateString(propertyMetaData.service),
815
+ required: true
816
+ });
817
+ }
818
+ }
819
+ if (generatedEntity.parentName) {
820
+ references.push(...resolveReferences(generatedEntity.parentName, entities));
821
+ }
822
+ }
823
+ return references;
824
+ };
825
+ const resolveReferencedEntities = (entity, entities) => {
826
+ const referencedEntities = [];
827
+ const generatedEntity = entities.get(entity);
828
+ if (generatedEntity) {
829
+ for (const [, propertyMetaData] of generatedEntity.properties) {
830
+ if (propertyMetaData.entity && propertyMetaData.service) {
831
+ referencedEntities.push({
832
+ name: propertyMetaData.service,
833
+ type: `${pascalCase(propertyMetaData.entity)}[]`,
834
+ required: true
835
+ });
836
+ }
837
+ }
838
+ if (generatedEntity.parentName) {
839
+ referencedEntities.push(...resolveReferencedEntities(generatedEntity.parentName, entities));
840
+ }
841
+ }
842
+ return referencedEntities;
843
+ };
844
+ const generateSomeEndpoint = ({ endpoint, target, path, entities, aliases }) => {
845
+ const functionName = 'some';
846
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
847
+ const entity = aliases.get(endpoint.service) ?? pascalCase(endpoint.service);
848
+ const parametersTypeName = `${functionTypeName}_Parameters`;
849
+ const parameters = path.parameters?.filter((v) => (isParameterObject(v) ? !excludedParameters.includes(v.name) : false)) ?? [];
850
+ const parametersType = createObjectType({
851
+ params: convertToTypeScriptType(convertParametersToSchema(parameters))
753
852
  });
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}>>`
853
+ const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
854
+ const filterTypeName = `${functionTypeName}_Filter`;
855
+ const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${entity}_${FILTER_PROPS_SUFFIX}`]);
856
+ const referencesTypeName = `${functionTypeName}_References`;
857
+ const referencesTypeSource = generateInterfaceType(referencesTypeName, resolveReferences(endpoint.service, entities));
858
+ const additionalPropertyTypeName = `${functionTypeName}_AdditionalProperty`;
859
+ const additionalPropertyTypeSource = generateType(additionalPropertyTypeName, 'string');
860
+ const queryTypeName = `${functionTypeName}_Query`;
861
+ const queryTypeSource = generateType(queryTypeName, `SomeQuery<${entity}, ${filterTypeName}, ${referencesTypeName}, ${additionalPropertyTypeName}> & ${parametersTypeName}`);
862
+ const referencedEntitiesTypeName = `${functionTypeName}_ReferencedEntities`;
863
+ const referencedEntitiesTypeSource = generateInterfaceType(referencedEntitiesTypeName, resolveReferencedEntities(endpoint.service, entities));
864
+ const additionalPropertiesTypeName = `${functionTypeName}_AdditionalProperties`;
865
+ const additionalPropertiesSchema = resolveAdditionalPropertiesSchema(path);
866
+ const additionalPropertiesTypeSource = generateType(additionalPropertiesTypeName, additionalPropertiesSchema ? convertToTypeScriptType(additionalPropertiesSchema).toString() : '{}');
867
+ const functionTypeSource = generateArrowFunctionType({
868
+ type: functionTypeName,
869
+ params: [`query${parametersType.isFullyOptional() ? '?' : ''}: ${queryTypeName}, requestOptions?: RequestOptions`],
870
+ returns: `${resolveResponseType(target)}<SomeQueryReturn<${entity}, ${referencedEntitiesTypeName}, ${additionalPropertiesTypeName}>>`
763
871
  });
764
872
  const functionSource = generateArrowFunction({
765
- name: functionName$2,
766
- signature: interfaceName,
767
- returns: `_${functionName$2}(cfg, ${generateString(endpoint.path)}, query)`,
768
- params: ['query']
873
+ name: functionName,
874
+ signature: functionTypeName,
875
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
876
+ params: ['query', 'requestOptions?: RequestOptions']
769
877
  });
770
878
  return {
771
879
  entity,
772
- name: functionName$2,
773
- type: { name: interfaceName, source: interfaceSource },
774
- func: { name: functionName$2, source: functionSource },
880
+ name: functionName,
881
+ type: { name: functionTypeName, source: functionTypeSource },
882
+ func: { name: functionName, source: functionSource },
775
883
  interfaces: [
776
- {
777
- name: entityParameters,
778
- source: generateInterfaceFromObject(entityParameters, parameters, true)
779
- }
884
+ { name: parametersTypeName, source: parametersTypeSource },
885
+ { name: filterTypeName, source: filterTypeSource },
886
+ { name: referencesTypeName, source: referencesTypeSource },
887
+ { name: additionalPropertyTypeName, source: additionalPropertyTypeSource },
888
+ { name: queryTypeName, source: queryTypeSource },
889
+ { name: referencedEntitiesTypeName, source: referencedEntitiesTypeSource },
890
+ { name: additionalPropertiesTypeName, source: additionalPropertiesTypeSource }
780
891
  ]
781
892
  };
782
893
  };
783
894
 
784
- const functionName$1 = 'unique';
785
895
  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'],
896
+ const functionName = 'unique';
897
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
898
+ const functionTypeSource = generateArrowFunctionType({
899
+ type: functionTypeName,
900
+ params: ['id: string', 'query?: Q', 'requestOptions?: RequestOptions'],
797
901
  generics: ['Q extends UniqueQuery'],
798
902
  returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
799
903
  });
904
+ const functionSource = generateArrowFunction({
905
+ name: functionName,
906
+ signature: functionTypeName,
907
+ params: ['id', 'query', 'requestOptions?: RequestOptions'],
908
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, requestOptions)`
909
+ });
800
910
  return {
801
- entity,
802
- name: functionName$1,
803
- type: { name: interfaceName, source: interfaceSource },
804
- func: { name: functionName$1, source: functionSource }
911
+ entity: pascalCase(endpoint.service),
912
+ name: functionName,
913
+ type: { name: functionTypeName, source: functionTypeSource },
914
+ func: { name: functionName, source: functionSource }
805
915
  };
806
916
  };
807
917
 
808
- const functionName = 'update';
809
918
  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'],
919
+ const functionName = 'update';
920
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
921
+ const functionTypeSource = generateArrowFunctionType({
922
+ type: functionTypeName,
923
+ params: [
924
+ 'id: string',
925
+ `data: DeepPartial<${generateRequestBodyType(path).toString()}>`,
926
+ 'options?: UpdateQuery',
927
+ 'requestOptions?: RequestOptions'
928
+ ],
815
929
  returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
816
930
  });
817
931
  const functionSource = generateArrowFunction({
818
932
  name: functionName,
819
- signature: interfaceName,
820
- returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options)`,
821
- params: ['id', 'data', 'options']
933
+ signature: functionTypeName,
934
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options, requestOptions)`,
935
+ params: ['id', 'data', 'options', 'requestOptions?: RequestOptions']
822
936
  });
823
937
  return {
824
- entity,
938
+ entity: pascalCase(endpoint.service),
825
939
  name: functionName,
826
- type: { name: interfaceName, source: interfaceSource },
940
+ type: { name: functionTypeName, source: functionTypeSource },
827
941
  func: { name: functionName, source: functionSource }
828
942
  };
829
943
  };
830
944
 
831
- const groupEndpointsByEntity = (paths) => {
945
+ const isMultiPartUploadPath = (path) => {
946
+ const [, entity, ...rest] = path.split('/');
947
+ return entity && rest.length === 2 && rest[1] === 'multipartUpload';
948
+ };
949
+ const parseEndpointsAndGroupByEntity = (paths) => {
832
950
  const endpoints = new Map();
833
951
  for (const [rawPath, path] of Object.entries(paths)) {
834
952
  const endpoint = parseEndpointPath(rawPath);
835
953
  if (!endpoint || !path) {
954
+ // Todo: Should be removed if sdk supports multi part upload.
955
+ if (isMultiPartUploadPath(rawPath)) {
956
+ continue;
957
+ }
836
958
  logger.errorLn(`Failed to parse ${rawPath}`);
837
959
  continue;
838
960
  }
839
- if (endpoints.has(endpoint.entity)) {
840
- endpoints.get(endpoint.entity)?.push({ endpoint, path });
961
+ if (endpoints.has(endpoint.service)) {
962
+ endpoints.get(endpoint.service)?.push({ endpoint, path });
841
963
  }
842
964
  else {
843
- endpoints.set(endpoint.entity, [{ endpoint, path }]);
965
+ endpoints.set(endpoint.service, [{ endpoint, path }]);
844
966
  }
845
967
  }
846
968
  return endpoints;
@@ -873,26 +995,35 @@ const generators = {
873
995
  post: generateGenericEndpoint()
874
996
  }
875
997
  };
876
- const generateServices = (doc, aliases, options) => {
998
+ const generateServices = (paths, entities, aliases, options) => {
877
999
  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
1000
+ const endpoints = parseEndpointsAndGroupByEntity(paths);
1001
+ for (const [serviceName, paths] of endpoints) {
1002
+ const serviceFnName = camelCase(`${serviceName}Service`);
1003
+ const serviceTypeName = pascalCase(`${serviceName}Service`);
883
1004
  const functions = [];
884
1005
  for (const { path, endpoint } of paths) {
885
- const resolver = generators[endpoint.type];
1006
+ const generator = generators[endpoint.type];
886
1007
  for (const [method, config] of Object.entries(path)) {
887
- if (method === 'get' && endpoint.type === WeclappEndpointType.ENTITY && !options.generateUnique) {
1008
+ if ((method === 'get' && endpoint.type === WeclappEndpointType.ENTITY && !options.generateUnique) ||
1009
+ (method === 'post' && (endpoint.type === WeclappEndpointType.COUNT || endpoint.path.endsWith('query')))) {
1010
+ // Skip unique endpoints if generateUnique option is not set or if POST is used for filter queries
888
1011
  continue;
889
1012
  }
890
- if (resolver[method]) {
1013
+ const generatorFn = generator[method];
1014
+ if (generatorFn) {
891
1015
  const path = config;
892
1016
  const target = options.target;
893
1017
  if (!path.deprecated || options.deprecated) {
894
1018
  functions.push({
895
- ...resolver[method]({ endpoint, method, target, path, aliases }),
1019
+ ...generatorFn({
1020
+ endpoint,
1021
+ method,
1022
+ target,
1023
+ path,
1024
+ entities,
1025
+ aliases
1026
+ }),
896
1027
  path
897
1028
  });
898
1029
  }
@@ -905,26 +1036,28 @@ const generateServices = (doc, aliases, options) => {
905
1036
  if (!functions.length) {
906
1037
  continue;
907
1038
  }
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 => ({
1039
+ 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, [
1040
+ ...functions.map((v) => ({
911
1041
  required: true,
912
1042
  comment: v.path.deprecated ? '@deprecated' : undefined,
913
1043
  name: v.func.name,
914
1044
  type: v.type.name
915
1045
  }))
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 });
1046
+ ])));
1047
+ const serviceFn = `export const ${serviceFnName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${generateBlockStatements(...functions.map((v) => v.func.source), `return {${concat(functions.map((v) => v.func.name))}};`)};`;
1048
+ services.set(serviceName, {
1049
+ name: serviceName,
1050
+ serviceFnName,
1051
+ serviceTypeName,
1052
+ functions,
1053
+ source: generateStatements(serviceTypes, serviceFn),
1054
+ deprecated: functions.every((v) => v.path.deprecated)
1055
+ });
923
1056
  }
924
1057
  return services;
925
1058
  };
926
1059
 
927
- const generateCustomValueUtilities = (entities, services) => {
1060
+ const generateCustomValueServices = (entities, services) => {
928
1061
  const customValueEntity = entities.get('customValue');
929
1062
  const customValueEntities = [];
930
1063
  if (!customValueEntity) {
@@ -932,7 +1065,7 @@ const generateCustomValueUtilities = (entities, services) => {
932
1065
  return '';
933
1066
  }
934
1067
  serviceLoop: for (const service of services) {
935
- const someFunction = service.functions.find(v => v.name === 'some');
1068
+ const someFunction = service.functions.find((v) => v.name === 'some');
936
1069
  if (!someFunction) {
937
1070
  continue;
938
1071
  }
@@ -945,55 +1078,57 @@ const generateCustomValueUtilities = (entities, services) => {
945
1078
  continue serviceLoop;
946
1079
  }
947
1080
  }
948
- customValueEntities.push(service.entity);
1081
+ customValueEntities.push(service.name);
949
1082
  }
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);')}`));
1083
+ 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
1084
  };
952
1085
 
953
1086
  const generateObject = (properties) => {
954
1087
  const body = [];
955
- for (const { key, value } of properties) {
1088
+ for (const { key, value, comment } of properties) {
956
1089
  if (value === undefined) {
957
1090
  continue;
958
1091
  }
959
1092
  if (Array.isArray(value)) {
960
1093
  const str = generateObject(value);
961
1094
  if (str.length > 2) {
962
- body.push(`${key}: ${str}`);
1095
+ body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${str}`);
963
1096
  }
964
1097
  }
965
1098
  else {
966
- body.push(`${key}: ${String(value)}`);
1099
+ body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${String(value)}`);
967
1100
  }
968
1101
  }
969
1102
  return body.length ? `{\n${indent(body.join(',\n'))}\n}` : `{}`;
970
1103
  };
971
1104
 
972
1105
  const resolveInheritedEntities = (root, entities) => {
973
- const parent = root.extends ? entities.get(root.extends) : undefined;
1106
+ const parent = root.parentName ? entities.get(root.parentName) : undefined;
974
1107
  return parent ? [parent, ...resolveInheritedEntities(parent, entities)] : [];
975
1108
  };
976
- const generatePropertyDescriptors = (entity, entities, services, options) => [
977
- ...resolveInheritedEntities(entity, entities).flatMap(v => [...v.properties]),
978
- ...entity.properties
979
- ].filter(([, meta]) => {
1109
+ const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
1110
+ .filter(([, meta]) => {
980
1111
  // If we generate deprecated things we can skip the filtering
981
1112
  if (options.deprecated) {
982
1113
  return true;
983
1114
  }
984
1115
  // Check if corresponding service is deprecated and can be removed
985
- const service = services.find(v => v.entity === meta.service);
1116
+ const service = services.find((v) => v.name === meta.service);
986
1117
  return !meta.service || (service && !service.deprecated);
987
- }).map(([property, meta]) => ({
1118
+ })
1119
+ .map(([property, meta]) => ({
988
1120
  key: property,
989
1121
  value: Object.entries(meta).map(([key, value]) => ({
990
1122
  key,
991
- value: value ? generateString(value) : undefined
1123
+ value: value !== undefined ? (typeof value === 'number' ? value : generateString(value)) : undefined
992
1124
  }))
993
1125
  }));
994
- const generateEntityPropertyMap = (entities, services, options) => {
1126
+ const generateEntityProperties = (entities, aliases, services, options) => {
995
1127
  const typeName = 'WEntityProperties';
996
- const propertyMap = [...entities].map(([entity, data]) => ({
1128
+ const propertyMap = [
1129
+ ...entities.entries(),
1130
+ ...[...aliases.entries()].map(([service, type]) => [service, entities.get(camelCase(type))])
1131
+ ].map(([entity, data]) => ({
997
1132
  key: entity,
998
1133
  value: generatePropertyDescriptors(data, entities, services, options)
999
1134
  }));
@@ -1001,7 +1136,7 @@ const generateEntityPropertyMap = (entities, services, options) => {
1001
1136
  };
1002
1137
 
1003
1138
  const generateArray = (values) => {
1004
- return `[${concat(values.map(v => generateString(String(v))))}]`;
1139
+ return `[${concat(values.map((v) => generateString(String(v))))}]`;
1005
1140
  };
1006
1141
 
1007
1142
  // Only functions matching this regex are included in the generation.
@@ -1014,16 +1149,17 @@ const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
1014
1149
  */
1015
1150
  const generateGroupedServices = (services) => {
1016
1151
  const entityDescriptors = new Map();
1017
- for (const { entity, functions } of services) {
1018
- for (const { name } of functions) {
1019
- if (!FILTER_REGEX.test(name)) {
1152
+ for (const service of services) {
1153
+ for (const fn of service.functions) {
1154
+ if (!FILTER_REGEX.test(fn.name)) {
1020
1155
  continue;
1021
1156
  }
1022
- entityDescriptors.set(name, [
1023
- ...(entityDescriptors.get(name) ?? []), {
1024
- name: entity,
1157
+ entityDescriptors.set(fn.name, [
1158
+ ...(entityDescriptors.get(fn.name) ?? []),
1159
+ {
1160
+ name: service.name,
1025
1161
  required: true,
1026
- type: `${pascalCase(entity)}Service_${pascalCase(name)}`
1162
+ type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}`
1027
1163
  }
1028
1164
  ]);
1029
1165
  }
@@ -1036,123 +1172,86 @@ const generateGroupedServices = (services) => {
1036
1172
  const guard = `(service: string | undefined): service is ${service} =>\n${indent(`${constant}.includes(service as ${service});`)}`;
1037
1173
  typeGuards.push(`export const is${service} = ${guard}`);
1038
1174
  }
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
- ];
1175
+ 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]) => {
1176
+ const constant = camelCase(`wServiceWith_${name}_Names`);
1177
+ const type = pascalCase(`WServiceWith_${name}`);
1178
+ const value = generateArray(props.map((v) => v.name));
1179
+ return `export const ${constant}: ${type}[] = ${value};`;
1180
+ }), ...typeGuards);
1050
1181
  };
1051
1182
 
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') })));
1183
+ const generateMaps = (enums, entities, services, aliases, options) => {
1184
+ const enumInstances = `export const wEnums = ${generateObject([...enums.keys()].map((v) => ({ key: v, value: v })))};`;
1185
+ const entityNames = `export const wEntityNames: WEntity[] = ${generateArray([...entities.keys()])};`;
1186
+ const generatedServices = [...services.values()];
1187
+ const serviceInstances = `export const wServices = ${generateObject(generatedServices.map((v) => ({
1188
+ key: v.name,
1189
+ value: `${v.serviceFnName}()`,
1190
+ comment: v.deprecated ? '@deprecated' : undefined
1191
+ })))};`;
1192
+ const serviceFactories = `export const wServiceFactories = ${generateObject(generatedServices.map((v) => ({
1193
+ key: v.name,
1194
+ value: v.serviceFnName,
1195
+ comment: v.deprecated ? '@deprecated' : undefined
1196
+ })))};`;
1085
1197
  return {
1086
1198
  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))
1199
+ /* Enums */
1200
+ generateInterface('WEnums', [...enums.keys()].map((name) => ({ name, type: name, required: true }))), generateType('WEnum', 'keyof WEnums'), enumInstances,
1201
+ /* Entities */
1202
+ generateInterface('WEntities', [
1203
+ ...[...entities.keys()].map((name) => ({ name, type: loosePascalCase(name), required: true })),
1204
+ ...[...aliases.entries()].map(([name, type]) => ({ name, type, required: true }))
1205
+ ].sort((a, b) => (a.name > b.name ? 1 : -1))), generateType('WEntity', 'keyof WEntities'), entityNames,
1206
+ /* Services */
1207
+ serviceInstances, generateType('WServices', 'typeof wServices'), generateType('WService', 'keyof WServices'), serviceFactories, generateType('WServiceFactories', 'typeof wServiceFactories'),
1208
+ /* Service Utils */
1209
+ generateGroupedServices(generatedServices), generateCustomValueServices(entities, generatedServices),
1210
+ /* Entity Properties (Runtime Meta Infos) */
1211
+ generateEntityProperties(entities, aliases, generatedServices, options))
1113
1212
  };
1114
1213
  };
1115
1214
 
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();
1215
+ const parseReferencedEntityType = (obj) => pascalCase(obj.$ref.replace(/.*\//, ''));
1216
+ const extractServiceAliases = (doc, schemas) => {
1120
1217
  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
1218
  for (const [path, methods] of Object.entries(doc.paths)) {
1131
1219
  const parsed = parseEndpointPath(path);
1132
- if (!parsed || schemas.has(parsed.entity)) {
1220
+ if (!parsed || !methods || parsed.type !== WeclappEndpointType.ROOT || schemas.has(parsed.service)) {
1133
1221
  continue;
1134
1222
  }
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
- }
1223
+ const body = methods[OpenAPIV3.HttpMethods.GET]?.responses['200'];
1224
+ if (isResponseObject(body) && body.content?.['application/json']) {
1225
+ const responseSchema = body.content['application/json'].schema;
1226
+ if (!responseSchema || isReferenceObject(responseSchema)) {
1227
+ continue;
1228
+ }
1229
+ const resultSchema = responseSchema.properties?.result;
1230
+ if (!resultSchema) {
1231
+ continue;
1232
+ }
1233
+ if (isReferenceObject(resultSchema)) {
1234
+ aliases.set(parsed.service, parseReferencedEntityType(resultSchema));
1235
+ continue;
1236
+ }
1237
+ if (isArraySchemaObject(resultSchema)) {
1238
+ const resultItemSchema = resultSchema.items;
1239
+ if (isReferenceObject(resultItemSchema)) {
1240
+ aliases.set(parsed.service, parseReferencedEntityType(resultItemSchema));
1152
1241
  }
1153
1242
  }
1154
1243
  }
1155
1244
  }
1245
+ return aliases;
1246
+ };
1247
+ const extractSchemas = (doc) => {
1248
+ const schemas = new Map();
1249
+ for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
1250
+ if (!isReferenceObject(schema)) {
1251
+ schemas.set(name, schema);
1252
+ }
1253
+ }
1254
+ const aliases = extractServiceAliases(doc, schemas);
1156
1255
  return { schemas, aliases };
1157
1256
  };
1158
1257
 
@@ -1160,14 +1259,10 @@ const generate = (doc, options) => {
1160
1259
  const { schemas, aliases } = extractSchemas(doc);
1161
1260
  const enums = generateEnums(schemas);
1162
1261
  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));
1262
+ const entityFilterProps = generateEntityFilterProps(entities, enums);
1263
+ const services = generateServices(doc.paths, entities, aliases, options);
1264
+ const maps = generateMaps(enums, entities, services, aliases, options);
1265
+ 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
1266
  };
1172
1267
 
1173
1268
  const hash = (content, algorithm = 'sha256') => {
@@ -1183,12 +1278,13 @@ const hash = (content, algorithm = 'sha256') => {
1183
1278
 
1184
1279
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
1185
1280
  const cli = async () => {
1186
- const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
1281
+ const version = pkg.version;
1187
1282
  const { argv } = yargs(hideBin(process.argv))
1188
1283
  .scriptName('build-weclapp-sdk')
1189
1284
  .usage('Usage: $0 <source> [flags]')
1190
1285
  .version(version)
1191
1286
  .example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
1287
+ .example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
1192
1288
  .example('$0 xxx.weclapp.com --key ...', 'Generate the SDK based on the openapi file from the given weclapp instance')
1193
1289
  .help('h')
1194
1290
  .alias('v', 'version')
@@ -1228,36 +1324,47 @@ const cli = async () => {
1228
1324
  type: 'string',
1229
1325
  choices: ['browser', 'browser.rx', 'node', 'node.rx']
1230
1326
  })
1231
- .option('d', {
1232
- alias: 'deprecated',
1233
- describe: 'Include deprecated functions and services',
1327
+ .option('use-query-language', {
1328
+ describe: 'Generate the new where property for some and count queries',
1234
1329
  type: 'boolean'
1330
+ })
1331
+ .option('apiVersion', {
1332
+ describe: 'Specify the api version (only needed when not using a local file)',
1333
+ type: 'string'
1235
1334
  })
1236
1335
  .epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
1237
1336
  if (argv.fromEnv) {
1238
1337
  config();
1239
1338
  }
1240
1339
  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;
1340
+ const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, apiVersion, _: [src = WECLAPP_BACKEND_URL] } = argv;
1242
1341
  const options = {
1243
1342
  deprecated,
1244
1343
  generateUnique: argv.generateUnique ?? false,
1245
- target: argv.target ?? Target.BROWSER_PROMISES
1344
+ target: argv.target ?? Target.BROWSER_PROMISES,
1345
+ useQueryLanguage: argv.useQueryLanguage ?? false
1246
1346
  };
1247
- if (typeof src === 'number') {
1248
- return Promise.reject('Expected string as command');
1347
+ if (!src || typeof src === 'number') {
1348
+ return Promise.reject(new Error('Expected string as command'));
1249
1349
  }
1250
1350
  if (!Object.values(Target).includes(options.target)) {
1251
1351
  logger.errorLn(`Unknown target: ${options.target}. Possible values are ${Object.values(Target).join(', ')}`);
1252
- return Promise.reject();
1352
+ return Promise.reject(new Error());
1253
1353
  }
1254
1354
  if (await stat(src).catch(() => false)) {
1255
- logger.infoLn(`Source is a file.`);
1355
+ logger.infoLn(`Source is a file`);
1256
1356
  const content = JSON.parse(await readFile(src, 'utf-8'));
1257
1357
  return { cache, content, options };
1258
1358
  }
1359
+ logger.infoLn(`Source is a URL`);
1360
+ if (!key) {
1361
+ return Promise.reject(new Error('API key is missing'));
1362
+ }
1363
+ if (!apiVersion) {
1364
+ return Promise.reject(new Error('API version is missing'));
1365
+ }
1259
1366
  const url = new URL(src.startsWith('http') ? src : `https://${src}`);
1260
- url.pathname = '/webapp/api/v1/meta/openapi.json';
1367
+ url.pathname = `/webapp/api/${apiVersion}/meta/openapi.json`;
1261
1368
  if (query?.length) {
1262
1369
  for (const param of query.split(',')) {
1263
1370
  const [name, value] = param.split('=');
@@ -1265,11 +1372,11 @@ const cli = async () => {
1265
1372
  }
1266
1373
  }
1267
1374
  const content = await fetch(url.toString(), {
1268
- headers: { 'Accept': 'application/json', 'AuthenticationToken': key }
1269
- }).then(res => res.ok ? res.json() : undefined);
1375
+ headers: { Accept: 'application/json', AuthenticationToken: key }
1376
+ }).then((res) => (res.ok ? res.json() : undefined));
1270
1377
  if (!content) {
1271
1378
  logger.errorLn(`Couldn't fetch file ${url.toString()} `);
1272
- return Promise.reject();
1379
+ return Promise.reject(new Error());
1273
1380
  }
1274
1381
  else {
1275
1382
  logger.infoLn(`Use remote file: ${url.toString()}`);
@@ -1277,50 +1384,59 @@ const cli = async () => {
1277
1384
  return { cache, content, options };
1278
1385
  };
1279
1386
 
1280
- const workingDirectory = resolve(currentDirname(), './sdk');
1281
- const folders = ['docs', 'main', 'node', 'raw', 'rx', 'utils'];
1387
+ const workingDir = resolve(currentDirname(), './sdk');
1388
+ const cacheDir = resolve(currentDirname(), './.cache');
1282
1389
  void (async () => {
1283
1390
  const start = process.hrtime.bigint();
1284
- const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
1285
1391
  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);
1392
+ const workingDirPath = async (...paths) => {
1393
+ const fullPath = resolve(workingDir, ...paths);
1394
+ await mkdir(dirname(fullPath), { recursive: true });
1293
1395
  return fullPath;
1294
1396
  };
1397
+ // Resolve cache dir and key
1398
+ const cacheKey = hash([pkg.version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
1399
+ const cachedSdkDir = resolve(cacheDir, cacheKey);
1400
+ // Remove old SDK
1401
+ await rm(workingDir, { recursive: true, force: true });
1295
1402
  if (useCache) {
1296
1403
  logger.infoLn(`Cache ID: ${cacheKey}`);
1297
1404
  }
1298
- if (useCache && await stat(cacheDir).catch(() => false)) {
1299
- logger.successLn(`Cache match! (${cacheDir})`);
1405
+ if (useCache && (await stat(cachedSdkDir).catch(() => false))) {
1406
+ // Copy cached SDK to working dir
1407
+ logger.successLn(`Cache match! (${cachedSdkDir})`);
1408
+ await cp(cachedSdkDir, workingDir, { recursive: true });
1300
1409
  }
1301
1410
  else {
1302
- // Store swagger.json file
1303
- await writeFile(await tmp('openapi.json'), JSON.stringify(doc, null, 2));
1411
+ // Write openapi.json file
1412
+ await writeFile(await workingDirPath('openapi.json'), JSON.stringify(doc, null, 2));
1304
1413
  logger.infoLn(`Generate sdk (target: ${options.target})`);
1305
- // Generate SDKs
1414
+ // Generate and write SDK (index.ts)
1306
1415
  const sdk = generate(doc, options);
1307
- await writeFile(await tmp('src', `${options.target}.ts`), sdk.trim() + '\n');
1308
- // Bundle
1416
+ await writeFile(await workingDirPath('src', 'index.ts'), sdk.trim() + '\n');
1417
+ // Bundle and write SDK
1309
1418
  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)));
1419
+ await bundle(workingDir, options.target);
1420
+ // Remove index.ts (only bundle is required)
1421
+ await rm(await workingDirPath('src'), { recursive: true, force: true });
1422
+ if (useCache) {
1423
+ // Copy SDK to cache
1424
+ logger.successLn(`Caching SDK: (${cachedSdkDir})`);
1425
+ await mkdir(cachedSdkDir, { recursive: true });
1426
+ await cp(workingDir, cachedSdkDir, { recursive: true });
1427
+ }
1313
1428
  }
1314
- // Copy bundled SDK
1315
- await cp(cacheDir, workingDirectory, { recursive: true });
1316
1429
  // Print job summary
1317
1430
  const duration = (process.hrtime.bigint() - start) / 1000000n;
1318
1431
  logger.successLn(`SDK built in ${prettyMs(Number(duration))}`);
1319
1432
  logger.printSummary();
1320
- })().catch((error) => {
1433
+ })()
1434
+ .catch((error) => {
1321
1435
  logger.errorLn(`Fatal error:`);
1322
1436
  /* eslint-disable no-console */
1323
1437
  console.error(error);
1324
- }).finally(() => {
1325
- logger.errors && process.exit(1);
1438
+ })
1439
+ .finally(() => {
1440
+ if (logger.errors)
1441
+ process.exit(1);
1326
1442
  });