@weclapp/sdk 2.0.0-dev.6 → 2.0.0-dev.61

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
7
  import { snakeCase, pascalCase, camelCase } from 'change-case';
9
8
  import chalk from 'chalk';
10
9
  import { OpenAPIV3 } from 'openapi-types';
11
10
  import { createHash } from 'crypto';
11
+ import { stat, readFile, rm, cp, writeFile, mkdir } from 'fs/promises';
12
12
  import { config } from 'dotenv';
13
13
  import yargs from 'yargs';
14
14
  import { hideBin } from 'yargs/helpers';
15
+ import pkg from '../package.json' with { type: 'json' };
15
16
  import prettyMs from 'pretty-ms';
16
17
 
18
+ const currentDirname = () => {
19
+ // Go one level up as the CLI is inside a folder
20
+ return fileURLToPath(new URL('..', import.meta.url));
21
+ };
22
+
17
23
  var Target;
18
24
  (function (Target) {
19
25
  Target["BROWSER_PROMISES"] = "browser";
@@ -34,15 +40,10 @@ const resolveBinaryType = (target) => {
34
40
  return isNodeTarget(target) ? 'Buffer' : 'Blob';
35
41
  };
36
42
 
37
- const currentDirname = () => {
38
- // Go one level up as the CLI is inside a folder
39
- return fileURLToPath(new URL('..', import.meta.url));
40
- };
41
-
42
- const tsconfig = resolve(currentDirname(), './tsconfig.lib.json');
43
- const resolveGlobals = (...globals) => Object.fromEntries(globals.map(v => [v, '*']));
43
+ const tsconfig = resolve(currentDirname(), './tsconfig.sdk.json');
44
+ const resolveGlobals = (...globals) => Object.fromEntries(globals.map((v) => [v, '*']));
44
45
  const generateOutput = (config) => ({
45
- sourcemap: true,
46
+ sourcemap: false,
46
47
  banner: `/* weclapp sdk */`,
47
48
  ...config
48
49
  });
@@ -61,35 +62,22 @@ const bundle = async (workingDirectory, target) => {
61
62
  globals: resolveGlobals('node-fetch', 'url')
62
63
  })
63
64
  ];
64
- // Remove build dir
65
- await rmdir(dist()).catch(() => void 0);
66
65
  const bundles = {
67
66
  [Target.BROWSER_PROMISES]: () => ({
68
- input: src('browser.ts'),
67
+ input: src('index.ts'),
68
+ plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
69
69
  output: [
70
- generateOutput({
71
- file: dist('index.cjs'),
72
- name: 'Weclapp',
73
- format: 'umd'
74
- }),
75
70
  generateOutput({
76
71
  file: dist('index.js'),
77
72
  format: 'es'
78
73
  })
79
- ],
80
- plugins: [ts({ tsconfig }), terser()]
74
+ ]
81
75
  }),
