@weclapp/sdk 2.0.0-dev.3 → 2.0.0-dev.31

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,13 +40,8 @@ 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
46
  sourcemap: true,
46
47
  banner: `/* weclapp sdk */`,
@@ -61,35 +62,22 @@ const bundle = async (workingDirectory, target) => {
61
62
  globals: resolveGlobals('node-fetch', 'url')
62
63
  })
63
64
  ];
64
- // Remove build dir
65
- await rmdir(dist()).catch(() => void 0);
66
65
  const bundles = {
67
66
  [Target.BROWSER_PROMISES]: () => ({
68
- input: src('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) => wrapResponse(() => raw(cfg, endpoint, {\n method: 'DELETE'\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}: UpdateQuery = {}\n) => wrapResponse(() => raw(cfg, endpoint, {\n method: 'PUT',\n body: data,\n query: {ignoreMissingProperties}\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\ntype ServiceConfigWithoutMultiRequest = Omit<ServiceConfig, 'multiRequest'>;\n\nlet globalConfig: ServiceConfig | undefined;\nexport const getGlobalConfig = (): ServiceConfig | undefined => globalConfig;\nexport const setGlobalConfig = (cfg?: ServiceConfig) => (globalConfig = cfg);\n\nexport const getHost = (cfg: ServiceConfig) => {\n let host = cfg.host?.replace(/^https?:\\/\\//, '');\n if (!host && typeof location !== 'undefined') {\n host = location.host;\n }\n\n if (!host) {\n throw new Error('Please specify a host');\n }\n\n return host;\n};\n\nexport const getProtocol = (cfg: ServiceConfig) => {\n const protocol =\n cfg.secure !== undefined\n ? cfg.secure\n ? 'https:'\n : 'http:'\n : typeof location !== 'undefined'\n ? location.protocol\n : undefined;\n\n if (!protocol) {\n throw new Error('Please specify a protocol (secure)');\n }\n\n return protocol;\n};\n";
136
+
137
+ var multiRequest = "type RequestTask = {\n uri: string;\n resolve: (result: unknown) => void;\n reject: (error: unknown) => void;\n};\n\ntype BatchRequestTask = RequestTask & {\n settled: boolean;\n};\n\ntype MultiRequestResponse = {\n status: number;\n body: object;\n};\n\nlet microtaskQueued: boolean = false;\nconst tasksSet: Set<RequestTask> = new Set<RequestTask>();\n\nconst SQUARE_BRACKET_OPEN = '['.charCodeAt(0);\nconst COMMA = ','.charCodeAt(0);\nconst DECODER = new TextDecoder();\n\nconst readNextResponse = (bytes: Uint8Array) => {\n let headerStart: number | undefined = undefined;\n let commasSeen = 0;\n\n for (let i = 0; i < bytes.length; i++) {\n const byte = bytes[i];\n if (headerStart === undefined) {\n if (byte === SQUARE_BRACKET_OPEN || byte === COMMA) {\n headerStart = i + 1;\n }\n } else {\n if (byte === COMMA) {\n commasSeen++;\n }\n if (commasSeen === 2) {\n const headerArrayString = `[${DECODER.decode(bytes.subarray(headerStart, i))}]`;\n const [index, jsonLength] = JSON.parse(headerArrayString);\n if (!(typeof index === 'number') || !(typeof jsonLength === 'number')) {\n throw new Error(`unexpected header: ${headerArrayString}`);\n }\n\n const endIndex = i + 1 + jsonLength;\n if (endIndex > bytes.length) {\n // not all bytes available yet\n return undefined;\n }\n const jsonString = DECODER.decode(bytes.subarray(i + 1, endIndex));\n const data = JSON.parse(jsonString) as MultiRequestResponse;\n return {\n index,\n data,\n remainingBytes: bytes.subarray(endIndex)\n };\n }\n }\n }\n return undefined;\n};\n\nconst fetchMultiRequest = async (requests: string[]) => {\n const cfg = getGlobalConfig();\n\n if (!cfg) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const host = getHost(cfg);\n const protocol = getProtocol(cfg);\n\n return await fetch(`${protocol}//${host}/webapp/api/v2/batch/query`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(cfg.key && { AuthenticationToken: cfg.key })\n },\n body: JSON.stringify({ requests })\n });\n};\n\nconst rejectTasks = (tasks: BatchRequestTask[], error: unknown) => {\n for (const task of tasks) {\n if (!task.settled) {\n task.reject(error);\n }\n }\n};\n\nconst processStream = (\n { value: chunk, done }: ReadableStreamReadResult<Uint8Array>,\n remainingBytes: Uint8Array,\n reader: ReadableStreamDefaultReader<Uint8Array>,\n tasks: BatchRequestTask[]\n) => {\n if (done) {\n return;\n }\n if (chunk) {\n let bytes = new Uint8Array(remainingBytes.length + chunk.length);\n bytes.set(remainingBytes);\n bytes.set(chunk, remainingBytes.length);\n\n while (bytes.length) {\n const result = readNextResponse(bytes);\n if (!result) {\n break;\n }\n const task = tasks[result.index];\n if (result.data.status >= 100 && result.data.status < 400) {\n task.resolve({\n ...result.data.body\n });\n } else {\n task.reject({\n ...result.data.body\n });\n }\n task.settled = true;\n bytes = result.remainingBytes;\n }\n reader\n .read()\n .then((readResult) => processStream(readResult, bytes, reader, tasks))\n .catch((error) => rejectTasks(tasks, error));\n }\n};\n\nconst batch = async (tasks: BatchRequestTask[]) => {\n try {\n const requests = tasks.map(({ uri }) => uri);\n const resp = await fetchMultiRequest(requests);\n const reader = resp.body?.getReader();\n\n if (!reader) {\n throw new Error('Stream reader is undefined');\n }\n reader\n .read()\n .then((readResult) => processStream(readResult, new Uint8Array(0), reader, tasks))\n .catch((error) => rejectTasks(tasks, error));\n } catch (e) {\n rejectTasks(tasks, e);\n throw e;\n }\n};\n\nconst addTask = (task: RequestTask) => {\n tasksSet.add(task);\n\n if (!microtaskQueued) {\n queueMicrotask(() => {\n microtaskQueued = false;\n if (tasksSet.size > 0) {\n const batchTasks = Array.from(tasksSet).map((task) => ({ ...task, settled: false }));\n void batch(batchTasks);\n tasksSet.clear();\n }\n });\n microtaskQueued = true;\n }\n};\n\nconst addRequest = (uri: string) => new Promise((resolve, reject) => addTask({ uri, resolve, reject }));\n";
138
+
139
+ var queriesWithFilter = "export type EqualityOperator = 'EQ' | 'NE';\n\nexport type ComparisonOperator =\n | 'LT'\n | 'GT'\n | 'LE'\n | 'GE'\n | 'LIKE'\n | 'ILIKE'\n | 'NOT_LIKE'\n | 'NOT_ILIKE'\n | 'IEQ'\n | 'NOT_IEQ';\n\nexport type ArrayOperator = 'IN' | 'NOT_IN';\n\nexport type Operator = EqualityOperator | ComparisonOperator | ArrayOperator;\n\nexport type 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) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n unwrap: true,\n query: {\n ...flattenFilter(query?.filter),\n ...flattenOrFilter(query?.or),\n ...query?.params\n }\n })\n );\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> }\n) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n query: {\n serializeNulls: query?.serializeNulls,\n additionalProperties: query?.properties?.join(','),\n properties: query?.select ? flattenSelect(query.select).join(',') : undefined,\n includeReferencedEntities: query?.include ? Object.keys(query.include).join(',') : undefined,\n ...flattenOrFilter(query?.or),\n ...flattenFilter(query?.filter),\n ...flattenSort(query?.sort),\n ...query?.params,\n ...query?.pagination\n }\n }).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n );\n";
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> & CustomAttributeFilter;\n};\n\nexport type SomeQuery<E, F, I, P> = {\n serializeNulls?: boolean;\n include?: QuerySelect<I>;\n properties?: P;\n where?: QueryFilter<F> & CustomAttributeFilter;\n select?: QuerySelect<E>;\n sort?: Sort<E>[];\n pagination?: Pagination;\n};\n\nconst comparisonOperatorList: ComparisonOperator[] = [\n 'EQ',\n 'NE',\n 'LT',\n 'GT',\n 'LE',\n 'GE',\n 'LIKE'\n];\n\nconst comparisonOperatorMap: Record<Operator, string> = {\n EQ: '=',\n NE: '!=',\n LT: '<',\n GT: '>',\n LE: '<=',\n GE: '>=',\n LIKE: '~',\n IN: 'in',\n NULL: 'null'\n};\n\nconst modifierFunctionList: ModifierFunction[] = ['lower'];\n\nconst flattenWhere = (\n obj: QueryFilter<any> = {},\n nestedPaths: string[]\n): string[] => {\n const entries: string[] = [];\n for (const [prop, propValue] of Object.entries(obj)) {\n const setModifiers = findAllModifierFunctions(propValue ?? {}, modifierFunctionList).filter(\n (modifier) => modifier[1]\n );\n if (prop === 'OR') {\n const flattedOr: string[][] = [];\n for (let i = 0; i < (obj.OR?.length ?? 0); i++) {\n flattedOr.push(flattenWhere(obj.OR?.[i], nestedPaths));\n }\n entries.push(\n `(${flattedOr\n .map((x) => {\n const joined = x.join(' and ');\n\n if (x.length > 1) {\n return `(${joined})`;\n } else {\n return joined;\n }\n })\n .join(' or ')})`\n );\n } else if (prop === 'AND') {\n const flattedAnd: string[][] = [];\n for (let i = 0; i < (obj.AND?.length ?? 0); i++) {\n flattedAnd.push(flattenWhere(obj.AND?.[i], nestedPaths));\n }\n entries.push(\n `(${flattedAnd\n .map((x) => {\n const joined = x.join(' and ');\n\n if (x.length > 1) {\n return `(${joined})`;\n } else {\n return joined;\n }\n })\n .join(' and ')})`\n );\n } else if (prop === 'NOT') {\n const flattedNot = flattenWhere(obj.NOT, nestedPaths);\n entries.push(\n `not ${flattedNot.length > 1 ? '(' : ''}${flattedNot.join(' and ')}${flattedNot.length > 1 ? ')' : ''}`\n );\n } else if (propValue) {\n for (const [operator, value] of Object.entries(propValue)) {\n if (value === undefined) continue;\n if (comparisonOperatorList.includes(operator as ComparisonOperator)) {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${comparisonOperatorMap[operator as Operator]} ${\n typeof value === 'string'\n ? setModifiers.reduce((acc, [first]) => `${first}(${acc})`, JSON.stringify(value))\n : value\n }`\n );\n } else if ((operator as Operator) === 'NULL') {\n entries.push(\n `${!value ? 'not ' : ''}${nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')} ${comparisonOperatorMap[operator as Operator]}`\n );\n } else if ((operator as Operator) === 'IN') {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${comparisonOperatorMap[operator as Operator]} [${value.map((v: string | number) =>\n typeof v === 'string' ? setModifiers.reduce((acc, [first]) => `${first}(${acc})`, JSON.stringify(v)) : v\n )}]`\n );\n } else if (\n !modifierFunctionList.includes(operator as ModifierFunction)\n ) {\n entries.push(\n ...flattenWhere(propValue as QueryFilter<any>, [\n ...nestedPaths,\n prop\n ])\n );\n break;\n }\n }\n }\n }\n return entries;\n};\n\nconst assembleFilterParam = (\n obj: QueryFilter<any> = {}\n): Record<string, string> => {\n const flattedFilter = flattenWhere(obj, []);\n return flattedFilter.length ? { filter: flattedFilter.join(' and ') } : {};\n};\n\nconst findAllModifierFunctions = (\n obj: Record<string, any>,\n types: ModifierFunction[]\n) => {\n const result: Record<string, any> = {};\n for (const key in obj) {\n if (types.includes(key as ModifierFunction)) {\n result[key] = obj[key];\n }\n }\n return Object.entries(result);\n};\n\nconst _count = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: CountQuery<any> & { params?: Record<any, any> }\n) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n unwrap: true,\n query: {\n ...assembleFilterParam(query?.where),\n ...query?.params\n }\n })\n );\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> }\n) =>\n wrapResponse(() =>\n raw(cfg, endpoint, {\n query: {\n serializeNulls: query?.serializeNulls,\n additionalProperties: query?.properties?.join(','),\n properties: query?.select\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 }).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n );\n";
142
+
143
+ var root = "export const raw = async (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n payload: RequestPayload = {}\n): Promise<any> => {\n const globalConfig = getGlobalConfig();\n if (!cfg && !globalConfig) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const localCfg = {\n ...globalConfig,\n ...cfg,\n interceptors: { ...globalConfig?.interceptors, ...cfg?.interceptors }\n };\n\n const isBinaryData = payload.body instanceof resolveBinaryObject();\n const params = new URLSearchParams(\n Object.entries(payload.query ?? {})\n .filter((v) => v[1] !== undefined)\n .map(([key, value]) => [key, typeof value === 'string' ? value : JSON.stringify(value)])\n );\n\n const protocol = getProtocol(localCfg);\n\n const interceptRequest = localCfg.interceptors?.request ?? ((v) => v);\n const interceptResponse = localCfg.interceptors?.response ?? ((v) => v);\n\n const host = getHost(localCfg);\n\n let data;\n if (!cfg && localCfg.multiRequest) {\n let ep = endpoint;\n if (endpoint.startsWith('/')) {\n ep = endpoint.replace('/', '');\n }\n data = await addRequest(`${ep}?${params}`);\n } else {\n const request = new Request(`${protocol}//${host}/webapp/api/v${apiVersion}${endpoint}?${params}`, {\n ...(payload.body && {\n body: isBinaryData\n ? payload.body\n : JSON.stringify(payload.body, (_key, value) => (value === undefined ? null : value))\n }),\n ...(!localCfg.key && { credentials: 'same-origin' }),\n method: payload.method ?? 'get',\n headers: {\n Accept: 'application/json',\n ...(localCfg.key && { AuthenticationToken: localCfg.key }),\n ...(!isBinaryData && { 'Content-Type': 'application/json' })\n }\n });\n let res = (await interceptRequest(request, payload)) ?? request;\n if (!(res instanceof Response)) {\n res = await fetch(res);\n }\n res = (await interceptResponse(res)) ?? res;\n data =\n (!payload.forceBlob || !res.ok) && res.headers?.get('content-type')?.includes('application/json')\n ? await res.json()\n : await res.blob();\n\n // Check if response was successful\n if (!res.ok) {\n return Promise.reject(data);\n }\n }\n\n return payload.unwrap ? data.result : data;\n};\n\nconst _remove = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n { dryRun = false }: RemoveQuery = {}\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'DELETE',\n query: { dryRun }\n }).then(() => undefined)\n );\n\nconst _create = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, data: any) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'POST',\n body: data\n })\n );\n\nconst _update = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n data: any,\n { ignoreMissingProperties, dryRun = false }: UpdateQuery = {}\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'PUT',\n body: data,\n query: {\n ignoreMissingProperties:\n ignoreMissingProperties ?? cfg?.ignoreMissingProperties ?? globalConfig?.ignoreMissingProperties,\n dryRun\n }\n })\n );\n\nconst _generic = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n method: RequestPayloadMethod,\n endpoint: string,\n payload?: GenericQuery<any, any>,\n forceBlob?: boolean\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method,\n forceBlob,\n body: payload?.body,\n query: payload?.params\n })\n );\n";
148
144
 
149
- var types = "export type DeepPartial<T> = T extends object ? {\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}\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";
145
+ var unique = "const _unique = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, query?: UniqueQuery) =>\n wrapResponse(() => raw({ ...cfg, multiRequest: false }, endpoint, { query }));\n";
146
+
147
+ var types = "export type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>;\n }\n : T;\n\nexport type Sort<T> = {\n [K in keyof T]?: {\n [V in keyof T]?: V extends K\n ? T[V] extends Array<infer U> | undefined\n ? U extends object\n ? Sort<U>\n : never\n : T[V] extends object | undefined\n ? Sort<T[V]>\n : 'asc' | 'desc'\n : never;\n };\n}[keyof T];\n\nexport type CustomAttributeFilter = {\n [K in number]:| string | number | boolean | { id: string } | { entityName: string; entityId: string };\n};\n\nexport type QuerySelect<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? QuerySelect<U> | boolean\n : T[P] extends Record<any, any> | undefined\n ? QuerySelect<T[P]> | boolean\n : boolean;\n};\n\nexport type Select<T, Q extends QuerySelect<T> | undefined> =\n Q extends QuerySelect<T>\n ? {\n // Filter out excluded properties beforehand\n [P in keyof T as Q[P] extends boolean ? P : Q[P] extends object ? P : never]: // Property\n Q[P] extends true\n ? T[P]\n : // Array\n T[P] extends Array<infer U>\n ? Select<U, Q[P] & QuerySelect<any>>[]\n : // Object\n T[P] extends Record<any, any>\n ? Select<T[P], Q[P] & QuerySelect<any>>\n : never;\n }\n : undefined;\n\nexport type MapKeys<T, S extends Record<keyof T, string>> = {\n [K in keyof T as S[K]]: T[K];\n};\n\nexport type ValueOf<T> = T[keyof T];\n\nexport type Pagination = {\n page: number;\n pageSize: number;\n};\n\nexport type UniqueQuery = {\n serializeNulls?: boolean;\n};\n\nexport type SomeQueryReturn<E, R, P> = {\n entities: E[];\n references?: R;\n properties?: P[];\n};\n\nexport type GenericQuery<P, B> = {\n params?: P;\n body?: B;\n};\n\nexport type UpdateQuery = {\n ignoreMissingProperties?: boolean;\n dryRun?: boolean;\n};\n\nexport type RemoveQuery = {\n dryRun?: boolean;\n};\n\nexport type WEntityPropertyMeta =\n | {\n type: 'string';\n format?: 'decimal' | 'html' | 'email' | 'password';\n maxLength?: number;\n pattern?: string;\n entity?: WEntity;\n service?: WService;\n }\n | {\n type: 'integer';\n format: 'int32' | 'int64' | 'duration' | 'date' | 'timestamp';\n }\n | { type: 'array'; format: 'reference'; entity: WEntity; service?: WService }\n | { type: 'array'; format: 'reference'; enum: WEnum }\n | { type: 'array'; format: 'string' }\n | { type: 'number'; format: 'double' }\n | { type: 'reference'; entity: WEntity }\n | { type: 'reference'; enum: WEnum }\n | { type: 'boolean' }\n | { type: 'object' };\n";
148
+
149
+ var utils = "const flattenSelect = (obj: Select<any, any> = {}): string[] => {\n const entries: string[] = [];\n\n for (const [prop, value] of Object.entries(obj)) {\n if (typeof value === 'object' && value) {\n entries.push(...flattenSelect(value).map((v) => `${prop}.${v}`));\n } else if (value) {\n entries.push(prop);\n }\n }\n\n return entries;\n};\n\nexport const flattenSort = (obj: Sort<any>[] = []): { 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
 
@@ -200,13 +200,14 @@ const isRelatedEntitySchema = (v) => {
200
200
 
201
201
  const generateEnums = (schemas) => {
202
202
  const enums = new Map();
203
- for (const [propName, schema] of schemas) {
203
+ for (const [schemaName, schema] of schemas) {
204
204
  if (isEnumSchemaObject(schema)) {
205
- const name = loosePascalCase(propName);
206
- if (!enums.has(name)) {
207
- enums.set(name, {
205
+ const enumName = loosePascalCase(schemaName);
206
+ if (!enums.has(enumName)) {
207
+ enums.set(enumName, {
208
+ name: enumName,
208
209
  properties: schema.enum,
209
- source: generateEnum(name, schema.enum)
210
+ source: generateEnum(enumName, schema.enum)
210
211
  });
211
212
  }
212
213
  }
@@ -218,16 +219,13 @@ const concat = (strings, separator = ', ', maxLength = 80) => {
218
219
  const joined = strings.join(separator);
219
220
  if (joined.length > maxLength) {
220
221
  const length = strings.length - 1;
221
- return `\n${indent(strings
222
- .map((value, index) => index === length ? value : `${(value + separator).trim()}\n`)
223
- .join(''))}\n`;
222
+ return `\n${indent(strings.map((value, index) => (index === length ? value : `${(value + separator).trim()}\n`)).join(''))}\n`;
224
223
  }
225
224
  else {
226
225
  return joined;
227
226
  }
228
227
  };
229
228
 
230
- /* eslint-disable no-use-before-define */
231
229
  const createReferenceType = (value) => ({
232
230
  type: 'reference',
233
231
  toString: () => loosePascalCase(value)
@@ -238,26 +236,28 @@ const createRawType = (value) => ({
238
236
  });
239
237
  const createArrayType = (value) => ({
240
238
  type: 'array',
241
- toString: () => `${value.toString()}[]`
239
+ toString: () => `(${value.toString()})[]`
242
240
  });
243
241
  const createTupleType = (value) => ({
244
242
  type: 'tuple',
245
- toString: () => concat([...new Set(value.map(v => typeof v === 'string' ? `'${v}'` : v.toString()))], ' | ')
243
+ toString: () => concat([...new Set(value.map((v) => (typeof v === 'string' ? `'${v}'` : v.toString())))], ' | ')
246
244
  });
247
245
  const createObjectType = (value, required = []) => ({
248
246
  type: 'object',
249
247
  isFullyOptional: () => {
250
- return !required.length && Object.values(value)
251
- .filter(v => v?.type === 'object')
252
- .every(v => v.isFullyOptional());
248
+ return (!required.length &&
249
+ Object.values(value)
250
+ .filter((v) => v?.type === 'object')
251
+ .every((v) => v.isFullyOptional()));
253
252
  },
254
253
  toString: (propagateOptionalProperties = false) => {
255
254
  const properties = Object.entries(value)
256
- .filter(v => v[1])
257
- .map(v => {
255
+ .filter((v) => v[1])
256
+ .map((v) => {
258
257
  const name = v[0];
259
258
  const value = v[1];
260
- const isRequired = required.includes(name) || (value.type === 'object' && !value.isFullyOptional() && propagateOptionalProperties);
259
+ const isRequired = required.includes(name) ||
260
+ (value.type === 'object' && !value.isFullyOptional() && propagateOptionalProperties);
261
261
  return `${name + (isRequired ? '' : '?')}: ${value.toString()};`;
262
262
  });
263
263
  return properties.length ? `{\n${indent(properties.join('\n'))}\n}` : '{}';
@@ -286,8 +286,7 @@ const convertToTypeScriptType = (schema, property) => {
286
286
  return createRawType('boolean');
287
287
  case 'object': {
288
288
  const { properties = {}, required = [] } = schema;
289
- return createObjectType(Object.fromEntries(Object.entries(properties)
290
- .map(v => [v[0], convertToTypeScriptType(v[1])])), required);
289
+ return createObjectType(Object.fromEntries(Object.entries(properties).map((v) => [v[0], convertToTypeScriptType(v[1])])), required);
291
290
  }
292
291
  case 'array':
293
292
  return createArrayType(convertToTypeScriptType(schema.items, property));
@@ -308,14 +307,19 @@ const setEntityEnumProperty = (enums, prop, meta) => {
308
307
  }
309
308
  };
310
309
  const extractPropertyMetaData = (enums, meta, prop) => {
311
- const result = { service: meta.service, entity: meta.entity };
310
+ const result = {
311
+ service: meta.service,
312
+ entity: meta.entity
313
+ };
312
314
  if (isReferenceObject(prop)) {
313
315
  setEntityEnumProperty(enums, prop, result);
314
316
  result.type = 'reference';
315
317
  return result;
316
318
  }
317
- result.format = prop.format;
318
319
  result.type = prop.type;
320
+ result.format = prop.format;
321
+ result.maxLength = prop.maxLength;
322
+ result.pattern = prop.pattern;
319
323
  if (isArraySchemaObject(prop)) {
320
324
  if (isReferenceObject(prop.items)) {
321
325
  setEntityEnumProperty(enums, prop.items, result);
@@ -335,12 +339,12 @@ const generateType = (name, value) => {
335
339
  return `export type ${name} = ${value.trim()};`;
336
340
  };
337
341
 
338
- const arrayify = (v) => Array.isArray(v) ? v : [v];
342
+ const arrayify = (v) => (Array.isArray(v) ? v : [v]);
339
343
 
340
344
  const generateInterfaceProperties = (entries) => {
341
345
  const properties = entries
342
- .filter(v => v.type !== undefined)
343
- .filter((value, index, array) => array.findIndex(v => v.name === value.name) === index)
346
+ .filter((v) => v.type !== undefined)
347
+ .filter((value, index, array) => array.findIndex((v) => v.name === value.name) === index)
344
348
  .map(({ name, type, required, readonly, comment }) => {
345
349
  const cmd = comment ? `${generateInlineComment(comment)}\n` : '';
346
350
  const req = required ? '' : '?';
@@ -369,34 +373,25 @@ const generateEntities = (schemas, enums) => {
369
373
  if (isEnumSchemaObject(schema)) {
370
374
  continue;
371
375
  }
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 = [];
376
+ const entityInterfaceName = loosePascalCase(schemaName);
377
+ let parentEntityInterfaceName = undefined;
378
+ const entityInterfaceProperties = [];
379
379
  const properties = new Map();
380
- // The parent entity
381
- let extend = undefined;
382
380
  const processProperties = (props = {}) => {
383
381
  for (const [name, property] of Object.entries(props)) {
384
382
  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 });
389
- }
390
- if (meta.service) {
391
- referenceMappingsInterface.push({ name, type: generateString(meta.service), required: true });
392
- }
393
383
  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,
384
+ const comment = isNonArraySchemaObject(property)
385
+ ? property.deprecated
386
+ ? '@deprecated will be removed.'
387
+ : property.format
388
+ ? `format: ${property.format}`
389
+ : undefined
390
+ : undefined;
391
+ entityInterfaceProperties.push({
392
+ name,
393
+ type,
394
+ comment,
400
395
  required: meta.required,
401
396
  readonly: !isReferenceObject(property) && property.readOnly
402
397
  });
@@ -406,7 +401,7 @@ const generateEntities = (schemas, enums) => {
406
401
  if (schema.allOf?.length) {
407
402
  for (const item of schema.allOf) {
408
403
  if (isReferenceObject(item)) {
409
- extend = convertToTypeScriptType(item).toString();
404
+ parentEntityInterfaceName = convertToTypeScriptType(item).toString();
410
405
  }
411
406
  else if (isObjectSchemaObject(item)) {
412
407
  processProperties(item.properties);
@@ -414,11 +409,11 @@ const generateEntities = (schemas, enums) => {
414
409
  }
415
410
  }
416
411
  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
412
  entities.set(schemaName, {
419
- extends: extend ? camelCase(extend) : extend,
413
+ name: schemaName,
420
414
  properties,
421
- source
415
+ parentName: parentEntityInterfaceName ? camelCase(parentEntityInterfaceName) : undefined,
416
+ source: generateStatements(generateInterface(entityInterfaceName, entityInterfaceProperties, parentEntityInterfaceName))
422
417
  });
423
418
  }
424
419
  return entities;
@@ -429,13 +424,10 @@ const generateEntities = (schemas, enums) => {
429
424
  * @param s String to pluralize.
430
425
  */
431
426
  const pluralize = (s) => {
432
- return s.endsWith('s') ? s :
433
- s.endsWith('y') ? `${s.slice(0, -1)}ies` :
434
- `${s}s`;
427
+ return s.endsWith('s') ? s : s.endsWith('y') ? `${s.slice(0, -1)}ies` : `${s}s`;
435
428
  };
436
429
 
437
- /* eslint-disable no-console */
438
- const logger = new class {
430
+ const logger = new (class {
439
431
  active = true;
440
432
  warnings = 0;
441
433
  errors = 0;
@@ -483,21 +475,24 @@ const logger = new class {
483
475
  printSummary() {
484
476
  const format = (v, name, fail, ok) => {
485
477
  const color = v ? fail : ok;
486
- return v === 0 ? `${color('zero')} ${pluralize(name)}` :
487
- v === 1 ? `${color('one')} ${name}` : `${color(v)} ${pluralize(name)}`;
478
+ return v === 0
479
+ ? `${color('zero')} ${pluralize(name)}`
480
+ : v === 1
481
+ ? `${color('one')} ${name}`
482
+ : `${color(v)} ${pluralize(name)}`;
488
483
  };
489
484
  const warnings = format(this.warnings, 'warning', chalk.yellowBright, chalk.greenBright);
490
485
  const errors = format(this.errors, 'error', chalk.redBright, chalk.greenBright);
491
486
  const info = `Finished with ${warnings} and ${errors}.`;
492
487
  this[this.errors ? 'errorLn' : this.warnings ? 'warnLn' : 'successLn'](info);
493
488
  }
494
- };
489
+ })();
495
490
 
496
491
  /**
497
- * ROOT => /article
498
- * COUNT => /article/count
499
- * ENTITY => /article/{id}
500
- * SPECIAL_ROOT => /article/generateImage
492
+ * ROOT => /article
493
+ * COUNT => /article/count
494
+ * ENTITY => /article/{id}
495
+ * SPECIAL_ROOT => /article/generateImage
501
496
  * SPECIAL_ENTITY => /article/id/{id}/generateImag
502
497
  */
503
498
  var WeclappEndpointType;
@@ -509,23 +504,33 @@ var WeclappEndpointType;
509
504
  WeclappEndpointType["GENERIC_ENTITY"] = "GENERIC_ENTITY";
510
505
  })(WeclappEndpointType || (WeclappEndpointType = {}));
511
506
  const parseEndpointPath = (path) => {
512
- const [, entity, ...rest] = path.split('/');
513
- if (!entity) {
507
+ const [, service, ...rest] = path.split('/');
508
+ if (!service) {
514
509
  return undefined;
515
510
  }
516
511
  if (!rest.length) {
517
- return { path, entity, type: WeclappEndpointType.ROOT };
512
+ return { path, service, type: WeclappEndpointType.ROOT };
518
513
  }
519
514
  else if (rest[0] === 'count') {
520
- return { path, entity, type: WeclappEndpointType.COUNT };
515
+ return { path, service, type: WeclappEndpointType.COUNT };
521
516
  }
522
517
  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 };
518
+ return rest.length === 2
519
+ ? { path, service, type: WeclappEndpointType.ENTITY }
520
+ : {
521
+ path,
522
+ service,
523
+ method: rest[2],
524
+ type: WeclappEndpointType.GENERIC_ENTITY
525
+ };
526
526
  }
527
527
  else if (rest.length === 1) {
528
- return { path, entity, method: rest[1], type: WeclappEndpointType.GENERIC_ROOT };
528
+ return {
529
+ path,
530
+ service,
531
+ method: rest[1],
532
+ type: WeclappEndpointType.GENERIC_ROOT
533
+ };
529
534
  }
530
535
  return undefined;
531
536
  };
@@ -547,48 +552,50 @@ const convertParametersToSchema = (parameters = []) => {
547
552
  if (isParameterObject(param) && param.in === 'query') {
548
553
  if (param.schema) {
549
554
  properties.push([param.name, param.schema]);
550
- param.required && required.push(param.name);
555
+ if (param.required)
556
+ required.push(param.name);
551
557
  }
552
558
  }
553
559
  }
554
560
  return {
555
- type: 'object', required,
561
+ type: 'object',
562
+ required,
556
563
  properties: Object.fromEntries(properties)
557
564
  };
558
565
  };
559
566
 
560
- const functionName$5 = 'count';
561
567
  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)
568
+ const functionName = 'count';
569
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
570
+ const entity = aliases.get(endpoint.service) ?? pascalCase(endpoint.service);
571
+ const parametersTypeName = `${functionTypeName}_Parameters`;
572
+ const parametersType = createObjectType({
573
+ params: convertToTypeScriptType(convertParametersToSchema(path.parameters))
574
+ });
575
+ const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, true);
576
+ const filterTypeName = `${functionTypeName}_Filter`;
577
+ const filterTypeSource = generateInterfaceType(filterTypeName, [], [entity]);
578
+ const functionTypeSource = generateArrowFunctionType({
579
+ type: functionTypeName,
580
+ params: [
581
+ `query${parametersType.isFullyOptional() ? '?' : ''}: CountQuery<${filterTypeName}>${path.parameters?.length ? ' & ' + parametersTypeName : ''}`
582
+ ],
583
+ returns: `${resolveResponseType(target)}<number>`
570
584
  });
571
585
  const functionSource = generateArrowFunction({
572
- name: functionName$5,
573
- signature: interfaceName,
574
- returns: `_${functionName$5}(cfg, ${generateString(endpoint.path)}, query)`,
586
+ name: functionName,
587
+ signature: functionTypeName,
588
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query)`,
575
589
  params: ['query']
576
590
  });
577
- const interfaceSource = generateArrowFunctionType({
578
- type: interfaceName,
579
- params: [`query${parameters.isFullyOptional() ? '?' : ''}: CountQuery<${entityFilter}> & ${entityParameters}`],
580
- returns: `${resolveResponseType(target)}<number>`
581
- });
582
591
  return {
583
592
  entity,
584
- name: functionName$5,
585
- type: { name: interfaceName, source: interfaceSource },
586
- func: { name: functionName$5, source: functionSource },
593
+ name: functionName,
594
+ type: { name: functionTypeName, source: functionTypeSource },
595
+ func: { name: functionName, source: functionSource },
587
596
  interfaces: [
588
- {
589
- name: entityParameters,
590
- source: generateInterfaceFromObject(entityParameters, parameters, true)
591
- }
597
+ ...(path.parameters?.length ? [{ name: parametersTypeName, source: parametersTypeSource }] : []),
598
+ { name: filterTypeName, source: filterTypeSource }
592
599
  ]
593
600
  };
594
601
  };
@@ -610,39 +617,33 @@ const generateRequestBodyType = ({ requestBody }) => {
610
617
  return generateBodyType(requestBody) ?? createRawType('unknown');
611
618
  };
612
619
 
613
- const resolveBodyType = ({ responses }) => Object.entries(responses)
614
- .filter(v => v[0].startsWith('2'))[0]?.[1];
620
+ const resolveBodyType = ({ responses }) => Object.entries(responses).filter((v) => v[0].startsWith('2'))[0]?.[1];
615
621
  const generateResponseBodyType = (object) => generateBodyType(resolveBodyType(object)) ?? createRawType('void');
616
622
 
617
- const functionName$4 = 'create';
618
623
  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,
624
+ const functionName = 'create';
625
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
626
+ const functionTypeSource = generateArrowFunctionType({
627
+ type: functionTypeName,
629
628
  params: [`data: DeepPartial<${generateRequestBodyType(path).toString()}>`],
630
629
  returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
631
630
  });
631
+ const functionSource = generateArrowFunction({
632
+ name: functionName,
633
+ signature: functionTypeName,
634
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, data)`,
635
+ params: ['data']
636
+ });
632
637
  return {
633
- entity,
634
- name: functionName$4,
635
- type: { name: interfaceName, source: interfaceSource },
636
- func: { name: functionName$4, source: functionSource }
638
+ entity: pascalCase(endpoint.service),
639
+ name: functionName,
640
+ type: { name: functionTypeName, source: functionTypeSource },
641
+ func: { name: functionName, source: functionSource }
637
642
  };
638
643
  };
639
644
 
640
645
  const generateGenericFunctionName = (path, suffix = '', prefix = '') => {
641
- return camelCase(`${prefix}_` +
642
- path
643
- .replace(/.*\//, '')
644
- .replace(/\W+/, '_')
645
- .replace(/[_]+/, '_') + `_${suffix}`);
646
+ return camelCase(`${prefix}_` + path.replace(/.*\//, '').replace(/\W+/, '_').replace(/[_]+/, '_') + `_${suffix}`);
646
647
  };
647
648
 
648
649
  const insertPathPlaceholder = (path, record) => {
@@ -650,37 +651,33 @@ const insertPathPlaceholder = (path, record) => {
650
651
  };
651
652
 
652
653
  const wrapBody = (type, target) => {
653
- return type.toString() === 'binary' ?
654
- createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') :
655
- type; // node-fetch returns a Blob as well
654
+ return type.toString() === 'binary' ? createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') : type; // node-fetch returns a Blob as well
656
655
  };
657
656
  const generateGenericEndpoint = (suffix) => ({ target, method, path, endpoint }) => {
658
657
  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`;
658
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
659
+ const entityQuery = `${functionTypeName}_Query`;
662
660
  const hasId = endpoint.path.includes('{id}');
663
661
  const params = createObjectType({
664
662
  params: convertToTypeScriptType(convertParametersToSchema(path.parameters)),
665
663
  body: method === 'get' ? undefined : wrapBody(generateRequestBodyType(path), target)
666
664
  });
667
665
  const responseBody = generateResponseBodyType(path);
668
- const forceBlobResponse = String(responseBody.toString() === 'binary');
666
+ const functionTypeSource = generateArrowFunctionType({
667
+ type: functionTypeName,
668
+ params: [...(hasId ? ['id: string'] : []), `query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`],
669
+ returns: `${resolveResponseType(target)}<${wrapBody(responseBody, target).toString()}>`
670
+ });
669
671
  const functionSource = generateArrowFunction({
670
672
  name: functionName,
671
- signature: interfaceName,
673
+ signature: functionTypeName,
672
674
  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()}>`
675
+ returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${String(responseBody.toString() === 'binary')})`
679
676
  });
680
677
  return {
681
- entity,
678
+ entity: pascalCase(endpoint.service),
682
679
  name: functionName,
683
- type: { name: interfaceName, source: interfaceSource },
680
+ type: { name: functionTypeName, source: functionTypeSource },
684
681
  func: { name: functionName, source: functionSource },
685
682
  interfaces: [
686
683
  {
@@ -691,35 +688,38 @@ const generateGenericEndpoint = (suffix) => ({ target, method, path, endpoint })
691
688
  };
692
689
  };
693
690
 
694
- const functionName$3 = 'remove';
695
691
  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}' })}\`)`,
702
- params: ['id']
703
- });
704
- const interfaceSource = generateArrowFunctionType({
705
- type: interfaceName,
706
- params: ['id: string'],
692
+ const functionName = 'remove';
693
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
694
+ const functionTypeSource = generateArrowFunctionType({
695
+ type: functionTypeName,
696
+ params: ['id: string', 'options?: RemoveQuery'],
707
697
  returns: `${resolveResponseType(target)}<void>`
708
698
  });
699
+ const functionSource = generateArrowFunction({
700
+ name: functionName,
701
+ signature: functionTypeName,
702
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options)`,
703
+ params: ['id', 'options?: RemoveQuery']
704
+ });
709
705
  return {
710
- entity,
711
- name: functionName$3,
712
- type: { name: interfaceName, source: interfaceSource },
713
- func: { name: functionName$3, source: functionSource }
706
+ entity: pascalCase(endpoint.service),
707
+ name: functionName,
708
+ type: { name: functionTypeName, source: functionTypeSource },
709
+ func: { name: functionName, source: functionSource }
714
710
  };
715
711
  };
716
712
 
717
- const functionName$2 = 'some';
718
713
  const excludedParameters = [
719
- 'page', 'pageSize', 'sort',
720
- 'serializeNulls', 'properties', 'includeReferencedEntities'
714
+ 'page',
715
+ 'pageSize',
716
+ 'sort',
717
+ 'serializeNulls',
718
+ 'properties',
719
+ 'includeReferencedEntities',
720
+ 'additionalProperties'
721
721
  ];
722
- const resolveAdditionalProperties = (path) => {
722
+ const resolveAdditionalPropertiesSchema = (path) => {
723
723
  const body = resolveBodyType(path);
724
724
  if (isResponseObject(body)) {
725
725
  const schema = body?.content?.['application/json']?.schema;
@@ -732,115 +732,161 @@ const resolveAdditionalProperties = (path) => {
732
732
  }
733
733
  return undefined;
734
734
  };
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)
735
+ const resolveReferences = (entity, entities) => {
736
+ const references = [];
737
+ const generatedEntity = entities.get(entity);
738
+ if (generatedEntity) {
739
+ for (const [property, propertyMetaData] of generatedEntity.properties) {
740
+ if (propertyMetaData.service) {
741
+ references.push({
742
+ name: property,
743
+ type: generateString(propertyMetaData.service),
744
+ required: true
745
+ });
746
+ }
747
+ }
748
+ if (generatedEntity.parentName) {
749
+ references.push(...resolveReferences(generatedEntity.parentName, entities));
750
+ }
751
+ }
752
+ return references;
753
+ };
754
+ const resolveReferencedEntities = (entity, entities) => {
755
+ const referencedEntities = [];
756
+ const generatedEntity = entities.get(entity);
757
+ if (generatedEntity) {
758
+ for (const [, propertyMetaData] of generatedEntity.properties) {
759
+ if (propertyMetaData.entity && propertyMetaData.service) {
760
+ referencedEntities.push({
761
+ name: propertyMetaData.service,
762
+ type: `${pascalCase(propertyMetaData.entity)}[]`,
763
+ required: true
764
+ });
765
+ }
766
+ }
767
+ if (generatedEntity.parentName) {
768
+ referencedEntities.push(...resolveReferencedEntities(generatedEntity.parentName, entities));
769
+ }
770
+ }
771
+ return referencedEntities;
772
+ };
773
+ const generateSomeEndpoint = ({ endpoint, target, path, entities, aliases }) => {
774
+ const functionName = 'some';
775
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
776
+ const entity = aliases.get(endpoint.service) ?? pascalCase(endpoint.service);
777
+ const parametersTypeName = `${functionTypeName}_Parameters`;
778
+ const parameters = path.parameters?.filter((v) => (isParameterObject(v) ? !excludedParameters.includes(v.name) : false)) ?? [];
779
+ const parametersType = createObjectType({
780
+ params: convertToTypeScriptType(convertParametersToSchema(parameters))
753
781
  });
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}>>`
782
+ const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, true);
783
+ const filterTypeName = `${functionTypeName}_Filter`;
784
+ const filterTypeSource = generateInterfaceType(filterTypeName, [], [entity]);
785
+ const referencesTypeName = `${functionTypeName}_References`;
786
+ const referencesTypeSource = generateInterfaceType(referencesTypeName, resolveReferences(endpoint.service, entities));
787
+ const additionalPropertyTypeName = `${functionTypeName}_AdditionalProperty`;
788
+ const additionalPropertyTypeSource = generateType(additionalPropertyTypeName, 'string');
789
+ const queryTypeName = `${functionTypeName}_Query`;
790
+ const queryTypeSource = generateType(queryTypeName, `SomeQuery<${entity}, ${filterTypeName}, ${referencesTypeName}, ${additionalPropertyTypeName}> & ${parametersTypeName}`);
791
+ const referencedEntitiesTypeName = `${functionTypeName}_ReferencedEntities`;
792
+ const referencedEntitiesTypeSource = generateInterfaceType(referencedEntitiesTypeName, resolveReferencedEntities(endpoint.service, entities));
793
+ const additionalPropertiesTypeName = `${functionTypeName}_AdditionalProperties`;
794
+ const additionalPropertiesSchema = resolveAdditionalPropertiesSchema(path);
795
+ const additionalPropertiesTypeSource = generateType(additionalPropertiesTypeName, additionalPropertiesSchema ? convertToTypeScriptType(additionalPropertiesSchema).toString() : '{}');
796
+ const functionTypeSource = generateArrowFunctionType({
797
+ type: functionTypeName,
798
+ params: [`query${parametersType.isFullyOptional() ? '?' : ''}: ${queryTypeName}`],
799
+ returns: `${resolveResponseType(target)}<SomeQueryReturn<${entity}, ${referencedEntitiesTypeName}, ${additionalPropertiesTypeName}>>`
763
800
  });
764
801
  const functionSource = generateArrowFunction({
765
- name: functionName$2,
766
- signature: interfaceName,
767
- returns: `_${functionName$2}(cfg, ${generateString(endpoint.path)}, query)`,
802
+ name: functionName,
803
+ signature: functionTypeName,
804
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query)`,
768
805
  params: ['query']
769
806
  });
770
807
  return {
771
808
  entity,
772
- name: functionName$2,
773
- type: { name: interfaceName, source: interfaceSource },
774
- func: { name: functionName$2, source: functionSource },
809
+ name: functionName,
810
+ type: { name: functionTypeName, source: functionTypeSource },
811
+ func: { name: functionName, source: functionSource },
775
812
  interfaces: [
776
- {
777
- name: entityParameters,
778
- source: generateInterfaceFromObject(entityParameters, parameters, true)
779
- }
813
+ { name: parametersTypeName, source: parametersTypeSource },
814
+ { name: filterTypeName, source: filterTypeSource },
815
+ { name: referencesTypeName, source: referencesTypeSource },
816
+ { name: additionalPropertyTypeName, source: additionalPropertyTypeSource },
817
+ { name: queryTypeName, source: queryTypeSource },
818
+ { name: referencedEntitiesTypeName, source: referencedEntitiesTypeSource },
819
+ { name: additionalPropertiesTypeName, source: additionalPropertiesTypeSource }
780
820
  ]
781
821
  };
782
822
  };
783
823
 
784
- const functionName$1 = 'unique';
785
824
  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,
825
+ const functionName = 'unique';
826
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
827
+ const functionTypeSource = generateArrowFunctionType({
828
+ type: functionTypeName,
796
829
  params: ['id: string', 'query?: Q'],
797
830
  generics: ['Q extends UniqueQuery'],
798
831
  returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
799
832
  });
833
+ const functionSource = generateArrowFunction({
834
+ name: functionName,
835
+ signature: functionTypeName,
836
+ params: ['id', 'query'],
837
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query)`
838
+ });
800
839
  return {
801
- entity,
802
- name: functionName$1,
803
- type: { name: interfaceName, source: interfaceSource },
804
- func: { name: functionName$1, source: functionSource }
840
+ entity: pascalCase(endpoint.service),
841
+ name: functionName,
842
+ type: { name: functionTypeName, source: functionTypeSource },
843
+ func: { name: functionName, source: functionSource }
805
844
  };
806
845
  };
807
846
 
808
- const functionName = 'update';
809
847
  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,
848
+ const functionName = 'update';
849
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
850
+ const functionTypeSource = generateArrowFunctionType({
851
+ type: functionTypeName,
814
852
  params: ['id: string', `data: DeepPartial<${generateRequestBodyType(path).toString()}>`, 'options?: UpdateQuery'],
815
853
  returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
816
854
  });
817
855
  const functionSource = generateArrowFunction({
818
856
  name: functionName,
819
- signature: interfaceName,
857
+ signature: functionTypeName,
820
858
  returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options)`,
821
859
  params: ['id', 'data', 'options']
822
860
  });
823
861
  return {
824
- entity,
862
+ entity: pascalCase(endpoint.service),
825
863
  name: functionName,
826
- type: { name: interfaceName, source: interfaceSource },
864
+ type: { name: functionTypeName, source: functionTypeSource },
827
865
  func: { name: functionName, source: functionSource }
828
866
  };
829
867
  };
830
868
 
831
- const groupEndpointsByEntity = (paths) => {
869
+ const isMultiPartUploadPath = (path) => {
870
+ const [, entity, ...rest] = path.split('/');
871
+ return entity && rest.length === 2 && rest[1] === 'multipartUpload';
872
+ };
873
+ const parseEndpointsAndGroupByEntity = (paths) => {
832
874
  const endpoints = new Map();
833
875
  for (const [rawPath, path] of Object.entries(paths)) {
834
876
  const endpoint = parseEndpointPath(rawPath);
835
877
  if (!endpoint || !path) {
878
+ // Todo: Should be removed if sdk supports multi part upload.
879
+ if (isMultiPartUploadPath(rawPath)) {
880
+ continue;
881
+ }
836
882
  logger.errorLn(`Failed to parse ${rawPath}`);
837
883
  continue;
838
884
  }
839
- if (endpoints.has(endpoint.entity)) {
840
- endpoints.get(endpoint.entity)?.push({ endpoint, path });
885
+ if (endpoints.has(endpoint.service)) {
886
+ endpoints.get(endpoint.service)?.push({ endpoint, path });
841
887
  }
842
888
  else {
843
- endpoints.set(endpoint.entity, [{ endpoint, path }]);
889
+ endpoints.set(endpoint.service, [{ endpoint, path }]);
844
890
  }
845
891
  }
846
892
  return endpoints;
@@ -873,26 +919,34 @@ const generators = {
873
919
  post: generateGenericEndpoint()
874
920
  }
875
921
  };
876
- const generateServices = (doc, aliases, options) => {
922
+ const generateServices = (paths, entities, aliases, options) => {
877
923
  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
924
+ const endpoints = parseEndpointsAndGroupByEntity(paths);
925
+ for (const [serviceName, paths] of endpoints) {
926
+ const serviceFnName = camelCase(`${serviceName}Service`);
927
+ const serviceTypeName = pascalCase(`${serviceName}Service`);
883
928
  const functions = [];
884
929
  for (const { path, endpoint } of paths) {
885
- const resolver = generators[endpoint.type];
930
+ const generator = generators[endpoint.type];
886
931
  for (const [method, config] of Object.entries(path)) {
887
932
  if (method === 'get' && endpoint.type === WeclappEndpointType.ENTITY && !options.generateUnique) {
933
+ // Skip unique endpoints if generateUnique option is not set
888
934
  continue;
889
935
  }
890
- if (resolver[method]) {
936
+ const generatorFn = generator[method];
937
+ if (generatorFn) {
891
938
  const path = config;
892
939
  const target = options.target;
893
940
  if (!path.deprecated || options.deprecated) {
894
941
  functions.push({
895
- ...resolver[method]({ endpoint, method, target, path, aliases }),
942
+ ...generatorFn({
943
+ endpoint,
944
+ method,
945
+ target,
946
+ path,
947
+ entities,
948
+ aliases
949
+ }),
896
950
  path
897
951
  });
898
952
  }
@@ -905,26 +959,28 @@ const generateServices = (doc, aliases, options) => {
905
959
  if (!functions.length) {
906
960
  continue;
907
961
  }
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 => ({
962
+ 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, [
963
+ ...functions.map((v) => ({
911
964
  required: true,
912
965
  comment: v.path.deprecated ? '@deprecated' : undefined,
913
966
  name: v.func.name,
914
967
  type: v.type.name
915
968
  }))
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 });
969
+ ])));
970
+ const serviceFn = `export const ${serviceFnName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${generateBlockStatements(...functions.map((v) => v.func.source), `return {${concat(functions.map((v) => v.func.name))}};`)};`;
971
+ services.set(serviceName, {
972
+ name: serviceName,
973
+ serviceFnName,
974
+ serviceTypeName,
975
+ functions,
976
+ source: generateStatements(serviceTypes, serviceFn),
977
+ deprecated: functions.every((v) => v.path.deprecated)
978
+ });
923
979
  }
924
980
  return services;
925
981
  };
926
982
 
927
- const generateCustomValueUtilities = (entities, services) => {
983
+ const generateCustomValueServices = (entities, services) => {
928
984
  const customValueEntity = entities.get('customValue');
929
985
  const customValueEntities = [];
930
986
  if (!customValueEntity) {
@@ -932,7 +988,7 @@ const generateCustomValueUtilities = (entities, services) => {
932
988
  return '';
933
989
  }
934
990
  serviceLoop: for (const service of services) {
935
- const someFunction = service.functions.find(v => v.name === 'some');
991
+ const someFunction = service.functions.find((v) => v.name === 'some');
936
992
  if (!someFunction) {
937
993
  continue;
938
994
  }
@@ -945,55 +1001,57 @@ const generateCustomValueUtilities = (entities, services) => {
945
1001
  continue serviceLoop;
946
1002
  }
947
1003
  }
948
- customValueEntities.push(service.entity);
1004
+ customValueEntities.push(service.name);
949
1005
  }
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);')}`));
1006
+ 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
1007
  };
952
1008
 
953
1009
  const generateObject = (properties) => {
954
1010
  const body = [];
955
- for (const { key, value } of properties) {
1011
+ for (const { key, value, comment } of properties) {
956
1012
  if (value === undefined) {
957
1013
  continue;
958
1014
  }
959
1015
  if (Array.isArray(value)) {
960
1016
  const str = generateObject(value);
961
1017
  if (str.length > 2) {
962
- body.push(`${key}: ${str}`);
1018
+ body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${str}`);
963
1019
  }
964
1020
  }
965
1021
  else {
966
- body.push(`${key}: ${String(value)}`);
1022
+ body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${String(value)}`);
967
1023
  }
968
1024
  }
969
1025
  return body.length ? `{\n${indent(body.join(',\n'))}\n}` : `{}`;
970
1026
  };
971
1027
 
972
1028
  const resolveInheritedEntities = (root, entities) => {
973
- const parent = root.extends ? entities.get(root.extends) : undefined;
1029
+ const parent = root.parentName ? entities.get(root.parentName) : undefined;
974
1030
  return parent ? [parent, ...resolveInheritedEntities(parent, entities)] : [];
975
1031
  };
976
- const generatePropertyDescriptors = (entity, entities, services, options) => [
977
- ...resolveInheritedEntities(entity, entities).flatMap(v => [...v.properties]),
978
- ...entity.properties
979
- ].filter(([, meta]) => {
1032
+ const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
1033
+ .filter(([, meta]) => {
980
1034
  // If we generate deprecated things we can skip the filtering
981
1035
  if (options.deprecated) {
982
1036
  return true;
983
1037
  }
984
1038
  // Check if corresponding service is deprecated and can be removed
985
- const service = services.find(v => v.entity === meta.service);
1039
+ const service = services.find((v) => v.name === meta.service);
986
1040
  return !meta.service || (service && !service.deprecated);
987
- }).map(([property, meta]) => ({
1041
+ })
1042
+ .map(([property, meta]) => ({
988
1043
  key: property,
989
1044
  value: Object.entries(meta).map(([key, value]) => ({
990
1045
  key,
991
- value: value ? generateString(value) : undefined
1046
+ value: value !== undefined ? (typeof value === 'number' ? value : generateString(value)) : undefined
992
1047
  }))
993
1048
  }));
994
- const generateEntityPropertyMap = (entities, services, options) => {
1049
+ const generateEntityProperties = (entities, aliases, services, options) => {
995
1050
  const typeName = 'WEntityProperties';
996
- const propertyMap = [...entities].map(([entity, data]) => ({
1051
+ const propertyMap = [
1052
+ ...entities.entries(),
1053
+ ...aliases.entries().map(([service, type]) => [service, entities.get(camelCase(type))])
1054
+ ].map(([entity, data]) => ({
997
1055
  key: entity,
998
1056
  value: generatePropertyDescriptors(data, entities, services, options)
999
1057
  }));
@@ -1001,7 +1059,7 @@ const generateEntityPropertyMap = (entities, services, options) => {
1001
1059
  };
1002
1060
 
1003
1061
  const generateArray = (values) => {
1004
- return `[${concat(values.map(v => generateString(String(v))))}]`;
1062
+ return `[${concat(values.map((v) => generateString(String(v))))}]`;
1005
1063
  };
1006
1064
 
1007
1065
  // Only functions matching this regex are included in the generation.
@@ -1014,16 +1072,17 @@ const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
1014
1072
  */
1015
1073
  const generateGroupedServices = (services) => {
1016
1074
  const entityDescriptors = new Map();
1017
- for (const { entity, functions } of services) {
1018
- for (const { name } of functions) {
1019
- if (!FILTER_REGEX.test(name)) {
1075
+ for (const service of services) {
1076
+ for (const fn of service.functions) {
1077
+ if (!FILTER_REGEX.test(fn.name)) {
1020
1078
  continue;
1021
1079
  }
1022
- entityDescriptors.set(name, [
1023
- ...(entityDescriptors.get(name) ?? []), {
1024
- name: entity,
1080
+ entityDescriptors.set(fn.name, [
1081
+ ...(entityDescriptors.get(fn.name) ?? []),
1082
+ {
1083
+ name: service.name,
1025
1084
  required: true,
1026
- type: `${pascalCase(entity)}Service_${pascalCase(name)}`
1085
+ type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}`
1027
1086
  }
1028
1087
  ]);
1029
1088
  }
@@ -1036,123 +1095,92 @@ const generateGroupedServices = (services) => {
1036
1095
  const guard = `(service: string | undefined): service is ${service} =>\n${indent(`${constant}.includes(service as ${service});`)}`;
1037
1096
  typeGuards.push(`export const is${service} = ${guard}`);
1038
1097
  }
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
- ];
1098
+ 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]) => {
1099
+ const constant = camelCase(`wServiceWith_${name}_Names`);
1100
+ const type = pascalCase(`WServiceWith_${name}`);
1101
+ const value = generateArray(props.map((v) => v.name));
1102
+ return `export const ${constant}: ${type}[] = ${value};`;
1103
+ }), ...typeGuards);
1050
1104
  };
1051
1105
 
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') })));
1106
+ const generateMaps = (enums, entities, services, aliases, options) => {
1107
+ const enumInstances = `export const wEnums = ${generateObject(enums
1108
+ .keys()
1109
+ .toArray()
1110
+ .map((v) => ({ key: v, value: v })))};`;
1111
+ const entityNames = `export const wEntityNames: WEntity[] = ${generateArray(entities.keys().toArray())};`;
1112
+ const generatedServices = services.values().toArray();
1113
+ const serviceInstances = `export const wServices = ${generateObject(generatedServices.map((v) => ({
1114
+ key: v.name,
1115
+ value: `${v.serviceFnName}()`,
1116
+ comment: v.deprecated ? '@deprecated' : undefined
1117
+ })))};`;
1118
+ const serviceFactories = `export const wServiceFactories = ${generateObject(generatedServices.map((v) => ({
1119
+ key: v.name,
1120
+ value: v.serviceFnName,
1121
+ comment: v.deprecated ? '@deprecated' : undefined
1122
+ })))};`;
1085
1123
  return {
1086
1124
  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))
1125
+ /* Enums */
1126
+ generateInterface('WEnums', enums
1127
+ .keys()
1128
+ .toArray()
1129
+ .map((name) => ({ name, type: name, required: true }))), generateType('WEnum', 'keyof WEnums'), enumInstances,
1130
+ /* Entities */
1131
+ generateInterface('WEntities', [
1132
+ ...entities.keys().map((name) => ({ name, type: loosePascalCase(name), required: true })),
1133
+ ...aliases.entries().map(([name, type]) => ({ name, type, required: true }))
1134
+ ].sort((a, b) => (a.name > b.name ? 1 : -1))), generateType('WEntity', 'keyof WEntities'), entityNames,
1135
+ /* Services */
1136
+ serviceInstances, generateType('WServices', 'typeof wServices'), generateType('WService', 'keyof WServices'), serviceFactories, generateType('WServiceFactories', 'typeof wServiceFactories'),
1137
+ /* Service Utils */
1138
+ generateGroupedServices(generatedServices), generateCustomValueServices(entities, generatedServices),
1139
+ /* Entity Properties (Runtime Meta Infos) */
1140
+ generateEntityProperties(entities, aliases, generatedServices, options))
1113
1141
  };
1114
1142
  };
1115
1143
 
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();
1144
+ const parseReferencedEntityType = (obj) => pascalCase(obj.$ref.replace(/.*\//, ''));
1145
+ const extractServiceAliases = (doc, schemas) => {
1120
1146
  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
1147
  for (const [path, methods] of Object.entries(doc.paths)) {
1131
1148
  const parsed = parseEndpointPath(path);
1132
- if (!parsed || schemas.has(parsed.entity)) {
1149
+ if (!parsed || !methods || parsed.type !== WeclappEndpointType.ROOT || schemas.has(parsed.service)) {
1133
1150
  continue;
1134
1151
  }
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
- }
1152
+ const body = methods[OpenAPIV3.HttpMethods.GET]?.responses['200'];
1153
+ if (isResponseObject(body) && body.content?.['application/json']) {
1154
+ const responseSchema = body.content['application/json'].schema;
1155
+ if (!responseSchema || isReferenceObject(responseSchema)) {
1156
+ continue;
1157
+ }
1158
+ const resultSchema = responseSchema.properties?.result;
1159
+ if (!resultSchema) {
1160
+ continue;
1161
+ }
1162
+ if (isReferenceObject(resultSchema)) {
1163
+ aliases.set(parsed.service, parseReferencedEntityType(resultSchema));
1164
+ continue;
1165
+ }
1166
+ if (isArraySchemaObject(resultSchema)) {
1167
+ const resultItemSchema = resultSchema.items;
1168
+ if (isReferenceObject(resultItemSchema)) {
1169
+ aliases.set(parsed.service, parseReferencedEntityType(resultItemSchema));
1152
1170
  }
1153
1171
  }
1154
1172
  }
1155
1173
  }
1174
+ return aliases;
1175
+ };
1176
+ const extractSchemas = (doc) => {
1177
+ const schemas = new Map();
1178
+ for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
1179
+ if (!isReferenceObject(schema)) {
1180
+ schemas.set(name, schema);
1181
+ }
1182
+ }
1183
+ const aliases = extractServiceAliases(doc, schemas);
1156
1184
  return { schemas, aliases };
1157
1185
  };
1158
1186
 
@@ -1160,14 +1188,18 @@ const generate = (doc, options) => {
1160
1188
  const { schemas, aliases } = extractSchemas(doc);
1161
1189
  const enums = generateEnums(schemas);
1162
1190
  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));
1191
+ const services = generateServices(doc.paths, entities, aliases, options);
1192
+ const maps = generateMaps(enums, entities, services, aliases, options);
1193
+ return generateStatements(generateBase(options.target, doc.info.version, options), generateBlockComment('ENUMS', generateStatements(...enums
1194
+ .values()
1195
+ .map((v) => v.source)
1196
+ .toArray())), generateBlockComment('ENTITIES', generateStatements(...entities
1197
+ .values()
1198
+ .map((v) => v.source)
1199
+ .toArray())), generateBlockComment('SERVICES', generateStatements(...services
1200
+ .values()
1201
+ .map((v) => v.source)
1202
+ .toArray())), generateBlockComment('MAPS', generateStatements(maps.source)));
1171
1203
  };
1172
1204
 
1173
1205
  const hash = (content, algorithm = 'sha256') => {
@@ -1183,12 +1215,13 @@ const hash = (content, algorithm = 'sha256') => {
1183
1215
 
1184
1216
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
1185
1217
  const cli = async () => {
1186
- const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
1218
+ const version = pkg.version;
1187
1219
  const { argv } = yargs(hideBin(process.argv))
1188
1220
  .scriptName('build-weclapp-sdk')
1189
1221
  .usage('Usage: $0 <source> [flags]')
1190
1222
  .version(version)
1191
1223
  .example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
1224
+ .example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
1192
1225
  .example('$0 xxx.weclapp.com --key ...', 'Generate the SDK based on the openapi file from the given weclapp instance')
1193
1226
  .help('h')
1194
1227
  .alias('v', 'version')
@@ -1228,9 +1261,8 @@ const cli = async () => {
1228
1261
  type: 'string',
1229
1262
  choices: ['browser', 'browser.rx', 'node', 'node.rx']
1230
1263
  })
1231
- .option('d', {
1232
- alias: 'deprecated',
1233
- describe: 'Include deprecated functions and services',
1264
+ .option('use-query-language', {
1265
+ describe: 'Generate the new where property for some and count queries',
1234
1266
  type: 'boolean'
1235
1267
  })
1236
1268
  .epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
@@ -1242,21 +1274,27 @@ const cli = async () => {
1242
1274
  const options = {
1243
1275
  deprecated,
1244
1276
  generateUnique: argv.generateUnique ?? false,
1245
- target: argv.target ?? Target.BROWSER_PROMISES
1277
+ target: argv.target ?? Target.BROWSER_PROMISES,
1278
+ useQueryLanguage: argv.useQueryLanguage ?? false
1246
1279
  };
1247
- if (typeof src === 'number') {
1248
- return Promise.reject('Expected string as command');
1280
+ if (!src || typeof src === 'number') {
1281
+ return Promise.reject(new Error('Expected string as command'));
1249
1282
  }
1250
1283
  if (!Object.values(Target).includes(options.target)) {
1251
1284
  logger.errorLn(`Unknown target: ${options.target}. Possible values are ${Object.values(Target).join(', ')}`);
1252
- return Promise.reject();
1285
+ return Promise.reject(new Error());
1253
1286
  }
1254
1287
  if (await stat(src).catch(() => false)) {
1255
- logger.infoLn(`Source is a file.`);
1288
+ logger.infoLn(`Source is a file`);
1256
1289
  const content = JSON.parse(await readFile(src, 'utf-8'));
1257
1290
  return { cache, content, options };
1258
1291
  }
1292
+ logger.infoLn(`Source is a URL`);
1293
+ if (!key) {
1294
+ return Promise.reject(new Error('API key is missing'));
1295
+ }
1259
1296
  const url = new URL(src.startsWith('http') ? src : `https://${src}`);
1297
+ // At the moment just v1
1260
1298
  url.pathname = '/webapp/api/v1/meta/openapi.json';
1261
1299
  if (query?.length) {
1262
1300
  for (const param of query.split(',')) {
@@ -1265,11 +1303,11 @@ const cli = async () => {
1265
1303
  }
1266
1304
  }
1267
1305
  const content = await fetch(url.toString(), {
1268
- headers: { 'Accept': 'application/json', 'AuthenticationToken': key }
1269
- }).then(res => res.ok ? res.json() : undefined);
1306
+ headers: { Accept: 'application/json', AuthenticationToken: key }
1307
+ }).then((res) => (res.ok ? res.json() : undefined));
1270
1308
  if (!content) {
1271
1309
  logger.errorLn(`Couldn't fetch file ${url.toString()} `);
1272
- return Promise.reject();
1310
+ return Promise.reject(new Error());
1273
1311
  }
1274
1312
  else {
1275
1313
  logger.infoLn(`Use remote file: ${url.toString()}`);
@@ -1277,50 +1315,59 @@ const cli = async () => {
1277
1315
  return { cache, content, options };
1278
1316
  };
1279
1317
 
1280
- const workingDirectory = resolve(currentDirname(), './sdk');
1281
- const folders = ['docs', 'main', 'node', 'raw', 'rx', 'utils'];
1318
+ const workingDir = resolve(currentDirname(), './sdk');
1319
+ const cacheDir = resolve(currentDirname(), './.cache');
1282
1320
  void (async () => {
1283
1321
  const start = process.hrtime.bigint();
1284
- const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
1285
1322
  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);
1323
+ const workingDirPath = async (...paths) => {
1324
+ const fullPath = resolve(workingDir, ...paths);
1325
+ await mkdir(dirname(fullPath), { recursive: true });
1293
1326
  return fullPath;
1294
1327
  };
1328
+ // Resolve cache dir and key
1329
+ const cacheKey = hash([pkg.version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
1330
+ const cachedSdkDir = resolve(cacheDir, cacheKey);
1331
+ // Remove old SDK
1332
+ await rm(workingDir, { recursive: true, force: true });
1295
1333
  if (useCache) {
1296
1334
  logger.infoLn(`Cache ID: ${cacheKey}`);
1297
1335
  }
1298
- if (useCache && await stat(cacheDir).catch(() => false)) {
1299
- logger.successLn(`Cache match! (${cacheDir})`);
1336
+ if (useCache && (await stat(cachedSdkDir).catch(() => false))) {
1337
+ // Copy cached SDK to working dir
1338
+ logger.successLn(`Cache match! (${cachedSdkDir})`);
1339
+ await cp(cachedSdkDir, workingDir, { recursive: true });
1300
1340
  }
1301
1341
  else {
1302
- // Store swagger.json file
1303
- await writeFile(await tmp('openapi.json'), JSON.stringify(doc, null, 2));
1342
+ // Write openapi.json file
1343
+ await writeFile(await workingDirPath('openapi.json'), JSON.stringify(doc, null, 2));
1304
1344
  logger.infoLn(`Generate sdk (target: ${options.target})`);
1305
- // Generate SDKs
1345
+ // Generate and write SDK (index.ts)
1306
1346
  const sdk = generate(doc, options);
1307
- await writeFile(await tmp('src', `${options.target}.ts`), sdk.trim() + '\n');
1308
- // Bundle
1347
+ await writeFile(await workingDirPath('src', 'index.ts'), sdk.trim() + '\n');
1348
+ // Bundle and write SDK
1309
1349
  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)));
1350
+ await bundle(workingDir, options.target);
1351
+ // Remove index.ts (only bundle is required)
1352
+ // await rm(await workingDirPath('src'), { recursive: true, force: true });
1353
+ if (useCache) {
1354
+ // Copy SDK to cache
1355
+ logger.successLn(`Caching SDK: (${cachedSdkDir})`);
1356
+ await mkdir(cachedSdkDir, { recursive: true });
1357
+ await cp(workingDir, cachedSdkDir, { recursive: true });
1358
+ }
1313
1359
  }
1314
- // Copy bundled SDK
1315
- await cp(cacheDir, workingDirectory, { recursive: true });
1316
1360
  // Print job summary
1317
1361
  const duration = (process.hrtime.bigint() - start) / 1000000n;
1318
1362
  logger.successLn(`SDK built in ${prettyMs(Number(duration))}`);
1319
1363
  logger.printSummary();
1320
- })().catch((error) => {
1364
+ })()
1365
+ .catch((error) => {
1321
1366
  logger.errorLn(`Fatal error:`);
1322
1367
  /* eslint-disable no-console */
1323
1368
  console.error(error);
1324
- }).finally(() => {
1325
- logger.errors && process.exit(1);
1369
+ })
1370
+ .finally(() => {
1371
+ if (logger.errors)
1372
+ process.exit(1);
1326
1373
  });