@weclapp/sdk 2.0.0-dev.26 → 2.0.0-dev.29

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.
Files changed (3) hide show
  1. package/README.md +82 -48
  2. package/dist/cli.js +33 -10
  3. package/package.json +2 -4
package/README.md CHANGED
@@ -54,6 +54,7 @@ This way, every time someone installs or updates dependencies, the SDK is genera
54
54
  | `--deprecated` / `-d` | Include deprecated functions and services. | `boolean` |
55
55
  | `--from-env` / `-e` | Use env variables `WECLAPP_BACKEND_URL` and `WECLAPP_API_KEY` as credentials. | `boolean` |
56
56
  | `--generate-unique` | Generate additional `.unique` functions. | `boolean` |
57
+ | `--use-query-language`| Use the advanced query language. The property _filter_ will be removed from _SomeQuery_ and _CountQuery_ and the property _where_ will be added instead. | `boolean` |
57
58
  | `--help` / `-h` | Show help. | `boolean` |
58
59
  | `--key` / `-k` | API Key in case of using a remote. | `string` |
59
60
  | `--target` / `-t` | Specify the target platform. | `browser`, `browser.rx`, `node` or `node.rx` |
@@ -205,6 +206,7 @@ The generator generates various utilities that can be used to integrate it in a
205
206
  | `WEntityProperties` | Generalized type of `wEntityProperties`. | |
206
207
 
207
208
  # Services and Raw in depth
209
+
208
210
  As already described above, the api endpoints can be requested via services or the raw function. The advantage of wServices over the raw function is that all endpoints of the entities are available as functions and these functions are typed. This makes it easier to work with the data provided via the weclapp API.
209
211
 
210
212
  A service of an entity has in general the following base function:
@@ -216,6 +218,7 @@ A service of an entity has in general the following base function:
216
218
  update: // updates a entity
217
219
 
218
220
  In addition there are some custom endpoint functions. The generated PartyService is shown below as an example:
221
+
219
222
  ```ts
220
223
  interface PartyService {
221
224
  some: PartyService_Some;
@@ -232,147 +235,178 @@ interface PartyService {
232
235
  ```
233
236
 
234
237
  ## Comparison
238
+
235
239
  ```ts
236
- import { PartyType, setGlobalConfig, wServices } from '@weclapp/sdk';
240
+ import { PartyType, setGlobalConfig, wServices } from "@weclapp/sdk";
237
241
 
238
242
  setGlobalConfig({
239
- domain: 'company.weclapp.com',
240
- secure: true
243
+ domain: "company.weclapp.com",
244
+ secure: true,
241
245
  });
242
246
 
243
247
  // to get the count of parties via service
244
- const serviceN = await wServices['party'].count();
248
+ const serviceN = await wServices["party"].count();
245
249
 
246
250
  // to get the count of parties via raw
247
- const rawN = await raw(undefined, '/party/count');
251
+ const rawN = await raw(undefined, "/party/count");
248
252
 
249
253
  // to get all parties via service
250
- const partiesService = await wServices['party'].some();
254
+ const partiesService = await wServices["party"].some();
251
255
 
252
256
  // to get all parties via raw
253
- const partiesRaw = await raw(undefined, '/party');
257
+ const partiesRaw = await raw(undefined, "/party");
254
258
 
255
259
  // to create a party via service
256
- const contact = await wServices['party'].create({ partyType: PartyType.PERSON, lastName: 'Mueller' }); // the returned object is already typed as Party
260
+ const contact = await wServices["party"].create({
261
+ partyType: PartyType.PERSON,
262
+ lastName: "Mueller",
263
+ }); // the returned object is already typed as Party
257
264
 
258
265
  // to create a party via raw
259
- const contactRaw = await await raw(undefined, '/party', { // the returned object has the type any.
260
- method: 'POST',
261
- body: { partyType: PartyType.PERSON, lastName: 'Mueller' }
266
+ const contactRaw = await await raw(undefined, "/party", {
267
+ // the returned object has the type any.
268
+ method: "POST",
269
+ body: { partyType: PartyType.PERSON, lastName: "Mueller" },
262
270
  });
263
271
 
264
272
  // to delete a party via service
265
- await wServices['party'].remove(contact.id);
273
+ await wServices["party"].remove(contact.id);
266
274
 
267
275
  // to delete a party via raw
268
- if (contactRaw && typeof contactRaw.id === 'string') {
276
+ if (contactRaw && typeof contactRaw.id === "string") {
269
277
  await raw(undefined, `/party/id/${contactRaw.id}`, {
270
- method: 'DELETE'
278
+ method: "DELETE",
271
279
  });
272
280
  }
273
281
  ```
274
282
 
275
283
  ## Service request arguments
284
+
276
285
  ### Filtering
286
+
277
287
  With the some and count functions you can filter the requested data.
288
+
278
289
  ```ts
279
- wServices['article'].some({
290
+ wServices["article"].some({
280
291
  filter: {
281
- name: { EQ: 'toy 1'},
282
- articleNumber: { EQ: '12345' }
283
- }
292
+ name: { EQ: "toy 1" },
293
+ articleNumber: { EQ: "12345" },
294
+ },
284
295
  });
285
296
  ```