82
76
  [Target.BROWSER_RX]: () => ({
77
+ input: src('index.ts'),
78
+ plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
83
79
  external: ['rxjs'],
84
- input: src('browser.rx.ts'),
85
- plugins: [ts({ tsconfig }), terser()],
86
80
  output: [
87
- generateOutput({
88
- file: dist('index.cjs'),
89
- name: 'Weclapp',
90
- format: 'umd',
91
- globals: resolveGlobals('rxjs')
92
- }),
93
81
  generateOutput({
94
82
  file: dist('index.js'),
95
83
  format: 'es',
@@ -98,16 +86,16 @@ const bundle = async (workingDirectory, target) => {
98
86
  ]
99
87
  }),
100
88
  [Target.NODE_PROMISES]: () => ({
101
- input: src('node.ts'),
102
- output: generateNodeOutput(),
89
+ input: src('index.ts'),
90
+ plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
103
91
  external: ['node-fetch', 'url'],
104
- plugins: [ts({ tsconfig })]
92
+ output: generateNodeOutput()
105
93
  }),
106
94
  [Target.NODE_RX]: () => ({
107
- input: src('node.rx.ts'),
108
- output: generateNodeOutput(),
95
+ input: src('index.ts'),
96
+ plugins: [ts({ tsconfig, declarationDir: dist(), filterRoot: src() }), terser()],
109
97
  external: ['node-fetch', 'url', 'rxjs'],
110
- plugins: [ts({ tsconfig })]
98
+ output: generateNodeOutput()
111
99
  })
112
100
  };
113
101
  const config = bundles[target]();
@@ -139,14 +127,26 @@ const indent = (s, level = 1) => {
139
127
  };
140
128
 
141
129
  const generateStatements = (...statements) => statements
142
- .map(v => v.trim())
143
- .filter(v => v.length)
130
+ .map((v) => v.trim())
131
+ .filter((v) => v.length)
144
132
  .join('\n\n');
145
133
  const generateBlockStatements = (...statements) => `{\n${indent(generateStatements(...statements))}\n}`;
146
134
 
147
- var root = "export type RequestPayloadMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH';\n\nexport interface RequestPayload {\n method?: RequestPayloadMethod;\n query?: Record<string, any>;\n body?: any;\n unwrap?: boolean;\n forceBlob?: boolean;\n}\n\nexport interface ServiceConfig {\n\n // Your API-Key, this is optional in the sense of if you omit this, and you're in a browser, the\n // cookie-authentication (include-credentials) will be used.\n key?: string;\n\n // Your domain, if omitted location.host will be used.\n host?: string;\n\n // If you want to use https, defaults to true.\n secure?: boolean;\n\n // Optional request/response interceptors.\n interceptors?: {\n\n // Takes the generated request, you can either return a new request,\n // a response (which will be taken as \"the\" response) or nothing.\n // The payload contains the raw input generated by the SDK.\n request?: (request: Request, payload: RequestPayload) => Request | Response | void | Promise<Request | Response | void>;\n\n // Takes the response. This can either be the one from the server or an\n // artificially-crafted one by the request interceptor.\n response?: (response: Response) => Response | void | Promise<Response | void>;\n };\n}\n\nlet globalConfig: ServiceConfig | undefined;\nexport const getGlobalConfig = (): ServiceConfig | undefined => globalConfig;\nexport const setGlobalConfig = (cfg?: ServiceConfig) => globalConfig = cfg;\n\nexport const raw = async (\n cfg: ServiceConfig | undefined = globalConfig,\n endpoint: string,\n payload: RequestPayload = {}\n): Promise<any> => {\n if (!cfg) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n cfg = {\n ...globalConfig, ...cfg,\n interceptors: {...globalConfig?.interceptors, ...cfg?.interceptors}\n };\n\n const isBinaryData = payload.body instanceof resolveBinaryObject();\n const params = new URLSearchParams(Object.entries(payload.query ?? {}).filter(v => v[1] !== undefined));\n const protocol = (cfg.secure ?? true) ? 'https' : 'http';\n\n const interceptRequest = cfg.interceptors?.request ?? (v => v);\n const interceptResponse = cfg.interceptors?.response ?? (v => v);\n\n let host = cfg.host?.replace(/^https?:\\/\\//, '');\n if (!host && typeof location !== 'undefined') {\n host = location.host;\n }\n\n if (!host) {\n throw new Error('Please specify a domain');\n }\n\n const request = new Request(`${protocol}://${host}/webapp/api/v1${endpoint}?${params}`, {\n ...(payload.body && {\n body: isBinaryData\n ? payload.body\n : JSON.stringify(payload.body, (key, value) => value === undefined ? null : value)\n }),\n ...(!cfg.key && {credentials: 'same-origin'}),\n method: payload.method ?? 'get',\n headers: {\n 'Accept': 'application/json',\n ...(cfg.key && {'AuthenticationToken': cfg.key}),\n ...(!isBinaryData && {'Content-Type': 'application/json'})\n }\n });\n\n let res = await interceptRequest(request, payload) ?? request;\n if (!(res instanceof Response)) {\n res = await fetch(res);\n }\n\n res = await interceptResponse(res) ?? res;\n const data = (!payload.forceBlob || !res.ok) && res.headers?.get('content-type')?.includes('application/json') ?\n await res.json() : await res.blob();\n\n // Check if response was successful\n if (!res.ok) {\n return Promise.reject(data);\n }\n\n return payload.unwrap ? data.result : data;\n};\n\nconst _count = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: CountQuery<any> & {params?: Record<any, any>}\n) => wrapResponse(() => raw(cfg, endpoint, {\n unwrap: true,\n query: {\n ...flattenFilter(query?.filter),\n ...flattenOrFilter(query?.or),\n ...query?.params\n }\n}));\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any, any> & {params?: Record<any, any>}\n) => wrapResponse(() => raw(cfg, endpoint, {\n query: {\n serializeNulls: query?.serializeNulls,\n additionalProperties: query?.properties?.join(','),\n properties: query?.select ? flattenSelect(query.select).join(',') : undefined,\n includeReferencedEntities: query?.include ? Object.keys(query.include).join(',') : undefined,\n ...flattenOrFilter(query?.or),\n ...flattenFilter(query?.filter),\n ...flattenSort(query?.sort),\n ...query?.params,\n ...query?.pagination\n }\n }).then(data => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n);\n\nconst _remove = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n {dryRun = false}: RemoveQuery = {}\n) => wrapResponse(() => raw(cfg, endpoint, {\n method: 'DELETE',\n query: {dryRun}\n}).then(() => undefined));\n\nconst _create = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n data: any\n) => wrapResponse(() => raw(cfg, endpoint, {\n method: 'POST',\n body: data\n}));\n\nconst _update = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n data: any,\n {ignoreMissingProperties = true, dryRun = false}: UpdateQuery = {}\n) => wrapResponse(() => raw(cfg, endpoint, {\n method: 'PUT',\n body: data,\n query: {ignoreMissingProperties, dryRun}\n}));\n\nconst _unique = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: UniqueQuery\n) => wrapResponse(() => raw(cfg, endpoint, {query}));\n\nconst _generic = (\n cfg: ServiceConfig | undefined,\n method: RequestPayloadMethod,\n endpoint: string,\n payload?: GenericQuery<any, any>,\n forceBlob?: boolean\n) => wrapResponse(() => raw(cfg, endpoint, {\n method,\n forceBlob,\n body: payload?.body,\n query: payload?.params\n}));\n";
135
+ var globalConfig = "export type RequestPayloadMethod =\n | 'GET'\n | 'HEAD'\n | 'POST'\n | 'PUT'\n | 'DELETE'\n | 'CONNECT'\n | 'OPTIONS'\n | 'TRACE'\n | 'PATCH';\n\nexport interface RequestPayload {\n method?: RequestPayloadMethod;\n query?: Record<string, any>;\n body?: any;\n unwrap?: boolean;\n forceBlob?: boolean;\n}\n\nexport interface ServiceConfig {\n // Your API-Key, this is optional in the sense of if you omit this, and you're in a browser, the\n // cookie-authentication (include-credentials) will be used.\n key?: string;\n\n // Your domain, if omitted location.host will be used (browser env).\n host?: string;\n\n // If you want to use https, defaults to location.protocol (browser env).\n secure?: boolean;\n\n // If you want that some and count requests are bundled into multi requests.\n multiRequest?: boolean;\n\n // If you want that the ignoreMissingProperties parameter to be set to true for every post request.\n ignoreMissingProperties?: boolean;\n\n // Optional request/response interceptors.\n interceptors?: {\n // Takes the generated request, you can either return a new request,\n // a response (which will be taken as \"the\" response) or nothing.\n // The payload contains the raw input generated by the SDK.\n request?: (\n request: Request,\n payload: RequestPayload\n ) => Request | Response | void | Promise<Request | Response | void>;\n\n // Takes the response. This can either be the one from the server or an\n // artificially-crafted one by the request interceptor.\n response?: (response: Response) => Response | void | Promise<Response | void>;\n };\n\n // Whether POST should be used instead of GET for some() and count() operations\n usePost?: boolean;\n}\n\nexport interface RequestOptions {\n signal?: AbortSignal;\n}\n\ntype ServiceConfigWithoutMultiRequest = Omit<ServiceConfig, 'multiRequest'>;\n\nlet globalConfig: ServiceConfig | undefined;\nexport const getGlobalConfig = (): ServiceConfig | undefined => globalConfig;\nexport const setGlobalConfig = (cfg?: ServiceConfig) => (globalConfig = cfg);\n\nexport const getHost = (cfg: ServiceConfig) => {\n let host = cfg.host?.replace(/^https?:\\/\\//, '');\n if (!host && typeof location !== 'undefined') {\n host = location.host;\n }\n\n if (!host) {\n throw new Error('Please specify a host');\n }\n\n return host;\n};\n\nexport const getProtocol = (cfg: ServiceConfig) => {\n const protocol =\n cfg.secure !== undefined\n ? cfg.secure\n ? 'https:'\n : 'http:'\n : typeof location !== 'undefined'\n ? location.protocol\n : undefined;\n\n if (!protocol) {\n throw new Error('Please specify a protocol (secure)');\n }\n\n return protocol;\n};\n";
136
+
137
+ var multiRequest = "type RequestTask = {\n uri: string;\n resolve: (result: unknown) => void;\n reject: (error: unknown) => void;\n};\n\ntype BatchRequestTask = RequestTask & {\n settled: boolean;\n};\n\ntype MultiRequestResponse = {\n status: number;\n body: object;\n};\n\nlet microtaskQueued: boolean = false;\nconst tasksSet: Set<RequestTask> = new Set<RequestTask>();\n\nconst SQUARE_BRACKET_OPEN = '['.charCodeAt(0);\nconst COMMA = ','.charCodeAt(0);\nconst DECODER = new TextDecoder();\n\nconst readNextResponse = (bytes: Uint8Array<ArrayBuffer>) => {\n let headerStart: number | undefined = undefined;\n let commasSeen = 0;\n\n for (let i = 0; i < bytes.length; i++) {\n const byte = bytes[i];\n if (headerStart === undefined) {\n if (byte === SQUARE_BRACKET_OPEN || byte === COMMA) {\n headerStart = i + 1;\n }\n } else {\n if (byte === COMMA) {\n commasSeen++;\n }\n if (commasSeen === 2) {\n const headerArrayString = `[${DECODER.decode(bytes.subarray(headerStart, i))}]`;\n const [index, jsonLength] = JSON.parse(headerArrayString);\n if (!(typeof index === 'number') || !(typeof jsonLength === 'number')) {\n throw new Error(`unexpected header: ${headerArrayString}`);\n }\n\n const endIndex = i + 1 + jsonLength;\n if (endIndex > bytes.length) {\n // not all bytes available yet\n return undefined;\n }\n const jsonString = DECODER.decode(bytes.subarray(i + 1, endIndex));\n const data = JSON.parse(jsonString) as MultiRequestResponse;\n return {\n index,\n data,\n remainingBytes: bytes.subarray(endIndex)\n };\n }\n }\n }\n return undefined;\n};\n\nconst fetchMultiRequest = async (requests: string[]) => {\n const cfg = getGlobalConfig();\n\n if (!cfg) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const host = getHost(cfg);\n const protocol = getProtocol(cfg);\n\n return await fetch(`${protocol}//${host}/webapp/api/v2/batch/query`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(cfg.key && { AuthenticationToken: cfg.key })\n },\n body: JSON.stringify({ requests })\n });\n};\n\nconst rejectTasks = (tasks: BatchRequestTask[], error: unknown) => {\n for (const task of tasks) {\n if (!task.settled) {\n task.reject(error);\n }\n }\n};\n\nconst processStream = (\n { value: chunk, done }: ReadableStreamReadResult<Uint8Array>,\n remainingBytes: Uint8Array,\n reader: ReadableStreamDefaultReader<Uint8Array>,\n tasks: BatchRequestTask[]\n) => {\n if (done) {\n return;\n }\n if (chunk) {\n let bytes = new Uint8Array(remainingBytes.length + chunk.length);\n bytes.set(remainingBytes);\n bytes.set(chunk, remainingBytes.length);\n\n while (bytes.length) {\n const result = readNextResponse(bytes);\n if (!result) {\n break;\n }\n const task = tasks[result.index];\n if (result.data.status >= 100 && result.data.status < 400) {\n task.resolve({\n ...result.data.body\n });\n } else {\n task.reject({\n ...result.data.body\n });\n }\n task.settled = true;\n bytes = result.remainingBytes;\n }\n reader\n .read()\n .then((readResult) => processStream(readResult, bytes, reader, tasks))\n .catch((error) => rejectTasks(tasks, error));\n }\n};\n\nconst batch = async (tasks: BatchRequestTask[]) => {\n try {\n const requests = tasks.map(({ uri }) => uri);\n const resp = await fetchMultiRequest(requests);\n const reader = resp.body?.getReader();\n\n if (!reader) {\n throw new Error('Stream reader is undefined');\n }\n reader\n .read()\n .then((readResult) => processStream(readResult, new Uint8Array(0), reader, tasks))\n .catch((error) => rejectTasks(tasks, error));\n } catch (e) {\n rejectTasks(tasks, e);\n throw e;\n }\n};\n\nconst addTask = (task: RequestTask) => {\n tasksSet.add(task);\n\n if (!microtaskQueued) {\n queueMicrotask(() => {\n microtaskQueued = false;\n if (tasksSet.size > 0) {\n const batchTasks = Array.from(tasksSet).map((task) => ({ ...task, settled: false }));\n void batch(batchTasks);\n tasksSet.clear();\n }\n });\n microtaskQueued = true;\n }\n};\n\nconst addRequest = (uri: string) => new Promise((resolve, reject) => addTask({ uri, resolve, reject }));\n";
138
+
139
+ var queriesWithFilter = "export type EqualityOperator = 'EQ' | 'NE';\n\nexport type ComparisonOperator =\n | 'LT'\n | 'GT'\n | 'LE'\n | 'GE'\n | 'LIKE'\n | 'ILIKE'\n | 'NOT_LIKE'\n | 'NOT_ILIKE'\n | 'IEQ'\n | 'NOT_IEQ';\n\nexport type ArrayOperator = 'IN' | 'NOT_IN';\n\nexport type Operator = EqualityOperator | ComparisonOperator | ArrayOperator;\n\nexport type MapOperators<T> = { [K in EqualityOperator]?: T | null } & { [K in ComparisonOperator]?: T } & {\n [K in ArrayOperator]?: T[];\n};\n\nexport type QueryFilter<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? U extends Record<any, any>\n ? QueryFilter<U>\n : MapOperators<U>\n : T[P] extends Record<any, any> | undefined\n ? QueryFilter<T[P]>\n : MapOperators<T[P]>;\n};\n\nexport type CountQuery<F> = {\n filter?: QueryFilter<F>;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\n};\n\nexport type SomeQuery<E, F, I, P> = {\n serializeNulls?: boolean;\n include?: QuerySelect<I>;\n properties?: P;\n filter?: QueryFilter<F> & CustomAttributeFilter;\n select?: QuerySelect<E>;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\n sort?: Sort<E>[];\n pagination?: Pagination;\n};\n\nconst equality: string[] = ['EQ', 'NE', 'IEQ', 'NOT_IEQ'];\n\nconst simple: string[] = [...equality, 'LT', 'GT', 'LE', 'GE', 'LIKE', 'NOT_LIKE', 'ILIKE', 'NOT_ILIKE'];\n\nconst array: string[] = ['IN', 'NOT_IN'];\n\nconst filterMap: Record<Operator, string> = {\n EQ: 'eq',\n NE: 'ne',\n LT: 'lt',\n GT: 'gt',\n LE: 'le',\n GE: 'ge',\n LIKE: 'like',\n NOT_LIKE: 'notlike',\n ILIKE: 'ilike',\n NOT_ILIKE: 'notilike',\n IN: 'in',\n NOT_IN: 'notin',\n IEQ: 'ieq',\n NOT_IEQ: 'notieq'\n};\n\nconst flattenCustomAttributes = (obj: CustomAttributeFilter = {}): [string, string][] => {\n const entries: [string, string][] = [];\n\n for (const [id, filter] of Object.entries(obj)) {\n const key = `customAttribute${id}`;\n\n if (typeof filter === 'object') {\n for (const [prop, value] of Object.entries(filter)) {\n entries.push([`${key}.${prop}-eq`, String(value)]);\n }\n } else if (filter !== undefined) {\n entries.push([`${key}-eq`, String(filter)]);\n }\n }\n\n return entries;\n};\n\nconst flatten = (obj: QueryFilter<any> = {}): [string, string][] => {\n const entries: [string, string][] = [];\n\n for (const [prop, propValue] of Object.entries(obj)) {\n for (const [filter, value] of Object.entries(propValue as object)) {\n if (value === undefined) continue;\n\n if (simple.includes(filter) || array.includes(filter)) {\n if (value === null && equality.includes(filter)) {\n entries.push([`${prop}-${filter === 'EQ' ? 'null' : 'notnull'}`, '']);\n } else {\n entries.push([`${prop}-${filterMap[filter as Operator]}`, value]);\n }\n } else {\n entries.push(\n ...(flatten(propValue as QueryFilter<any>).map((v) => [`${prop}.${v[0]}`, v[1]]) as [string, string][])\n );\n break;\n }\n }\n }\n\n return entries;\n};\n\nconst flattenFilter = (obj: QueryFilter<any> = {}): Record<string, string> => {\n const filter: [string, any][] = [],\n customAttributes: [string, any][] = [];\n\n Object.entries(obj).forEach((value) => {\n (value[0].match(/^\\d+$/) ? customAttributes : filter).push(value);\n });\n\n return Object.fromEntries([\n ...flatten(Object.fromEntries(filter)),\n ...flattenCustomAttributes(Object.fromEntries(customAttributes) as CustomAttributeFilter)\n ]);\n};\n\nconst flattenOrFilter = (obj: QueryFilter<any>[] = []): Record<string, string> => {\n const entries: [string, any][] = [];\n\n for (let i = 0; i < obj.length; i++) {\n entries.push(...(flatten(obj[i]).map((v) => [`or${i || ''}-${v[0]}`, v[1]]) as [string, string][]));\n }\n\n return Object.fromEntries(entries);\n};\n\nconst _count = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: CountQuery<any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n{\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n ...flattenFilter(query?.filter),\n ...flattenOrFilter(query?.or),\n ...query?.params\n }\n\n return wrapResponse(() =>\n raw(cfg, endpoint, {\n method: usePost ? 'POST' : 'GET',\n unwrap: true,\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions)\n )\n };\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n{\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n serializeNulls: query?.serializeNulls,\n additionalProperties: usePost ? query?.properties : query?.properties?.join(','),\n properties: query?.select\n ? usePost\n ? flattenSelect(query.select)\n : flattenSelect(query.select).join(',')\n : undefined,\n includeReferencedEntities: query?.include\n ? usePost\n ? Object.keys(query.include)\n : Object.keys(query.include).join(',')\n : undefined,\n ...flattenOrFilter(query?.or),\n ...flattenFilter(query?.filter),\n ...flattenSort(query?.sort, usePost),\n ...query?.params,\n ...query?.pagination\n }\n\n return wrapResponse(() =>\n raw(cfg, usePost ? `${endpoint}/query` : endpoint, {\n method: usePost ? 'POST' : 'GET',\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n )\n };\n";
148
140
 
149
- var types = "export type DeepPartial<T> = T extends object ? {\n [P in keyof T]?: DeepPartial<T[P]>;\n} : T;\n\n// Filter properties\nexport type EqualityOperators = 'EQ' | 'NE';\nexport type ComparisonOperators = 'LT' | 'GT' | 'LE' | 'GE' | 'LIKE' | 'ILIKE' | 'NOT_LIKE' | 'NOT_ILIKE';\nexport type ArrayOperators = 'IN' | 'NOT_IN';\nexport type Operator = EqualityOperators | ComparisonOperators | ArrayOperators;\n\nexport type MapOperators<T> = { [K in EqualityOperators]?: T | null; } &\n { [K in ComparisonOperators]?: T; } &\n { [K in ArrayOperators]?: T[]; };\n\nexport type QueryFilter<T> = {\n [P in keyof T]?:\n T[P] extends Array<infer U> | undefined ?\n U extends Record<any, any> ? QueryFilter<U> : MapOperators<U> :\n T[P] extends Record<any, any> | undefined ? QueryFilter<T[P]> : MapOperators<T[P]>;\n};\n\nexport type Sort<T> = {\n [K in keyof T]?: {\n [V in keyof T]?:\n V extends K ?\n T[V] extends Array<infer U> | undefined ?\n U extends object ?\n Sort<U> : never :\n T[V] extends object | undefined ?\n Sort<T[V]> : 'asc' | 'desc' : never;\n };\n}[keyof T];\n\n// Select properties\nexport type CustomAttributeFilter = {\n [K in number]: string | number | boolean |\n {id: string;} |\n {entityName: string; entityId: string;};\n}\n\nexport type QuerySelect<T> = {\n [P in keyof T]?:\n T[P] extends Array<infer U> | undefined ? (QuerySelect<U> | boolean) :\n T[P] extends Record<any, any> | undefined ? (QuerySelect<T[P]> | boolean) : boolean;\n}\n\nexport type Select<T, Q extends (QuerySelect<T> | undefined)> = Q extends QuerySelect<T> ? {\n\n // Filter out excluded properties beforehand\n [P in keyof T as Q[P] extends boolean ? P : Q[P] extends object ? P : never]:\n\n // Property\n Q[P] extends true ? T[P] :\n\n // Array\n T[P] extends Array<infer U> ? Select<U, Q[P] & QuerySelect<any>>[] :\n\n // Object\n T[P] extends Record<any, any> ? Select<T[P], Q[P] & QuerySelect<any>> : never\n} : undefined;\n\nexport type MapKeys<T, S extends Record<keyof T, string>> = {\n [K in keyof T as S[K]]: T[K];\n};\n\n// Endpoint configurations\nexport type CountQuery<F> = {\n filter?: QueryFilter<F>;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\n};\n\nexport type Pagination = {\n page: number;\n pageSize: number;\n};\n\nexport type SomeQuery<\n E, // Entity\n F, // Entity filter\n I extends (QuerySelect<any> | undefined), // Select for referenced entities\n S extends (QuerySelect<any> | undefined), // Select for entity properties\n P extends string[] // Select for additional properties\n> = {\n serializeNulls?: boolean;\n include?: I;\n properties?: P\n filter?: QueryFilter<F> & CustomAttributeFilter;\n select?: S;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\n sort?: Sort<E>[];\n pagination?: Pagination;\n};\n\nexport type UniqueQuery = {\n serializeNulls?: boolean;\n}\n\nexport type SomeQueryReturn<\n E, // Entity\n R, // Map of referenced-entity names to the type\n M, // Map of referenced-entity-id names to their entity name\n I extends (QuerySelect<any> | undefined), // Select for referenced entities\n S extends (QuerySelect<any> | undefined), // Select for entity properties\n P // Additional properties\n> = {\n entities: (S extends QuerySelect<E> ? Select<E, S> : E)[];\n references: I extends QuerySelect<R> ? Partial<MapKeys<Select<R, I>, M & Record<any, any>>> : {};\n properties: P\n};\n\nexport type GenericQuery<P, B> = {\n params?: P;\n body?: B;\n};\n\nexport type UpdateQuery = {\n ignoreMissingProperties?: boolean;\n dryRun?: boolean;\n}\n\nexport type RemoveQuery = {\n dryRun?: boolean;\n}\n\n// Entity meta types\nexport type WEntityPropertyMeta = (\n { type: 'string'; format?: 'decimal' | 'html' | 'email' | 'password'; maxLength?: number; pattern?: string; 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";
141
+ var queriesWithQueryLanguage = "const flattenOrderBy = (orderBy: OrderBy<any>[] = []): string[] => {\n\n const applyModifiers = (property: string, modifier: { TRIM?: boolean; LOWER?: boolean; LENGTH?: boolean }) => {\n let result = property;\n if (modifier.TRIM) {\n result = `trim(${result})`;\n }\n if (modifier.LOWER) {\n result = `lower(${result})`;\n }\n if (modifier.LENGTH) {\n result = `length(${result})`;\n }\n return result;\n };\n\n const isConditionalOrderBy = (\n orderByItem: OrderBy<any>\n ): orderByItem is ConditionalOrderBy<any> =>\n !!orderByItem && typeof orderByItem === 'object' && 'CASE' in orderByItem;\n\n const flattenValue = (value: number | FieldOrderBy<any>): string => {\n if (typeof value === 'number') {\n return value.toString();\n }\n return applyModifiers(value.FIELD, value);\n };\n\n const flattenConditional = (item: ConditionalOrderBy<any>): string => {\n const cases = item.CASE.map((c) => {\n const whenStr = flattenWhere(c.WHEN as QueryFilter<any>, []).join(' and ');\n return `(${whenStr}) ? ${flattenValue(c.THEN)}`;\n });\n\n return cases.reduceRight((acc, caseStr) => `${caseStr} : ${acc}`, flattenValue(item.ELSE));\n };\n\n const flattenSingle = (orderByItem: OrderBy<any>): string => {\n if (isConditionalOrderBy(orderByItem)) {\n const expr = flattenConditional(orderByItem);\n const sort = orderByItem.SORT === 'desc' ? 'desc' : 'asc';\n return `${expr} ${sort}`;\n }\n\n const field = orderByItem as FieldOrderBy<any>;\n const property = applyModifiers(field.FIELD, field);\n const sort = field.SORT === 'desc' ? 'desc' : 'asc';\n return `${property} ${sort}`;\n };\n\n return orderBy.map(flattenSingle);\n}\n\nexport type ComparisonOperator =\n | 'EQ'\n | 'NE'\n | 'LT'\n | 'GT'\n | 'LE'\n | 'GE'\n | 'LIKE';\n\nexport type LengthOperator = 'LENGTH';\n\nexport type ArrayOperator = 'IN';\n\nexport type NullOperator = 'NULL';\n\nexport type Operator = ComparisonOperator | ArrayOperator | NullOperator;\n\nexport type ModifierFunction = 'LOWER' | 'TRIM';\n\n// use globalThis to use typescript's 'Pick', since the SDK contains an interface which is also called 'Pick'\nexport type RequireAtLeastOne<T> = {\n [K in keyof T]-?: Required<globalThis.Pick<T, K>> & Partial<Omit<T, K>>;\n}[keyof T];\n\nexport type LengthExpr = {\n [K in LengthOperator]: {\n [K in Exclude<ComparisonOperator, 'LIKE'>]?: number\n }\n};\n\nexport type LengthExprWithModifier =\n LengthExpr &\n { [K in ComparisonOperator]?: never } &\n { [K in ArrayOperator]?: never } &\n { [K in NullOperator]?: never } &\n { [K in ModifierFunction]?: boolean };\n\nexport type FilterExpr<T> =\n { [K in ComparisonOperator]?: T } &\n { [K in ArrayOperator]?: T[] } &\n { [K in keyof LengthExpr]?: never };\n\nexport type FilterExprWithoutNull<T> =\n RequireAtLeastOne<FilterExpr<T>> &\n { [K in NullOperator]?: never } &\n { [K in ModifierFunction]?: boolean };\n\nexport type FilterExprWithNull<T> =\n FilterExpr<T> &\n { [K in NullOperator]?: boolean} &\n { [K in ModifierFunction]?: never };\n\nexport type MapOperators<T> = LengthExprWithModifier | FilterExprWithoutNull<T> | FilterExprWithNull<T>;\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 CASE?: {\n IF: QueryFilter<T>;\n THEN: QueryFilter<T>;\n ELSE: QueryFilter<T>;\n };\n};\n\nexport type ConditionalOrderByCase<E> = {\n WHEN: QueryFilter<E>;\n THEN: number | FieldOrderBy<E>;\n};\n\nexport type ConditionalOrderBy<E> = {\n CASE: ConditionalOrderByCase<E>[];\n ELSE: number | FieldOrderBy<E>;\n SORT?: 'asc' | 'desc';\n};\n\nexport type FieldPath<T> = {\n [K in keyof T & string]: NonNullable<T[K]> extends Array<infer U>\n ? U extends Record<any, any>\n ? `${K}.${FieldPath<U>}`\n : never\n : NonNullable<T[K]> extends Record<any, any>\n ? K | `${K}.${FieldPath<NonNullable<T[K]>>}`\n : K;\n}[keyof T & string];\n\nexport type FieldOrderBy<E> = {\n FIELD: FieldPath<E>;\n SORT?: 'asc' | 'desc';\n LOWER?: boolean;\n TRIM?: boolean;\n LENGTH?: boolean;\n};\n\nexport type OrderBy<E> = FieldOrderBy<E> | ConditionalOrderBy<E>;\n\nexport type CountQuery<F> = {\n where?: QueryFilter<F>;\n};\n\ntype SomeQueryBase<E, F, I, P> = {\n serializeNulls?: boolean;\n include?: QuerySelect<I>;\n properties?: P;\n where?: QueryFilter<F>;\n select?: QuerySelect<E>;\n pagination?: Pagination;\n};\n\nexport type SomeQuery<E, F, I, P> = SomeQueryBase<E, F, I, P> &\n ({ sort?: Sort<E>[]; orderBy?: never } | { sort?: never; orderBy?: OrderBy<E>[] });\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', 'TRIM'];\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 (prop === 'CASE') {\n entries.push(evaluateCaseExpression(propValue as QueryFilter<any>, nestedPaths));\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.toLowerCase()}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${comparisonOperatorMap[operator as Operator]} ${\n typeof value === 'string' ? JSON.stringify(value) : value\n }`\n );\n } else if ((operator as Operator) === 'NULL') {\n entries.push(\n `${!value ? 'not ' : ''}${nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')} ${comparisonOperatorMap[operator as Operator]}`\n );\n } else if ((operator as Operator) === 'IN') {\n if(value.length === 0) {\n entries.push('1 = 0')\n } else {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first.toLowerCase()}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${comparisonOperatorMap[operator as Operator]} [${value.map((v: string | number) =>\n typeof v === 'string' ? setModifiers.reduce((acc, [first]) => `${first}(${acc})`, JSON.stringify(v)) : v\n )}]`\n );\n }\n } else if ((operator as LengthOperator) === 'LENGTH') {\n const lengthProp = `length(${setModifiers.reduce(\n (acc, [first]) => `${first.toLowerCase()}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )})`;\n entries.push(...flattenWhere({ [lengthProp]: value } as QueryFilter<any>, []));\n } else if (\n !modifierFunctionList.includes(operator as ModifierFunction)\n && typeof value === 'object'\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 evaluateCaseExpression = (exp: QueryFilter<any>, nestedPaths: string[]): string => {\n const ifExp = flattenWhere(exp.IF as QueryFilter<any>, nestedPaths).join(' and ');\n const thenBranch = flattenWhere(exp.THEN as QueryFilter<any>, nestedPaths).join(' and ');\n const elseBranch = flattenWhere(exp.ELSE as QueryFilter<any>, nestedPaths).join(' and ');\n return `(${ifExp} ? ${thenBranch} : ${elseBranch})`;\n};\n\nconst assembleOrderBy = (orderBy: OrderBy<any>[] = [], usePost?: boolean): Record<string, string | string[]> => {\n if(!orderBy.length) {\n return {}\n }\n const flattedOrderByList = flattenOrderBy(orderBy);\n\n if(!flattedOrderByList.length) {\n return {};\n }\n\n if(usePost) {\n return { orderBy: flattedOrderByList };\n }\n\n const flattedOrderBy = flattedOrderByList.join(',');\n return { orderBy: flattedOrderBy };\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: [string, any][] = [];\n for (const key in obj) {\n if (types.includes(key as ModifierFunction)) {\n result[types.indexOf(key as ModifierFunction)] = [key, obj[key]];\n }\n }\n return result.filter((modifierTuple) => modifierTuple);\n};\n\nconst _count = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: CountQuery<any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n {\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n ...assembleFilterParam(query?.where),\n ...query?.params\n }\n\n return wrapResponse(() =>\n raw(cfg, endpoint, {\n unwrap: true,\n method: usePost ? 'POST' : 'GET',\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions)\n )\n };\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) => {\n const usePost = cfg?.usePost ?? globalConfig?.usePost;\n const payload = {\n serializeNulls: query?.serializeNulls,\n additionalProperties: usePost ? query?.properties : query?.properties?.join(','),\n properties: query?.select\n ? usePost\n ? flattenSelect(query.select)\n : flattenSelect(query.select).join(',')\n : undefined,\n includeReferencedEntities: query?.include\n ? usePost\n ? Object.keys(query.include)\n : Object.keys(query.include).join(',')\n : undefined,\n ...assembleFilterParam(query?.where),\n ...flattenSort(query?.sort, usePost),\n ...assembleOrderBy(query?.orderBy, usePost),\n ...query?.params,\n ...query?.pagination\n };\n return wrapResponse(() =>\n raw(cfg, usePost ? `${endpoint}/query` : endpoint, {\n method: usePost ? 'POST' : 'GET',\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n );\n};\n";
142
+
143
+ var root = "export const raw = async (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n payload: RequestPayload = {},\n requestOptions?: RequestOptions\n): Promise<any> => {\n if (!cfg && !globalConfig) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const localCfg = {\n ...globalConfig,\n ...cfg,\n interceptors: { ...globalConfig?.interceptors, ...cfg?.interceptors }\n };\n\n const isBinaryData = payload.body instanceof resolveBinaryObject();\n const params = new URLSearchParams(Object.entries(payload.query ?? {}).filter((v) => v[1] !== undefined)\n .map(([key, value]) => [key, typeof value === 'string' ? value : JSON.stringify(value)])\n );\n\n const protocol = getProtocol(localCfg);\n\n const interceptRequest = localCfg.interceptors?.request ?? ((v) => v);\n const interceptResponse = localCfg.interceptors?.response ?? ((v) => v);\n\n const host = getHost(localCfg);\n\n let data;\n if (!cfg && localCfg.multiRequest) {\n let ep = endpoint;\n if (endpoint.startsWith('/')) {\n ep = endpoint.replace('/', '');\n }\n data = await addRequest(`${ep}?${params}`);\n } else {\n const request = new Request(`${protocol}//${host}/webapp/api/v${apiVersion}${endpoint}?${params}`, {\n ...(payload.body && {\n body: isBinaryData\n ? payload.body\n : JSON.stringify(payload.body, (_key, value) => (value === undefined ? null : value))\n }),\n ...(!localCfg.key && { credentials: 'same-origin' }),\n method: payload.method ?? 'get',\n headers: {\n Accept: 'application/json',\n ...(localCfg.key && { AuthenticationToken: localCfg.key }),\n ...(!isBinaryData && { 'Content-Type': 'application/json' })\n }\n });\n let res = (await interceptRequest(request, payload)) ?? request;\n if (!(res instanceof Response)) {\n res = requestOptions?.signal ? await fetch(res, { signal: requestOptions.signal } ) : await fetch(res);\n }\n res = (await interceptResponse(res)) ?? res;\n\n if (res.ok) {\n data =\n payload.forceBlob || !res.headers?.get('content-type')?.includes('application/json')\n ? await res.blob()\n : await res.json();\n } else {\n data = res.headers?.get('content-type')?.includes('application/json')\n ? await res.json()\n : res;\n }\n\n // Check if response was successful\n if (!res.ok) {\n return Promise.reject(data);\n }\n }\n\n return payload.unwrap ? data.result : data;\n};\n\nconst _remove = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n { dryRun = false }: RemoveQuery = {},\n requestOptions?: RequestOptions\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'DELETE',\n query: { dryRun }\n }, requestOptions).then(() => undefined)\n );\n\nconst _create = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, data: any, requestOptions?: RequestOptions) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'POST',\n body: data\n }, requestOptions)\n );\n\nconst _update = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n data: any,\n { ignoreMissingProperties, dryRun = false }: UpdateQuery = {},\n requestOptions?: RequestOptions\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'PUT',\n body: data,\n query: {\n ignoreMissingProperties:\n ignoreMissingProperties ?? cfg?.ignoreMissingProperties ?? globalConfig?.ignoreMissingProperties,\n dryRun\n }\n }, requestOptions)\n );\n\nconst _generic = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n method: RequestPayloadMethod,\n endpoint: string,\n payload?: GenericQuery<any, any>,\n forceBlob?: boolean,\n requestOptions?: RequestOptions\n) =>\n{\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n return wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: usePost ? 'POST' : method,\n forceBlob,\n ...(usePost && method === 'GET' ?\n { body: { ...payload?.params } }\n :\n { body: payload?.body, query: payload?.params } )\n }, requestOptions))\n};\n";
144
+
145
+ var unique = "const _unique = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, query?: UniqueQuery, requestOptions?: RequestOptions) =>\n wrapResponse(() => raw({ ...cfg, multiRequest: false }, endpoint, { query }, requestOptions));\n";
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>[] = [], usePost?: boolean):\n{ sort?: string } | { orderBy?: string[] } | Record<string, never> => {\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 `${!usePost && value === 'desc' ? '-' : ''}${path}${usePost && value === 'desc' ? ' desc' : ''}`;\n }\n }\n\n return undefined;\n };\n\n\n const sorts = obj.map((v) => flatten(v)).filter((x): x is string => typeof x === 'string');\n\n if (!sorts.length) {\n return {};\n }\n\n if (usePost) {\n return { orderBy: sorts };\n } else {\n return { sort: sorts.join(',') };\n }\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 = (apiVersion, { target, useQueryLanguage, generateUnique }) => {
161
+ return generateStatements(resolveImports(target), `const apiVersion = ${apiVersion}`, resolveMappings(target), resolveBinaryClass(target), globalConfig, types, utils, root, useQueryLanguage ? queriesWithQueryLanguage : queriesWithFilter, 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
 
@@ -191,22 +191,17 @@ const isArraySchemaObject = (v) => {
191
191
  const isResponseObject = (v) => {
192
192
  return isObject(v) && typeof v.description === 'string';
193
193
  };
194
- const isNonArraySchemaObject = (v) => {
195
- return isObject(v) && ['string', 'undefined'].includes(typeof v.type);
196
- };
197
- const isRelatedEntitySchema = (v) => {
198
- return isObject(v) && isNonArraySchemaObject(v) && 'x-weclapp' in v && isObject(v['x-weclapp']);
199
- };
200
194
 
201
- const generateEnums = (schemas) => {
195
+ const generateEnums = (context) => {
202
196
  const enums = new Map();
203
- for (const [propName, schema] of schemas) {
197
+ for (const [schemaName, schema] of context.schemas) {
204
198
  if (isEnumSchemaObject(schema)) {
205
- const name = loosePascalCase(propName);
206
- if (!enums.has(name)) {
207
- enums.set(name, {
199
+ const enumName = loosePascalCase(schemaName);
200
+ if (!enums.has(enumName)) {
201
+ enums.set(enumName, {
202
+ name: enumName,
208
203
  properties: schema.enum,
209
- source: generateEnum(name, schema.enum)
204
+ source: generateEnum(enumName, schema.enum)
210
205
  });
211
206
  }
212
207
  }
@@ -218,16 +213,30 @@ const concat = (strings, separator = ', ', maxLength = 80) => {
218
213
  const joined = strings.join(separator);
219
214
  if (joined.length > maxLength) {
220
215
  const length = strings.length - 1;
221
- return `\n${indent(strings
222
- .map((value, index) => index === length ? value : `${(value + separator).trim()}\n`)
223
- .join(''))}\n`;
216
+ return `\n${indent(strings.map((value, index) => (index === length ? value : `${(value + separator).trim()}\n`)).join(''))}\n`;
224
217
  }
225
218
  else {
226
219
  return joined;
227
220
  }
228
221
  };
229
222
 
230
- /* eslint-disable no-use-before-define */
223
+ const convertParametersToSchemaObject = (parameters) => {
224
+ const properties = [];
225
+ const required = [];
226
+ for (const param of parameters) {
227
+ if (param.in === 'query' && param.schema) {
228
+ properties.push([param.name, param.schema]);
229
+ if (param.required)
230
+ required.push(param.name);
231
+ }
232
+ }
233
+ return {
234
+ type: 'object',
235
+ properties: Object.fromEntries(properties),
236
+ required
237
+ };
238
+ };
239
+
231
240
  const createReferenceType = (value) => ({
232
241
  type: 'reference',
233
242
  toString: () => loosePascalCase(value)
@@ -242,22 +251,25 @@ const createArrayType = (value) => ({
242
251
  });
243
252
  const createTupleType = (value) => ({
244
253
  type: 'tuple',
245
- toString: () => concat([...new Set(value.map(v => typeof v === 'string' ? `'${v}'` : v.toString()))], ' | ')
254
+ toString: () => concat([...new Set(value.map((v) => (typeof v === 'string' ? `'${v}'` : v.toString())))], ' | ')
246
255
  });
247
256
  const createObjectType = (value, required = []) => ({
248
257
  type: 'object',
249
258
  isFullyOptional: () => {
250
- return !required.length && Object.values(value)
251
- .filter(v => v?.type === 'object')
252
- .every(v => v.isFullyOptional());
259
+ return (!required.length &&
260
+ Object.values(value)
261
+ .filter((v) => v?.type === 'object')
262
+ .every((v) => v.isFullyOptional()));
253
263
  },
254
- toString: (propagateOptionalProperties = false) => {
264
+ toString: (propertyPropagationOption = 'ignore') => {
255
265
  const properties = Object.entries(value)
256
- .filter(v => v[1])
257
- .map(v => {
266
+ .filter((v) => v[1])
267
+ .map((v) => {
258
268
  const name = v[0];
259
269
  const value = v[1];
260
- const isRequired = required.includes(name) || (value.type === 'object' && !value.isFullyOptional() && propagateOptionalProperties);
270
+ const isRequired = required.includes(name) ||
271
+ propertyPropagationOption === 'force' ||
272
+ (value.type === 'object' && !value.isFullyOptional() && propertyPropagationOption === 'propagate');
261
273
  return `${name + (isRequired ? '' : '?')}: ${value.toString()};`;
262
274
  });
263
275
  return properties.length ? `{\n${indent(properties.join('\n'))}\n}` : '{}';
@@ -266,8 +278,11 @@ const createObjectType = (value, required = []) => ({
266
278
  const getRefName = (obj) => {
267
279
  return obj.$ref.replace(/.*\//, '');
268
280
  };
269
- const convertToTypeScriptType = (schema, property) => {
270
- if (isReferenceObject(schema)) {
281
+ const convertToTypeScriptType = (schema) => {
282
+ if (Array.isArray(schema)) {
283
+ return convertToTypeScriptType(convertParametersToSchemaObject(schema));
284
+ }
285
+ else if (isReferenceObject(schema)) {
271
286
  return createReferenceType(getRefName(schema));
272
287
  }
273
288
  else {
@@ -277,7 +292,7 @@ const convertToTypeScriptType = (schema, property) => {
277
292
  return createRawType('number');
278
293
  case 'string':
279
294
  if (schema.enum) {
280
- return property ? createReferenceType(property) : createTupleType(schema.enum);
295
+ return createTupleType(schema.enum);
281
296
  }
282
297
  else {
283
298
  return schema.format === 'binary' ? createRawType('binary') : createRawType('string');
@@ -286,48 +301,53 @@ const convertToTypeScriptType = (schema, property) => {
286
301
  return createRawType('boolean');
287
302
  case 'object': {
288
303
  const { properties = {}, required = [] } = schema;
289
- return createObjectType(Object.fromEntries(Object.entries(properties)
290
- .map(v => [v[0], convertToTypeScriptType(v[1])])), required);
304
+ return createObjectType(Object.fromEntries(Object.entries(properties).map(([prop, propSchema]) => [prop, convertToTypeScriptType(propSchema)])), required);
291
305
  }
292
306
  case 'array':
293
- return createArrayType(convertToTypeScriptType(schema.items, property));
307
+ return createArrayType(convertToTypeScriptType(schema.items));
294
308
  default:
295
309
  return createRawType('unknown');
296
310
  }
297
311
  }
298
312
  };
299
313
 
300
- const setEntityEnumProperty = (enums, prop, meta) => {
314
+ const setReferenceMeta = (prop, metaData, context) => {
301
315
  const referenceName = getRefName(prop);
302
- const enumName = loosePascalCase(referenceName);
303
- if (enums.has(enumName)) {
304
- meta.enum = enumName;
316
+ const referenceSchema = context.schemas.get(referenceName);
317
+ if (isEnumSchemaObject(referenceSchema)) {
318
+ metaData.enum = loosePascalCase(referenceName);
305
319
  }
306
320
  else {
307
- meta.entity = referenceName;
321
+ metaData.entity = referenceName;
308
322
  }
309
323
  };
310
- const extractPropertyMetaData = (enums, meta, prop) => {
311
- const result = { service: meta.service, entity: meta.entity };
324
+ const extractPropertyMetaData = (prop, context) => {
325
+ const metaData = {};
326
+ const weclappExtension = prop['x-weclapp'];
327
+ if (weclappExtension) {
328
+ metaData.service = weclappExtension.service;
329
+ metaData.entity = weclappExtension.entity;
330
+ }
312
331
  if (isReferenceObject(prop)) {
313
- setEntityEnumProperty(enums, prop, result);
314
- result.type = 'reference';
315
- return result;
316
- }
317
- result.type = prop.type;
318
- result.format = prop.format;
319
- result.maxLength = prop.maxLength;
320
- result.pattern = prop.pattern;
321
- if (isArraySchemaObject(prop)) {
322
- if (isReferenceObject(prop.items)) {
323
- setEntityEnumProperty(enums, prop.items, result);
324
- result.format = 'reference';
325
- }
326
- else {
327
- result.format = 'string';
332
+ metaData.type = 'reference';
333
+ setReferenceMeta(prop, metaData, context);
334
+ }
335
+ else {
336
+ metaData.type = prop.type;
337
+ metaData.format = prop.format;
338
+ metaData.maxLength = prop.maxLength;
339
+ metaData.pattern = prop.pattern;
340
+ if (isArraySchemaObject(prop)) {
341
+ if (isReferenceObject(prop.items)) {
342
+ metaData.format = 'reference';
343
+ setReferenceMeta(prop.items, metaData, context);
344
+ }
345
+ else {
346
+ metaData.format = 'string';
347
+ }
328
348
  }
329
349
  }
330
- return result;
350
+ return metaData;
331
351
  };
332
352
 
333
353
  const generateInlineComment = (comment) => `/** ${comment} */`;
@@ -337,12 +357,12 @@ const generateType = (name, value) => {
337
357
  return `export type ${name} = ${value.trim()};`;
338
358
  };
339
359
 
340
- const arrayify = (v) => Array.isArray(v) ? v : [v];
360
+ const arrayify = (v) => (Array.isArray(v) ? v : [v]);
341
361
 
342
362
  const generateInterfaceProperties = (entries) => {
343
363
  const properties = entries
344
- .filter(v => v.type !== undefined)
345
- .filter((value, index, array) => array.findIndex(v => v.name === value.name) === index)
364
+ .filter((v) => v.type !== undefined)
365
+ .filter((value, index, array) => array.findIndex((v) => v.name === value.name) === index)
346
366
  .map(({ name, type, required, readonly, comment }) => {
347
367
  const cmd = comment ? `${generateInlineComment(comment)}\n` : '';
348
368
  const req = required ? '' : '?';
@@ -352,7 +372,7 @@ const generateInterfaceProperties = (entries) => {
352
372
  .join('\n');
353
373
  return properties.length ? `{\n${indent(properties)}\n}` : `{}`;
354
374
  };
355
- const generateInterfaceFromObject = (name, obj, propagateOptionalProperties) => `export interface ${name} ${obj.toString(propagateOptionalProperties)}`;
375
+ const generateInterfaceFromObject = (name, obj, propertyPropagationOption) => `export interface ${name} ${obj.toString(propertyPropagationOption)}`;
356
376
  const generateInterface = (name, entries, extend) => {
357
377
  const signature = `${name} ${extend ? `extends ${arrayify(extend).join(', ')}` : ''}`.trim();
358
378
  const body = generateInterfaceProperties(entries);
@@ -361,69 +381,17 @@ const generateInterface = (name, entries, extend) => {
361
381
  const generateInterfaceType = (name, entries, extend) => {
362
382
  const body = generateInterfaceProperties(entries);
363
383
  const bases = extend ? arrayify(extend).join(' & ') : undefined;
364
- return generateType(name, `${bases ? `${bases} & ` : ''}${body}`);
365
- };
366
-
367
- const generateEntities = (schemas, enums) => {
368
- const entities = new Map();
369
- for (const [schemaName, schema] of schemas) {
370
- // Enums are generated separately
371
- if (isEnumSchemaObject(schema)) {
372
- continue;
373
- }
374
- const entity = pascalCase(schemaName);
375
- // Entity and filter
376
- const entityInterface = [];
377
- const filterInterface = [];
378
- // Referenced entities and property-to-referenced-entity mapping
379
- const referenceInterface = [];
380
- const referenceMappingsInterface = [];
381
- const properties = new Map();
382
- // The parent entity
383
- let extend = undefined;
384
- const processProperties = (props = {}) => {
385
- for (const [name, property] of Object.entries(props)) {
386
- const meta = isRelatedEntitySchema(property) ? property['x-weclapp'] : {};
387
- if (meta.entity) {
388
- const type = `${pascalCase(meta.entity)}[]`;
389
- referenceInterface.push({ name, type, required: true });
390
- filterInterface.push({ name: meta.entity, type, required: true });
391
- }
392
- if (meta.service) {
393
- referenceMappingsInterface.push({ name, type: generateString(meta.service), required: true });
394
- }
395
- const type = convertToTypeScriptType(property, name).toString();
396
- const comment = isNonArraySchemaObject(property) ?
397
- property.deprecated ? '@deprecated will be removed.' :
398
- property.format ? `format: ${property.format}` :
399
- undefined : undefined;
400
- entityInterface.push({
401
- name, type, comment,
402
- required: meta.required,
403
- readonly: !isReferenceObject(property) && property.readOnly
404
- });
405
- properties.set(name, extractPropertyMetaData(enums, meta, property));
406
- }
407
- };
408
- if (schema.allOf?.length) {
409
- for (const item of schema.allOf) {
410
- if (isReferenceObject(item)) {
411
- extend = convertToTypeScriptType(item).toString();
412
- }
413
- else if (isObjectSchemaObject(item)) {
414
- processProperties(item.properties);
415
- }
416
- }
417
- }
418
- processProperties(schema.properties);
419
- 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));
420
- entities.set(schemaName, {
421
- extends: extend ? camelCase(extend) : extend,
422
- properties,
423
- source
424
- });
384
+ let typeDefinition = '';
385
+ if (bases) {
386
+ typeDefinition = bases;
425
387
  }
426
- return entities;
388
+ else {
389
+ typeDefinition = body;
390
+ }
391
+ if (bases && body !== '{}') {
392
+ typeDefinition += ` & ${body}`;
393
+ }
394
+ return generateType(name, typeDefinition);
427
395
  };
428
396
 
429
397
  /**
@@ -431,13 +399,10 @@ const generateEntities = (schemas, enums) => {
431
399
  * @param s String to pluralize.
432
400
  */
433
401
  const pluralize = (s) => {
434
- return s.endsWith('s') ? s :
435
- s.endsWith('y') ? `${s.slice(0, -1)}ies` :
436
- `${s}s`;
402
+ return s.endsWith('s') ? s : s.endsWith('y') ? `${s.slice(0, -1)}ies` : `${s}s`;
437
403
  };
438
404
 
439
- /* eslint-disable no-console */
440
- const logger = new class {
405
+ const logger = new (class {
441
406
  active = true;
442
407
  warnings = 0;
443
408
  errors = 0;
@@ -485,21 +450,126 @@ const logger = new class {
485
450
  printSummary() {
486
451
  const format = (v, name, fail, ok) => {
487
452
  const color = v ? fail : ok;
488
- return v === 0 ? `${color('zero')} ${pluralize(name)}` :
489
- v === 1 ? `${color('one')} ${name}` : `${color(v)} ${pluralize(name)}`;
453
+ return v === 0
454
+ ? `${color('zero')} ${pluralize(name)}`
455
+ : v === 1
456
+ ? `${color('one')} ${name}`
457
+ : `${color(v)} ${pluralize(name)}`;
490
458
  };
491
459
  const warnings = format(this.warnings, 'warning', chalk.yellowBright, chalk.greenBright);
492
460
  const errors = format(this.errors, 'error', chalk.redBright, chalk.greenBright);
493
461
  const info = `Finished with ${warnings} and ${errors}.`;
494
462
  this[this.errors ? 'errorLn' : this.warnings ? 'warnLn' : 'successLn'](info);
495
463
  }
464
+ })();
465
+
466
+ const FILTER_PROPS_SUFFIX = 'Filter_Props';
467
+ const generateEntities = (context) => {
468
+ const entities = new Map();
469
+ for (const [schemaName, schema] of context.schemas) {
470
+ // Enums are generated separately
471
+ if (isEnumSchemaObject(schema)) {
472
+ continue;
473
+ }
474
+ const entityName = schemaName;
475
+ const entityInterfaceName = loosePascalCase(entityName);
476
+ const entityInterfaceProperties = [];
477
+ const properties = new Map();
478
+ let parentEntityName = undefined;
479
+ let parentEntityInterfaceName = undefined;
480
+ const entityFilterInterfaceName = `${entityInterfaceName}_${FILTER_PROPS_SUFFIX}`;
481
+ const entityFilterInterfaceProperties = [];
482
+ let parentEntityFilterInterfaceName = undefined;
483
+ const processProperties = (props = {}) => {
484
+ for (const [propertyName, propertySchema] of Object.entries(props)) {
485
+ const weclappExtension = propertySchema['x-weclapp'];
486
+ properties.set(propertyName, extractPropertyMetaData(propertySchema, context));
487
+ const type = convertToTypeScriptType(propertySchema).toString();
488
+ // cast to SchemaObject to access deprecated and readOnly properties (ReferenceObject can also include these props in OpenAPI 3.1)
489
+ const castedSchema = propertySchema;
490
+ const comment = castedSchema.deprecated
491
+ ? '@deprecated will be removed.'
492
+ : castedSchema.format
493
+ ? `format: ${castedSchema.format}`
494
+ : undefined;
495
+ if (weclappExtension?.filterable !== false) {
496
+ entityFilterInterfaceProperties.push({ name: propertyName, type, comment });
497
+ }
498
+ entityInterfaceProperties.push({
499
+ name: propertyName,
500
+ type,
501
+ required: weclappExtension?.required,
502
+ readonly: castedSchema.readOnly,
503
+ comment
504
+ });
505
+ }
506
+ };
507
+ const processExtraFilterProperties = (props = {}) => {
508
+ for (const [propertyName, propertySchema] of Object.entries(props)) {
509
+ if (isReferenceObject(propertySchema))
510
+ continue;
511
+ const type = convertToTypeScriptType(propertySchema).toString();
512
+ const comment = propertySchema.deprecated
513
+ ? '@deprecated will be removed.'
514
+ : propertySchema.format
515
+ ? `format: ${propertySchema.format}`
516
+ : undefined;
517
+ entityFilterInterfaceProperties.push({ name: propertyName, type, comment });
518
+ }
519
+ };
520
+ const processFilterPaths = (filterPaths = {}) => {
521
+ for (const [filterProp, entityName] of Object.entries(filterPaths)) {
522
+ if (!filterProp.includes('.') && context.schemas.get(entityName)) {
523
+ entityFilterInterfaceProperties.push({
524
+ name: filterProp,
525
+ type: `${loosePascalCase(entityName)}_${FILTER_PROPS_SUFFIX}`
526
+ });
527
+ }
528
+ }
529
+ };
530
+ if (schema.allOf?.length) {
531
+ if (schema.allOf.length > 2) {
532
+ logger.errorLn(`Failed to process schema for ${schemaName}: invalid allOf length`);
533
+ continue;
534
+ }
535
+ for (const item of schema.allOf) {
536
+ if (isReferenceObject(item)) {
537
+ parentEntityName = getRefName(item);
538
+ parentEntityInterfaceName = createReferenceType(parentEntityName).toString();
539
+ parentEntityFilterInterfaceName = `${parentEntityInterfaceName}_${FILTER_PROPS_SUFFIX}`;
540
+ }
541
+ else if (item.type === 'object') {
542
+ processProperties(item.properties);
543
+ processExtraFilterProperties(item['x-weclapp-filterProperties']);
544
+ processFilterPaths(item['x-weclapp-filterPaths']);
545
+ }
546
+ else {
547
+ logger.errorLn(`Failed to process schema for ${schemaName}: invalid schema type in allOf`);
548
+ }
549
+ }
550
+ }
551
+ else {
552
+ processProperties(schema.properties);
553
+ }
554
+ entities.set(entityName, {
555
+ name: entityName,
556
+ interfaceName: entityInterfaceName,
557
+ properties,
558
+ source: generateStatements(generateInterface(entityInterfaceName, entityInterfaceProperties, parentEntityInterfaceName)),
559
+ filterInterfaceName: entityFilterInterfaceName,
560
+ filterSource: generateStatements(generateInterface(entityFilterInterfaceName, entityFilterInterfaceProperties, parentEntityFilterInterfaceName)),
561
+ parentName: parentEntityName,
562
+ parentInterfaceName: parentEntityInterfaceName
563
+ });
564
+ }
565
+ return entities;
496
566
  };
497
567
 
498
568
  /**
499
- * ROOT => /article
500
- * COUNT => /article/count
501
- * ENTITY => /article/{id}
502
- * SPECIAL_ROOT => /article/generateImage
569
+ * ROOT => /article
570
+ * COUNT => /article/count
571
+ * ENTITY => /article/{id}
572
+ * SPECIAL_ROOT => /article/generateImage
503
573
  * SPECIAL_ENTITY => /article/id/{id}/generateImag
504
574
  */
505
575
  var WeclappEndpointType;
@@ -511,26 +581,61 @@ var WeclappEndpointType;
511
581
  WeclappEndpointType["GENERIC_ENTITY"] = "GENERIC_ENTITY";
512
582
  })(WeclappEndpointType || (WeclappEndpointType = {}));
513
583
  const parseEndpointPath = (path) => {
514
- const [, entity, ...rest] = path.split('/');
515
- if (!entity) {
584
+ const [, service, ...rest] = path.split('/');
585
+ if (!service) {
516
586
  return undefined;
517
587
  }
518
588
  if (!rest.length) {
519
- return { path, entity, type: WeclappEndpointType.ROOT };
589
+ return { path, service, type: WeclappEndpointType.ROOT };
520
590
  }
521
591
  else if (rest[0] === 'count') {
522
- return { path, entity, type: WeclappEndpointType.COUNT };
592
+ return { path, service, type: WeclappEndpointType.COUNT };
523
593
  }
524
594
  else if (rest[0] === 'id') {
525
- return rest.length === 2 ?
526
- { path, entity, type: WeclappEndpointType.ENTITY } :
527
- { path, entity, method: rest[2], type: WeclappEndpointType.GENERIC_ENTITY };
595
+ return rest.length === 2
596
+ ? { path, service, type: WeclappEndpointType.ENTITY }
597
+ : {
598
+ path,
599
+ service,
600
+ method: rest[2],
601
+ type: WeclappEndpointType.GENERIC_ENTITY
602
+ };
528
603
  }
529
604
  else if (rest.length === 1) {
530
- return { path, entity, method: rest[1], type: WeclappEndpointType.GENERIC_ROOT };
605
+ return {
606
+ path,
607
+ service,
608
+ method: rest[1],
609
+ type: WeclappEndpointType.GENERIC_ROOT
610
+ };
531
611
  }
532
612
  return undefined;
533
613
  };
614
+ const isMultiPartUploadPath = (path) => {
615
+ const [, entity, ...rest] = path.split('/');
616
+ return entity && rest.length === 2 && rest[1] === 'multipartUpload';
617
+ };
618
+ const parseEndpointsPaths = (paths) => {
619
+ const endpoints = new Map();
620
+ for (const [rawPath, path] of Object.entries(paths)) {
621
+ const endpoint = parseEndpointPath(rawPath);
622
+ if (!endpoint || !path) {
623
+ // Todo: Should be removed if sdk supports multi part upload.
624
+ if (isMultiPartUploadPath(rawPath)) {
625
+ continue;
626
+ }
627
+ logger.errorLn(`Failed to parse ${rawPath}`);
628
+ continue;
629
+ }
630
+ if (endpoints.has(endpoint.service)) {
631
+ endpoints.get(endpoint.service)?.push({ endpoint, path });
632
+ }
633
+ else {
634
+ endpoints.set(endpoint.service, [{ endpoint, path }]);
635
+ }
636
+ }
637
+ return endpoints;
638
+ };
534
639
 
535
640
  const generateArrowFunction = ({ name, signature, returns, params }) => {
536
641
  return `const ${name}: ${signature} = (${params?.join(', ') ?? ''}) =>\n${indent(returns)};`;
@@ -542,109 +647,109 @@ const generateArrowFunctionType = ({ type, returns = 'void', generics, params })
542
647
  return generateType(type, `${genericsString + paramsString} =>\n${indent(returns)}`);
543
648
  };
544
649
 
545
- const convertParametersToSchema = (parameters = []) => {
546
- const properties = [];
547
- const required = [];
548
- for (const param of parameters) {
549
- if (isParameterObject(param) && param.in === 'query') {
550
- if (param.schema) {
551
- properties.push([param.name, param.schema]);
552
- param.required && required.push(param.name);
553
- }
650
+ const resolveParameters = (resolvableParameters = [], parameters) => {
651
+ if (!resolvableParameters)
652
+ return [];
653
+ return resolvableParameters.flatMap((param) => {
654
+ if (isReferenceObject(param)) {
655
+ const resolved = parameters.get(getRefName(param));
656
+ return resolved ? [resolved] : [];
554
657
  }
555
- }
556
- return {
557
- type: 'object', required,
558
- properties: Object.fromEntries(properties)
559
- };
658
+ return [param];
659
+ });
560
660
  };
561
661
 
562
- const functionName$5 = 'count';
563
- const generateCountEndpoint = ({ aliases, path, target, endpoint }) => {
564
- const service = pascalCase(endpoint.entity);
565
- const entity = aliases.get(endpoint.entity) ?? service;
566
- const entityFilter = `${entity}_Filter`;
567
- const interfaceName = `${service}Service_${pascalCase(functionName$5)}`;
568
- const entityParameters = `${interfaceName}_Parameters`;
569
- const parameterSchema = convertParametersToSchema(path.parameters);
570
- const parameters = createObjectType({
571
- params: convertToTypeScriptType(parameterSchema)
662
+ const generateCountEndpoint = ({ endpoint, operationObject, entities, context, options }) => {
663
+ const functionName = 'count';
664
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
665
+ const relatedEntityName = context.aliases.get(endpoint.service);
666
+ const relatedEntity = !!relatedEntityName && entities.get(relatedEntityName);
667
+ if (!relatedEntity) {
668
+ throw Error(`Related entity schema for service ${endpoint.service} not found`);
669
+ }
670
+ const parametersTypeName = `${functionTypeName}_Parameters`;
671
+ const parametersType = createObjectType({
672
+ params: convertToTypeScriptType(resolveParameters(operationObject.parameters, context.parameters))
572
673
  });
573
- const functionSource = generateArrowFunction({
574
- name: functionName$5,
575
- signature: interfaceName,
576
- returns: `_${functionName$5}(cfg, ${generateString(endpoint.path)}, query)`,
577
- params: ['query']
674
+ const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
675
+ const filterTypeName = `${functionTypeName}_Filter`;
676
+ const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${relatedEntity.filterInterfaceName}`]);
677
+ const functionTypeSource = generateArrowFunctionType({
678
+ type: functionTypeName,
679
+ params: [
680
+ `query${parametersType.isFullyOptional() ? '?' : ''}: CountQuery<${filterTypeName}>${operationObject.parameters?.length ? ' & ' + parametersTypeName : ''}`,
681
+ 'requestOptions?: RequestOptions'
682
+ ],
683
+ returns: `${resolveResponseType(options.target)}<number>`
578
684
  });
579
- const interfaceSource = generateArrowFunctionType({
580
- type: interfaceName,
581
- params: [`query${parameters.isFullyOptional() ? '?' : ''}: CountQuery<${entityFilter}> & ${entityParameters}`],
582
- returns: `${resolveResponseType(target)}<number>`
685
+ const functionSource = generateArrowFunction({
686
+ name: functionName,
687
+ signature: functionTypeName,
688
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
689
+ params: ['query', 'requestOptions?: RequestOptions']
583
690
  });
584
691
  return {
585
- entity,
586
- name: functionName$5,
587
- type: { name: interfaceName, source: interfaceSource },
588
- func: { name: functionName$5, source: functionSource },
692
+ name: functionName,
693
+ type: { name: functionTypeName, source: functionTypeSource },
694
+ func: { name: functionName, source: functionSource },
589
695
  interfaces: [
590
- {
591
- name: entityParameters,
592
- source: generateInterfaceFromObject(entityParameters, parameters, true)
593
- }
696
+ ...(operationObject.parameters?.length ? [{ name: parametersTypeName, source: parametersTypeSource }] : []),
697
+ { name: filterTypeName, source: filterTypeSource }
594
698
  ]
595
699
  };
596
700
  };
597
701
 
598
- const generateBodyType = (body) => {
599
- if (isReferenceObject(body)) {
600
- return convertToTypeScriptType(body);
601
- }
702
+ const generateContentType = (body, fallback = 'unknown') => {
703
+ if (!body?.content)
704
+ return createRawType(fallback);
602
705
  const types = [];
603
- for (const { schema } of Object.values(body?.content ?? {})) {
706
+ for (const { schema } of Object.values(body.content)) {
604
707
  if (schema) {
605
708
  types.push(convertToTypeScriptType(schema));
606
709
  }
607
710
  }
608
- return types.length ? createTupleType(types) : undefined;
711
+ return (types.length > 1 ? createTupleType(types) : types[0]) ?? createRawType(fallback);
609
712
  };
610
713
 
611
- const generateRequestBodyType = ({ requestBody }) => {
612
- return generateBodyType(requestBody) ?? createRawType('unknown');
714
+ const generateRequestBodyType = ({ requestBody }, requestBodies) => {
715
+ const requestBodyObject = requestBody && isReferenceObject(requestBody) ? requestBodies.get(getRefName(requestBody)) : requestBody;
716
+ return generateContentType(requestBodyObject);
613
717
  };
614
718
 
615
- const resolveBodyType = ({ responses }) => Object.entries(responses)
616
- .filter(v => v[0].startsWith('2'))[0]?.[1];
617
- const generateResponseBodyType = (object) => generateBodyType(resolveBodyType(object)) ?? createRawType('void');
719
+ const resolveResponsesObject = (responses) => Object.entries(responses).find(([statusCode]) => statusCode.startsWith('2'))?.[1];
618
720
 
619
- const functionName$4 = 'create';
620
- const generateCreateEndpoint = ({ target, path, endpoint }) => {
621
- const entity = pascalCase(endpoint.entity);
622
- const interfaceName = `${entity}Service_${pascalCase(functionName$4)}`;
623
- const functionSource = generateArrowFunction({
624
- name: functionName$4,
625
- signature: interfaceName,
626
- returns: `_${functionName$4}(cfg, ${generateString(endpoint.path)}, data)`,
627
- params: ['data']
721
+ const generateResponseType = (operationObject, responses) => {
722
+ const response = resolveResponsesObject(operationObject.responses);
723
+ const responseObject = response && isReferenceObject(response) ? responses.get(getRefName(response)) : response;
724
+ return generateContentType(responseObject, 'void');
725
+ };
726
+
727
+ const generateCreateEndpoint = ({ endpoint, operationObject, context, options }) => {
728
+ const functionName = 'create';
729
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
730
+ const functionTypeSource = generateArrowFunctionType({
731
+ type: functionTypeName,
732
+ params: [
733
+ `data: DeepPartial<${generateRequestBodyType(operationObject, context.requestBodies).toString()}>`,
734
+ 'requestOptions?: RequestOptions'
735
+ ],
736
+ returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
628
737
  });
629
- const interfaceSource = generateArrowFunctionType({
630
- type: interfaceName,
631
- params: [`data: DeepPartial<${generateRequestBodyType(path).toString()}>`],
632
- returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
738
+ const functionSource = generateArrowFunction({
739
+ name: functionName,
740
+ signature: functionTypeName,
741
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, data, requestOptions)`,
742
+ params: ['data', 'requestOptions?: RequestOptions']
633
743
  });
634
744
  return {
635
- entity,
636
- name: functionName$4,
637
- type: { name: interfaceName, source: interfaceSource },
638
- func: { name: functionName$4, source: functionSource }
745
+ name: functionName,
746
+ type: { name: functionTypeName, source: functionTypeSource },
747
+ func: { name: functionName, source: functionSource }
639
748
  };
640
749
  };
641
750
 
642
751
  const generateGenericFunctionName = (path, suffix = '', prefix = '') => {
643
- return camelCase(`${prefix}_` +
644
- path
645
- .replace(/.*\//, '')
646
- .replace(/\W+/, '_')
647
- .replace(/[_]+/, '_') + `_${suffix}`);
752
+ return camelCase(`${prefix}_` + path.replace(/.*\//, '').replace(/\W+/, '_').replace(/[_]+/, '_') + `_${suffix}`);
648
753
  };
649
754
 
650
755
  const insertPathPlaceholder = (path, record) => {
@@ -652,77 +757,84 @@ const insertPathPlaceholder = (path, record) => {
652
757
  };
653
758
 
654
759
  const wrapBody = (type, target) => {
655
- return type.toString() === 'binary' ?
656
- createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') :
657
- type; // node-fetch returns a Blob as well
760
+ return type.toString() === 'binary' ? createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') : type; // node-fetch returns a Blob as well
658
761
  };
659
- const generateGenericEndpoint = (suffix) => ({ target, method, path, endpoint }) => {
762
+ const generateGenericEndpoint = (suffix) => ({ method, endpoint, operationObject, context, options }) => {
660
763
  const functionName = generateGenericFunctionName(endpoint.path, suffix, method);
661
- const entity = pascalCase(endpoint.entity);
662
- const interfaceName = `${entity}Service_${pascalCase(functionName)}`;
663
- const entityQuery = `${interfaceName}_Query`;
764
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
765
+ const entityQuery = `${functionTypeName}_Query`;
664
766
  const hasId = endpoint.path.includes('{id}');
665
767
  const params = createObjectType({
666
- params: convertToTypeScriptType(convertParametersToSchema(path.parameters)),
667
- body: method === 'get' ? undefined : wrapBody(generateRequestBodyType(path), target)
768
+ params: operationObject.parameters &&
769
+ convertToTypeScriptType(resolveParameters(operationObject.parameters, context.parameters)),
770
+ body: method === 'get'
771
+ ? undefined
772
+ : wrapBody(generateRequestBodyType(operationObject, context.requestBodies), options.target)
773
+ });
774
+ const responseBody = generateResponseType(operationObject, context.responses);
775
+ const functionTypeSource = generateArrowFunctionType({
776
+ type: functionTypeName,
777
+ params: [
778
+ ...(hasId ? ['id: string'] : []),
779
+ `query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`,
780
+ 'requestOptions?: RequestOptions'
781
+ ],
782
+ returns: `${resolveResponseType(options.target)}<${wrapBody(responseBody, options.target).toString('force')}>`
668
783
  });
669
- const responseBody = generateResponseBodyType(path);
670
- const forceBlobResponse = String(responseBody.toString() === 'binary');
671
784
  const functionSource = generateArrowFunction({
672
785
  name: functionName,
673
- signature: interfaceName,
674
- params: hasId ? ['id', 'query'] : ['query'],
675
- returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${forceBlobResponse})`
676
- });
677
- const interfaceSource = generateArrowFunctionType({
678
- type: interfaceName,
679
- params: [...(hasId ? ['id: string'] : []), `query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`],
680
- returns: `${resolveResponseType(target)}<${wrapBody(responseBody, target).toString()}>`
786
+ signature: functionTypeName,
787
+ params: hasId ? ['id', 'query', 'requestOptions?: RequestOptions'] : ['query', 'requestOptions?: RequestOptions'],
788
+ returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${String(responseBody.toString() === 'binary')}, requestOptions)`
681
789
  });
682
790
  return {
683
- entity,
684
791
  name: functionName,
685
- type: { name: interfaceName, source: interfaceSource },
792
+ type: { name: functionTypeName, source: functionTypeSource },
686
793
  func: { name: functionName, source: functionSource },
687
794
  interfaces: [
688
795
  {
689
796
  name: entityQuery,
690
- source: generateInterfaceFromObject(entityQuery, params, true)
797
+ source: generateInterfaceFromObject(entityQuery, params, 'propagate')
691
798
  }
692
799
  ]
693
800
  };
694
801
  };
695
802
 
696
- const functionName$3 = 'remove';
697
- const generateRemoveEndpoint = ({ target, endpoint }) => {
698
- const entity = pascalCase(endpoint.entity);
699
- const interfaceName = `${entity}Service_${pascalCase(functionName$3)}`;
700
- const functionSource = generateArrowFunction({
701
- name: functionName$3,
702
- signature: interfaceName,
703
- returns: `_${functionName$3}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options)`,
704
- params: ['id', 'options?: RemoveQuery']
803
+ const generateRemoveEndpoint = ({ endpoint, options }) => {
804
+ const functionName = 'remove';
805
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
806
+ const functionTypeSource = generateArrowFunctionType({
807
+ type: functionTypeName,
808
+ params: ['id: string', 'options?: RemoveQuery', 'requestOptions?: RequestOptions'],
809
+ returns: `${resolveResponseType(options.target)}<void>`
705
810
  });
706
- const interfaceSource = generateArrowFunctionType({
707
- type: interfaceName,
708
- params: ['id: string', 'options?: RemoveQuery'],
709
- returns: `${resolveResponseType(target)}<void>`
811
+ const functionSource = generateArrowFunction({
812
+ name: functionName,
813
+ signature: functionTypeName,
814
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options, requestOptions)`,
815
+ params: ['id', 'options?: RemoveQuery', 'requestOptions?: RequestOptions']
710
816
  });
711
817
  return {
712
- entity,
713
- name: functionName$3,
714
- type: { name: interfaceName, source: interfaceSource },
715
- func: { name: functionName$3, source: functionSource }
818
+ name: functionName,
819
+ type: { name: functionTypeName, source: functionTypeSource },
820
+ func: { name: functionName, source: functionSource }
716
821
  };
717
822
  };
718
823
 
719
- const functionName$2 = 'some';
824
+ const generateTupleArray = (values) => `(${concat(values.map(generateString), ' | ')})[]`;
825
+
720
826
  const excludedParameters = [
721
- 'page', 'pageSize', 'sort',
722
- 'serializeNulls', 'properties', 'includeReferencedEntities'
827
+ 'page',
828
+ 'pageSize',
829
+ 'sort',
830
+ 'serializeNulls',
831
+ 'properties',
832
+ 'includeReferencedEntities',
833
+ 'additionalProperties'
723
834
  ];
724
- const resolveAdditionalProperties = (path) => {
725
- const body = resolveBodyType(path);
835
+ const resolveAdditionalPropertiesSchema = ({ responses }, contextResponses) => {
836
+ const response = resolveResponsesObject(responses);
837
+ const body = response && isReferenceObject(response) ? contextResponses.get(getRefName(response)) : response;
726
838
  if (isResponseObject(body)) {
727
839
  const schema = body?.content?.['application/json']?.schema;
728
840
  if (isObjectSchemaObject(schema)) {
@@ -734,280 +846,316 @@ const resolveAdditionalProperties = (path) => {
734
846
  }
735
847
  return undefined;
736
848
  };
737
- const generateSomeEndpoint = ({ aliases, target, path, endpoint }) => {
738
- // Required interface names
739
- const service = pascalCase(endpoint.entity);
740
- const entity = aliases.get(endpoint.entity) ?? service;
741
- const interfaceName = `${service}Service_${pascalCase(functionName$2)}`;
742
- const entityFilter = `${entity}_Filter`;
743
- const entityMappings = `${entity}_Mappings`;
744
- const entityReferences = `${entity}_References`;
745
- const entityParameters = `${service}_Parameters`;
746
- const parameterSchema = convertParametersToSchema(path.parameters);
747
- const additionalProperties = resolveAdditionalProperties(path);
748
- const additionalPropertyNames = generateStrings(Object.keys(additionalProperties?.properties ?? {}));
749
- const additionalPropertyNamesType = additionalPropertyNames.length ? `(${concat(additionalPropertyNames, ' | ')})[]` : '[]';
750
- // We already cover some properties
751
- parameterSchema.properties = Object.fromEntries(Object.entries(parameterSchema.properties ?? {})
752
- .filter(v => !excludedParameters.includes(v[0])));
753
- const parameters = createObjectType({
754
- params: convertToTypeScriptType(parameterSchema)
849
+ const resolveReferences = (entity, entities) => {
850
+ const references = [];
851
+ const generatedEntity = entities.get(entity);
852
+ if (generatedEntity) {
853
+ for (const [property, propertyMetaData] of generatedEntity.properties) {
854
+ if (propertyMetaData.service) {
855
+ references.push({
856
+ name: property,
857
+ type: generateString(propertyMetaData.service),
858
+ required: true
859
+ });
860
+ }
861
+ }
862
+ if (generatedEntity.parentName) {
863
+ references.push(...resolveReferences(generatedEntity.parentName, entities));
864
+ }
865
+ }
866
+ return references;
867
+ };
868
+ const resolveReferencedEntities = (entity, entities) => {
869
+ const referencedEntities = [];
870
+ const generatedEntity = entities.get(entity);
871
+ if (generatedEntity) {
872
+ for (const [, propertyMetaData] of generatedEntity.properties) {
873
+ if (propertyMetaData.service && propertyMetaData.entity) {
874
+ const referencedEntity = entities.get(propertyMetaData.entity);
875
+ if (referencedEntity)
876
+ referencedEntities.push({
877
+ name: propertyMetaData.service,
878
+ type: `${referencedEntity.interfaceName}[]`,
879
+ required: true
880
+ });
881
+ }
882
+ }
883
+ if (generatedEntity.parentName) {
884
+ referencedEntities.push(...resolveReferencedEntities(generatedEntity.parentName, entities));
885
+ }
886
+ }
887
+ return referencedEntities;
888
+ };
889
+ const generateSomeEndpoint = ({ endpoint, operationObject, entities, context, options }) => {
890
+ const functionName = 'some';
891
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
892
+ const relatedEntityName = context.aliases.get(endpoint.service);
893
+ const relatedEntity = !!relatedEntityName && entities.get(relatedEntityName);
894
+ if (!relatedEntity) {
895
+ throw Error(`Related entity schema for service ${endpoint.service} not found`);
896
+ }
897
+ const parametersTypeName = `${functionTypeName}_Parameters`;
898
+ const parameters = operationObject.parameters?.filter((v) => isParameterObject(v) ? !excludedParameters.includes(v.name) : false);
899
+ const parametersType = createObjectType({
900
+ params: parameters && convertToTypeScriptType(resolveParameters(parameters, context.parameters))
755
901
  });
756
- const properties = additionalProperties ? convertToTypeScriptType(additionalProperties).toString() : '{}';
757
- const interfaceSource = generateArrowFunctionType({
758
- type: interfaceName,
759
- generics: [
760
- `S extends (QuerySelect<${entity}> | undefined) = undefined`,
761
- `I extends (QuerySelect<${entityMappings}> | undefined) = undefined`
762
- ],
763
- params: [`query${parameters.isFullyOptional() ? '?' : ''}: SomeQuery<${entity}, ${entityFilter}, I, S, ${additionalPropertyNamesType}> & ${entityParameters}`],
764
- returns: `${resolveResponseType(target)}<SomeQueryReturn<${entity}, ${entityReferences}, ${entityMappings}, I, S, ${properties}>>`
902
+ const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
903
+ const filterTypeName = `${functionTypeName}_Filter`;
904
+ const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${relatedEntity.filterInterfaceName}`]);
905
+ const referencesTypeName = `${functionTypeName}_References`;
906
+ const referencesTypeSource = generateInterfaceType(referencesTypeName, resolveReferences(endpoint.service, entities));
907
+ const additionalPropertiesSchema = resolveAdditionalPropertiesSchema(operationObject, context.responses);
908
+ const additionalPropertyTypeName = `${functionTypeName}_AdditionalPropertyNames`;
909
+ const additionalPropertyTypeSource = generateType(additionalPropertyTypeName, additionalPropertiesSchema ? generateTupleArray(Object.keys(additionalPropertiesSchema?.properties)) : '[]');
910
+ const queryTypeName = `${functionTypeName}_Query`;
911
+ const queryTypeSource = generateType(queryTypeName, `SomeQuery<${relatedEntity.interfaceName}, ${filterTypeName}, ${referencesTypeName}, ${additionalPropertyTypeName}> & ${parametersTypeName}`);
912
+ const referencedEntitiesTypeName = `${functionTypeName}_ReferencedEntities`;
913
+ const referencedEntitiesTypeSource = generateInterfaceType(referencedEntitiesTypeName, resolveReferencedEntities(endpoint.service, entities));
914
+ const additionalPropertiesTypeName = `${functionTypeName}_AdditionalProperties`;
915
+ const additionalPropertiesTypeSource = generateType(additionalPropertiesTypeName, additionalPropertiesSchema ? convertToTypeScriptType(additionalPropertiesSchema).toString() : '{}');
916
+ const functionTypeSource = generateArrowFunctionType({
917
+ type: functionTypeName,
918
+ params: [`query${parametersType.isFullyOptional() ? '?' : ''}: ${queryTypeName}, requestOptions?: RequestOptions`],
919
+ returns: `${resolveResponseType(options.target)}<SomeQueryReturn<${relatedEntity.interfaceName}, ${referencedEntitiesTypeName}, ${additionalPropertiesTypeName}>>`
765
920
  });
766
921
  const functionSource = generateArrowFunction({
767
- name: functionName$2,
768
- signature: interfaceName,
769
- returns: `_${functionName$2}(cfg, ${generateString(endpoint.path)}, query)`,
770
- params: ['query']
922
+ name: functionName,
923
+ signature: functionTypeName,
924
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
925
+ params: ['query', 'requestOptions?: RequestOptions']
771
926
  });
772
927
  return {
773
- entity,
774
- name: functionName$2,
775
- type: { name: interfaceName, source: interfaceSource },
776
- func: { name: functionName$2, source: functionSource },
928
+ name: functionName,
929
+ type: { name: functionTypeName, source: functionTypeSource },
930
+ func: { name: functionName, source: functionSource },
777
931
  interfaces: [
778
- {
779
- name: entityParameters,
780
- source: generateInterfaceFromObject(entityParameters, parameters, true)
781
- }
932
+ { name: parametersTypeName, source: parametersTypeSource },
933
+ { name: filterTypeName, source: filterTypeSource },
934
+ { name: referencesTypeName, source: referencesTypeSource },
935
+ { name: additionalPropertyTypeName, source: additionalPropertyTypeSource },
936
+ { name: queryTypeName, source: queryTypeSource },
937
+ { name: referencedEntitiesTypeName, source: referencedEntitiesTypeSource },
938
+ { name: additionalPropertiesTypeName, source: additionalPropertiesTypeSource }
782
939
  ]
783
940
  };
784
941
  };
785
942
 
786
- const functionName$1 = 'unique';
787
- const generateUniqueEndpoint = ({ target, path, endpoint }) => {
788
- const entity = pascalCase(endpoint.entity);
789
- const interfaceName = `${entity}Service_${pascalCase(functionName$1)}`;
790
- const functionSource = generateArrowFunction({
791
- name: functionName$1,
792
- signature: interfaceName,
793
- params: ['id', 'query'],
794
- returns: `_${functionName$1}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query)`
795
- });
796
- const interfaceSource = generateArrowFunctionType({
797
- type: interfaceName,
798
- params: ['id: string', 'query?: Q'],
943
+ const generateUniqueEndpoint = ({ operationObject, endpoint, context, options }) => {
944
+ const functionName = 'unique';
945
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
946
+ const functionTypeSource = generateArrowFunctionType({
947
+ type: functionTypeName,
948
+ params: ['id: string', 'query?: Q', 'requestOptions?: RequestOptions'],
799
949
  generics: ['Q extends UniqueQuery'],
800
- returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
950
+ returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
951
+ });
952
+ const functionSource = generateArrowFunction({
953
+ name: functionName,
954
+ signature: functionTypeName,
955
+ params: ['id', 'query', 'requestOptions?: RequestOptions'],
956
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, requestOptions)`
801
957
  });
802
958
  return {
803
- entity,
804
- name: functionName$1,
805
- type: { name: interfaceName, source: interfaceSource },
806
- func: { name: functionName$1, source: functionSource }
959
+ name: functionName,
960
+ type: { name: functionTypeName, source: functionTypeSource },
961
+ func: { name: functionName, source: functionSource }
807
962
  };
808
963
  };
809
964
 
810
- const functionName = 'update';
811
- const generateUpdateEndpoint = ({ target, path, endpoint }) => {
812
- const entity = pascalCase(endpoint.entity);
813
- const interfaceName = `${entity}Service_${pascalCase(functionName)}`;
814
- const interfaceSource = generateArrowFunctionType({
815
- type: interfaceName,
816
- params: ['id: string', `data: DeepPartial<${generateRequestBodyType(path).toString()}>`, 'options?: UpdateQuery'],
817
- returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
965
+ const generateUpdateEndpoint = ({ endpoint, operationObject, context, options }) => {
966
+ const functionName = 'update';
967
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
968
+ const functionTypeSource = generateArrowFunctionType({
969
+ type: functionTypeName,
970
+ params: [
971
+ 'id: string',
972
+ `data: DeepPartial<${generateRequestBodyType(operationObject, context.requestBodies).toString()}>`,
973
+ 'options?: UpdateQuery',
974
+ 'requestOptions?: RequestOptions'
975
+ ],
976
+ returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
818
977
  });
819
978
  const functionSource = generateArrowFunction({
820
979
  name: functionName,
821
- signature: interfaceName,
822
- returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options)`,
823
- params: ['id', 'data', 'options']
980
+ signature: functionTypeName,
981
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options, requestOptions)`,
982
+ params: ['id', 'data', 'options', 'requestOptions?: RequestOptions']
824
983
  });
825
984
  return {
826
- entity,
827
985
  name: functionName,
828
- type: { name: interfaceName, source: interfaceSource },
986
+ type: { name: functionTypeName, source: functionTypeSource },
829
987
  func: { name: functionName, source: functionSource }
830
988
  };
831
989
  };
832
990
 
833
- const groupEndpointsByEntity = (paths) => {
834
- const endpoints = new Map();
835
- for (const [rawPath, path] of Object.entries(paths)) {
836
- const endpoint = parseEndpointPath(rawPath);
837
- if (!endpoint || !path) {
838
- logger.errorLn(`Failed to parse ${rawPath}`);
839
- continue;
840
- }
841
- if (endpoints.has(endpoint.entity)) {
842
- endpoints.get(endpoint.entity)?.push({ endpoint, path });
843
- }
844
- else {
845
- endpoints.set(endpoint.entity, [{ endpoint, path }]);
846
- }
847
- }
848
- return endpoints;
849
- };
850
-
851
991
  const generators = {
852
992
  /* /article */
853
993
  [WeclappEndpointType.ROOT]: {
854
- get: generateSomeEndpoint,
855
- post: generateCreateEndpoint
994
+ [OpenAPIV3.HttpMethods.GET]: generateSomeEndpoint,
995
+ [OpenAPIV3.HttpMethods.POST]: generateCreateEndpoint
856
996
  },
857
997
  /* /article/count */
858
998
  [WeclappEndpointType.COUNT]: {
859
- get: generateCountEndpoint
999
+ [OpenAPIV3.HttpMethods.GET]: generateCountEndpoint
860
1000
  },
861
1001
  /* /article/:id */
862
1002
  [WeclappEndpointType.ENTITY]: {
863
- get: generateUniqueEndpoint,
864
- delete: generateRemoveEndpoint,
865
- put: generateUpdateEndpoint
1003
+ [OpenAPIV3.HttpMethods.GET]: generateUniqueEndpoint,
1004
+ [OpenAPIV3.HttpMethods.PUT]: generateUpdateEndpoint,
1005
+ [OpenAPIV3.HttpMethods.DELETE]: generateRemoveEndpoint
866
1006
  },
867
1007
  /* /article/:id/method */
868
1008
  [WeclappEndpointType.GENERIC_ENTITY]: {
869
- get: generateGenericEndpoint('ById'),
870
- post: generateGenericEndpoint('ById')
1009
+ [OpenAPIV3.HttpMethods.GET]: generateGenericEndpoint('ById'),
1010
+ [OpenAPIV3.HttpMethods.POST]: generateGenericEndpoint('ById')
871
1011
  },
872
1012
  /* /article/method */
873
1013
  [WeclappEndpointType.GENERIC_ROOT]: {
874
- get: generateGenericEndpoint(),
875
- post: generateGenericEndpoint()
1014
+ [OpenAPIV3.HttpMethods.GET]: generateGenericEndpoint(),
1015
+ [OpenAPIV3.HttpMethods.POST]: generateGenericEndpoint()
876
1016
  }
877
1017
  };
878
- const generateServices = (doc, aliases, options) => {
1018
+ const generateServices = (entities, context, options) => {
879
1019
  const services = new Map();
880
- const grouped = groupEndpointsByEntity(doc.paths);
881
- for (const [endpoint, paths] of grouped) {
882
- const serviceName = camelCase(`${endpoint}Service`);
883
- const serviceTypeName = pascalCase(`${endpoint}Service`);
884
- // Service functions
1020
+ for (const [serviceName, serviceEndpoints] of context.endpoints) {
1021
+ const serviceFnName = camelCase(`${serviceName}Service`);
1022
+ const serviceTypeName = pascalCase(`${serviceName}Service`);
885
1023
  const functions = [];
886
- for (const { path, endpoint } of paths) {
887
- const resolver = generators[endpoint.type];
888
- for (const [method, config] of Object.entries(path)) {
889
- if (method === 'get' && endpoint.type === WeclappEndpointType.ENTITY && !options.generateUnique) {
1024
+ for (const { path, endpoint } of serviceEndpoints) {
1025
+ for (const method of [
1026
+ OpenAPIV3.HttpMethods.GET,
1027
+ OpenAPIV3.HttpMethods.POST,
1028
+ OpenAPIV3.HttpMethods.PUT,
1029
+ OpenAPIV3.HttpMethods.DELETE
1030
+ ]) {
1031
+ if ((method === OpenAPIV3.HttpMethods.GET &&
1032
+ endpoint.type === WeclappEndpointType.ENTITY &&
1033
+ !options.generateUnique) ||
1034
+ (method === OpenAPIV3.HttpMethods.POST &&
1035
+ (endpoint.type === WeclappEndpointType.COUNT || endpoint.path.endsWith('query')))) {
1036
+ // Skip unique endpoints if generateUnique option is not set or if POST is used for filter queries
890
1037
  continue;
891
1038
  }
892
- if (resolver[method]) {
893
- const path = config;
894
- const target = options.target;
895
- if (!path.deprecated || options.deprecated) {
1039
+ const operationObject = path[method];
1040
+ const generatorFn = generators[endpoint.type][method];
1041
+ if (operationObject && generatorFn) {
1042
+ if (!operationObject.deprecated || options.deprecated) {
896
1043
  functions.push({
897
- ...resolver[method]({ endpoint, method, target, path, aliases }),
898
- path
1044
+ ...generatorFn({
1045
+ method,
1046
+ endpoint,
1047
+ operationObject,
1048
+ entities,
1049
+ context,
1050
+ options
1051
+ }),
1052
+ path: operationObject
899
1053
  });
900
1054
  }
901
1055
  }
902
- else {
903
- logger.errorLn(`Failed to generate a function for ${method.toUpperCase()}:${endpoint.type} ${endpoint.path}`);
904
- }
905
1056
  }
906
1057
  }
907
1058
  if (!functions.length) {
908
1059
  continue;
909
1060
  }
910
- // Construct service type
911
- const types = generateStatements(...functions.flatMap(v => v.interfaces?.map(v => v.source) ?? []), ...functions.map(v => v.type.source), generateInterface(serviceTypeName, [
912
- ...functions.map(v => ({
1061
+ 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, [
1062
+ ...functions.map((v) => ({
913
1063
  required: true,
914
1064
  comment: v.path.deprecated ? '@deprecated' : undefined,
915
1065
  name: v.func.name,
916
1066
  type: v.type.name
917
1067
  }))
918
- ]));
919
- // Construct service value
920
- const funcBody = generateBlockStatements(...functions.map(v => v.func.source), `return {${concat(functions.map(v => v.func.name))}};`);
921
- const func = `export const ${serviceName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${funcBody};`;
922
- const source = generateBlockComment(`${pascalCase(endpoint)} service`, generateStatements(types, func));
923
- const deprecated = functions.every(v => v.path.deprecated);
924
- services.set(endpoint, { entity: endpoint, deprecated, serviceName, serviceTypeName, source, functions });
1068
+ ])));
1069
+ const serviceFn = `export const ${serviceFnName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${generateBlockStatements(...functions.map((v) => v.func.source), `return {${concat(functions.map((v) => v.func.name))}};`)};`;
1070
+ const relatedEntityName = context.aliases.get(serviceName);
1071
+ const relatedEntity = relatedEntityName ? entities.get(relatedEntityName) : undefined;
1072
+ services.set(serviceName, {
1073
+ name: serviceName,
1074
+ serviceFnName,
1075
+ functions,
1076
+ source: generateStatements(serviceTypes, serviceFn),
1077
+ deprecated: functions.every((v) => v.path.deprecated),
1078
+ relatedEntity
1079
+ });
925
1080
  }
926
1081
  return services;
927
1082
  };
928
1083
 
929
- const generateCustomValueUtilities = (entities, services) => {
930
- const customValueEntity = entities.get('customValue');
1084
+ const generateCustomValueServices = (services) => {
931
1085
  const customValueEntities = [];
932
- if (!customValueEntity) {
933
- logger.warn('Cannot generate custom value utils, type not found.');
934
- return '';
935
- }
936
- serviceLoop: for (const service of services) {
937
- const someFunction = service.functions.find(v => v.name === 'some');
938
- if (!someFunction) {
939
- continue;
940
- }
941
- const entity = entities.get(camelCase(someFunction.entity));
942
- if (entity?.properties.size !== customValueEntity.properties.size) {
943
- continue;
1086
+ for (const service of services) {
1087
+ const relatedEntity = service.relatedEntity;
1088
+ if (relatedEntity?.name === 'customValue') {
1089
+ customValueEntities.push(service.name);
944
1090
  }
945
- for (const [prop, { type }] of entity.properties) {
946
- if (customValueEntity.properties.get(prop)?.type !== type) {
947
- continue serviceLoop;
948
- }
949
- }
950
- customValueEntities.push(service.entity);
951
1091
  }
952
- 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);')}`));
1092
+ 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);')}`);
953
1093
  };
954
1094
 
955
1095
  const generateObject = (properties) => {
956
1096
  const body = [];
957
- for (const { key, value } of properties) {
1097
+ for (const { key, value, comment } of properties) {
958
1098
  if (value === undefined) {
959
1099
  continue;
960
1100
  }
961
1101
  if (Array.isArray(value)) {
962
1102
  const str = generateObject(value);
963
1103
  if (str.length > 2) {
964
- body.push(`${key}: ${str}`);
1104
+ body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${str}`);
965
1105
  }
966
1106
  }
967
1107
  else {
968
- body.push(`${key}: ${String(value)}`);
1108
+ body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${String(value)}`);
969
1109
  }
970
1110
  }
971
1111
  return body.length ? `{\n${indent(body.join(',\n'))}\n}` : `{}`;
972
1112
  };
973
1113
 
974
1114
  const resolveInheritedEntities = (root, entities) => {
975
- const parent = root.extends ? entities.get(root.extends) : undefined;
1115
+ const parent = root.parentName ? entities.get(root.parentName) : undefined;
976
1116
  return parent ? [parent, ...resolveInheritedEntities(parent, entities)] : [];
977
1117
  };
978
- const generatePropertyDescriptors = (entity, entities, services, options) => [
979
- ...resolveInheritedEntities(entity, entities).flatMap(v => [...v.properties]),
980
- ...entity.properties
981
- ].filter(([, meta]) => {
1118
+ const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
1119
+ .filter(([, meta]) => {
982
1120
  // If we generate deprecated things we can skip the filtering
983
1121
  if (options.deprecated) {
984
1122
  return true;
985
1123
  }
986
1124
  // Check if corresponding service is deprecated and can be removed
987
- const service = services.find(v => v.entity === meta.service);
1125
+ const service = services.find((v) => v.name === meta.service);
988
1126
  return !meta.service || (service && !service.deprecated);
989
- }).map(([property, meta]) => ({
1127
+ })
1128
+ .map(([property, meta]) => ({
990
1129
  key: property,
991
1130
  value: Object.entries(meta).map(([key, value]) => ({
992
1131
  key,
993
- value: value !== undefined ? typeof value === 'number' ? value : generateString(value) : undefined
1132
+ value: value !== undefined ? (typeof value === 'number' ? value : generateString(value)) : undefined
994
1133
  }))
995
1134
  }));
996
- const generateEntityPropertyMap = (entities, services, options) => {
1135
+ const generateEntityProperties = (entities, services, options) => {
997
1136
  const typeName = 'WEntityProperties';
998
- const propertyMap = [...entities].map(([entity, data]) => ({
999
- key: entity,
1000
- value: generatePropertyDescriptors(data, entities, services, options)
1137
+ const propertyMap = [
1138
+ ...entities.entries(),
1139
+ ...services
1140
+ .filter(({ relatedEntity }) => !!relatedEntity)
1141
+ .filter(({ name }) => !entities.get(name))
1142
+ .map(({ name, relatedEntity }) => {
1143
+ return [name, relatedEntity];
1144
+ })
1145
+ ].map(([entityName, entity]) => ({
1146
+ key: entityName,
1147
+ value: generatePropertyDescriptors(entity, entities, services, options)
1001
1148
  }));
1002
1149
  return generateStatements(`export type ${typeName} = Partial<Record<WEntity, Partial<Record<string, WEntityPropertyMeta>>>>;`, `export const wEntityProperties: ${typeName} = ${generateObject(propertyMap)};`);
1003
1150
  };
1004
1151
 
1005
1152
  const generateArray = (values) => {
1006
- return `[${concat(values.map(v => generateString(String(v))))}]`;
1153
+ return `[${concat(values.map((v) => generateString(String(v))))}]`;
1007
1154
  };
1008
1155
 
1009
1156
  // Only functions matching this regex are included in the generation.
1010
1157
  const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
1158
+ const SOME_FILTER_REGEX = /^(some)$/;
1011
1159
  /**
1012
1160
  * Generates for each function a map with the entity-name as key and service type as value.
1013
1161
  * E.g. WServicesWith[Function] where [Function] may be something like "some" or "create".
@@ -1016,160 +1164,173 @@ const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
1016
1164
  */
1017
1165
  const generateGroupedServices = (services) => {
1018
1166
  const entityDescriptors = new Map();
1019
- for (const { entity, functions } of services) {
1020
- for (const { name } of functions) {
1021
- if (!FILTER_REGEX.test(name)) {
1167
+ const someQueryDescriptors = new Map();
1168
+ for (const service of services) {
1169
+ for (const fn of service.functions) {
1170
+ if (!FILTER_REGEX.test(fn.name)) {
1022
1171
  continue;
1023
1172
  }
1024
- entityDescriptors.set(name, [
1025
- ...(entityDescriptors.get(name) ?? []), {
1026
- name: entity,
1173
+ entityDescriptors.set(fn.name, [
1174
+ ...(entityDescriptors.get(fn.name) ?? []),
1175
+ {
1176
+ name: service.name,
1027
1177
  required: true,
1028
- type: `${pascalCase(entity)}Service_${pascalCase(name)}`
1178
+ type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}`
1029
1179
  }
1030
1180
  ]);
1181
+ if (SOME_FILTER_REGEX.test(fn.name)) {
1182
+ someQueryDescriptors.set(fn.name, [
1183
+ ...(someQueryDescriptors.get(fn.name) ?? []),
1184
+ {
1185
+ name: service.name,
1186
+ required: true,
1187
+ type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}_Query`
1188
+ }
1189
+ ]);
1190
+ }
1031
1191
  }
1032
1192
  }
1033
- const descriptors = [...entityDescriptors.entries()];
1193
+ const entityDescriptorEntries = [...entityDescriptors.entries()];
1194
+ const someQueryDescriptorEntries = [...someQueryDescriptors.entries()];
1034
1195
  const typeGuards = [];
1035
- for (const [name] of descriptors) {
1196
+ for (const [name] of entityDescriptorEntries) {
1036
1197
  const constant = camelCase(`wServiceWith_${name}_Names`);
1037
1198
  const service = pascalCase(`WServiceWith_${name}`);
1038
1199
  const guard = `(service: string | undefined): service is ${service} =>\n${indent(`${constant}.includes(service as ${service});`)}`;
1039
1200
  typeGuards.push(`export const is${service} = ${guard}`);
1040
1201
  }
1041
- return [
1042
- ...descriptors.map(([name, props]) => generateInterface(pascalCase(`WServicesWith_${name}`), props)),
1043
- ...descriptors.map(([name]) => generateType(pascalCase(`WServiceWith_${name}`), `keyof ${pascalCase(`WServicesWith_${name}`)}`)),
1044
- ...descriptors.map(([name, props]) => {
1045
- const constant = camelCase(`wServiceWith_${name}_Names`);
1046
- const type = pascalCase(`WServiceWith_${name}`);
1047
- const value = generateArray(props.map(v => v.name));
1048
- return `export const ${constant}: ${type}[] = ${value};`;
1049
- }),
1050
- generateBlockComment('Type guards for service classes.', generateStatements(...typeGuards))
1051
- ];
1202
+ return generateStatements(...entityDescriptorEntries.map(([name, props]) => generateInterface(pascalCase(`WServicesWith_${name}`), props)), ...entityDescriptorEntries.map(([name]) => generateType(pascalCase(`WServiceWith_${name}`), `keyof ${pascalCase(`WServicesWith_${name}`)}`)), ...someQueryDescriptorEntries.map(([name, props]) => generateInterface(pascalCase(`WServicesWith_${name}_Query`), props)), ...someQueryDescriptorEntries.map(([name]) => generateType(pascalCase(`WServiceWith_${name}_Query`), `keyof ${pascalCase(`WServicesWith_${name}_Query`)}`)), ...entityDescriptorEntries.map(([name, props]) => {
1203
+ const constant = camelCase(`wServiceWith_${name}_Names`);
1204
+ const type = pascalCase(`WServiceWith_${name}`);
1205
+ const value = generateArray(props.map((v) => v.name));
1206
+ return `export const ${constant}: ${type}[] = ${value};`;
1207
+ }), ...typeGuards);
1052
1208
  };
1053
1209
 
1054
- const obj = (list) => `{\n${indent(list.join(',\n'))}\n}`;
1055
- const arr = (list) => `[\n${indent(list.join(',\n'))}\n]`;
1056
- const generateMaps = ({ services, entities, aliases, enums, options }) => {
1057
- const entitiesKeys = [...entities.keys()];
1058
- const enumsArray = `export const wEnums = ${obj(enums)};`;
1059
- const entityNames = `export const wEntityNames: WEntity[] = ${arr(entitiesKeys.map(v => `'${v}'`))};`;
1060
- const serviceNames = `export const wServiceNames: WService[] = ${arr(services.map(v => `'${v.entity}'`))};`;
1061
- const serviceValues = `export const wServiceFactories = ${obj(services.map(v => `${v.entity}: ${v.serviceName}`))};`;
1062
- const serviceInstanceValues = `export const wServices = ${obj(services.map(v => {
1063
- const src = `${v.entity}: ${v.serviceName}()`;
1064
- return v.deprecated ? generateInlineComment('@deprecated') + `\n${src}` : src;
1065
- }))};`;
1066
- const entityInterfaces = [
1067
- ...entitiesKeys.map(entity => ({
1068
- name: entity,
1069
- type: pascalCase(entity),
1210
+ const generateMaps = (enums, entities, services, context, options) => {
1211
+ const enumInstances = `export const wEnums = ${generateObject([...enums.keys()].map((v) => ({ key: v, value: v })))};`;
1212
+ const entityNames = `export const wEntityNames: WEntity[] = ${generateArray([...entities.keys()])};`;
1213
+ const generatedServices = [...services.values()];
1214
+ const serviceInstances = `export const wServices = ${generateObject(generatedServices.map((v) => ({
1215
+ key: v.name,
1216
+ value: `${v.serviceFnName}()`,
1217
+ comment: v.deprecated ? '@deprecated' : undefined
1218
+ })))};`;
1219
+ const serviceFactories = `export const wServiceFactories = ${generateObject(generatedServices.map((v) => ({
1220
+ key: v.name,
1221
+ value: v.serviceFnName,
1222
+ comment: v.deprecated ? '@deprecated' : undefined
1223
+ })))};`;
1224
+ return generateStatements(
1225
+ /* Enums */
1226
+ generateInterface('WEnums', [...enums.keys()].map((name) => ({ name, type: name, required: true }))), generateType('WEnum', 'keyof WEnums'), enumInstances,
1227
+ /* Entities */
1228
+ generateInterface('WEntities', [
1229
+ ...[...entities.entries()].map(([name, entity]) => ({
1230
+ name,
1231
+ type: entity.interfaceName,
1070
1232
  required: true
1071
1233
  })),
1072
- ...services.map(service => {
1073
- const alias = aliases.get(service.entity);
1074
- return {
1075
- name: service.entity,
1076
- type: alias ?? 'never',
1077
- required: true,
1078
- comment: alias ? undefined : 'no response defined or inlined'
1079
- };
1080
- })
1081
- ];
1082
- const createMappingType = (type, prefix) => type !== 'never' ? `${type}_${prefix}` : type;
1083
- const entitiesList = generateInterface('WEntities', entityInterfaces);
1084
- const entityReferences = generateInterface('WEntityReferences', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'References') })));
1085
- const entityMappings = generateInterface('WEntityMappings', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'Mappings') })));
1086
- const entityFilter = generateInterface('WEntityFilters', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'Filter') })));
1087
- return {
1088
- source: generateStatements(
1089
- /* JS Values */
1090
- serviceValues, serviceInstanceValues, entityNames, serviceNames, enumsArray, generateEntityPropertyMap(entities, services, options),
1091
- /* Map of entity to references / mappings and filters*/
1092
- entityReferences, entityMappings, entityFilter,
1093
- /* List of all entities with their corresponding service */
1094
- generateBlockComment(`
1095
- This interfaces merges two maps:
1096
- - Map<[entityName], [entityInterfaceName]>
1097
- - Map<[serviceName], [entityInterfaceName]>
1098
-
1099
- Where [entityName] is
1100
- - the name of a nested entity (e.g. 'address' from Party)
1101
- - the name of an entity (e.g. 'party', 'article' etc.)
1102
-
1103
- Where [serviceName] is the name of an endpoint (e.g. for /article its 'article')
1104
-
1105
- Where [entityInterfaceName] is
1106
- - the underlying type for this entity
1107
- - the type for what is returned by the api
1108
- `, entitiesList),
1109
- /* type-ofs and types */
1110
- generateType('WServices', 'typeof wServices'), generateType('WServiceFactories', 'typeof wServiceFactories'), generateType('WService', 'keyof WServices'), generateType('WEntity', 'keyof WEntities'), generateType('WEnums', 'typeof wEnums'), generateType('WEnum', 'keyof WEnums'),
1111
- /* Utilities. */
1112
- generateCustomValueUtilities(entities, services),
1113
- /* All functions grouped by service supporting it */
1114
- ...generateGroupedServices(services))
1115
- };
1234
+ ...generatedServices
1235
+ .filter(({ relatedEntity }) => !!relatedEntity)
1236
+ .filter(({ name }) => !entities.get(name))
1237
+ .map(({ name, relatedEntity }) => ({
1238
+ name,
1239
+ type: relatedEntity.interfaceName,
1240
+ required: true
1241
+ }))
1242
+ ].sort((a, b) => (a.name > b.name ? 1 : -1))), generateType('WEntity', 'keyof WEntities'), entityNames,
1243
+ /* Services */
1244
+ serviceInstances, generateType('WServices', 'typeof wServices'), generateType('WService', 'keyof WServices'), serviceFactories, generateType('WServiceFactories', 'typeof wServiceFactories'),
1245
+ /* Service Utils */
1246
+ generateGroupedServices(generatedServices), generateCustomValueServices(generatedServices),
1247
+ /* Entity Properties (Runtime Meta Infos) */
1248
+ generateEntityProperties(entities, generatedServices, options));
1116
1249
  };
1117
1250
 
1118
- const parseReferencedEntity = (obj) => pascalCase(obj.$ref.replace(/.*\//, ''));
1119
- /* eslint-disable @typescript-eslint/no-unsafe-return */
1120
- const extractSchemas = (doc) => {
1121
- const schemas = new Map();
1251
+ function extractRelatedEntityName(serviceEndpoints, responses) {
1252
+ const rootEndpoint = serviceEndpoints.find((v) => v.endpoint.type === WeclappEndpointType.ROOT);
1253
+ if (!rootEndpoint)
1254
+ return;
1255
+ const response = rootEndpoint?.path.get?.responses['200'];
1256
+ if (!response)
1257
+ return;
1258
+ let responseObject;
1259
+ if (isReferenceObject(response)) {
1260
+ const refName = getRefName(response);
1261
+ responseObject = responses.get(refName);
1262
+ }
1263
+ else {
1264
+ responseObject = response;
1265
+ }
1266
+ const responseSchema = responseObject?.content?.['application/json'].schema;
1267
+ if (responseSchema) {
1268
+ if (isReferenceObject(responseSchema)) {
1269
+ return;
1270
+ }
1271
+ const resultSchema = responseSchema.properties?.result;
1272
+ if (!resultSchema) {
1273
+ return;
1274
+ }
1275
+ if (isReferenceObject(resultSchema)) {
1276
+ return getRefName(resultSchema);
1277
+ }
1278
+ else if (isArraySchemaObject(resultSchema)) {
1279
+ const resultItemSchema = resultSchema.items;
1280
+ if (isReferenceObject(resultItemSchema)) {
1281
+ return getRefName(resultItemSchema);
1282
+ }
1283
+ }
1284
+ }
1285
+ }
1286
+
1287
+ const extractServiceAliases = (endpoints, responses) => {
1122
1288
  const aliases = new Map();
1289
+ for (const [serviceName, serviceEndpoints] of endpoints) {
1290
+ const relatedEntityName = extractRelatedEntityName(serviceEndpoints, responses);
1291
+ if (relatedEntityName)
1292
+ aliases.set(serviceName, relatedEntityName);
1293
+ }
1294
+ return aliases;
1295
+ };
1296
+ const extractContext = (doc) => {
1297
+ const endpoints = parseEndpointsPaths(doc.paths);
1298
+ const schemas = new Map();
1123
1299
  for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
1124
1300
  if (!isReferenceObject(schema)) {
1125
1301
  schemas.set(name, schema);
1126
1302
  }
1127
1303
  }
1128
- /**
1129
- * Referenced schemas in responses, in some case the response from the root endpoint
1130
- * refers to a schema with a different name
1131
- */
1132
- for (const [path, methods] of Object.entries(doc.paths)) {
1133
- const parsed = parseEndpointPath(path);
1134
- if (!parsed || schemas.has(parsed.entity)) {
1135
- continue;
1304
+ const responses = new Map();
1305
+ for (const [name, response] of Object.entries(doc.components?.responses ?? {})) {
1306
+ if (!isReferenceObject(response)) {
1307
+ responses.set(name, response);
1136
1308
  }
1137
- for (const method of Object.values(OpenAPIV3.HttpMethods)) {
1138
- const body = methods[method]?.responses['200'];
1139
- if (isResponseObject(body) && body.content) {
1140
- const responseSchema = Object.values(body.content)[0]?.schema;
1141
- if (isReferenceObject(responseSchema)) {
1142
- continue;
1143
- }
1144
- const itemsSchema = responseSchema?.properties?.result;
1145
- if (isReferenceObject(itemsSchema)) {
1146
- aliases.set(parsed.entity, parseReferencedEntity(itemsSchema));
1147
- continue;
1148
- }
1149
- if (isArraySchemaObject(itemsSchema)) {
1150
- const { items } = itemsSchema;
1151
- if (isReferenceObject(items)) {
1152
- aliases.set(parsed.entity, parseReferencedEntity(items));
1153
- }
1154
- }
1155
- }
1309
+ }
1310
+ const parameters = new Map();
1311
+ for (const [name, parameter] of Object.entries(doc.components?.parameters ?? {})) {
1312
+ if (!isReferenceObject(parameter)) {
1313
+ parameters.set(name, parameter);
1314
+ }
1315
+ }
1316
+ const requestBodies = new Map();
1317
+ for (const [name, requestBody] of Object.entries(doc.components?.requestBodies ?? {})) {
1318
+ if (!isReferenceObject(requestBody)) {
1319
+ requestBodies.set(name, requestBody);
1156
1320
  }
1157
1321
  }
1158
- return { schemas, aliases };
1322
+ const aliases = extractServiceAliases(endpoints, responses);
1323
+ return { endpoints, schemas, responses, parameters, requestBodies, aliases };
1159
1324
  };
1160
1325
 
1161
1326
  const generate = (doc, options) => {
1162
- const { schemas, aliases } = extractSchemas(doc);
1163
- const enums = generateEnums(schemas);
1164
- const entities = generateEntities(schemas, enums);
1165
- const services = generateServices(doc, aliases, options);
1166
- 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({
1167
- services: [...services.values()],
1168
- enums: [...enums.keys()],
1169
- options,
1170
- entities,
1171
- aliases
1172
- }).source));
1327
+ const context = extractContext(doc);
1328
+ const base = generateBase(doc.info.version, options);
1329
+ const enums = generateEnums(context);
1330
+ const entities = generateEntities(context);
1331
+ const services = generateServices(entities, context, options);
1332
+ const maps = generateMaps(enums, entities, services, context, options);
1333
+ return generateStatements(generateBlockComment('BASE', base), generateBlockComment('ENUMS', generateStatements(...[...enums.values()].map((v) => v.source))), generateBlockComment('ENTITIES', generateStatements(...[...entities.values()].map((v) => v.source))), generateBlockComment('FILTERS', generateStatements(...[...entities.values()].map((v) => v.filterSource))), generateBlockComment('SERVICES', generateStatements(...[...services.values()].map((v) => v.source))), generateBlockComment('MAPS', maps));
1173
1334
  };
1174
1335
 
1175
1336
  const hash = (content, algorithm = 'sha256') => {
@@ -1185,12 +1346,13 @@ const hash = (content, algorithm = 'sha256') => {
1185
1346
 
1186
1347
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
1187
1348
  const cli = async () => {
1188
- const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
1349
+ const version = pkg.version;
1189
1350
  const { argv } = yargs(hideBin(process.argv))
1190
1351
  .scriptName('build-weclapp-sdk')
1191
1352
  .usage('Usage: $0 <source> [flags]')
1192
1353
  .version(version)
1193
1354
  .example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
1355
+ .example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
1194
1356
  .example('$0 xxx.weclapp.com --key ...', 'Generate the SDK based on the openapi file from the given weclapp instance')
1195
1357
  .help('h')
1196
1358
  .alias('v', 'version')
@@ -1230,36 +1392,47 @@ const cli = async () => {
1230
1392
  type: 'string',
1231
1393
  choices: ['browser', 'browser.rx', 'node', 'node.rx']
1232
1394
  })
1233
- .option('d', {
1234
- alias: 'deprecated',
1235
- describe: 'Include deprecated functions and services',
1395
+ .option('use-query-language', {
1396
+ describe: 'Generate the new where property for some and count queries',
1236
1397
  type: 'boolean'
1398
+ })
1399
+ .option('apiVersion', {
1400
+ describe: 'Specify the api version (only needed when not using a local file)',
1401
+ type: 'string'
1237
1402
  })
1238
1403
  .epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
1239
1404
  if (argv.fromEnv) {
1240
1405
  config();
1241
1406
  }
1242
1407
  const { WECLAPP_API_KEY, WECLAPP_BACKEND_URL } = process.env;
1243
- const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, _: [src = WECLAPP_BACKEND_URL] } = argv;
1408
+ const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, apiVersion, _: [src = WECLAPP_BACKEND_URL] } = argv;
1244
1409
  const options = {
1245
1410
  deprecated,
1246
1411
  generateUnique: argv.generateUnique ?? false,
1247
- target: argv.target ?? Target.BROWSER_PROMISES
1412
+ target: argv.target ?? Target.BROWSER_PROMISES,
1413
+ useQueryLanguage: argv.useQueryLanguage ?? false
1248
1414
  };
1249
- if (typeof src === 'number') {
1250
- return Promise.reject('Expected string as command');
1415
+ if (!src || typeof src === 'number') {
1416
+ return Promise.reject(new Error('Expected string as command'));
1251
1417
  }
1252
1418
  if (!Object.values(Target).includes(options.target)) {
1253
1419
  logger.errorLn(`Unknown target: ${options.target}. Possible values are ${Object.values(Target).join(', ')}`);
1254
- return Promise.reject();
1420
+ return Promise.reject(new Error());
1255
1421
  }
1256
1422
  if (await stat(src).catch(() => false)) {
1257
- logger.infoLn(`Source is a file.`);
1423
+ logger.infoLn(`Source is a file`);
1258
1424
  const content = JSON.parse(await readFile(src, 'utf-8'));
1259
1425
  return { cache, content, options };
1260
1426
  }
1427
+ logger.infoLn(`Source is a URL`);
1428
+ if (!key) {
1429
+ return Promise.reject(new Error('API key is missing'));
1430
+ }
1431
+ if (!apiVersion) {
1432
+ return Promise.reject(new Error('API version is missing'));
1433
+ }
1261
1434
  const url = new URL(src.startsWith('http') ? src : `https://${src}`);
1262
- url.pathname = '/webapp/api/v1/meta/openapi.json';
1435
+ url.pathname = `/webapp/api/${apiVersion}/meta/openapi.json`;
1263
1436
  if (query?.length) {
1264
1437
  for (const param of query.split(',')) {
1265
1438
  const [name, value] = param.split('=');
@@ -1267,11 +1440,11 @@ const cli = async () => {
1267
1440
  }
1268
1441
  }
1269
1442
  const content = await fetch(url.toString(), {
1270
- headers: { 'Accept': 'application/json', 'AuthenticationToken': key }
1271
- }).then(res => res.ok ? res.json() : undefined);
1443
+ headers: { Accept: 'application/json', AuthenticationToken: key }
1444
+ }).then((res) => (res.ok ? res.json() : undefined));
1272
1445
  if (!content) {
1273
1446
  logger.errorLn(`Couldn't fetch file ${url.toString()} `);
1274
- return Promise.reject();
1447
+ return Promise.reject(new Error());
1275
1448
  }
1276
1449
  else {
1277
1450
  logger.infoLn(`Use remote file: ${url.toString()}`);
@@ -1279,50 +1452,59 @@ const cli = async () => {
1279
1452
  return { cache, content, options };
1280
1453
  };
1281
1454
 
1282
- const workingDirectory = resolve(currentDirname(), './sdk');
1283
- const folders = ['docs', 'main', 'node', 'raw', 'rx', 'utils'];
1455
+ const workingDir = resolve(currentDirname(), './sdk');
1456
+ const cacheDir = resolve(currentDirname(), './.cache');
1284
1457
  void (async () => {
1285
1458
  const start = process.hrtime.bigint();
1286
- const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
1287
1459
  const { content: doc, cache: useCache, options } = await cli();
1288
- // Resolve cache dir and key
1289
- const cacheKey = hash([version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
1290
- const cacheDir = resolve(currentDirname(), '.tmp', cacheKey);
1291
- const dist = (...paths) => resolve(workingDirectory, ...paths);
1292
- const tmp = async (...paths) => {
1293
- const fullPath = resolve(cacheDir, ...paths);
1294
- await mkdir(dirname(fullPath), { recursive: true }).catch(() => null);
1460
+ const workingDirPath = async (...paths) => {
1461
+ const fullPath = resolve(workingDir, ...paths);
1462
+ await mkdir(dirname(fullPath), { recursive: true });
1295
1463
  return fullPath;
1296
1464
  };
1465
+ // Resolve cache dir and key
1466
+ const cacheKey = hash([pkg.version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
1467
+ const cachedSdkDir = resolve(cacheDir, cacheKey);
1468
+ // Remove old SDK
1469
+ await rm(workingDir, { recursive: true, force: true });
1297
1470
  if (useCache) {
1298
1471
  logger.infoLn(`Cache ID: ${cacheKey}`);
1299
1472
  }
1300
- if (useCache && await stat(cacheDir).catch(() => false)) {
1301
- logger.successLn(`Cache match! (${cacheDir})`);
1473
+ if (useCache && (await stat(cachedSdkDir).catch(() => false))) {
1474
+ // Copy cached SDK to working dir
1475
+ logger.successLn(`Cache match! (${cachedSdkDir})`);
1476
+ await cp(cachedSdkDir, workingDir, { recursive: true });
1302
1477
  }
1303
1478
  else {
1304
- // Store swagger.json file
1305
- await writeFile(await tmp('openapi.json'), JSON.stringify(doc, null, 2));
1479
+ // Write openapi.json file
1480
+ await writeFile(await workingDirPath('openapi.json'), JSON.stringify(doc, null, 2));
1306
1481
  logger.infoLn(`Generate sdk (target: ${options.target})`);
1307
- // Generate SDKs
1482
+ // Generate and write SDK (index.ts)
1308
1483
  const sdk = generate(doc, options);
1309
- await writeFile(await tmp('src', `${options.target}.ts`), sdk.trim() + '\n');
1310
- // Bundle
1484
+ await writeFile(await workingDirPath('src', 'index.ts'), sdk.trim() + '\n');
1485
+ // Bundle and write SDK
1311
1486
  logger.infoLn('Bundle... (this may take some time)');
1312
- await bundle(cacheDir, options.target);
1313
- // Remove old SDK
1314
- await Promise.all(folders.map(async (dir) => rm(dist(dir), { recursive: true }).catch(() => 0)));
1487
+ await bundle(workingDir, options.target);
1488
+ // Remove index.ts (only bundle is required)
1489
+ await rm(await workingDirPath('src'), { recursive: true, force: true });
1490
+ if (useCache) {
1491
+ // Copy SDK to cache
1492
+ logger.successLn(`Caching SDK: (${cachedSdkDir})`);
1493
+ await mkdir(cachedSdkDir, { recursive: true });
1494
+ await cp(workingDir, cachedSdkDir, { recursive: true });
1495
+ }
1315
1496
  }
1316
- // Copy bundled SDK
1317
- await cp(cacheDir, workingDirectory, { recursive: true });
1318
1497
  // Print job summary
1319
1498
  const duration = (process.hrtime.bigint() - start) / 1000000n;
1320
1499
  logger.successLn(`SDK built in ${prettyMs(Number(duration))}`);
1321
1500
  logger.printSummary();
1322
- })().catch((error) => {
1501
+ })()
1502
+ .catch((error) => {
1323
1503
  logger.errorLn(`Fatal error:`);
1324
1504
  /* eslint-disable no-console */
1325
1505
  console.error(error);
1326
- }).finally(() => {
1327
- logger.errors && process.exit(1);
1506
+ })
1507
+ .finally(() => {
1508
+ if (logger.errors)
1509
+ process.exit(1);
1328
1510
  });