@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.
- package/README.md +82 -48
- package/dist/cli.js +33 -10
- 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
|
|
240
|
+
import { PartyType, setGlobalConfig, wServices } from "@weclapp/sdk";
|
|
237
241
|
|
|
238
242
|
setGlobalConfig({
|
|
239
|
-
domain:
|
|
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[
|
|
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,
|
|
251
|
+
const rawN = await raw(undefined, "/party/count");
|
|
248
252
|
|
|
249
253
|
// to get all parties via service
|
|
250
|
-
const partiesService = await wServices[
|
|
254
|
+
const partiesService = await wServices["party"].some();
|
|
251
255
|
|
|
252
256
|
// to get all parties via raw
|
|
253
|
-
const partiesRaw = await raw(undefined,
|
|
257
|
+
const partiesRaw = await raw(undefined, "/party");
|
|
254
258
|
|
|
255
259
|
// to create a party via service
|
|
256
|
-
const contact = await wServices[
|
|
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,
|
|
260
|
-
|
|
261
|
-
|
|
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[
|
|
273
|
+
await wServices["party"].remove(contact.id);
|
|
266
274
|
|
|
267
275
|
// to delete a party via raw
|
|
268
|
-
if (contactRaw && typeof contactRaw.id ===
|
|
276
|
+
if (contactRaw && typeof contactRaw.id === "string") {
|
|
269
277
|
await raw(undefined, `/party/id/${contactRaw.id}`, {
|
|
270
|
-
method:
|
|
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[
|
|
290
|
+
wServices["article"].some({
|
|
280
291
|
filter: {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
292
|
+
name: { EQ: "toy 1" },
|
|
293
|
+
articleNumber: { EQ: "12345" },
|
|
294
|
+
},
|
|
284
295
|
});
|
|
285
296
|
```
|
|
286
|
-
|
|
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[
|
|
305
|
+
wServices["article"].some({
|
|
293
306
|
or: [
|
|
294
307
|
{
|
|
295
|
-
name: { EQ:
|
|
296
|
-
articleNumber: { EQ:
|
|
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[
|
|
322
|
+
wServices["article"].some({
|
|
308
323
|
or: [
|
|
309
324
|
{
|
|
310
|
-
name: { EQ:
|
|
311
|
-
articleNumber: { EQ:
|
|
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
|
-
|
|
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[
|
|
345
|
+
wServices["article"].some({
|
|
328
346
|
where: {
|
|
329
347
|
AND: [
|
|
330
|
-
{
|
|
331
|
-
|
|
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[
|
|
342
|
-
sort: [{ name:
|
|
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[
|
|
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[
|
|
363
|
-
|
|
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[
|
|
406
|
+
wServices["article"].some({
|
|
373
407
|
filter: {
|
|
374
|
-
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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
|
|
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.
|
|
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": {
|