286
- The SDK makes an AND operator between the properties. So this equivalent to the follwing expression:
297
+
298
+ The SDK makes an AND operator between the properties. So this equivalent to the follwing expression:
287
299
 
288
300
  name EQ 'toy 1' AND articleNumber EQ '12345'.
289
301
 
290
302
  If you want an OR operator you have to set an array in the or property:
303
+
291
304
  ```ts
292
- wServices['article'].some({
305
+ wServices["article"].some({
293
306
  or: [
294
307
  {
295
- name: { EQ: 'toy 1'},
296
- articleNumber: { EQ: '12345' }
297
- }
298
- ]
308
+ name: { EQ: "toy 1" },
309
+ articleNumber: { EQ: "12345" },
310
+ },
311
+ ],
299
312
  });
300
313
  ```
314
+
301
315
  The above example is the equivalent of the expression
302
316
 
303
317
  name EQ 'toy 1' OR articleNumber EQ '12345'.
304
318
 
305
319
  To combine OR and AND clauses, you can also group OR expressions by adding several objects to the array:
320
+
306
321
  ```ts
307
- wServices['article'].some({
322
+ wServices["article"].some({
308
323
  or: [
309
324
  {
310
- name: { EQ: 'toy 1'},
311
- articleNumber: { EQ: '12345' }
325
+ name: { EQ: "toy 1" },
326
+ articleNumber: { EQ: "12345" },
312
327
  },
313
328
  {
314
- batchNumberRequired: { EQ: true}
315
- }
316
- ]
317
- })
329
+ batchNumberRequired: { EQ: true },
330
+ },
331
+ ],
332
+ });
318
333
  ```
334
+
319
335
  This is evaluated to:
320
- (name EQ 'toy 1' OR articleNumber EQ '12345') AND batchNumberRequired EQ true
336
+ (name EQ 'toy 1' OR articleNumber EQ '12345') AND batchNumberRequired EQ true
321
337
 
322
338
  ### Where filter
339
+
323
340
  <strong>Warning: This is still a beta feature.</strong>
324
341
 
325
342
  It is also possible to specify complex filter expressions that can combine multiple conditions and express relations between properties:
343
+
326
344
  ```ts
327
- wServices['article'].some({
345
+ wServices["article"].some({
328
346
  where: {
329
347
  AND: [
330
- { OR: [{ name: { LIKE: '%test%', lower: true } }, { articleNumber: { LIKE: '%345%' } }] },
331
- { batchNumberRequired: { EQ: true } }
332
- ]
333
- }
348
+ {
349
+ OR: [
350
+ { name: { LIKE: "%test%", lower: true } },
351
+ { articleNumber: { LIKE: "%345%" } },
352
+ ],
353
+ },
354
+ { batchNumberRequired: { EQ: true } },
355
+ ],
356
+ },
334
357
  });
335
358
  ```
359
+
336
360
  "where" parameters are ANDed with other filter parameters.
337
361
 
338
362
  ### Sort
363
+
339
364
  You can sort your requested data with an array properties.
365
+
340
366
  ```ts
341
- wServices['article'].some({
342
- sort: [{ name: 'asc' }, { minimumPurchaseQuantity: 'desc' }]
367
+ wServices["article"].some({
368
+ sort: [{ name: "asc" }, { minimumPurchaseQuantity: "desc" }],
343
369
  });
344
370
  ```
371
+
345
372
  Sort by name (ascending) and then minimumPurchaseQuantity descending.
346
373
 
347
374
  ### Pagination
375
+
348
376
  By default the API returns only the first 100 entities. You can increase the size of one response to the maximum of 1000. To get the next 1000 entities you have increase the page number.
377
+
349
378
  ```ts
350
- wServices['article'].some({
379
+ wServices["article"].some({
351
380
  pagination: {
352
381
  page: 2,
353
- pageSize: 10
354
- }
382
+ pageSize: 10,
383
+ },
355
384
  });
356
385
  ```
386
+
357
387
  This returns the first 10 articles of the second page.
358
388
 
359
389
  ### Select
390
+
360
391
  With the select option you can fetch specific subset of properties:
392
+
361
393
  ```ts
362
- wServices['article'].some({
363
- select: { articleNumber: true }
364
- });
394
+ wServices["article"].some({
395
+ select: { articleNumber: true },
396
+ });
365
397
  ```
398
+
366
399
  This only returns the articleNumber property of all articles.
367
400
 
368
401
  # Enums
369
402
 
370
403
  The generated enums are a good posibility to check if an entity is of a specific type. For example, you can get all articles of a certain article type:
404
+
371
405
  ```ts
372
- wServices['article'].some({
406
+ wServices["article"].some({
373
407
  filter: {
374
- articleType: { EQ: ArticleType.STORABLE }
375
- }
408
+ articleType: { EQ: ArticleType.STORABLE },
409
+ },
376
410
  });
377
411
  ```
378
412
 
