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

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: query?.properties?.join(','),\n properties: query?.select ? flattenSelect(query.select).join(',') : undefined,\n includeReferencedEntities: query?.include ? Object.keys(query.include).join(',') : undefined,\n ...flattenOrFilter(query?.or),\n ...flattenFilter(query?.filter),\n ...flattenSort(query?.sort),\n ...query?.params,\n ...query?.pagination\n }\n\n return wrapResponse(() =>\n raw(cfg, usePost ? `${endpoint}/query` : endpoint, {\n method: usePost ? 'POST' : 'GET',\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n )\n };\n";
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'; 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 = "export type ComparisonOperator =\n | 'EQ'\n | 'NE'\n | 'LT'\n | 'GT'\n | 'LE'\n | 'GE'\n | 'LIKE';\n\nexport type ArrayOperator = 'IN';\n\nexport type NullOperator = 'NULL';\n\nexport type Operator = ComparisonOperator | ArrayOperator | NullOperator;\n\nexport type ModifierFunction = 'lower';\n\nexport type MapOperators<T> =\n | ({ [K in ComparisonOperator]?: T } & { [K in ArrayOperator]?: T[] } & {\n [K in NullOperator]?: never } & {\n [K in ModifierFunction]?: boolean\n })\n | ({ [K in ComparisonOperator]?: T } & { [K in ArrayOperator]?: T[] } & {\n [K in NullOperator]?: boolean } & {\n [K in ModifierFunction]?: never\n });\n\nexport type SingleFilterExpr<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? U extends Record<any, any>\n ? SingleFilterExpr<U> | { NOT?: SingleFilterExpr<U> }\n : MapOperators<U>\n : T[P] extends Record<any, any> | undefined\n ? SingleFilterExpr<T[P]> | { NOT?: SingleFilterExpr<T[P]> }\n : MapOperators<T[P]>;\n};\n\nexport type QueryFilter<T> = SingleFilterExpr<T> & {\n OR?: QueryFilter<T>[];\n AND?: QueryFilter<T>[];\n NOT?: QueryFilter<T>;\n};\n\nexport type CountQuery<F> = {\n where?: QueryFilter<F>;\n};\n\nexport type SomeQuery<E, F, I, P> = {\n serializeNulls?: boolean;\n include?: QuerySelect<I>;\n properties?: P;\n where?: QueryFilter<F>;\n select?: QuerySelect<E>;\n sort?: Sort<E>[];\n pagination?: Pagination;\n};\n\nconst comparisonOperatorList: ComparisonOperator[] = [\n 'EQ',\n 'NE',\n 'LT',\n 'GT',\n 'LE',\n 'GE',\n 'LIKE'\n];\n\nconst comparisonOperatorMap: Record<Operator, string> = {\n EQ: '=',\n NE: '!=',\n LT: '<',\n GT: '>',\n LE: '<=',\n GE: '>=',\n LIKE: '~',\n IN: 'in',\n NULL: 'null'\n};\n\nconst modifierFunctionList: ModifierFunction[] = ['lower'];\n\nconst flattenWhere = (\n obj: QueryFilter<any> = {},\n nestedPaths: string[]\n): string[] => {\n const entries: string[] = [];\n for (const [prop, propValue] of Object.entries(obj)) {\n const setModifiers = findAllModifierFunctions(propValue ?? {}, modifierFunctionList).filter(\n (modifier) => modifier[1]\n );\n if (prop === 'OR') {\n const flattedOr: string[][] = [];\n for (let i = 0; i < (obj.OR?.length ?? 0); i++) {\n flattedOr.push(flattenWhere(obj.OR?.[i], nestedPaths));\n }\n entries.push(\n `(${flattedOr\n .map((x) => {\n const joined = x.join(' and ');\n\n if (x.length > 1) {\n return `(${joined})`;\n } else {\n return joined;\n }\n })\n .join(' or ')})`\n );\n } else if (prop === 'AND') {\n const flattedAnd: string[][] = [];\n for (let i = 0; i < (obj.AND?.length ?? 0); i++) {\n flattedAnd.push(flattenWhere(obj.AND?.[i], nestedPaths));\n }\n entries.push(\n `(${flattedAnd\n .map((x) => {\n const joined = x.join(' and ');\n\n if (x.length > 1) {\n return `(${joined})`;\n } else {\n return joined;\n }\n })\n .join(' and ')})`\n );\n } else if (prop === 'NOT') {\n const flattedNot = flattenWhere(obj.NOT, nestedPaths);\n entries.push(\n `not ${flattedNot.length > 1 ? '(' : ''}${flattedNot.join(' and ')}${flattedNot.length > 1 ? ')' : ''}`\n );\n } else if (propValue) {\n for (const [operator, value] of Object.entries(propValue)) {\n if (value === undefined) continue;\n if (comparisonOperatorList.includes(operator as ComparisonOperator)) {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${comparisonOperatorMap[operator as Operator]} ${\n typeof value === 'string'\n ? setModifiers.reduce((acc, [first]) => `${first}(${acc})`, JSON.stringify(value))\n : value\n }`\n );\n } else if ((operator as Operator) === 'NULL') {\n entries.push(\n `${!value ? 'not ' : ''}${nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')} ${comparisonOperatorMap[operator as Operator]}`\n );\n } else if ((operator as Operator) === 'IN') {\n if(value.length === 0) {\n entries.push('1 = 0')\n } else {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n nestedPaths.some((path) => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${comparisonOperatorMap[operator as Operator]} [${value.map((v: string | number) =>\n typeof v === 'string' ? setModifiers.reduce((acc, [first]) => `${first}(${acc})`, JSON.stringify(v)) : v\n )}]`\n );\n }\n } else if (\n !modifierFunctionList.includes(operator as ModifierFunction)\n ) {\n entries.push(\n ...flattenWhere(propValue as QueryFilter<any>, [\n ...nestedPaths,\n prop\n ])\n );\n break;\n }\n }\n }\n }\n return entries;\n};\n\nconst assembleFilterParam = (\n obj: QueryFilter<any> = {}\n): Record<string, string> => {\n const flattedFilter = flattenWhere(obj, []);\n return flattedFilter.length ? { filter: flattedFilter.join(' and ') } : {};\n};\n\nconst findAllModifierFunctions = (\n obj: Record<string, any>,\n types: ModifierFunction[]\n) => {\n const result: Record<string, any> = {};\n for (const key in obj) {\n if (types.includes(key as ModifierFunction)) {\n result[key] = obj[key];\n }\n }\n return Object.entries(result);\n};\n\nconst _count = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: CountQuery<any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n {\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n ...assembleFilterParam(query?.where),\n ...query?.params\n }\n\n return wrapResponse(() =>\n raw(cfg, endpoint, {\n unwrap: true,\n method: usePost ? 'POST' : 'GET',\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions)\n )\n };\n\nconst _some = (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n query?: SomeQuery<any, any, any, any> & { params?: Record<any, any> },\n requestOptions?: RequestOptions\n) =>\n {\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n const payload = {\n serializeNulls: query?.serializeNulls,\n additionalProperties: query?.properties?.join(','),\n properties: query?.select\n ? flattenSelect(query.select).join(',')\n : undefined,\n includeReferencedEntities: query?.include\n ? Object.keys(query.include).join(',')\n : undefined,\n ...assembleFilterParam(query?.where),\n ...flattenSort(query?.sort),\n ...query?.params,\n ...query?.pagination\n }\n\n return wrapResponse(() =>\n raw(cfg, usePost ? `${endpoint}/query` : endpoint, {\n method: usePost ? 'POST' : 'GET',\n ...(usePost ? { body: payload } : { query: payload })\n }, requestOptions).then((data) => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n )\n };\n";
142
+
143
+ var root = "export const raw = async (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n payload: RequestPayload = {},\n requestOptions?: RequestOptions\n): Promise<any> => {\n if (!cfg && !globalConfig) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const localCfg = {\n ...globalConfig,\n ...cfg,\n interceptors: { ...globalConfig?.interceptors, ...cfg?.interceptors }\n };\n\n const isBinaryData = payload.body instanceof resolveBinaryObject();\n const params = new URLSearchParams(Object.entries(payload.query ?? {}).filter((v) => v[1] !== undefined)\n .map(([key, value]) => [key, typeof value === 'string' ? value : JSON.stringify(value)])\n );\n\n const protocol = getProtocol(localCfg);\n\n const interceptRequest = localCfg.interceptors?.request ?? ((v) => v);\n const interceptResponse = localCfg.interceptors?.response ?? ((v) => v);\n\n const host = getHost(localCfg);\n\n let data;\n if (!cfg && localCfg.multiRequest) {\n let ep = endpoint;\n if (endpoint.startsWith('/')) {\n ep = endpoint.replace('/', '');\n }\n data = await addRequest(`${ep}?${params}`);\n } else {\n const request = new Request(`${protocol}//${host}/webapp/api/v${apiVersion}${endpoint}?${params}`, {\n ...(payload.body && {\n body: isBinaryData\n ? payload.body\n : JSON.stringify(payload.body, (_key, value) => (value === undefined ? null : value))\n }),\n ...(!localCfg.key && { credentials: 'same-origin' }),\n method: payload.method ?? 'get',\n headers: {\n Accept: 'application/json',\n ...(localCfg.key && { AuthenticationToken: localCfg.key }),\n ...(!isBinaryData && { 'Content-Type': 'application/json' })\n }\n });\n let res = (await interceptRequest(request, payload)) ?? request;\n if (!(res instanceof Response)) {\n res = requestOptions?.signal ? await fetch(res, { signal: requestOptions.signal } ) : await fetch(res);\n }\n res = (await interceptResponse(res)) ?? res;\n\n if (res.ok) {\n data =\n payload.forceBlob || !res.headers?.get('content-type')?.includes('application/json')\n ? await res.blob()\n : await res.json();\n } else {\n data = res.headers?.get('content-type')?.includes('application/json')\n ? await res.json()\n : res;\n }\n\n // Check if response was successful\n if (!res.ok) {\n return Promise.reject(data);\n }\n }\n\n return payload.unwrap ? data.result : data;\n};\n\nconst _remove = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n { dryRun = false }: RemoveQuery = {},\n requestOptions?: RequestOptions\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'DELETE',\n query: { dryRun }\n }, requestOptions).then(() => undefined)\n );\n\nconst _create = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, data: any, requestOptions?: RequestOptions) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'POST',\n body: data\n }, requestOptions)\n );\n\nconst _update = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n data: any,\n { ignoreMissingProperties, dryRun = false }: UpdateQuery = {},\n requestOptions?: RequestOptions\n) =>\n wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: 'PUT',\n body: data,\n query: {\n ignoreMissingProperties:\n ignoreMissingProperties ?? cfg?.ignoreMissingProperties ?? globalConfig?.ignoreMissingProperties,\n dryRun\n }\n }, requestOptions)\n );\n\nconst _generic = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n method: RequestPayloadMethod,\n endpoint: string,\n payload?: GenericQuery<any, any>,\n forceBlob?: boolean,\n requestOptions?: RequestOptions\n) =>\n{\n const usePost = cfg?.usePost ?? globalConfig?.usePost\n return wrapResponse(() =>\n raw({ ...cfg, multiRequest: false }, endpoint, {\n method: usePost ? 'POST' : method,\n forceBlob,\n ...(usePost && method === 'GET' ?\n { body: { ...payload?.params } }\n :\n { body: payload?.body, query: payload?.params } )\n }, requestOptions))\n};\n";
144
+
145
+ var unique = "const _unique = (cfg: ServiceConfigWithoutMultiRequest | undefined, endpoint: string, query?: UniqueQuery, requestOptions?: RequestOptions) =>\n wrapResponse(() => raw({ ...cfg, multiRequest: false }, endpoint, { query }, requestOptions));\n";
146
+
147
+ var types = "export type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>;\n }\n : T;\n\nexport type Sort<T> = {\n [K in keyof T]?: {\n [V in keyof T]?: V extends K\n ? T[V] extends Array<infer U> | undefined\n ? U extends object\n ? Sort<U>\n : never\n : T[V] extends object | undefined\n ? Sort<T[V]>\n : 'asc' | 'desc'\n : never;\n };\n}[keyof T];\n\nexport type CustomAttributeFilter = {\n [K in number]:| string | number | boolean | { id: string } | { entityName: string; entityId: string };\n};\n\nexport type QuerySelect<T> = {\n [P in keyof T]?: T[P] extends Array<infer U> | undefined\n ? QuerySelect<U> | boolean\n : T[P] extends Record<any, any> | undefined\n ? QuerySelect<T[P]> | boolean\n : boolean;\n};\n\nexport type Select<T, Q extends QuerySelect<T> | undefined> =\n Q extends QuerySelect<T>\n ? {\n // Filter out excluded properties beforehand\n [P in keyof T as Q[P] extends boolean ? P : Q[P] extends object ? P : never]: // Property\n Q[P] extends true\n ? T[P]\n : // Array\n T[P] extends Array<infer U>\n ? Select<U, Q[P] & QuerySelect<any>>[]\n : // Object\n T[P] extends Record<any, any>\n ? Select<T[P], Q[P] & QuerySelect<any>>\n : never;\n }\n : undefined;\n\nexport type MapKeys<T, S extends Record<keyof T, string>> = {\n [K in keyof T as S[K]]: T[K];\n};\n\nexport type ValueOf<T> = T[keyof T];\n\nexport type Pagination = {\n page: number;\n pageSize: number;\n};\n\nexport type UniqueQuery = {\n serializeNulls?: boolean;\n};\n\nexport type SomeQueryReturn<E, R, P> = {\n entities: E[];\n references?: R;\n properties?: P[];\n};\n\nexport type GenericQuery<P, B> = {\n params?: P;\n body?: B;\n};\n\nexport type UpdateQuery = {\n ignoreMissingProperties?: boolean;\n dryRun?: boolean;\n};\n\nexport type RemoveQuery = {\n dryRun?: boolean;\n};\n\nexport type WEntityPropertyMeta =\n | {\n type: 'string';\n format?: 'decimal' | 'html' | 'email' | 'password';\n maxLength?: number;\n pattern?: string;\n entity?: WEntity;\n service?: WService;\n }\n | {\n type: 'integer';\n format: 'int32' | 'int64' | 'duration' | 'date' | 'timestamp';\n }\n | { type: 'array'; format: 'reference'; entity: WEntity; service?: WService }\n | { type: 'array'; format: 'reference'; enum: WEnum }\n | { type: 'array'; format: 'string' }\n | { type: 'number'; format: 'double' }\n | { type: 'reference'; entity: WEntity }\n | { type: 'reference'; enum: WEnum }\n | { type: 'boolean' }\n | { type: 'object' };\n";
148
+
149
+ var utils = "const flattenSelect = (obj: Select<any, any> = {}): string[] => {\n const entries: string[] = [];\n\n for (const [prop, value] of Object.entries(obj)) {\n if (typeof value === 'object' && value) {\n entries.push(...flattenSelect(value).map((v) => `${prop}.${v}`));\n } else if (value) {\n entries.push(prop);\n }\n }\n\n return entries;\n};\n\nexport const flattenSort = (obj: Sort<any>[] = []): { sort?: string } => {\n const flatten = (obj: Sort<any>, base = ''): string | undefined => {\n const [key, value] = Object.entries(obj ?? {})[0] ?? [];\n\n if (key && value) {\n const path = base + key;\n\n if (typeof value === 'object') {\n return flatten(value, path ? `${path}.` : '');\n } else if (['asc', 'desc'].includes(value)) {\n return `${value === 'desc' ? '-' : ''}${path}`;\n }\n }\n\n return undefined;\n };\n\n const sorts = obj.map((v) => flatten(v)).filter(Boolean);\n return sorts.length ? { sort: sorts.join(',') } : {};\n};\n";
150
150
 
151
151
  const resolveImports = (target) => {
152
152
  const imports = [];
@@ -157,13 +157,13 @@ const resolveImports = (target) => {
157
157
  };
158
158
  const resolveMappings = (target) => `const wrapResponse = ${isRXTarget(target) ? 'defer' : '(v: (...args: any[]) => any) => v()'};`;
159
159
  const resolveBinaryClass = (target) => `const resolveBinaryObject = () => ${resolveBinaryType(target)};`;
160
- const generateBase = (target) => {
161
- return generateStatements(resolveImports(target), resolveMappings(target), resolveBinaryClass(target), types, root);
160
+ const generateBase = (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,46 +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.format = prop.format;
318
- result.type = prop.type;
319
- if (isArraySchemaObject(prop)) {
320
- if (isReferenceObject(prop.items)) {
321
- setEntityEnumProperty(enums, prop.items, result);
322
- result.format = 'reference';
323
- }
324
- else {
325
- 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
+ }
326
348
  }
327
349
  }
328
- return result;
350
+ return metaData;
329
351
  };
330
352
 
331
353
  const generateInlineComment = (comment) => `/** ${comment} */`;
@@ -335,12 +357,12 @@ const generateType = (name, value) => {
335
357
  return `export type ${name} = ${value.trim()};`;
336
358
  };
337
359
 
338
- const arrayify = (v) => Array.isArray(v) ? v : [v];
360
+ const arrayify = (v) => (Array.isArray(v) ? v : [v]);
339
361
 
340
362
  const generateInterfaceProperties = (entries) => {
341
363
  const properties = entries
342
- .filter(v => v.type !== undefined)
343
- .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)
344
366
  .map(({ name, type, required, readonly, comment }) => {
345
367
  const cmd = comment ? `${generateInlineComment(comment)}\n` : '';
346
368
  const req = required ? '' : '?';
@@ -350,7 +372,7 @@ const generateInterfaceProperties = (entries) => {
350
372
  .join('\n');
351
373
  return properties.length ? `{\n${indent(properties)}\n}` : `{}`;
352
374
  };
353
- const generateInterfaceFromObject = (name, obj, propagateOptionalProperties) => `export interface ${name} ${obj.toString(propagateOptionalProperties)}`;
375
+ const generateInterfaceFromObject = (name, obj, propertyPropagationOption) => `export interface ${name} ${obj.toString(propertyPropagationOption)}`;
354
376
  const generateInterface = (name, entries, extend) => {
355
377
  const signature = `${name} ${extend ? `extends ${arrayify(extend).join(', ')}` : ''}`.trim();
356
378
  const body = generateInterfaceProperties(entries);
@@ -359,69 +381,17 @@ const generateInterface = (name, entries, extend) => {
359
381
  const generateInterfaceType = (name, entries, extend) => {
360
382
  const body = generateInterfaceProperties(entries);
361
383
  const bases = extend ? arrayify(extend).join(' & ') : undefined;
362
- return generateType(name, `${bases ? `${bases} & ` : ''}${body}`);
363
- };
364
-
365
- const generateEntities = (schemas, enums) => {
366
- const entities = new Map();
367
- for (const [schemaName, schema] of schemas) {
368
- // Enums are generated separately
369
- if (isEnumSchemaObject(schema)) {
370
- continue;
371
- }
372
- const entity = pascalCase(schemaName);
373
- // Entity and filter
374
- const entityInterface = [];
375
- const filterInterface = [];
376
- // Referenced entities and property-to-referenced-entity mapping
377
- const referenceInterface = [];
378
- const referenceMappingsInterface = [];
379
- const properties = new Map();
380
- // The parent entity
381
- let extend = undefined;
382
- const processProperties = (props = {}) => {
383
- for (const [name, property] of Object.entries(props)) {
384
- const meta = isRelatedEntitySchema(property) ? property['x-weclapp'] : {};
385
- if (meta.entity) {
386
- const type = `${pascalCase(meta.entity)}[]`;
387
- referenceInterface.push({ name, type, required: true });
388
- filterInterface.push({ name: meta.entity, type, required: true });
389
- }
390
- if (meta.service) {
391
- referenceMappingsInterface.push({ name, type: generateString(meta.service), required: true });
392
- }
393
- const type = convertToTypeScriptType(property, name).toString();
394
- const comment = isNonArraySchemaObject(property) ?
395
- property.deprecated ? '@deprecated will be removed.' :
396
- property.format ? `format: ${property.format}` :
397
- undefined : undefined;
398
- entityInterface.push({
399
- name, type, comment,
400
- required: meta.required,
401
- readonly: !isReferenceObject(property) && property.readOnly
402
- });
403
- properties.set(name, extractPropertyMetaData(enums, meta, property));
404
- }
405
- };
406
- if (schema.allOf?.length) {
407
- for (const item of schema.allOf) {
408
- if (isReferenceObject(item)) {
409
- extend = convertToTypeScriptType(item).toString();
410
- }
411
- else if (isObjectSchemaObject(item)) {
412
- processProperties(item.properties);
413
- }
414
- }
415
- }
416
- processProperties(schema.properties);
417
- const source = generateStatements(generateInterface(entity, entityInterface, extend), generateInterface(`${entity}_References`, referenceInterface, extend ? [`${extend}_References`] : undefined), generateInterface(`${entity}_Mappings`, referenceMappingsInterface, extend ? [`${extend}_Mappings`] : undefined), generateInterfaceType(`${entity}_Filter`, filterInterface, extend ? [entity, `${extend}_Filter`] : undefined));
418
- entities.set(schemaName, {
419
- extends: extend ? camelCase(extend) : extend,
420
- properties,
421
- source
422
- });
384
+ let typeDefinition = '';
385
+ if (bases) {
386
+ typeDefinition = bases;
423
387
  }
424
- return entities;
388
+ else {
389
+ typeDefinition = body;
390
+ }
391
+ if (bases && body !== '{}') {
392
+ typeDefinition += ` & ${body}`;
393
+ }
394
+ return generateType(name, typeDefinition);
425
395
  };
426
396
 
427
397
  /**
@@ -429,13 +399,10 @@ const generateEntities = (schemas, enums) => {
429
399
  * @param s String to pluralize.
430
400
  */
431
401
  const pluralize = (s) => {
432
- return s.endsWith('s') ? s :
433
- s.endsWith('y') ? `${s.slice(0, -1)}ies` :
434
- `${s}s`;
402
+ return s.endsWith('s') ? s : s.endsWith('y') ? `${s.slice(0, -1)}ies` : `${s}s`;
435
403
  };
436
404
 
437
- /* eslint-disable no-console */
438
- const logger = new class {
405
+ const logger = new (class {
439
406
  active = true;
440
407
  warnings = 0;
441
408
  errors = 0;
@@ -483,21 +450,126 @@ const logger = new class {
483
450
  printSummary() {
484
451
  const format = (v, name, fail, ok) => {
485
452
  const color = v ? fail : ok;
486
- return v === 0 ? `${color('zero')} ${pluralize(name)}` :
487
- 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)}`;
488
458
  };
489
459
  const warnings = format(this.warnings, 'warning', chalk.yellowBright, chalk.greenBright);
490
460
  const errors = format(this.errors, 'error', chalk.redBright, chalk.greenBright);
491
461
  const info = `Finished with ${warnings} and ${errors}.`;
492
462
  this[this.errors ? 'errorLn' : this.warnings ? 'warnLn' : 'successLn'](info);
493
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;
494
566
  };
495
567
 
496
568
  /**
497
- * ROOT => /article
498
- * COUNT => /article/count
499
- * ENTITY => /article/{id}
500
- * SPECIAL_ROOT => /article/generateImage
569
+ * ROOT => /article
570
+ * COUNT => /article/count
571
+ * ENTITY => /article/{id}
572
+ * SPECIAL_ROOT => /article/generateImage
501
573
  * SPECIAL_ENTITY => /article/id/{id}/generateImag
502
574
  */
503
575
  var WeclappEndpointType;
@@ -509,26 +581,61 @@ var WeclappEndpointType;
509
581
  WeclappEndpointType["GENERIC_ENTITY"] = "GENERIC_ENTITY";
510
582
  })(WeclappEndpointType || (WeclappEndpointType = {}));
511
583
  const parseEndpointPath = (path) => {
512
- const [, entity, ...rest] = path.split('/');
513
- if (!entity) {
584
+ const [, service, ...rest] = path.split('/');
585
+ if (!service) {
514
586
  return undefined;
515
587
  }
516
588
  if (!rest.length) {
517
- return { path, entity, type: WeclappEndpointType.ROOT };
589
+ return { path, service, type: WeclappEndpointType.ROOT };
518
590
  }
519
591
  else if (rest[0] === 'count') {
520
- return { path, entity, type: WeclappEndpointType.COUNT };
592
+ return { path, service, type: WeclappEndpointType.COUNT };
521
593
  }
522
594
  else if (rest[0] === 'id') {
523
- return rest.length === 2 ?
524
- { path, entity, type: WeclappEndpointType.ENTITY } :
525
- { path, entity, method: rest[2], type: WeclappEndpointType.GENERIC_ENTITY };
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
+ };
526
603
  }
527
604
  else if (rest.length === 1) {
528
- 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
+ };
529
611
  }
530
612
  return undefined;
531
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
+ };
532
639
 
533
640
  const generateArrowFunction = ({ name, signature, returns, params }) => {
534
641
  return `const ${name}: ${signature} = (${params?.join(', ') ?? ''}) =>\n${indent(returns)};`;
@@ -540,109 +647,109 @@ const generateArrowFunctionType = ({ type, returns = 'void', generics, params })
540
647
  return generateType(type, `${genericsString + paramsString} =>\n${indent(returns)}`);
541
648
  };
542
649
 
543
- const convertParametersToSchema = (parameters = []) => {
544
- const properties = [];
545
- const required = [];
546
- for (const param of parameters) {
547
- if (isParameterObject(param) && param.in === 'query') {
548
- if (param.schema) {
549
- properties.push([param.name, param.schema]);
550
- param.required && required.push(param.name);
551
- }
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] : [];
552
657
  }
553
- }
554
- return {
555
- type: 'object', required,
556
- properties: Object.fromEntries(properties)
557
- };
658
+ return [param];
659
+ });
558
660
  };
559
661
 
560
- const functionName$5 = 'count';
561
- const generateCountEndpoint = ({ aliases, path, target, endpoint }) => {
562
- const service = pascalCase(endpoint.entity);
563
- const entity = aliases.get(endpoint.entity) ?? service;
564
- const entityFilter = `${entity}_Filter`;
565
- const interfaceName = `${service}Service_${pascalCase(functionName$5)}`;
566
- const entityParameters = `${interfaceName}_Parameters`;
567
- const parameterSchema = convertParametersToSchema(path.parameters);
568
- const parameters = createObjectType({
569
- params: convertToTypeScriptType(parameterSchema)
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))
570
673
  });
571
- const functionSource = generateArrowFunction({
572
- name: functionName$5,
573
- signature: interfaceName,
574
- returns: `_${functionName$5}(cfg, ${generateString(endpoint.path)}, query)`,
575
- 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>`
576
684
  });
577
- const interfaceSource = generateArrowFunctionType({
578
- type: interfaceName,
579
- params: [`query${parameters.isFullyOptional() ? '?' : ''}: CountQuery<${entityFilter}> & ${entityParameters}`],
580
- 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']
581
690
  });
582
691
  return {
583
- entity,
584
- name: functionName$5,
585
- type: { name: interfaceName, source: interfaceSource },
586
- func: { name: functionName$5, source: functionSource },
692
+ name: functionName,
693
+ type: { name: functionTypeName, source: functionTypeSource },
694
+ func: { name: functionName, source: functionSource },
587
695
  interfaces: [
588
- {
589
- name: entityParameters,
590
- source: generateInterfaceFromObject(entityParameters, parameters, true)
591
- }
696
+ ...(operationObject.parameters?.length ? [{ name: parametersTypeName, source: parametersTypeSource }] : []),
697
+ { name: filterTypeName, source: filterTypeSource }
592
698
  ]
593
699
  };
594
700
  };
595
701
 
596
- const generateBodyType = (body) => {
597
- if (isReferenceObject(body)) {
598
- return convertToTypeScriptType(body);
599
- }
702
+ const generateContentType = (body, fallback = 'unknown') => {
703
+ if (!body?.content)
704
+ return createRawType(fallback);
600
705
  const types = [];
601
- for (const { schema } of Object.values(body?.content ?? {})) {
706
+ for (const { schema } of Object.values(body.content)) {
602
707
  if (schema) {
603
708
  types.push(convertToTypeScriptType(schema));
604
709
  }
605
710
  }
606
- return types.length ? createTupleType(types) : undefined;
711
+ return (types.length > 1 ? createTupleType(types) : types[0]) ?? createRawType(fallback);
607
712
  };
608
713
 
609
- const generateRequestBodyType = ({ requestBody }) => {
610
- 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);
611
717
  };
612
718
 
613
- const resolveBodyType = ({ responses }) => Object.entries(responses)
614
- .filter(v => v[0].startsWith('2'))[0]?.[1];
615
- const generateResponseBodyType = (object) => generateBodyType(resolveBodyType(object)) ?? createRawType('void');
719
+ const resolveResponsesObject = (responses) => Object.entries(responses).find(([statusCode]) => statusCode.startsWith('2'))?.[1];
616
720
 
617
- const functionName$4 = 'create';
618
- const generateCreateEndpoint = ({ target, path, endpoint }) => {
619
- const entity = pascalCase(endpoint.entity);
620
- const interfaceName = `${entity}Service_${pascalCase(functionName$4)}`;
621
- const functionSource = generateArrowFunction({
622
- name: functionName$4,
623
- signature: interfaceName,
624
- returns: `_${functionName$4}(cfg, ${generateString(endpoint.path)}, data)`,
625
- params: ['data']
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()}>`
626
737
  });
627
- const interfaceSource = generateArrowFunctionType({
628
- type: interfaceName,
629
- params: [`data: DeepPartial<${generateRequestBodyType(path).toString()}>`],
630
- 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']
631
743
  });
632
744
  return {
633
- entity,
634
- name: functionName$4,
635
- type: { name: interfaceName, source: interfaceSource },
636
- func: { name: functionName$4, source: functionSource }
745
+ name: functionName,
746
+ type: { name: functionTypeName, source: functionTypeSource },
747
+ func: { name: functionName, source: functionSource }
637
748
  };
638
749
  };
639
750
 
640
751
  const generateGenericFunctionName = (path, suffix = '', prefix = '') => {
641
- return camelCase(`${prefix}_` +
642
- path
643
- .replace(/.*\//, '')
644
- .replace(/\W+/, '_')
645
- .replace(/[_]+/, '_') + `_${suffix}`);
752
+ return camelCase(`${prefix}_` + path.replace(/.*\//, '').replace(/\W+/, '_').replace(/[_]+/, '_') + `_${suffix}`);
646
753
  };
647
754
 
648
755
  const insertPathPlaceholder = (path, record) => {
@@ -650,77 +757,81 @@ const insertPathPlaceholder = (path, record) => {
650
757
  };
651
758
 
652
759
  const wrapBody = (type, target) => {
653
- return type.toString() === 'binary' ?
654
- createRawType(isNodeTarget(target) ? 'BodyInit' : 'Blob') :
655
- 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
656
761
  };
657
- const generateGenericEndpoint = (suffix) => ({ target, method, path, endpoint }) => {
762
+ const generateGenericEndpoint = (suffix) => ({ method, endpoint, operationObject, context, options }) => {
658
763
  const functionName = generateGenericFunctionName(endpoint.path, suffix, method);
659
- const entity = pascalCase(endpoint.entity);
660
- const interfaceName = `${entity}Service_${pascalCase(functionName)}`;
661
- const entityQuery = `${interfaceName}_Query`;
764
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
765
+ const entityQuery = `${functionTypeName}_Query`;
662
766
  const hasId = endpoint.path.includes('{id}');
663
767
  const params = createObjectType({
664
- params: convertToTypeScriptType(convertParametersToSchema(path.parameters)),
665
- 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')}>`
666
783
  });
667
- const responseBody = generateResponseBodyType(path);
668
- const forceBlobResponse = String(responseBody.toString() === 'binary');
669
784
  const functionSource = generateArrowFunction({
670
785
  name: functionName,
671
- signature: interfaceName,
672
- params: hasId ? ['id', 'query'] : ['query'],
673
- returns: `_generic(cfg, ${generateString(method.toUpperCase())}, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, ${forceBlobResponse})`
674
- });
675
- const interfaceSource = generateArrowFunctionType({
676
- type: interfaceName,
677
- params: [...(hasId ? ['id: string'] : []), `query${params.isFullyOptional() ? '?' : ''}: ${entityQuery}`],
678
- returns: `${resolveResponseType(target)}<${wrapBody(responseBody, target).toString()}>`
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)`
679
789
  });
680
790
  return {
681
- entity,
682
791
  name: functionName,
683
- type: { name: interfaceName, source: interfaceSource },
792
+ type: { name: functionTypeName, source: functionTypeSource },
684
793
  func: { name: functionName, source: functionSource },
685
794
  interfaces: [
686
795
  {
687
796
  name: entityQuery,
688
- source: generateInterfaceFromObject(entityQuery, params, true)
797
+ source: generateInterfaceFromObject(entityQuery, params, 'propagate')
689
798
  }
690
799
  ]
691
800
  };
692
801
  };
693
802
 
694
- const functionName$3 = 'remove';
695
- const generateRemoveEndpoint = ({ target, endpoint }) => {
696
- const entity = pascalCase(endpoint.entity);
697
- const interfaceName = `${entity}Service_${pascalCase(functionName$3)}`;
698
- const functionSource = generateArrowFunction({
699
- name: functionName$3,
700
- signature: interfaceName,
701
- returns: `_${functionName$3}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, options)`,
702
- params: ['id', 'options?: RemoveQuery']
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>`
703
810
  });
704
- const interfaceSource = generateArrowFunctionType({
705
- type: interfaceName,
706
- params: ['id: string', 'options?: RemoveQuery'],
707
- 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']
708
816
  });
709
817
  return {
710
- entity,
711
- name: functionName$3,
712
- type: { name: interfaceName, source: interfaceSource },
713
- func: { name: functionName$3, source: functionSource }
818
+ name: functionName,
819
+ type: { name: functionTypeName, source: functionTypeSource },
820
+ func: { name: functionName, source: functionSource }
714
821
  };
715
822
  };
716
823
 
717
- const functionName$2 = 'some';
718
824
  const excludedParameters = [
719
- 'page', 'pageSize', 'sort',
720
- 'serializeNulls', 'properties', 'includeReferencedEntities'
825
+ 'page',
826
+ 'pageSize',
827
+ 'sort',
828
+ 'serializeNulls',
829
+ 'properties',
830
+ 'includeReferencedEntities',
831
+ 'additionalProperties'
721
832
  ];
722
- const resolveAdditionalProperties = (path) => {
723
- const body = resolveBodyType(path);
833
+ const resolveAdditionalPropertiesSchema = ({ responses }) => {
834
+ const body = resolveResponsesObject(responses);
724
835
  if (isResponseObject(body)) {
725
836
  const schema = body?.content?.['application/json']?.schema;
726
837
  if (isObjectSchemaObject(schema)) {
@@ -732,276 +843,311 @@ const resolveAdditionalProperties = (path) => {
732
843
  }
733
844
  return undefined;
734
845
  };
735
- const generateSomeEndpoint = ({ aliases, target, path, endpoint }) => {
736
- // Required interface names
737
- const service = pascalCase(endpoint.entity);
738
- const entity = aliases.get(endpoint.entity) ?? service;
739
- const interfaceName = `${service}Service_${pascalCase(functionName$2)}`;
740
- const entityFilter = `${entity}_Filter`;
741
- const entityMappings = `${entity}_Mappings`;
742
- const entityReferences = `${entity}_References`;
743
- const entityParameters = `${service}_Parameters`;
744
- const parameterSchema = convertParametersToSchema(path.parameters);
745
- const additionalProperties = resolveAdditionalProperties(path);
746
- const additionalPropertyNames = generateStrings(Object.keys(additionalProperties?.properties ?? {}));
747
- const additionalPropertyNamesType = additionalPropertyNames.length ? `(${concat(additionalPropertyNames, ' | ')})[]` : '[]';
748
- // We already cover some properties
749
- parameterSchema.properties = Object.fromEntries(Object.entries(parameterSchema.properties ?? {})
750
- .filter(v => !excludedParameters.includes(v[0])));
751
- const parameters = createObjectType({
752
- params: convertToTypeScriptType(parameterSchema)
846
+ const resolveReferences = (entity, entities) => {
847
+ const references = [];
848
+ const generatedEntity = entities.get(entity);
849
+ if (generatedEntity) {
850
+ for (const [property, propertyMetaData] of generatedEntity.properties) {
851
+ if (propertyMetaData.service) {
852
+ references.push({
853
+ name: property,
854
+ type: generateString(propertyMetaData.service),
855
+ required: true
856
+ });
857
+ }
858
+ }
859
+ if (generatedEntity.parentName) {
860
+ references.push(...resolveReferences(generatedEntity.parentName, entities));
861
+ }
862
+ }
863
+ return references;
864
+ };
865
+ const resolveReferencedEntities = (entity, entities) => {
866
+ const referencedEntities = [];
867
+ const generatedEntity = entities.get(entity);
868
+ if (generatedEntity) {
869
+ for (const [, propertyMetaData] of generatedEntity.properties) {
870
+ if (propertyMetaData.service && propertyMetaData.entity) {
871
+ const referencedEntity = entities.get(propertyMetaData.entity);
872
+ if (referencedEntity)
873
+ referencedEntities.push({
874
+ name: propertyMetaData.service,
875
+ type: `${referencedEntity.interfaceName}[]`,
876
+ required: true
877
+ });
878
+ }
879
+ }
880
+ if (generatedEntity.parentName) {
881
+ referencedEntities.push(...resolveReferencedEntities(generatedEntity.parentName, entities));
882
+ }
883
+ }
884
+ return referencedEntities;
885
+ };
886
+ const generateSomeEndpoint = ({ endpoint, operationObject, entities, context, options }) => {
887
+ const functionName = 'some';
888
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
889
+ const relatedEntityName = context.aliases.get(endpoint.service);
890
+ const relatedEntity = !!relatedEntityName && entities.get(relatedEntityName);
891
+ if (!relatedEntity) {
892
+ throw Error(`Related entity schema for service ${endpoint.service} not found`);
893
+ }
894
+ const parametersTypeName = `${functionTypeName}_Parameters`;
895
+ const parameters = operationObject.parameters?.filter((v) => isParameterObject(v) ? !excludedParameters.includes(v.name) : false);
896
+ const parametersType = createObjectType({
897
+ params: parameters && convertToTypeScriptType(resolveParameters(parameters, context.parameters))
753
898
  });
754
- const properties = additionalProperties ? convertToTypeScriptType(additionalProperties).toString() : '{}';
755
- const interfaceSource = generateArrowFunctionType({
756
- type: interfaceName,
757
- generics: [
758
- `S extends (QuerySelect<${entity}> | undefined) = undefined`,
759
- `I extends (QuerySelect<${entityMappings}> | undefined) = undefined`
760
- ],
761
- params: [`query${parameters.isFullyOptional() ? '?' : ''}: SomeQuery<${entity}, ${entityFilter}, I, S, ${additionalPropertyNamesType}> & ${entityParameters}`],
762
- returns: `${resolveResponseType(target)}<SomeQueryReturn<${entity}, ${entityReferences}, ${entityMappings}, I, S, ${properties}>>`
899
+ const parametersTypeSource = generateInterfaceFromObject(parametersTypeName, parametersType, 'propagate');
900
+ const filterTypeName = `${functionTypeName}_Filter`;
901
+ const filterTypeSource = generateInterfaceType(filterTypeName, [], [`${relatedEntity.filterInterfaceName}`]);
902
+ const referencesTypeName = `${functionTypeName}_References`;
903
+ const referencesTypeSource = generateInterfaceType(referencesTypeName, resolveReferences(endpoint.service, entities));
904
+ const additionalPropertyTypeName = `${functionTypeName}_AdditionalProperty`;
905
+ const additionalPropertyTypeSource = generateType(additionalPropertyTypeName, 'string');
906
+ const queryTypeName = `${functionTypeName}_Query`;
907
+ const queryTypeSource = generateType(queryTypeName, `SomeQuery<${relatedEntity.interfaceName}, ${filterTypeName}, ${referencesTypeName}, ${additionalPropertyTypeName}> & ${parametersTypeName}`);
908
+ const referencedEntitiesTypeName = `${functionTypeName}_ReferencedEntities`;
909
+ const referencedEntitiesTypeSource = generateInterfaceType(referencedEntitiesTypeName, resolveReferencedEntities(endpoint.service, entities));
910
+ const additionalPropertiesTypeName = `${functionTypeName}_AdditionalProperties`;
911
+ const additionalPropertiesSchema = resolveAdditionalPropertiesSchema(operationObject);
912
+ const additionalPropertiesTypeSource = generateType(additionalPropertiesTypeName, additionalPropertiesSchema ? convertToTypeScriptType(additionalPropertiesSchema).toString() : '{}');
913
+ const functionTypeSource = generateArrowFunctionType({
914
+ type: functionTypeName,
915
+ params: [`query${parametersType.isFullyOptional() ? '?' : ''}: ${queryTypeName}, requestOptions?: RequestOptions`],
916
+ returns: `${resolveResponseType(options.target)}<SomeQueryReturn<${relatedEntity.interfaceName}, ${referencedEntitiesTypeName}, ${additionalPropertiesTypeName}>>`
763
917
  });
764
918
  const functionSource = generateArrowFunction({
765
- name: functionName$2,
766
- signature: interfaceName,
767
- returns: `_${functionName$2}(cfg, ${generateString(endpoint.path)}, query)`,
768
- params: ['query']
919
+ name: functionName,
920
+ signature: functionTypeName,
921
+ returns: `_${functionName}(cfg, ${generateString(endpoint.path)}, query, requestOptions)`,
922
+ params: ['query', 'requestOptions?: RequestOptions']
769
923
  });
770
924
  return {
771
- entity,
772
- name: functionName$2,
773
- type: { name: interfaceName, source: interfaceSource },
774
- func: { name: functionName$2, source: functionSource },
925
+ name: functionName,
926
+ type: { name: functionTypeName, source: functionTypeSource },
927
+ func: { name: functionName, source: functionSource },
775
928
  interfaces: [
776
- {
777
- name: entityParameters,
778
- source: generateInterfaceFromObject(entityParameters, parameters, true)
779
- }
929
+ { name: parametersTypeName, source: parametersTypeSource },
930
+ { name: filterTypeName, source: filterTypeSource },
931
+ { name: referencesTypeName, source: referencesTypeSource },
932
+ { name: additionalPropertyTypeName, source: additionalPropertyTypeSource },
933
+ { name: queryTypeName, source: queryTypeSource },
934
+ { name: referencedEntitiesTypeName, source: referencedEntitiesTypeSource },
935
+ { name: additionalPropertiesTypeName, source: additionalPropertiesTypeSource }
780
936
  ]
781
937
  };
782
938
  };
783
939
 
784
- const functionName$1 = 'unique';
785
- const generateUniqueEndpoint = ({ target, path, endpoint }) => {
786
- const entity = pascalCase(endpoint.entity);
787
- const interfaceName = `${entity}Service_${pascalCase(functionName$1)}`;
788
- const functionSource = generateArrowFunction({
789
- name: functionName$1,
790
- signature: interfaceName,
791
- params: ['id', 'query'],
792
- returns: `_${functionName$1}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query)`
793
- });
794
- const interfaceSource = generateArrowFunctionType({
795
- type: interfaceName,
796
- params: ['id: string', 'query?: Q'],
940
+ const generateUniqueEndpoint = ({ operationObject, endpoint, context, options }) => {
941
+ const functionName = 'unique';
942
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
943
+ const functionTypeSource = generateArrowFunctionType({
944
+ type: functionTypeName,
945
+ params: ['id: string', 'query?: Q', 'requestOptions?: RequestOptions'],
797
946
  generics: ['Q extends UniqueQuery'],
798
- returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
947
+ returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
948
+ });
949
+ const functionSource = generateArrowFunction({
950
+ name: functionName,
951
+ signature: functionTypeName,
952
+ params: ['id', 'query', 'requestOptions?: RequestOptions'],
953
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, query, requestOptions)`
799
954
  });
800
955
  return {
801
- entity,
802
- name: functionName$1,
803
- type: { name: interfaceName, source: interfaceSource },
804
- func: { name: functionName$1, source: functionSource }
956
+ name: functionName,
957
+ type: { name: functionTypeName, source: functionTypeSource },
958
+ func: { name: functionName, source: functionSource }
805
959
  };
806
960
  };
807
961
 
808
- const functionName = 'update';
809
- const generateUpdateEndpoint = ({ target, path, endpoint }) => {
810
- const entity = pascalCase(endpoint.entity);
811
- const interfaceName = `${entity}Service_${pascalCase(functionName)}`;
812
- const interfaceSource = generateArrowFunctionType({
813
- type: interfaceName,
814
- params: ['id: string', `data: DeepPartial<${generateRequestBodyType(path).toString()}>`, 'options?: UpdateQuery'],
815
- returns: `${resolveResponseType(target)}<${generateResponseBodyType(path).toString()}>`
962
+ const generateUpdateEndpoint = ({ endpoint, operationObject, context, options }) => {
963
+ const functionName = 'update';
964
+ const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`;
965
+ const functionTypeSource = generateArrowFunctionType({
966
+ type: functionTypeName,
967
+ params: [
968
+ 'id: string',
969
+ `data: DeepPartial<${generateRequestBodyType(operationObject, context.requestBodies).toString()}>`,
970
+ 'options?: UpdateQuery',
971
+ 'requestOptions?: RequestOptions'
972
+ ],
973
+ returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>`
816
974
  });
817
975
  const functionSource = generateArrowFunction({
818
976
  name: functionName,
819
- signature: interfaceName,
820
- returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options)`,
821
- params: ['id', 'data', 'options']
977
+ signature: functionTypeName,
978
+ returns: `_${functionName}(cfg, \`${insertPathPlaceholder(endpoint.path, { id: '${id}' })}\`, data, options, requestOptions)`,
979
+ params: ['id', 'data', 'options', 'requestOptions?: RequestOptions']
822
980
  });
823
981
  return {
824
- entity,
825
982
  name: functionName,
826
- type: { name: interfaceName, source: interfaceSource },
983
+ type: { name: functionTypeName, source: functionTypeSource },
827
984
  func: { name: functionName, source: functionSource }
828
985
  };
829
986
  };
830
987
 
831
- const groupEndpointsByEntity = (paths) => {
832
- const endpoints = new Map();
833
- for (const [rawPath, path] of Object.entries(paths)) {
834
- const endpoint = parseEndpointPath(rawPath);
835
- if (!endpoint || !path) {
836
- logger.errorLn(`Failed to parse ${rawPath}`);
837
- continue;
838
- }
839
- if (endpoints.has(endpoint.entity)) {
840
- endpoints.get(endpoint.entity)?.push({ endpoint, path });
841
- }
842
- else {
843
- endpoints.set(endpoint.entity, [{ endpoint, path }]);
844
- }
845
- }
846
- return endpoints;
847
- };
848
-
849
988
  const generators = {
850
989
  /* /article */
851
990
  [WeclappEndpointType.ROOT]: {
852
- get: generateSomeEndpoint,
853
- post: generateCreateEndpoint
991
+ [OpenAPIV3.HttpMethods.GET]: generateSomeEndpoint,
992
+ [OpenAPIV3.HttpMethods.POST]: generateCreateEndpoint
854
993
  },
855
994
  /* /article/count */
856
995
  [WeclappEndpointType.COUNT]: {
857
- get: generateCountEndpoint
996
+ [OpenAPIV3.HttpMethods.GET]: generateCountEndpoint
858
997
  },
859
998
  /* /article/:id */
860
999
  [WeclappEndpointType.ENTITY]: {
861
- get: generateUniqueEndpoint,
862
- delete: generateRemoveEndpoint,
863
- put: generateUpdateEndpoint
1000
+ [OpenAPIV3.HttpMethods.GET]: generateUniqueEndpoint,
1001
+ [OpenAPIV3.HttpMethods.PUT]: generateUpdateEndpoint,
1002
+ [OpenAPIV3.HttpMethods.DELETE]: generateRemoveEndpoint
864
1003
  },
865
1004
  /* /article/:id/method */
866
1005
  [WeclappEndpointType.GENERIC_ENTITY]: {
867
- get: generateGenericEndpoint('ById'),
868
- post: generateGenericEndpoint('ById')
1006
+ [OpenAPIV3.HttpMethods.GET]: generateGenericEndpoint('ById'),
1007
+ [OpenAPIV3.HttpMethods.POST]: generateGenericEndpoint('ById')
869
1008
  },
870
1009
  /* /article/method */
871
1010
  [WeclappEndpointType.GENERIC_ROOT]: {
872
- get: generateGenericEndpoint(),
873
- post: generateGenericEndpoint()
1011
+ [OpenAPIV3.HttpMethods.GET]: generateGenericEndpoint(),
1012
+ [OpenAPIV3.HttpMethods.POST]: generateGenericEndpoint()
874
1013
  }
875
1014
  };
876
- const generateServices = (doc, aliases, options) => {
1015
+ const generateServices = (entities, context, options) => {
877
1016
  const services = new Map();
878
- const grouped = groupEndpointsByEntity(doc.paths);
879
- for (const [endpoint, paths] of grouped) {
880
- const serviceName = camelCase(`${endpoint}Service`);
881
- const serviceTypeName = pascalCase(`${endpoint}Service`);
882
- // Service functions
1017
+ for (const [serviceName, serviceEndpoints] of context.endpoints) {
1018
+ const serviceFnName = camelCase(`${serviceName}Service`);
1019
+ const serviceTypeName = pascalCase(`${serviceName}Service`);
883
1020
  const functions = [];
884
- for (const { path, endpoint } of paths) {
885
- const resolver = generators[endpoint.type];
886
- for (const [method, config] of Object.entries(path)) {
887
- if (method === 'get' && endpoint.type === WeclappEndpointType.ENTITY && !options.generateUnique) {
1021
+ for (const { path, endpoint } of serviceEndpoints) {
1022
+ for (const method of [
1023
+ OpenAPIV3.HttpMethods.GET,
1024
+ OpenAPIV3.HttpMethods.POST,
1025
+ OpenAPIV3.HttpMethods.PUT,
1026
+ OpenAPIV3.HttpMethods.DELETE
1027
+ ]) {
1028
+ if ((method === OpenAPIV3.HttpMethods.GET &&
1029
+ endpoint.type === WeclappEndpointType.ENTITY &&
1030
+ !options.generateUnique) ||
1031
+ (method === OpenAPIV3.HttpMethods.POST &&
1032
+ (endpoint.type === WeclappEndpointType.COUNT || endpoint.path.endsWith('query')))) {
1033
+ // Skip unique endpoints if generateUnique option is not set or if POST is used for filter queries
888
1034
  continue;
889
1035
  }
890
- if (resolver[method]) {
891
- const path = config;
892
- const target = options.target;
893
- if (!path.deprecated || options.deprecated) {
1036
+ const operationObject = path[method];
1037
+ const generatorFn = generators[endpoint.type][method];
1038
+ if (operationObject && generatorFn) {
1039
+ if (!operationObject.deprecated || options.deprecated) {
894
1040
  functions.push({
895
- ...resolver[method]({ endpoint, method, target, path, aliases }),
896
- path
1041
+ ...generatorFn({
1042
+ method,
1043
+ endpoint,
1044
+ operationObject,
1045
+ entities,
1046
+ context,
1047
+ options
1048
+ }),
1049
+ path: operationObject
897
1050
  });
898
1051
  }
899
1052
  }
900
- else {
901
- logger.errorLn(`Failed to generate a function for ${method.toUpperCase()}:${endpoint.type} ${endpoint.path}`);
902
- }
903
1053
  }
904
1054
  }
905
1055
  if (!functions.length) {
906
1056
  continue;
907
1057
  }
908
- // Construct service type
909
- const types = generateStatements(...functions.flatMap(v => v.interfaces?.map(v => v.source) ?? []), ...functions.map(v => v.type.source), generateInterface(serviceTypeName, [
910
- ...functions.map(v => ({
1058
+ 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, [
1059
+ ...functions.map((v) => ({
911
1060
  required: true,
912
1061
  comment: v.path.deprecated ? '@deprecated' : undefined,
913
1062
  name: v.func.name,
914
1063
  type: v.type.name
915
1064
  }))
916
- ]));
917
- // Construct service value
918
- const funcBody = generateBlockStatements(...functions.map(v => v.func.source), `return {${concat(functions.map(v => v.func.name))}};`);
919
- const func = `export const ${serviceName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${funcBody};`;
920
- const source = generateBlockComment(`${pascalCase(endpoint)} service`, generateStatements(types, func));
921
- const deprecated = functions.every(v => v.path.deprecated);
922
- services.set(endpoint, { entity: endpoint, deprecated, serviceName, serviceTypeName, source, functions });
1065
+ ])));
1066
+ const serviceFn = `export const ${serviceFnName} = (cfg?: ServiceConfig): ${serviceTypeName} => ${generateBlockStatements(...functions.map((v) => v.func.source), `return {${concat(functions.map((v) => v.func.name))}};`)};`;
1067
+ const relatedEntityName = context.aliases.get(serviceName);
1068
+ const relatedEntity = relatedEntityName ? entities.get(relatedEntityName) : undefined;
1069
+ services.set(serviceName, {
1070
+ name: serviceName,
1071
+ serviceFnName,
1072
+ functions,
1073
+ source: generateStatements(serviceTypes, serviceFn),
1074
+ deprecated: functions.every((v) => v.path.deprecated),
1075
+ relatedEntity
1076
+ });
923
1077
  }
924
1078
  return services;
925
1079
  };
926
1080
 
927
- const generateCustomValueUtilities = (entities, services) => {
928
- const customValueEntity = entities.get('customValue');
1081
+ const generateCustomValueServices = (services) => {
929
1082
  const customValueEntities = [];
930
- if (!customValueEntity) {
931
- logger.warn('Cannot generate custom value utils, type not found.');
932
- return '';
933
- }
934
- serviceLoop: for (const service of services) {
935
- const someFunction = service.functions.find(v => v.name === 'some');
936
- if (!someFunction) {
937
- continue;
938
- }
939
- const entity = entities.get(camelCase(someFunction.entity));
940
- if (entity?.properties.size !== customValueEntity.properties.size) {
941
- continue;
942
- }
943
- for (const [prop, { type }] of entity.properties) {
944
- if (customValueEntity.properties.get(prop)?.type !== type) {
945
- continue serviceLoop;
946
- }
1083
+ for (const service of services) {
1084
+ const relatedEntity = service.relatedEntity;
1085
+ if (relatedEntity?.name === 'customValue') {
1086
+ customValueEntities.push(service.name);
947
1087
  }
948
- customValueEntities.push(service.entity);
949
1088
  }
950
- return generateBlockComment('Utilities to identify services that return an entity that is an alias to CustomValue.', generateStatements(generateType('WCustomValueService', concat(generateStrings(customValueEntities), ' | ')), `export const wCustomValueServiceNames: WCustomValueService[] = [${concat(generateStrings(customValueEntities))}];`, `export const isWCustomValueService = (service: string | undefined): service is WCustomValueService =>\n${indent('wCustomValueServiceNames.includes(service as WCustomValueService);')}`));
1089
+ return generateStatements(generateType('WCustomValueService', concat(generateStrings(customValueEntities), ' | ')), `export const wCustomValueServiceNames: WCustomValueService[] = [${concat(generateStrings(customValueEntities))}];`, `export const isWCustomValueService = (service: string | undefined): service is WCustomValueService =>\n${indent('wCustomValueServiceNames.includes(service as WCustomValueService);')}`);
951
1090
  };
952
1091
 
953
1092
  const generateObject = (properties) => {
954
1093
  const body = [];
955
- for (const { key, value } of properties) {
1094
+ for (const { key, value, comment } of properties) {
956
1095
  if (value === undefined) {
957
1096
  continue;
958
1097
  }
959
1098
  if (Array.isArray(value)) {
960
1099
  const str = generateObject(value);
961
1100
  if (str.length > 2) {
962
- body.push(`${key}: ${str}`);
1101
+ body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${str}`);
963
1102
  }
964
1103
  }
965
1104
  else {
966
- body.push(`${key}: ${String(value)}`);
1105
+ body.push(`${comment ? generateInlineComment(comment) + '\n' : ''}${key}: ${String(value)}`);
967
1106
  }
968
1107
  }
969
1108
  return body.length ? `{\n${indent(body.join(',\n'))}\n}` : `{}`;
970
1109
  };
971
1110
 
972
1111
  const resolveInheritedEntities = (root, entities) => {
973
- const parent = root.extends ? entities.get(root.extends) : undefined;
1112
+ const parent = root.parentName ? entities.get(root.parentName) : undefined;
974
1113
  return parent ? [parent, ...resolveInheritedEntities(parent, entities)] : [];
975
1114
  };
976
- const generatePropertyDescriptors = (entity, entities, services, options) => [
977
- ...resolveInheritedEntities(entity, entities).flatMap(v => [...v.properties]),
978
- ...entity.properties
979
- ].filter(([, meta]) => {
1115
+ const generatePropertyDescriptors = (entity, entities, services, options) => [...resolveInheritedEntities(entity, entities).flatMap((v) => [...v.properties]), ...entity.properties]
1116
+ .filter(([, meta]) => {
980
1117
  // If we generate deprecated things we can skip the filtering
981
1118
  if (options.deprecated) {
982
1119
  return true;
983
1120
  }
984
1121
  // Check if corresponding service is deprecated and can be removed
985
- const service = services.find(v => v.entity === meta.service);
1122
+ const service = services.find((v) => v.name === meta.service);
986
1123
  return !meta.service || (service && !service.deprecated);
987
- }).map(([property, meta]) => ({
1124
+ })
1125
+ .map(([property, meta]) => ({
988
1126
  key: property,
989
1127
  value: Object.entries(meta).map(([key, value]) => ({
990
1128
  key,
991
- value: value ? generateString(value) : undefined
1129
+ value: value !== undefined ? (typeof value === 'number' ? value : generateString(value)) : undefined
992
1130
  }))
993
1131
  }));
994
- const generateEntityPropertyMap = (entities, services, options) => {
1132
+ const generateEntityProperties = (entities, services, options) => {
995
1133
  const typeName = 'WEntityProperties';
996
- const propertyMap = [...entities].map(([entity, data]) => ({
997
- key: entity,
998
- value: generatePropertyDescriptors(data, entities, services, options)
1134
+ const propertyMap = [
1135
+ ...entities.entries(),
1136
+ ...services
1137
+ .filter(({ relatedEntity }) => !!relatedEntity)
1138
+ .filter(({ name }) => !entities.get(name))
1139
+ .map(({ name, relatedEntity }) => {
1140
+ return [name, relatedEntity];
1141
+ })
1142
+ ].map(([entityName, entity]) => ({
1143
+ key: entityName,
1144
+ value: generatePropertyDescriptors(entity, entities, services, options)
999
1145
  }));
1000
1146
  return generateStatements(`export type ${typeName} = Partial<Record<WEntity, Partial<Record<string, WEntityPropertyMeta>>>>;`, `export const wEntityProperties: ${typeName} = ${generateObject(propertyMap)};`);
1001
1147
  };
1002
1148
 
1003
1149
  const generateArray = (values) => {
1004
- return `[${concat(values.map(v => generateString(String(v))))}]`;
1150
+ return `[${concat(values.map((v) => generateString(String(v))))}]`;
1005
1151
  };
1006
1152
 
1007
1153
  // Only functions matching this regex are included in the generation.
@@ -1014,16 +1160,17 @@ const FILTER_REGEX = /^(some|count|create|remove|unique|update)$/;
1014
1160
  */
1015
1161
  const generateGroupedServices = (services) => {
1016
1162
  const entityDescriptors = new Map();
1017
- for (const { entity, functions } of services) {
1018
- for (const { name } of functions) {
1019
- if (!FILTER_REGEX.test(name)) {
1163
+ for (const service of services) {
1164
+ for (const fn of service.functions) {
1165
+ if (!FILTER_REGEX.test(fn.name)) {
1020
1166
  continue;
1021
1167
  }
1022
- entityDescriptors.set(name, [
1023
- ...(entityDescriptors.get(name) ?? []), {
1024
- name: entity,
1168
+ entityDescriptors.set(fn.name, [
1169
+ ...(entityDescriptors.get(fn.name) ?? []),
1170
+ {
1171
+ name: service.name,
1025
1172
  required: true,
1026
- type: `${pascalCase(entity)}Service_${pascalCase(name)}`
1173
+ type: `${pascalCase(service.name)}Service_${pascalCase(fn.name)}`
1027
1174
  }
1028
1175
  ]);
1029
1176
  }
@@ -1036,138 +1183,138 @@ const generateGroupedServices = (services) => {
1036
1183
  const guard = `(service: string | undefined): service is ${service} =>\n${indent(`${constant}.includes(service as ${service});`)}`;
1037
1184
  typeGuards.push(`export const is${service} = ${guard}`);
1038
1185
  }
1039
- return [
1040
- ...descriptors.map(([name, props]) => generateInterface(pascalCase(`WServicesWith_${name}`), props)),
1041
- ...descriptors.map(([name]) => generateType(pascalCase(`WServiceWith_${name}`), `keyof ${pascalCase(`WServicesWith_${name}`)}`)),
1042
- ...descriptors.map(([name, props]) => {
1043
- const constant = camelCase(`wServiceWith_${name}_Names`);
1044
- const type = pascalCase(`WServiceWith_${name}`);
1045
- const value = generateArray(props.map(v => v.name));
1046
- return `export const ${constant}: ${type}[] = ${value};`;
1047
- }),
1048
- generateBlockComment('Type guards for service classes.', generateStatements(...typeGuards))
1049
- ];
1186
+ return generateStatements(...descriptors.map(([name, props]) => generateInterface(pascalCase(`WServicesWith_${name}`), props)), ...descriptors.map(([name]) => generateType(pascalCase(`WServiceWith_${name}`), `keyof ${pascalCase(`WServicesWith_${name}`)}`)), ...descriptors.map(([name, props]) => {
1187
+ const constant = camelCase(`wServiceWith_${name}_Names`);
1188
+ const type = pascalCase(`WServiceWith_${name}`);
1189
+ const value = generateArray(props.map((v) => v.name));
1190
+ return `export const ${constant}: ${type}[] = ${value};`;
1191
+ }), ...typeGuards);
1050
1192
  };
1051
1193
 
1052
- const obj = (list) => `{\n${indent(list.join(',\n'))}\n}`;
1053
- const arr = (list) => `[\n${indent(list.join(',\n'))}\n]`;
1054
- const generateMaps = ({ services, entities, aliases, enums, options }) => {
1055
- const entitiesKeys = [...entities.keys()];
1056
- const enumsArray = `export const wEnums = ${obj(enums)};`;
1057
- const entityNames = `export const wEntityNames: WEntity[] = ${arr(entitiesKeys.map(v => `'${v}'`))};`;
1058
- const serviceNames = `export const wServiceNames: WService[] = ${arr(services.map(v => `'${v.entity}'`))};`;
1059
- const serviceValues = `export const wServiceFactories = ${obj(services.map(v => `${v.entity}: ${v.serviceName}`))};`;
1060
- const serviceInstanceValues = `export const wServices = ${obj(services.map(v => {
1061
- const src = `${v.entity}: ${v.serviceName}()`;
1062
- return v.deprecated ? generateInlineComment('@deprecated') + `\n${src}` : src;
1063
- }))};`;
1064
- const entityInterfaces = [
1065
- ...entitiesKeys.map(entity => ({
1066
- name: entity,
1067
- type: pascalCase(entity),
1194
+ const generateMaps = (enums, entities, services, context, options) => {
1195
+ const enumInstances = `export const wEnums = ${generateObject([...enums.keys()].map((v) => ({ key: v, value: v })))};`;
1196
+ const entityNames = `export const wEntityNames: WEntity[] = ${generateArray([...entities.keys()])};`;
1197
+ const generatedServices = [...services.values()];
1198
+ const serviceInstances = `export const wServices = ${generateObject(generatedServices.map((v) => ({
1199
+ key: v.name,
1200
+ value: `${v.serviceFnName}()`,
1201
+ comment: v.deprecated ? '@deprecated' : undefined
1202
+ })))};`;
1203
+ const serviceFactories = `export const wServiceFactories = ${generateObject(generatedServices.map((v) => ({
1204
+ key: v.name,
1205
+ value: v.serviceFnName,
1206
+ comment: v.deprecated ? '@deprecated' : undefined
1207
+ })))};`;
1208
+ return generateStatements(
1209
+ /* Enums */
1210
+ generateInterface('WEnums', [...enums.keys()].map((name) => ({ name, type: name, required: true }))), generateType('WEnum', 'keyof WEnums'), enumInstances,
1211
+ /* Entities */
1212
+ generateInterface('WEntities', [
1213
+ ...[...entities.entries()].map(([name, entity]) => ({
1214
+ name,
1215
+ type: entity.interfaceName,
1068
1216
  required: true
1069
1217
  })),
1070
- ...services.map(service => {
1071
- const alias = aliases.get(service.entity);
1072
- return {
1073
- name: service.entity,
1074
- type: alias ?? 'never',
1075
- required: true,
1076
- comment: alias ? undefined : 'no response defined or inlined'
1077
- };
1078
- })
1079
- ];
1080
- const createMappingType = (type, prefix) => type !== 'never' ? `${type}_${prefix}` : type;
1081
- const entitiesList = generateInterface('WEntities', entityInterfaces);
1082
- const entityReferences = generateInterface('WEntityReferences', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'References') })));
1083
- const entityMappings = generateInterface('WEntityMappings', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'Mappings') })));
1084
- const entityFilter = generateInterface('WEntityFilters', entityInterfaces.map(v => ({ ...v, type: createMappingType(v.type, 'Filter') })));
1085
- return {
1086
- source: generateStatements(
1087
- /* JS Values */
1088
- serviceValues, serviceInstanceValues, entityNames, serviceNames, enumsArray, generateEntityPropertyMap(entities, services, options),
1089
- /* Map of entity to references / mappings and filters*/
1090
- entityReferences, entityMappings, entityFilter,
1091
- /* List of all entities with their corresponding service */
1092
- generateBlockComment(`
1093
- This interfaces merges two maps:
1094
- - Map<[entityName], [entityInterfaceName]>
1095
- - Map<[serviceName], [entityInterfaceName]>
1096
-
1097
- Where [entityName] is
1098
- - the name of a nested entity (e.g. 'address' from Party)
1099
- - the name of an entity (e.g. 'party', 'article' etc.)
1100
-
1101
- Where [serviceName] is the name of an endpoint (e.g. for /article its 'article')
1102
-
1103
- Where [entityInterfaceName] is
1104
- - the underlying type for this entity
1105
- - the type for what is returned by the api
1106
- `, entitiesList),
1107
- /* type-ofs and types */
1108
- generateType('WServices', 'typeof wServices'), generateType('WServiceFactories', 'typeof wServiceFactories'), generateType('WService', 'keyof WServices'), generateType('WEntity', 'keyof WEntities'), generateType('WEnums', 'typeof wEnums'), generateType('WEnum', 'keyof WEnums'),
1109
- /* Utilities. */
1110
- generateCustomValueUtilities(entities, services),
1111
- /* All functions grouped by service supporting it */
1112
- ...generateGroupedServices(services))
1113
- };
1218
+ ...generatedServices
1219
+ .filter(({ relatedEntity }) => !!relatedEntity)
1220
+ .filter(({ name }) => !entities.get(name))
1221
+ .map(({ name, relatedEntity }) => ({
1222
+ name,
1223
+ type: relatedEntity.interfaceName,
1224
+ required: true
1225
+ }))
1226
+ ].sort((a, b) => (a.name > b.name ? 1 : -1))), generateType('WEntity', 'keyof WEntities'), entityNames,
1227
+ /* Services */
1228
+ serviceInstances, generateType('WServices', 'typeof wServices'), generateType('WService', 'keyof WServices'), serviceFactories, generateType('WServiceFactories', 'typeof wServiceFactories'),
1229
+ /* Service Utils */
1230
+ generateGroupedServices(generatedServices), generateCustomValueServices(generatedServices),
1231
+ /* Entity Properties (Runtime Meta Infos) */
1232
+ generateEntityProperties(entities, generatedServices, options));
1114
1233
  };
1115
1234
 
1116
- const parseReferencedEntity = (obj) => pascalCase(obj.$ref.replace(/.*\//, ''));
1117
- /* eslint-disable @typescript-eslint/no-unsafe-return */
1118
- const extractSchemas = (doc) => {
1119
- const schemas = new Map();
1235
+ function extractRelatedEntityName(serviceEndpoints, responses) {
1236
+ const rootEndpoint = serviceEndpoints.find((v) => v.endpoint.type === WeclappEndpointType.ROOT);
1237
+ if (!rootEndpoint)
1238
+ return;
1239
+ const response = rootEndpoint?.path.get?.responses['200'];
1240
+ if (!response)
1241
+ return;
1242
+ let responseObject;
1243
+ if (isReferenceObject(response)) {
1244
+ const refName = getRefName(response);
1245
+ responseObject = responses.get(refName);
1246
+ }
1247
+ else {
1248
+ responseObject = response;
1249
+ }
1250
+ const responseSchema = responseObject?.content?.['application/json'].schema;
1251
+ if (responseSchema) {
1252
+ if (isReferenceObject(responseSchema)) {
1253
+ return;
1254
+ }
1255
+ const resultSchema = responseSchema.properties?.result;
1256
+ if (!resultSchema) {
1257
+ return;
1258
+ }
1259
+ if (isReferenceObject(resultSchema)) {
1260
+ return getRefName(resultSchema);
1261
+ }
1262
+ else if (isArraySchemaObject(resultSchema)) {
1263
+ const resultItemSchema = resultSchema.items;
1264
+ if (isReferenceObject(resultItemSchema)) {
1265
+ return getRefName(resultItemSchema);
1266
+ }
1267
+ }
1268
+ }
1269
+ }
1270
+
1271
+ const extractServiceAliases = (endpoints, responses) => {
1120
1272
  const aliases = new Map();
1273
+ for (const [serviceName, serviceEndpoints] of endpoints) {
1274
+ const relatedEntityName = extractRelatedEntityName(serviceEndpoints, responses);
1275
+ if (relatedEntityName)
1276
+ aliases.set(serviceName, relatedEntityName);
1277
+ }
1278
+ return aliases;
1279
+ };
1280
+ const extractContext = (doc) => {
1281
+ const endpoints = parseEndpointsPaths(doc.paths);
1282
+ const schemas = new Map();
1121
1283
  for (const [name, schema] of Object.entries(doc.components?.schemas ?? {})) {
1122
1284
  if (!isReferenceObject(schema)) {
1123
1285
  schemas.set(name, schema);
1124
1286
  }
1125
1287
  }
1126
- /**
1127
- * Referenced schemas in responses, in some case the response from the root endpoint
1128
- * refers to a schema with a different name
1129
- */
1130
- for (const [path, methods] of Object.entries(doc.paths)) {
1131
- const parsed = parseEndpointPath(path);
1132
- if (!parsed || schemas.has(parsed.entity)) {
1133
- continue;
1288
+ const responses = new Map();
1289
+ for (const [name, response] of Object.entries(doc.components?.responses ?? {})) {
1290
+ if (!isReferenceObject(response)) {
1291
+ responses.set(name, response);
1134
1292
  }
1135
- for (const method of Object.values(OpenAPIV3.HttpMethods)) {
1136
- const body = methods[method]?.responses['200'];
1137
- if (isResponseObject(body) && body.content) {
1138
- const responseSchema = Object.values(body.content)[0]?.schema;
1139
- if (isReferenceObject(responseSchema)) {
1140
- continue;
1141
- }
1142
- const itemsSchema = responseSchema?.properties?.result;
1143
- if (isReferenceObject(itemsSchema)) {
1144
- aliases.set(parsed.entity, parseReferencedEntity(itemsSchema));
1145
- continue;
1146
- }
1147
- if (isArraySchemaObject(itemsSchema)) {
1148
- const { items } = itemsSchema;
1149
- if (isReferenceObject(items)) {
1150
- aliases.set(parsed.entity, parseReferencedEntity(items));
1151
- }
1152
- }
1153
- }
1293
+ }
1294
+ const parameters = new Map();
1295
+ for (const [name, parameter] of Object.entries(doc.components?.parameters ?? {})) {
1296
+ if (!isReferenceObject(parameter)) {
1297
+ parameters.set(name, parameter);
1298
+ }
1299
+ }
1300
+ const requestBodies = new Map();
1301
+ for (const [name, requestBody] of Object.entries(doc.components?.requestBodies ?? {})) {
1302
+ if (!isReferenceObject(requestBody)) {
1303
+ requestBodies.set(name, requestBody);
1154
1304
  }
1155
1305
  }
1156
- return { schemas, aliases };
1306
+ const aliases = extractServiceAliases(endpoints, responses);
1307
+ return { endpoints, schemas, responses, parameters, requestBodies, aliases };
1157
1308
  };
1158
1309
 
1159
1310
  const generate = (doc, options) => {
1160
- const { schemas, aliases } = extractSchemas(doc);
1161
- const enums = generateEnums(schemas);
1162
- const entities = generateEntities(schemas, enums);
1163
- const services = generateServices(doc, aliases, options);
1164
- return generateStatements(generateBase(options.target), generateBlockComment('ENUMS', generateStatements(...[...enums.values()].map(v => v.source))), generateBlockComment('ENTITIES', generateStatements(...[...entities.values()].map(v => v.source))), generateBlockComment('SERVICES', generateStatements(...[...services.values()].map(v => v.source))), generateBlockComment('MAPS', generateMaps({
1165
- services: [...services.values()],
1166
- enums: [...enums.keys()],
1167
- options,
1168
- entities,
1169
- aliases
1170
- }).source));
1311
+ const context = extractContext(doc);
1312
+ const base = generateBase(doc.info.version, options);
1313
+ const enums = generateEnums(context);
1314
+ const entities = generateEntities(context);
1315
+ const services = generateServices(entities, context, options);
1316
+ const maps = generateMaps(enums, entities, services, context, options);
1317
+ 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));
1171
1318
  };
1172
1319
 
1173
1320
  const hash = (content, algorithm = 'sha256') => {
@@ -1183,12 +1330,13 @@ const hash = (content, algorithm = 'sha256') => {
1183
1330
 
1184
1331
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
1185
1332
  const cli = async () => {
1186
- const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
1333
+ const version = pkg.version;
1187
1334
  const { argv } = yargs(hideBin(process.argv))
1188
1335
  .scriptName('build-weclapp-sdk')
1189
1336
  .usage('Usage: $0 <source> [flags]')
1190
1337
  .version(version)
1191
1338
  .example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
1339
+ .example('$0 openapi.json', 'Generate the SDK based on a local openapi file')
1192
1340
  .example('$0 xxx.weclapp.com --key ...', 'Generate the SDK based on the openapi file from the given weclapp instance')
1193
1341
  .help('h')
1194
1342
  .alias('v', 'version')
@@ -1228,36 +1376,47 @@ const cli = async () => {
1228
1376
  type: 'string',
1229
1377
  choices: ['browser', 'browser.rx', 'node', 'node.rx']
1230
1378
  })
1231
- .option('d', {
1232
- alias: 'deprecated',
1233
- describe: 'Include deprecated functions and services',
1379
+ .option('use-query-language', {
1380
+ describe: 'Generate the new where property for some and count queries',
1234
1381
  type: 'boolean'
1382
+ })
1383
+ .option('apiVersion', {
1384
+ describe: 'Specify the api version (only needed when not using a local file)',
1385
+ type: 'string'
1235
1386
  })
1236
1387
  .epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
1237
1388
  if (argv.fromEnv) {
1238
1389
  config();
1239
1390
  }
1240
1391
  const { WECLAPP_API_KEY, WECLAPP_BACKEND_URL } = process.env;
1241
- const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, _: [src = WECLAPP_BACKEND_URL] } = argv;
1392
+ const { query, cache = false, deprecated = false, key = WECLAPP_API_KEY, apiVersion, _: [src = WECLAPP_BACKEND_URL] } = argv;
1242
1393
  const options = {
1243
1394
  deprecated,
1244
1395
  generateUnique: argv.generateUnique ?? false,
1245
- target: argv.target ?? Target.BROWSER_PROMISES
1396
+ target: argv.target ?? Target.BROWSER_PROMISES,
1397
+ useQueryLanguage: argv.useQueryLanguage ?? false
1246
1398
  };
1247
- if (typeof src === 'number') {
1248
- return Promise.reject('Expected string as command');
1399
+ if (!src || typeof src === 'number') {
1400
+ return Promise.reject(new Error('Expected string as command'));
1249
1401
  }
1250
1402
  if (!Object.values(Target).includes(options.target)) {
1251
1403
  logger.errorLn(`Unknown target: ${options.target}. Possible values are ${Object.values(Target).join(', ')}`);
1252
- return Promise.reject();
1404
+ return Promise.reject(new Error());
1253
1405
  }
1254
1406
  if (await stat(src).catch(() => false)) {
1255
- logger.infoLn(`Source is a file.`);
1407
+ logger.infoLn(`Source is a file`);
1256
1408
  const content = JSON.parse(await readFile(src, 'utf-8'));
1257
1409
  return { cache, content, options };
1258
1410
  }
1411
+ logger.infoLn(`Source is a URL`);
1412
+ if (!key) {
1413
+ return Promise.reject(new Error('API key is missing'));
1414
+ }
1415
+ if (!apiVersion) {
1416
+ return Promise.reject(new Error('API version is missing'));
1417
+ }
1259
1418
  const url = new URL(src.startsWith('http') ? src : `https://${src}`);
1260
- url.pathname = '/webapp/api/v1/meta/openapi.json';
1419
+ url.pathname = `/webapp/api/${apiVersion}/meta/openapi.json`;
1261
1420
  if (query?.length) {
1262
1421
  for (const param of query.split(',')) {
1263
1422
  const [name, value] = param.split('=');
@@ -1265,11 +1424,11 @@ const cli = async () => {
1265
1424
  }
1266
1425
  }
1267
1426
  const content = await fetch(url.toString(), {
1268
- headers: { 'Accept': 'application/json', 'AuthenticationToken': key }
1269
- }).then(res => res.ok ? res.json() : undefined);
1427
+ headers: { Accept: 'application/json', AuthenticationToken: key }
1428
+ }).then((res) => (res.ok ? res.json() : undefined));
1270
1429
  if (!content) {
1271
1430
  logger.errorLn(`Couldn't fetch file ${url.toString()} `);
1272
- return Promise.reject();
1431
+ return Promise.reject(new Error());
1273
1432
  }
1274
1433
  else {
1275
1434
  logger.infoLn(`Use remote file: ${url.toString()}`);
@@ -1277,50 +1436,59 @@ const cli = async () => {
1277
1436
  return { cache, content, options };
1278
1437
  };
1279
1438
 
1280
- const workingDirectory = resolve(currentDirname(), './sdk');
1281
- const folders = ['docs', 'main', 'node', 'raw', 'rx', 'utils'];
1439
+ const workingDir = resolve(currentDirname(), './sdk');
1440
+ const cacheDir = resolve(currentDirname(), './.cache');
1282
1441
  void (async () => {
1283
1442
  const start = process.hrtime.bigint();
1284
- const { default: { version } } = await import('../package.json', { assert: { type: 'json' } });
1285
1443
  const { content: doc, cache: useCache, options } = await cli();
1286
- // Resolve cache dir and key
1287
- const cacheKey = hash([version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
1288
- const cacheDir = resolve(currentDirname(), '.tmp', cacheKey);
1289
- const dist = (...paths) => resolve(workingDirectory, ...paths);
1290
- const tmp = async (...paths) => {
1291
- const fullPath = resolve(cacheDir, ...paths);
1292
- await mkdir(dirname(fullPath), { recursive: true }).catch(() => null);
1444
+ const workingDirPath = async (...paths) => {
1445
+ const fullPath = resolve(workingDir, ...paths);
1446
+ await mkdir(dirname(fullPath), { recursive: true });
1293
1447
  return fullPath;
1294
1448
  };
1449
+ // Resolve cache dir and key
1450
+ const cacheKey = hash([pkg.version, JSON.stringify(doc), JSON.stringify(options)]).slice(-8);
1451
+ const cachedSdkDir = resolve(cacheDir, cacheKey);
1452
+ // Remove old SDK
1453
+ await rm(workingDir, { recursive: true, force: true });
1295
1454
  if (useCache) {
1296
1455
  logger.infoLn(`Cache ID: ${cacheKey}`);
1297
1456
  }
1298
- if (useCache && await stat(cacheDir).catch(() => false)) {
1299
- logger.successLn(`Cache match! (${cacheDir})`);
1457
+ if (useCache && (await stat(cachedSdkDir).catch(() => false))) {
1458
+ // Copy cached SDK to working dir
1459
+ logger.successLn(`Cache match! (${cachedSdkDir})`);
1460
+ await cp(cachedSdkDir, workingDir, { recursive: true });
1300
1461
  }
1301
1462
  else {
1302
- // Store swagger.json file
1303
- await writeFile(await tmp('openapi.json'), JSON.stringify(doc, null, 2));
1463
+ // Write openapi.json file
1464
+ await writeFile(await workingDirPath('openapi.json'), JSON.stringify(doc, null, 2));
1304
1465
  logger.infoLn(`Generate sdk (target: ${options.target})`);
1305
- // Generate SDKs
1466
+ // Generate and write SDK (index.ts)
1306
1467
  const sdk = generate(doc, options);
1307
- await writeFile(await tmp('src', `${options.target}.ts`), sdk.trim() + '\n');
1308
- // Bundle
1468
+ await writeFile(await workingDirPath('src', 'index.ts'), sdk.trim() + '\n');
1469
+ // Bundle and write SDK
1309
1470
  logger.infoLn('Bundle... (this may take some time)');
1310
- await bundle(cacheDir, options.target);
1311
- // Remove old SDK
1312
- await Promise.all(folders.map(async (dir) => rm(dist(dir), { recursive: true }).catch(() => 0)));
1471
+ await bundle(workingDir, options.target);
1472
+ // Remove index.ts (only bundle is required)
1473
+ await rm(await workingDirPath('src'), { recursive: true, force: true });
1474
+ if (useCache) {
1475
+ // Copy SDK to cache
1476
+ logger.successLn(`Caching SDK: (${cachedSdkDir})`);
1477
+ await mkdir(cachedSdkDir, { recursive: true });
1478
+ await cp(workingDir, cachedSdkDir, { recursive: true });
1479
+ }
1313
1480
  }
1314
- // Copy bundled SDK
1315
- await cp(cacheDir, workingDirectory, { recursive: true });
1316
1481
  // Print job summary
1317
1482
  const duration = (process.hrtime.bigint() - start) / 1000000n;
1318
1483
  logger.successLn(`SDK built in ${prettyMs(Number(duration))}`);
1319
1484
  logger.printSummary();
1320
- })().catch((error) => {
1485
+ })()
1486
+ .catch((error) => {
1321
1487
  logger.errorLn(`Fatal error:`);
1322
1488
  /* eslint-disable no-console */
1323
1489
  console.error(error);
1324
- }).finally(() => {
1325
- logger.errors && process.exit(1);
1490
+ })
1491
+ .finally(() => {
1492
+ if (logger.errors)
1493
+ process.exit(1);
1326
1494
  });