package/dist/cli.js CHANGED
@@ -65,7 +65,10 @@ const bundle = async (workingDirectory, target) => {
65
65
  const bundles = {
66
66
  [Target.BROWSER_PROMISES]: () => ({
67
67
  input: src("index.ts"),
68
- plugins: [ts({ tsconfig, declarationDir: dist() }), terser()],
68
+ plugins: [
69
+ ts({ tsconfig, declarationDir: dist(), filterRoot: src() }),
70
+ terser(),
71
+ ],
69
72
  output: [
70
73
  generateOutput({
71
74
  file: dist("index.js"),
@@ -75,7 +78,10 @@ const bundle = async (workingDirectory, target) => {
75
78
  }),
76
79
  [Target.BROWSER_RX]: () => ({
77
80
  input: src("index.ts"),
78
- plugins: [ts({ tsconfig, declarationDir: dist() }), terser()],
81
+ plugins: [
82
+ ts({ tsconfig, declarationDir: dist(), filterRoot: src() }),
83
+ terser(),
84
+ ],
79
85
  external: ["rxjs"],
80
86
  output: [
81
87
  generateOutput({
@@ -87,13 +93,19 @@ const bundle = async (workingDirectory, target) => {
87
93
  }),
88
94
  [Target.NODE_PROMISES]: () => ({
89
95
  input: src("index.ts"),
90
- plugins: [ts({ tsconfig, declarationDir: dist() }), terser()],
96
+ plugins: [
97
+ ts({ tsconfig, declarationDir: dist(), filterRoot: src() }),
98
+ terser(),
99
+ ],
91
100
  external: ["node-fetch", "url"],
92
101
  output: generateNodeOutput(),
93
102
  }),
94
103
  [Target.NODE_RX]: () => ({
95
104
  input: src("index.ts"),
96
- plugins: [ts({ tsconfig, declarationDir: dist() }), terser()],
105
+ plugins: [
106
+ ts({ tsconfig, declarationDir: dist(), filterRoot: src() }),
107
+ terser(),
108
+ ],
97
109
  external: ["node-fetch", "url", "rxjs"],
98
110
  output: generateNodeOutput(),
99
111
  }),
@@ -139,9 +151,13 @@ var globalConfig = "export type RequestPayloadMethod = 'GET' | 'HEAD' | 'POST' |
139
151
 
140
152
  var multiRequest = "type RequestTask = {\n uri: string;\n resolve: (result: unknown) => void;\n reject: (error: unknown) => void;\n};\n\ntype BatchRequestTask = RequestTask & {\n settled: boolean;\n};\n\ntype MultiRequestResponse = {\n status: number;\n body: object;\n};\n\nlet microtaskQueued: boolean = false;\nconst tasksSet: Set<RequestTask> = new Set<RequestTask>();\n\nconst SQUARE_BRACKET_OPEN = '['.charCodeAt(0);\nconst COMMA = ','.charCodeAt(0);\nconst DECODER = new TextDecoder();\n\nconst readNextResponse = (bytes: Uint8Array) => {\n let headerStart: number | undefined = undefined;\n let commasSeen = 0;\n\n for (let i = 0; i < bytes.length; i++) {\n const byte = bytes[i];\n if (headerStart === undefined) {\n if (byte === SQUARE_BRACKET_OPEN || byte === COMMA) {\n headerStart = i + 1;\n }\n } else {\n if (byte === COMMA) {\n commasSeen++;\n }\n if (commasSeen === 2) {\n const headerArrayString = `[${DECODER.decode(bytes.subarray(headerStart, i))}]`;\n const [index, jsonLength] = JSON.parse(headerArrayString);\n if (!(typeof index === 'number') || !(typeof jsonLength === 'number')) {\n throw new Error(`unexpected header: ${headerArrayString}`);\n }\n\n const endIndex = i + 1 + jsonLength;\n if (endIndex > bytes.length) {\n // not all bytes available yet\n return undefined;\n }\n const jsonString = DECODER.decode(bytes.subarray(i + 1, endIndex));\n const data = JSON.parse(jsonString) as MultiRequestResponse;\n return {\n index,\n data,\n remainingBytes: bytes.subarray(endIndex)\n };\n }\n }\n }\n return undefined;\n};\n\nconst fetchMultiRequest = async (requests: string[]) => {\n const cfg = getGlobalConfig();\n\n if (!cfg) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const host = getHost(cfg);\n const protocol = getProtocol(cfg);\n\n return await fetch(`${protocol}//${host}/webapp/api/v2/batch/query`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(cfg.key && {'AuthenticationToken': cfg.key})\n },\n body: JSON.stringify({requests})\n });\n};\n\nconst rejectTasks = (tasks: BatchRequestTask[], error: unknown) => {\n for (const task of tasks) {\n if (!task.settled) {\n task.reject(error);\n }\n }\n};\n\nconst processStream = (\n {value: chunk, done}: ReadableStreamReadResult<Uint8Array>,\n remainingBytes: Uint8Array,\n reader: ReadableStreamDefaultReader<Uint8Array>,\n tasks: BatchRequestTask[]\n ) => {\n if (done) {\n return;\n }\n if (chunk) {\n let bytes = new Uint8Array(remainingBytes.length + chunk.length);\n bytes.set(remainingBytes);\n bytes.set(chunk, remainingBytes.length);\n\n while (bytes.length) {\n const result = readNextResponse(bytes);\n if (!result) {\n break;\n }\n const task = tasks[result.index];\n if (result.data.status >= 100 && result.data.status < 400) {\n task.resolve({\n ...result.data.body\n });\n } else {\n task.reject({\n ...result.data.body\n });\n }\n task.settled = true;\n bytes = result.remainingBytes;\n }\n reader.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.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 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}));";
141
153
 
142
- var root = "export const raw = async (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n payload: RequestPayload = {}\n): Promise<any> => {\n const globalConfig = getGlobalConfig();\n if (!cfg && !globalConfig) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const localCfg = {\n ...globalConfig, ...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 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: any = undefined;\n if (!cfg && localCfg.multiRequest) {\n let ep = endpoint;\n if (endpoint.startsWith('/')) {\n ep = endpoint.replace('/', '');\n }\n data = await addRequest(`${ep}?${params}`);\n } else {\n const request = new Request(`${protocol}//${host}/webapp/api/v${apiVersion}${endpoint}?${params}`, {\n ...(payload.body && {\n body: isBinaryData ?\n payload.body :\n JSON.stringify(payload.body, (key, value) => value === undefined ? null : value)\n }),\n ...(!localCfg.key && {credentials: 'same-origin'}),\n method: payload.method ?? 'get',\n headers: {\n 'Accept': 'application/json',\n ...(localCfg.key && {'AuthenticationToken': localCfg.key}),\n ...(!isBinaryData && {'Content-Type': 'application/json'})\n }\n });\n let res = await interceptRequest(request, payload) ?? request;\n if (!(res instanceof Response)) {\n res = await fetch(res);\n }\n res = await interceptResponse(res) ?? res;\n data = (!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\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 ...assembleFilterParam(query?.where),\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 ...assembleFilterParam(query?.where),\n ...flattenSort(query?.sort),\n ...query?.params,\n ...query?.pagination\n }\n }).then(data => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n);\n\nconst _remove = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n {dryRun = false}: RemoveQuery = {}\n) => wrapResponse(() => raw({...cfg, multiRequest: false}, endpoint, {\n method: 'DELETE',\n query: {dryRun}\n}).then(() => undefined));\n\nconst _create = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n data: any\n) => wrapResponse(() => raw({...cfg, multiRequest: false}, endpoint, {\n method: 'POST',\n body: data\n}));\n\nconst _update = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n data: any,\n {ignoreMissingProperties, dryRun = false}: UpdateQuery = {}\n) => wrapResponse(() => raw({...cfg, multiRequest: false}, endpoint, {\n method: 'PUT',\n body: data,\n query: {ignoreMissingProperties: ignoreMissingProperties ?? cfg?.ignoreMissingProperties ?? globalConfig?.ignoreMissingProperties, dryRun}\n}));\n\nconst _unique = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n query?: UniqueQuery\n) => wrapResponse(() => raw({...cfg, multiRequest: false}, endpoint, {query}));\n\nconst _generic = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n method: RequestPayloadMethod,\n endpoint: string,\n payload?: GenericQuery<any, any>,\n forceBlob?: boolean\n) => wrapResponse(() => raw({...cfg, multiRequest: false}, endpoint, {\n method,\n forceBlob,\n body: payload?.body,\n query: payload?.params\n}));\n";
154
+ var queriesWithFilter = "// Filter properties\nexport type EqualityOperators = 'EQ' | 'NE';\nexport type ComparisonOperators = 'LT' | 'GT' | 'LE' | 'GE' | 'LIKE' | 'ILIKE' | 'NOT_LIKE' | 'NOT_ILIKE' | 'IEQ' | 'NOT_IEQ';\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\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\n\n// Endpoint configurations\nexport type CountQuery<F> = {\n filter?: QueryFilter<F>;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\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\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 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>)\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};";
143
155
 
144
- 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' | 'IEQ' | 'NOT_IEQ';\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\n// New filter language\n\nexport type ComparisonOperatorsNew = 'EQ' | 'NE' | 'LT' | 'GT' | 'LE' | 'GE' | 'LIKE';\nexport type ArrayOperatorsNew = 'IN';\nexport type NullOperator = 'NULL';\nexport type OperatorNew = ComparisonOperatorsNew | ArrayOperatorsNew | NullOperator;\n\n// Maybe we need more in the future, hence the type.\nexport type ModifierFunction = 'lower';\n\nexport type MapOperatorsNew<T> =\n | ( { [K in ComparisonOperatorsNew]?: T; } & { [K in ArrayOperatorsNew]?: T[]; } & { [K in NullOperator]?: never; } & { [K in ModifierFunction]?: boolean; })\n | ( { [K in ComparisonOperatorsNew]?: T; } & { [K in ArrayOperatorsNew]?: T[]; } & { [K in NullOperator]?: boolean; } & { [K in ModifierFunction]?: never; });\n\nexport type SingleFilterExpr<T> = {\n[P in keyof T]?:\n T[P] extends Array<infer U> | undefined ?\n U extends Record<any, any> ? SingleFilterExpr<U> | {NOT?: SingleFilterExpr<U>} : MapOperatorsNew<U> :\n T[P] extends Record<any, any> | undefined ? SingleFilterExpr<T[P]> | {NOT?: SingleFilterExpr<T[P]>} : MapOperatorsNew<T[P]>;\n};\n\nexport type QueryFilterNew<T> = SingleFilterExpr<T> & {\n OR?: QueryFilterNew<T>[];\n AND?: QueryFilterNew<T>[];\n NOT?: QueryFilterNew<T>\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 where?: QueryFilterNew<F> & CustomAttributeFilter;\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 where?: QueryFilterNew<F> & CustomAttributeFilter;\n select?: S;\n or?: (QueryFilter<F> & CustomAttributeFilter)[];\n sort?: Sort<E>[];\n pagination?: Pagination;\n};\n\nexport type UniqueQuery = {\n serializeNulls?: boolean;\n}\n\nexport type SomeQueryReturn<\n E, // Entity\n R, // Map of referenced-entity names to the type\n M, // Map of referenced-entity-id names to their entity name\n I extends (QuerySelect<any> | undefined), // Select for referenced entities\n S extends (QuerySelect<any> | undefined), // Select for entity properties\n P // Additional properties\n> = {\n entities: (S extends QuerySelect<E> ? Select<E, S> : E)[];\n references: I extends QuerySelect<R> ? Partial<MapKeys<Select<R, I>, M & Record<any, any>>> : {};\n properties: P\n};\n\nexport type GenericQuery<P, B> = {\n params?: P;\n body?: B;\n};\n\nexport type UpdateQuery = {\n ignoreMissingProperties?: boolean;\n dryRun?: boolean;\n}\n\nexport type RemoveQuery = {\n dryRun?: boolean;\n}\n\n// Entity meta types\nexport type WEntityPropertyMeta = (\n { type: 'string'; format?: 'decimal' | 'html' | 'email' | 'password'; maxLength?: number; pattern?: string; entity?: WEntity; service?: WService; } |\n { type: 'integer'; format: 'int32' | 'int64' | 'duration' | 'date' | 'timestamp'; } |\n { type: 'array'; format: 'reference'; entity: WEntity; service?: WService; } |\n { type: 'array'; format: 'reference'; enum: WEnum; } |\n { type: 'array'; format: 'string'; } |\n { type: 'number'; format: 'double'; } |\n { type: 'reference'; entity: WEntity; } |\n { type: 'reference'; enum: WEnum; } |\n { type: 'boolean'; } |\n { type: 'object'; }\n);\n\n// Utils\nconst equality: string[] = ['EQ', 'NE', 'IEQ', 'NOT_IEQ'];\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 IEQ: 'ieq',\n NOT_IEQ: 'notieq',\n};\n\n\nconst comparisonOperatorsList: ComparisonOperatorsNew[] = ['EQ', 'NE', 'LT', 'GT', 'LE', 'GE', 'LIKE'];\n\nconst filterMapNew: Record<OperatorNew, string> = {\n EQ: '=',\n NE: '!=',\n LT: '<',\n GT: '>',\n LE: '<=',\n GE: '>=',\n LIKE: '~',\n IN: 'in',\n NULL: 'null'\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>)\n .map(v => [`${prop}.${v[0]}`, v[1]]) as [string, string][]\n );\n break;\n }\n }\n }\n\n return entries;\n};\n\n\nconst findAllModifierFunctions = (obj: Record<string, any>, types: ModifierFunction[]) => {\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 possibleModifierFunctions: ModifierFunction[] = ['lower'];\n\nconst flattenWhere = (obj: QueryFilterNew<any> = {}, nestedPaths: string[]): string[] => {\n const entries: string[] = [];\n for (const [prop, propValue] of Object.entries(obj)) {\n const setModifiers = findAllModifierFunctions(propValue ?? {}, possibleModifierFunctions).filter(modifier => modifier[1]);\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 }\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 }\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 (comparisonOperatorsList.includes((operator as ComparisonOperatorsNew))) {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n nestedPaths.some(path => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${filterMapNew[operator as OperatorNew]} ${typeof value === 'string' ? setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n JSON.stringify(value)\n ) : value}`\n );\n } else if ((operator as OperatorNew) === 'NULL') {\n entries.push(\n `${!value ? 'not ' : ''}${nestedPaths.some(path => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')} ${filterMapNew[operator as OperatorNew]}`\n );\n } else if ((operator as OperatorNew) === 'IN') {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n nestedPaths.some(path => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${filterMapNew[operator as OperatorNew]} [${value.map((v: string | number) => typeof v === 'string' ? setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n JSON.stringify(v)\n ) : v)}]`\n );\n } else if (!possibleModifierFunctions.includes(operator as ModifierFunction)) {\n entries.push(...flattenWhere(propValue as QueryFilterNew<any>, [...nestedPaths, prop]));\n break;\n }\n }\n }\n }\n return entries;\n};\n\nconst assembleFilterParam = (obj: QueryFilterNew<any> = {}): Record<string, string> => {\n const flattedFilter = flattenWhere(obj, []);\n return flattedFilter.length ? { filter: flattedFilter.join(' and ') }: {}\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";
156
+ var queriesWithQueryLanguage = "// New filter language\n\nexport type ComparisonOperators = 'EQ' | 'NE' | 'LT' | 'GT' | 'LE' | 'GE' | 'LIKE';\nexport type ArrayOperators = 'IN';\nexport type NullOperator = 'NULL';\nexport type Operator = ComparisonOperators | ArrayOperators | NullOperator;\n\nconst comparisonOperatorsList: ComparisonOperators[] = ['EQ', 'NE', 'LT', 'GT', 'LE', 'GE', 'LIKE'];\n\nconst filterMap: 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\n// Maybe we need more in the future, hence the type.\nexport type ModifierFunction = 'lower';\n\nexport type MapOperators<T> =\n | ( { [K in ComparisonOperators]?: T; } & { [K in ArrayOperators]?: T[]; } & { [K in NullOperator]?: never; } & { [K in ModifierFunction]?: boolean; })\n | ( { [K in ComparisonOperators]?: T; } & { [K in ArrayOperators]?: T[]; } & { [K in NullOperator]?: boolean; } & { [K in ModifierFunction]?: never; });\n\nexport type SingleFilterExpr<T> = {\n[P in keyof T]?:\n T[P] extends Array<infer U> | undefined ?\n U extends Record<any, any> ? SingleFilterExpr<U> | {NOT?: SingleFilterExpr<U>} : MapOperators<U> :\n T[P] extends Record<any, any> | undefined ? SingleFilterExpr<T[P]> | {NOT?: SingleFilterExpr<T[P]>} : 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\n// Endpoint configurations\nexport type CountQuery<F> = {\n where?: QueryFilter<F> & CustomAttributeFilter;\n};\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 where?: QueryFilter<F> & CustomAttributeFilter;\n select?: S;\n sort?: Sort<E>[];\n pagination?: Pagination;\n};\n\nconst possibleModifierFunctions: ModifierFunction[] = ['lower'];\n\nconst flattenWhere = (obj: QueryFilter<any> = {}, nestedPaths: string[]): string[] => {\n const entries: string[] = [];\n for (const [prop, propValue] of Object.entries(obj)) {\n const setModifiers = findAllModifierFunctions(propValue ?? {}, possibleModifierFunctions).filter(modifier => modifier[1]);\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 }\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 }\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 (comparisonOperatorsList.includes((operator as ComparisonOperators))) {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n nestedPaths.some(path => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${filterMap[operator as Operator]} ${typeof value === 'string' ? setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n JSON.stringify(value)\n ) : value}`\n );\n } else if ((operator as Operator) === 'NULL') {\n entries.push(\n `${!value ? 'not ' : ''}${nestedPaths.some(path => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')} ${filterMap[operator as Operator]}`\n );\n } else if ((operator as Operator) === 'IN') {\n entries.push(\n `${setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n nestedPaths.some(path => path === prop) ? nestedPaths.join('.') : [...nestedPaths, prop].join('.')\n )} ${filterMap[operator as Operator]} [${value.map((v: string | number) => typeof v === 'string' ? setModifiers.reduce(\n (acc, [first]) => `${first}(${acc})`,\n JSON.stringify(v)\n ) : v)}]`\n );\n } else if (!possibleModifierFunctions.includes(operator as ModifierFunction)) {\n entries.push(...flattenWhere(propValue as QueryFilter<any>, [...nestedPaths, prop]));\n break;\n }\n }\n }\n }\n return entries;\n};\n\nconst assembleFilterParam = (obj: QueryFilter<any> = {}): Record<string, string> => {\n const flattedFilter = flattenWhere(obj, []);\n return flattedFilter.length ? { filter: flattedFilter.join(' and ') }: {}\n};\n\nconst findAllModifierFunctions = (obj: Record<string, any>, types: ModifierFunction[]) => {\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) => wrapResponse(() => raw(cfg, endpoint, {\n unwrap: true,\n query: {\n ...assembleFilterParam(query?.where),\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 ...assembleFilterParam(query?.where),\n ...flattenSort(query?.sort),\n ...query?.params,\n ...query?.pagination\n }\n }).then(data => ({\n entities: data.result,\n references: data.referencedEntities ?? {},\n properties: data.additionalProperties ?? {}\n }))\n);";
157
+
158
+ var root = "export const raw = async (\n cfg: ServiceConfig | undefined,\n endpoint: string,\n payload: RequestPayload = {}\n): Promise<any> => {\n const globalConfig = getGlobalConfig();\n if (!cfg && !globalConfig) {\n throw new Error(`ServiceConfig missing.`);\n }\n\n const localCfg = {\n ...globalConfig, ...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 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: any = undefined;\n if (!cfg && localCfg.multiRequest) {\n let ep = endpoint;\n if (endpoint.startsWith('/')) {\n ep = endpoint.replace('/', '');\n }\n data = await addRequest(`${ep}?${params}`);\n } else {\n const request = new Request(`${protocol}//${host}/webapp/api/v${apiVersion}${endpoint}?${params}`, {\n ...(payload.body && {\n body: isBinaryData ?\n payload.body :\n JSON.stringify(payload.body, (key, value) => value === undefined ? null : value)\n }),\n ...(!localCfg.key && {credentials: 'same-origin'}),\n method: payload.method ?? 'get',\n headers: {\n 'Accept': 'application/json',\n ...(localCfg.key && {'AuthenticationToken': localCfg.key}),\n ...(!isBinaryData && {'Content-Type': 'application/json'})\n }\n });\n let res = await interceptRequest(request, payload) ?? request;\n if (!(res instanceof Response)) {\n res = await fetch(res);\n }\n res = await interceptResponse(res) ?? res;\n data = (!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\n return payload.unwrap ? data.result : data;\n};\n\n\n\n\n\nconst _remove = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n {dryRun = false}: RemoveQuery = {}\n) => wrapResponse(() => raw({...cfg, multiRequest: false}, endpoint, {\n method: 'DELETE',\n query: {dryRun}\n}).then(() => undefined));\n\nconst _create = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n data: any\n) => wrapResponse(() => raw({...cfg, multiRequest: false}, endpoint, {\n method: 'POST',\n body: data\n}));\n\nconst _update = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n data: any,\n {ignoreMissingProperties, dryRun = false}: UpdateQuery = {}\n) => wrapResponse(() => raw({...cfg, multiRequest: false}, endpoint, {\n method: 'PUT',\n body: data,\n query: {ignoreMissingProperties: ignoreMissingProperties ?? cfg?.ignoreMissingProperties ?? globalConfig?.ignoreMissingProperties, dryRun}\n}));\n\nconst _unique = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n endpoint: string,\n query?: UniqueQuery\n) => wrapResponse(() => raw({...cfg, multiRequest: false}, endpoint, {query}));\n\nconst _generic = (\n cfg: ServiceConfigWithoutMultiRequest | undefined,\n method: RequestPayloadMethod,\n endpoint: string,\n payload?: GenericQuery<any, any>,\n forceBlob?: boolean\n) => wrapResponse(() => raw({...cfg, multiRequest: false}, endpoint, {\n method,\n forceBlob,\n body: payload?.body,\n query: payload?.params\n}));\n";
159
+
160
+ var types = "export type DeepPartial<T> = T extends object ? {\n [P in keyof T]?: DeepPartial<T[P]>;\n} : T;\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\nexport type Pagination = {\n page: number;\n pageSize: number;\n};\n\nexport type UniqueQuery = {\n serializeNulls?: boolean;\n}\n\nexport type SomeQueryReturn<\n E, // Entity\n R, // Map of referenced-entity names to the type\n M, // Map of referenced-entity-id names to their entity name\n I extends (QuerySelect<any> | undefined), // Select for referenced entities\n S extends (QuerySelect<any> | undefined), // Select for entity properties\n P // Additional properties\n> = {\n entities: (S extends QuerySelect<E> ? Select<E, S> : E)[];\n references: I extends QuerySelect<R> ? Partial<MapKeys<Select<R, I>, M & Record<any, any>>> : {};\n properties: P\n};\n\nexport type GenericQuery<P, B> = {\n params?: P;\n body?: B;\n};\n\nexport type UpdateQuery = {\n ignoreMissingProperties?: boolean;\n dryRun?: boolean;\n}\n\nexport type RemoveQuery = {\n dryRun?: boolean;\n}\n\n// Entity meta types\nexport type WEntityPropertyMeta = (\n { type: 'string'; format?: 'decimal' | 'html' | 'email' | 'password'; maxLength?: number; pattern?: string; entity?: WEntity; service?: WService; } |\n { type: 'integer'; format: 'int32' | 'int64' | 'duration' | 'date' | 'timestamp'; } |\n { type: 'array'; format: 'reference'; entity: WEntity; service?: WService; } |\n { type: 'array'; format: 'reference'; enum: WEnum; } |\n { type: 'array'; format: 'string'; } |\n { type: 'number'; format: 'double'; } |\n { type: 'reference'; entity: WEntity; } |\n { type: 'reference'; enum: WEnum; } |\n { type: 'boolean'; } |\n { type: 'object'; }\n);\n\n// Utils\nconst equality: string[] = ['EQ', 'NE', 'IEQ', 'NOT_IEQ'];\nconst simple: string[] = [...equality, 'LT', 'GT', 'LE', 'GE', 'LIKE', 'NOT_LIKE', 'ILIKE', 'NOT_ILIKE'];\nconst array: string[] = ['IN', 'NOT_IN'];\n\n\nconst flattenSelect = (obj: Select<any, any> = {}): string[] => {\n const entries: string[] = [];\n\n for (const [prop, value] of Object.entries(obj)) {\n if (typeof value === 'object' && value) {\n entries.push(...flattenSelect(value).map(v => `${prop}.${v}`));\n } else if (value) {\n entries.push(prop);\n }\n }\n\n return entries;\n};\n\nexport const flattenSort = (obj: Sort<any>[] = []): {sort?: string} => {\n const flatten = (obj: Sort<any>, base = ''): string | undefined => {\n const [key, value] = Object.entries(obj ?? {})[0] ?? [];\n\n if (key && value) {\n const path = base + key;\n\n if (typeof value === 'object') {\n return flatten(value, path ? `${path}.` : '');\n } else if (['asc', 'desc'].includes(value)) {\n return `${value === 'desc' ? '-' : ''}${path}`;\n }\n }\n\n return undefined;\n };\n\n const sorts = obj.map(v => flatten(v)).filter(Boolean);\n return sorts.length ? {sort: sorts.join(',')} : {};\n};\n";
145
161
 
146
162
  const resolveImports = (target) => {
147
163
  const imports = [];
@@ -152,8 +168,8 @@ const resolveImports = (target) => {
152
168
  };
153
169
  const resolveMappings = (target) => `const wrapResponse = ${isRXTarget(target) ? "defer" : "(v: (...args: any[]) => any) => v()"};`;
154
170
  const resolveBinaryClass = (target) => `const resolveBinaryObject = () => ${resolveBinaryType(target)};`;
155
- const generateBase = (target, apiVersion) => {
156
- return generateStatements(resolveImports(target), resolveMappings(target), resolveBinaryClass(target), `const apiVersion = ${apiVersion}`, globalConfig, multiRequest, types, root);
171
+ const generateBase = (target, apiVersion, useQueryLanguage) => {
172
+ return generateStatements(resolveImports(target), resolveMappings(target), resolveBinaryClass(target), `const apiVersion = ${apiVersion}`, globalConfig, multiRequest, useQueryLanguage ? queriesWithQueryLanguage : queriesWithFilter, types, root);
157
173
  };
158
174
 
159
175
  const transformKey = (s) => snakeCase(s).toUpperCase();
@@ -1261,7 +1277,7 @@ const generate = (doc, options) => {
1261
1277
  const enums = generateEnums(schemas);
1262
1278
  const entities = generateEntities(schemas, enums);
1263
1279
  const services = generateServices(doc, aliases, options);
1264
- return generateStatements(generateBase(options.target, doc.info.version), 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({
1280
+ return generateStatements(generateBase(options.target, doc.info.version, options.useQueryLanguage), 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({
1265
1281
  services: [...services.values()],
1266
1282
  enums: [...enums.keys()],
1267
1283
  options,
@@ -1327,6 +1343,10 @@ const cli = async () => {
1327
1343
  describe: "Specify the target platform",
1328
1344
  type: "string",
1329
1345
  choices: ["browser", "browser.rx", "node", "node.rx"],
1346
+ })
1347
+ .option("use-query-language", {
1348
+ describe: "Generate the new where property for some and count queries",
1349
+ type: "boolean",
1330
1350
  })
1331
1351
  .epilog(`Copyright ${new Date().getFullYear()} weclapp GmbH`);
1332
1352
  if (argv.fromEnv) {
@@ -1338,6 +1358,7 @@ const cli = async () => {
1338
1358
  deprecated,
1339
1359
  generateUnique: argv.generateUnique ?? false,
1340
1360
  target: argv.target ?? Target.BROWSER_PROMISES,
1361
+ useQueryLanguage: argv.useQueryLanguage ?? false,
1341
1362
  };
1342
1363
  if (!src || typeof src === "number") {
1343
1364
  return Promise.reject(new Error("Expected string as command"));
@@ -1408,12 +1429,14 @@ void (async () => {
1408
1429
  // Write swagger.json file
1409
1430
  await writeFile(await workingDirPath("openapi.json"), JSON.stringify(doc, null, 2));
1410
1431
  logger.infoLn(`Generate sdk (target: ${options.target})`);
1411
- // Generate and write SDK
1432
+ // Generate and write SDK (index.ts)
1412
1433
  const sdk = generate(doc, options);
1413
1434
  await writeFile(await workingDirPath("src", "index.ts"), sdk.trim() + "\n");
1414
1435
  // Bundle and write SDK
1415
1436
  logger.infoLn("Bundle... (this may take some time)");
1416
1437
  await bundle(workingDir, options.target);
1438
+ // Remove index.ts (only bundle is required)
1439
+ await rm(await workingDirPath("src"), { recursive: true, force: true });
1417
1440
  if (useCache) {
1418
1441
  // Copy SDK to cache
1419
1442
  logger.successLn(`Caching SDK: (${cachedSdkDir})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weclapp/sdk",
3
- "version": "2.0.0-dev.26",
3
+ "version": "2.0.0-dev.29",
4
4
  "description": "SDK generator based on a weclapp api swagger file",
5
5
  "author": "weclapp",
6
6
  "sideEffects": false,
@@ -25,14 +25,12 @@
25
25
  "npm": ">=10"
26
26
  },
27
27
  "types": "./sdk/dist/index.d.ts",
28
- "main": "./sdk/dist/index.cjs",
29
28
  "module": "./sdk/dist/index.js",
30
29
  "type": "module",
31
30
  "exports": {
32
31
  ".": {
33
32
  "types": "./sdk/dist/index.d.ts",
34
- "import": "./sdk/dist/index.js",
35
- "require": "./sdk/dist/index.cjs"
33
+ "import": "./sdk/dist/index.js"
36
34
  }
37
35
  },
38
36
  "scripts": {