@quadrokit/client 0.3.13 → 0.3.15
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 +93 -7
- package/dist/cli.mjs +20 -3
- package/dist/generate/codegen.d.ts +12 -1
- package/dist/generate/codegen.d.ts.map +1 -1
- package/dist/generate/codegen.mjs +507 -25
- package/dist/runtime/catalog-builder.d.ts +4 -1
- package/dist/runtime/catalog-builder.d.ts.map +1 -1
- package/dist/runtime/catalog-builder.mjs +4 -1
- package/dist/runtime/collection.d.ts.map +1 -1
- package/dist/runtime/collection.mjs +4 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ The package exposes:
|
|
|
16
16
|
|
|
17
17
|
- **`@quadrokit/client`** — runtime + types (and anything your generated code needs).
|
|
18
18
|
- **`@quadrokit/client/runtime`** — low-level helpers if you build custom integrations.
|
|
19
|
+
- **`@quadrokit/client/rx`** — optional RxJS bridge for `QuadroEventBus` (install **`rxjs`** in your app).
|
|
19
20
|
|
|
20
21
|
---
|
|
21
22
|
|
|
@@ -44,6 +45,7 @@ Use a URL that returns the **full** catalog (dataclasses **with attributes**). P
|
|
|
44
45
|
| **`--login-url`** | Full login URL (default: `{catalog origin}/api/login`). |
|
|
45
46
|
| **`-v` / `--verbose`** | Step-by-step logs on stderr. |
|
|
46
47
|
| **`--insecure-tls`** | Skip TLS verification (dev / self-signed HTTPS only). |
|
|
48
|
+
| **`--no-split-type-files`** | Emit a single **`types.gen.ts`** (legacy). **Default:** split typings — **`entities/<Class>.gen.ts`**, optional **`datastore.gen.ts`**, and **`types.gen.ts`** as the barrel. |
|
|
47
49
|
|
|
48
50
|
Environment variables (often via `.env` in the project root): `VITE_4D_ORIGIN`, `QUADROKIT_ACCESS_KEY`, `QUADROKIT_LOGIN_URL`, `QUADROKIT_CATALOG_TOKEN`, `QUADROKIT_GENERATE_VERBOSE`, `QUADROKIT_INSECURE_TLS`.
|
|
49
51
|
|
|
@@ -51,9 +53,11 @@ Environment variables (often via `.env` in the project root): `VITE_4D_ORIGIN`,
|
|
|
51
53
|
|
|
52
54
|
| File | Purpose |
|
|
53
55
|
|------|---------|
|
|
54
|
-
| **`types.gen.ts`** |
|
|
56
|
+
| **`types.gen.ts`** | Barrel: **`QuadroClient`** typing; re-exports entity types and **`*Path`** aliases. |
|
|
57
|
+
| **`entities/<ClassName>.gen.ts`** | One file per exposed dataclass (default layout): **`export interface`** + **`export type`** **`<ClassName>Path`** (`QuadroAttributePaths<…>`). Omit with **`--no-split-type-files`** (single `types.gen.ts`). |
|
|
58
|
+
| **`datastore.gen.ts`** | When the catalog lists datastore REST methods (e.g. `testFn`): **`QuadroDatastoreMethodFn`**. Omitted when there are no such exposed methods. **`authentify`** is not special-cased here — it is typed under **`QuadroClient`** only when the catalog exposes it (see **`hasAuthentify`** in **`catalog.gen.json`**). |
|
|
55
59
|
| **`catalog.gen.json`** | Catalog runtime spec (dataclass layouts, methods, relations) consumed by `@quadrokit/client/runtime` — keeps **`client.gen.ts`** tiny. |
|
|
56
|
-
| **`client.gen.ts`** | Thin `createClient(config)` that wires `QuadroHttp` + `buildQuadroClientFromCatalogSpec` + `catalog.gen.json`. |
|
|
60
|
+
| **`client.gen.ts`** | Thin `createClient(config)` that wires `QuadroHttp` + `buildQuadroClientFromCatalogSpec` + `catalog.gen.json`. Config supports optional **`events`** (see [Request lifecycle events](#request-lifecycle-events)). |
|
|
57
61
|
| **`meta.json`** | `__NAME`, `sessionCookieName` hint for 4D session cookies. |
|
|
58
62
|
|
|
59
63
|
Point your app imports at the generated folder, for example:
|
|
@@ -80,10 +84,90 @@ const quadro = createClient({
|
|
|
80
84
|
})
|
|
81
85
|
```
|
|
82
86
|
|
|
87
|
+
Optional **`events: new QuadroEventBus()`** (from **`@quadrokit/client/runtime`**) enables [request lifecycle events](#request-lifecycle-events) on every call through the client.
|
|
88
|
+
|
|
83
89
|
At runtime, requests use **`credentials: 'include'`** so session cookies (e.g. `4DSID_<datastore>`) are sent when your app and 4D share the same site or proxy. See `meta.json` → `sessionCookieName` after generate.
|
|
84
90
|
|
|
85
91
|
---
|
|
86
92
|
|
|
93
|
+
## Request lifecycle events
|
|
94
|
+
|
|
95
|
+
Pass a **`QuadroEventBus`** from **`@quadrokit/client/runtime`** into **`createClient({ …, events })`**. The runtime emits a **loading → success** or **loading → error** sequence per logical operation (lists, `get`, `delete`, class functions, datastore paths, login, etc.).
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { createClient } from './.quadrokit/generated/client.gen.js'
|
|
99
|
+
import { QuadroEventBus } from '@quadrokit/client/runtime'
|
|
100
|
+
|
|
101
|
+
const events = new QuadroEventBus()
|
|
102
|
+
|
|
103
|
+
const quadro = createClient({
|
|
104
|
+
baseURL: '/rest',
|
|
105
|
+
events,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const unsub = events.subscribe((e) => {
|
|
109
|
+
if (e.type === 'loading') {
|
|
110
|
+
console.debug(e.context.operation, e.path, e.method)
|
|
111
|
+
}
|
|
112
|
+
if (e.type === 'success') {
|
|
113
|
+
console.debug(e.durationMs, e.body)
|
|
114
|
+
}
|
|
115
|
+
if (e.type === 'error') {
|
|
116
|
+
console.warn(e.status, e.message, e.error)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// later: unsub()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
You can also subscribe on the HTTP instance: **`quadro._http.subscribe(listener)`** returns an unsubscribe function.
|
|
124
|
+
|
|
125
|
+
### Event shapes (`QuadroClientEvent`)
|
|
126
|
+
|
|
127
|
+
| Field | `loading` | `success` | `error` |
|
|
128
|
+
|-------|-----------|-----------|---------|
|
|
129
|
+
| **`type`** | `'loading'` | `'success'` | `'error'` |
|
|
130
|
+
| **`operationId`** | Correlates one start → one outcome | same | same |
|
|
131
|
+
| **`context`** | **`QuadroRequestContext`**: **`operation`** (**`QuadroOperation`** string), optional **`className`**, **`methodName`**, **`entityKey`**, **`attributes`** | same | same |
|
|
132
|
+
| **`path`** | Request path | same | same |
|
|
133
|
+
| **`method`** | HTTP method | — | — |
|
|
134
|
+
| **`startedAt`** | Timestamp | — | — |
|
|
135
|
+
| **`durationMs`** | — | Elapsed | Elapsed |
|
|
136
|
+
| **`status`** | — | If HTTP response is raw `Response` | If thrown value has HTTP **`status`** (e.g. **`QuadroHttpError`**) |
|
|
137
|
+
| **`body`** | — | Result: parsed JSON, rows, `void`, or a small summary for raw **`Response`** | — |
|
|
138
|
+
| **`message` / `error`** | — | — | User-facing message + original error |
|
|
139
|
+
|
|
140
|
+
### Operations (`QuadroOperation`)
|
|
141
|
+
|
|
142
|
+
Examples include: **`http.json`**, **`http.void`**, **`http.request`**, **`collection.list`**, **`collection.release`**, **`dataclass.get`**, **`dataclass.delete`**, **`function.dataclass`**, **`function.entity`**, **`function.entityCollection`**, **`datastore`**, **`auth.login`**.
|
|
143
|
+
|
|
144
|
+
### `QuadroHttp` helpers (advanced)
|
|
145
|
+
|
|
146
|
+
When **`events`** is set, **`json`**, **`void`**, and **`request`** accept an optional trailing **`QuadroRequestContext`** so emissions can be tagged. **`QuadroHttp`** also exposes:
|
|
147
|
+
|
|
148
|
+
- **`rawRequest(path, init)`** — fetch without emitting (used inside multi-step flows).
|
|
149
|
+
- **`runWithEvents(context, path, method, fn)`** — run an async function with the same loading/success/error envelope as the built-in methods.
|
|
150
|
+
- **`events`** getter — the bus instance, if any.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## RxJS (`@quadrokit/client/rx`)
|
|
155
|
+
|
|
156
|
+
Install **`rxjs`** in your app, then bridge the bus to an observable:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { quadroEventsObservable } from '@quadrokit/client/rx'
|
|
160
|
+
import { filter } from 'rxjs/operators'
|
|
161
|
+
|
|
162
|
+
const sub = quadroEventsObservable(events)
|
|
163
|
+
.pipe(filter((e) => e.type === 'error'))
|
|
164
|
+
.subscribe((e) => console.warn(e.message))
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**`rxjs`** is an **optional peer dependency** of `@quadrokit/client`; you only need it if you import `@quadrokit/client/rx`.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
87
171
|
## Dataclass API (generated)
|
|
88
172
|
|
|
89
173
|
For each exposed dataclass, the client exposes a namespace such as `quadro.Agency`, `quadro.Reservation`, etc.
|
|
@@ -143,11 +227,11 @@ If your 4D project exposes methods in the REST catalog, the generator wires them
|
|
|
143
227
|
|
|
144
228
|
| Catalog `applyTo` | Where it appears in JS | REST shape (simplified) |
|
|
145
229
|
|-------------------|-------------------------|-------------------------|
|
|
146
|
-
| **`dataClass`** | `quadro.Agency.agencyStats<R>(args, init?)` | `POST /rest/{Class}/{function}` with JSON array body |
|
|
230
|
+
| **`dataClass`** | `quadro.Agency.agencyStats<R, A>(args, init?)` — **`R`** = result, **`A`** = args tuple (defaults: `unknown`, `readonly unknown[]`) | `POST /rest/{Class}/{function}` with JSON array body |
|
|
147
231
|
| **`entityCollection`** | On **`all()` / `query()`** handles and on related `{ list, … }` APIs | `POST /rest/{Class}/{function}` + optional `selection`, `entitySet` |
|
|
148
232
|
| **`entity`** | On each **mapped entity** row | `POST /rest/{Class}({key})/{function}` |
|
|
149
233
|
|
|
150
|
-
- **`args`**: `readonly unknown[]` — 4D receives a **JSON array** of parameters (scalars, entities, entity selections per 4D rules).
|
|
234
|
+
- **`args`**: tuple type **`A`** (second generic on dataclass functions; default `readonly unknown[]`) — 4D receives a **JSON array** of parameters (scalars, entities, entity selections per 4D rules).
|
|
151
235
|
- **`init`**: optional `{ method: 'GET' \| 'POST', unwrapResult?, signal?, … }` — GET uses `?$params=…` for [HTTP GET functions](https://developer.4d.com/docs/ORDA/ordaClasses#onhttpget-keyword).
|
|
152
236
|
- **`unwrapResult`**: when `true` (default), `{ "result": x }` responses are unwrapped to `x`.
|
|
153
237
|
|
|
@@ -159,7 +243,8 @@ Entity-selection methods accept **`EntityCollectionMethodOptions`**:
|
|
|
159
243
|
Example:
|
|
160
244
|
|
|
161
245
|
```ts
|
|
162
|
-
const stats = await quadro.Agency.agencyStats<MyStatsRow>([from, to])
|
|
246
|
+
const stats = await quadro.Agency.agencyStats<MyStatsRow, [string, string]>([from, to])
|
|
247
|
+
// or: agencyStats<MyStatsRow>([from, to]) when you only need to fix the result type
|
|
163
248
|
|
|
164
249
|
const col = quadro.Reservation.all({ pageSize: 20 })
|
|
165
250
|
// After the handle exists, e.g. from a hook or manual create:
|
|
@@ -178,7 +263,8 @@ Import from **`@quadrokit/client/runtime`** when you need primitives without cod
|
|
|
178
263
|
|
|
179
264
|
| Export | Use |
|
|
180
265
|
|--------|-----|
|
|
181
|
-
| **`QuadroHttp`** | Thin wrapper:
|
|
266
|
+
| **`QuadroHttp`** | Thin wrapper: **`json()`**, **`void()`**, **`request()`** with base URL + default headers; optional **`events`**; **`rawRequest`**, **`runWithEvents`**, **`subscribe`**. |
|
|
267
|
+
| **`QuadroEventBus`**, **`QuadroClientEvent`**, **`QuadroLoadingEvent`**, **`QuadroSuccessEvent`**, **`QuadroErrorEvent`**, **`QuadroRequestContext`**, **`QuadroOperation`** | Lifecycle event types and bus (see [Request lifecycle events](#request-lifecycle-events)). |
|
|
182
268
|
| **`createCollection`** | Build a `CollectionHandle` from a `CollectionContext` + options + row mapper. |
|
|
183
269
|
| **`callDataClassFunction`**, **`callEntityFunction`**, **`callEntityCollectionFunction`** | Call ORDA functions by name and path (same REST rules as above). |
|
|
184
270
|
| **`callDatastorePath`**, **`createClient`’s `rpc`** | Arbitrary segments under the REST root, e.g. datastore or singleton paths. |
|
|
@@ -191,7 +277,7 @@ The generated `createClient` is the recommended surface; these are for tooling,
|
|
|
191
277
|
|
|
192
278
|
## Errors
|
|
193
279
|
|
|
194
|
-
Failed HTTP responses throw **`QuadroHttpError`** (status + response body). Handle network errors separately (`fetch` failures).
|
|
280
|
+
Failed HTTP responses throw **`QuadroHttpError`** (status + response body). Handle network errors separately (`fetch` failures). When **`events`** is configured, failures also emit a **`QuadroErrorEvent`** with **`message`**, **`error`**, and optional **`status`** before the error propagates.
|
|
195
281
|
|
|
196
282
|
---
|
|
197
283
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import { catalogFetchInit, defaultLoginUrlFromCatalogUrl, fetch4DAdminSessionCookie, vLog, wrapFetchError, } from './generate/catalog-session.mjs';
|
|
5
6
|
import { warnIfCatalogLacksAttributes, writeGenerated } from './generate/codegen.mjs';
|
|
@@ -13,6 +14,7 @@ function parseArgs(argv) {
|
|
|
13
14
|
let out = '.quadrokit/generated';
|
|
14
15
|
let verbose = false;
|
|
15
16
|
let insecureTls = false;
|
|
17
|
+
let splitTypeFiles = true;
|
|
16
18
|
for (let i = 0; i < argv.length; i++) {
|
|
17
19
|
const a = argv[i];
|
|
18
20
|
if (a === '--url' && argv[i + 1]) {
|
|
@@ -36,11 +38,14 @@ function parseArgs(argv) {
|
|
|
36
38
|
else if (a === '--insecure-tls') {
|
|
37
39
|
insecureTls = true;
|
|
38
40
|
}
|
|
41
|
+
else if (a === '--no-split-type-files') {
|
|
42
|
+
splitTypeFiles = false;
|
|
43
|
+
}
|
|
39
44
|
else if (!a.startsWith('-') && !command) {
|
|
40
45
|
command = a;
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
|
-
return { command, url, token, accessKey, loginUrl, out, verbose, insecureTls };
|
|
48
|
+
return { command, url, token, accessKey, loginUrl, out, verbose, insecureTls, splitTypeFiles };
|
|
44
49
|
}
|
|
45
50
|
/** 4D sometimes returns `dataClass` (summary) vs `dataClasses` (detail); align to `dataClasses`. */
|
|
46
51
|
function normalizeCatalogJson(raw) {
|
|
@@ -64,7 +69,15 @@ async function loadCatalog(url, auth, debug) {
|
|
|
64
69
|
vLog(debug, '🦙', 'QuadroKit catalog generate', 'Loading catalog…');
|
|
65
70
|
vLog(debug, '📍', 'Catalog URL', url);
|
|
66
71
|
if (url.startsWith('file:')) {
|
|
67
|
-
|
|
72
|
+
let p;
|
|
73
|
+
try {
|
|
74
|
+
p = fileURLToPath(new URL(url));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// e.g. `file://../../assets/catalog.json` (invalid file URL host on some platforms)
|
|
78
|
+
const rest = url.replace(/^file:/i, '').replace(/^\/+/, '');
|
|
79
|
+
p = path.resolve(process.cwd(), rest);
|
|
80
|
+
}
|
|
68
81
|
vLog(debug, '📂', 'Local file', p);
|
|
69
82
|
const text = await readFile(p, 'utf8');
|
|
70
83
|
vLog(debug, '✅', 'Catalog JSON', `Read ${text.length} bytes from disk`);
|
|
@@ -128,6 +141,10 @@ Debug / TLS:
|
|
|
128
141
|
-v, --verbose Friendly step-by-step logs (emojis) on stderr
|
|
129
142
|
--insecure-tls Skip TLS certificate verification (dev/self-signed HTTPS only)
|
|
130
143
|
|
|
144
|
+
Output layout (default: split entity + datastore typings):
|
|
145
|
+
--no-split-type-files Emit a single types.gen.ts (legacy); default is split files:
|
|
146
|
+
entities/<Class>.gen.ts, optional datastore.gen.ts, types.gen.ts barrel.
|
|
147
|
+
|
|
131
148
|
Env / .env: VITE_4D_ORIGIN, QUADROKIT_ACCESS_KEY, QUADROKIT_LOGIN_URL, QUADROKIT_CATALOG_TOKEN,
|
|
132
149
|
QUADROKIT_GENERATE_VERBOSE, QUADROKIT_INSECURE_TLS (set to 1 / true / yes)
|
|
133
150
|
|
|
@@ -157,7 +174,7 @@ Examples:
|
|
|
157
174
|
token: accessKey ? undefined : bearer,
|
|
158
175
|
}, debug);
|
|
159
176
|
warnIfCatalogLacksAttributes(catalog);
|
|
160
|
-
await writeGenerated(out, catalog);
|
|
177
|
+
await writeGenerated(out, catalog, { splitTypeFiles: parsed.splitTypeFiles });
|
|
161
178
|
if (debug.verbose) {
|
|
162
179
|
vLog(debug, '🎉', 'All set', `Generated client → ${out}`);
|
|
163
180
|
}
|
|
@@ -16,8 +16,19 @@ export declare function buildCatalogRuntimeSpec(catalog: CatalogJson): {
|
|
|
16
16
|
}[];
|
|
17
17
|
keyNamesByClass: Record<string, string[]>;
|
|
18
18
|
hasAuthentify: boolean;
|
|
19
|
+
datastoreMethodNames: string[];
|
|
19
20
|
};
|
|
20
21
|
/** Warn when the catalog lists dataclasses but omits attributes (e.g. plain `GET /rest/$catalog` instead of `/rest/$catalog/$all`). */
|
|
21
22
|
export declare function warnIfCatalogLacksAttributes(catalog: CatalogJson): void;
|
|
22
|
-
export
|
|
23
|
+
export interface WriteGeneratedOptions {
|
|
24
|
+
/**
|
|
25
|
+
* When `true` (default), emit each exposed dataclass as `entities/<Name>.gen.ts`, optional
|
|
26
|
+
* `datastore.gen.ts` (per-method `Datastore_*` aliases + `QuadroDatastoreMethodFn`), and a barrel `types.gen.ts`.
|
|
27
|
+
* When `false`, emit a single `types.gen.ts` (legacy layout).
|
|
28
|
+
* `entities/<Name>.overrides.ts` and `datastore.overrides.ts` are created only if missing; existing
|
|
29
|
+
* entity override files are preserved when the `entities/` folder is regenerated.
|
|
30
|
+
*/
|
|
31
|
+
splitTypeFiles?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare function writeGenerated(outDir: string, catalog: CatalogJson, options?: WriteGeneratedOptions): Promise<void>;
|
|
23
34
|
//# sourceMappingURL=codegen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/generate/codegen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/generate/codegen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,WAAW,EAEZ,MAAM,mBAAmB,CAAA;AAmd1B,yFAAyF;AACzF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,GAAG;IAC7D,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAA;QACZ,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACnC,QAAQ,EAAE,MAAM,EAAE,CAAA;QAClB,iBAAiB,EAAE,MAAM,EAAE,CAAA;QAC3B,2BAA2B,EAAE,MAAM,EAAE,CAAA;QACrC,oBAAoB,EAAE,MAAM,EAAE,CAAA;QAC9B,SAAS,EAAE;YACT,IAAI,EAAE,MAAM,CAAA;YACZ,WAAW,EAAE,MAAM,CAAA;YACnB,2BAA2B,EAAE,MAAM,EAAE,CAAA;SACtC,EAAE,CAAA;KACJ,EAAE,CAAA;IACH,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACzC,aAAa,EAAE,OAAO,CAAA;IACtB,oBAAoB,EAAE,MAAM,EAAE,CAAA;CAC/B,CAwBA;AAoOD,uIAAuI;AACvI,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAYvE;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAqCD,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CA2Ff"}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { sessionCookieName } from '@quadrokit/shared';
|
|
4
|
+
/**
|
|
5
|
+
* Suffix for import specifiers in generated `.ts` (`./Foo.gen.js`).
|
|
6
|
+
* Build with template + this const so `rename-dist-js-to-mjs` does not rewrite emitted paths in `dist/` to `.mjs`.
|
|
7
|
+
*/
|
|
8
|
+
const EMITTED_TS_IMPORT_SUFFIX = '.js';
|
|
4
9
|
function map4dType(attr) {
|
|
5
10
|
const t = attr.type ?? 'unknown';
|
|
6
11
|
switch (t) {
|
|
@@ -83,19 +88,315 @@ function emitInterface(dc) {
|
|
|
83
88
|
lines.push('}');
|
|
84
89
|
return lines.join('\n');
|
|
85
90
|
}
|
|
91
|
+
/** Class names referenced as `relatedEntity` types (for per-entity file imports). Excludes self-references. */
|
|
92
|
+
function referencedEntityTypes(dc) {
|
|
93
|
+
const names = new Set();
|
|
94
|
+
for (const a of dc.attributes ?? []) {
|
|
95
|
+
if (a.kind === 'relatedEntity' && a.type && a.type !== dc.name) {
|
|
96
|
+
names.add(a.type);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return [...names].sort((x, y) => x.localeCompare(y));
|
|
100
|
+
}
|
|
101
|
+
function entityGenNamespaceImportName(className) {
|
|
102
|
+
return `Gen${className}`;
|
|
103
|
+
}
|
|
104
|
+
function emitQuadroFnTypesFile() {
|
|
105
|
+
return `/* eslint-disable */
|
|
106
|
+
/* Auto-generated by @quadrokit/client — do not edit */
|
|
107
|
+
|
|
108
|
+
import type { ClassFunctionHttpOptions, EntityCollectionMethodOptions } from '@quadrokit/client/runtime';
|
|
109
|
+
|
|
110
|
+
/** When the matching \`*Overrides\` interface defines a key, that signature wins; otherwise \`D\`. */
|
|
111
|
+
export type ResolveOverride<O extends object, K extends string, D> = K extends keyof O
|
|
112
|
+
? O[Extract<K, keyof O>] extends infer V
|
|
113
|
+
? [V] extends [never]
|
|
114
|
+
? D
|
|
115
|
+
: V extends undefined
|
|
116
|
+
? D
|
|
117
|
+
: V
|
|
118
|
+
: D
|
|
119
|
+
: D;
|
|
120
|
+
|
|
121
|
+
export type QuadroDataClassFn = <
|
|
122
|
+
R = unknown,
|
|
123
|
+
A extends readonly unknown[] = readonly unknown[],
|
|
124
|
+
>(
|
|
125
|
+
args?: A,
|
|
126
|
+
init?: ClassFunctionHttpOptions & { unwrapResult?: boolean },
|
|
127
|
+
) => Promise<R>;
|
|
128
|
+
|
|
129
|
+
export type QuadroEntityFn = <
|
|
130
|
+
R = unknown,
|
|
131
|
+
A extends readonly unknown[] = readonly unknown[],
|
|
132
|
+
>(
|
|
133
|
+
args?: A,
|
|
134
|
+
init?: ClassFunctionHttpOptions & { unwrapResult?: boolean },
|
|
135
|
+
) => Promise<R>;
|
|
136
|
+
|
|
137
|
+
export type QuadroEntityCollectionFn = <
|
|
138
|
+
R = unknown,
|
|
139
|
+
A extends readonly unknown[] = readonly unknown[],
|
|
140
|
+
>(
|
|
141
|
+
args?: A,
|
|
142
|
+
init?: EntityCollectionMethodOptions,
|
|
143
|
+
) => Promise<R>;
|
|
144
|
+
|
|
145
|
+
export type QuadroDatastoreFn = <
|
|
146
|
+
B = unknown,
|
|
147
|
+
R = unknown,
|
|
148
|
+
>(
|
|
149
|
+
body?: B,
|
|
150
|
+
init?: { method?: 'GET' | 'POST' },
|
|
151
|
+
) => Promise<R>;
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
function emitOverridesStubFile(className, quadroFnRelativeToStub) {
|
|
155
|
+
return `/* eslint-disable */
|
|
156
|
+
/**
|
|
157
|
+
* Optional: narrow REST method typings for ${className}.
|
|
158
|
+
* Add entries under the interfaces (e.g. findACar: QuadroDataClassFn<[string, string], Car[]>).
|
|
159
|
+
* Import fn types from \`'${quadroFnRelativeToStub}'\` when you add entries (or reference re-exports below).
|
|
160
|
+
*/
|
|
161
|
+
export type {
|
|
162
|
+
QuadroDataClassFn,
|
|
163
|
+
QuadroEntityFn,
|
|
164
|
+
QuadroEntityCollectionFn,
|
|
165
|
+
} from '${quadroFnRelativeToStub}';
|
|
166
|
+
|
|
167
|
+
export interface ${className}DataclassOverrides {}
|
|
168
|
+
|
|
169
|
+
export interface ${className}EntityOverrides {}
|
|
170
|
+
|
|
171
|
+
export interface ${className}EntityCollectionOverrides {}
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
function emitDatastoreOverridesStub(quadroFnsRelativeToStub) {
|
|
175
|
+
return `/* eslint-disable */
|
|
176
|
+
/**
|
|
177
|
+
* Optional: narrow top-level datastore REST methods on the generated client (e.g. \`testFn\`).
|
|
178
|
+
* Add one property per catalog method name on \`DatastoreOverrides\`.
|
|
179
|
+
* Import \`QuadroDatastoreFn\` from \`'${quadroFnsRelativeToStub}'\` when you add entries.
|
|
180
|
+
*/
|
|
181
|
+
export type { QuadroDatastoreFn } from '${quadroFnsRelativeToStub}';
|
|
182
|
+
|
|
183
|
+
export interface DatastoreOverrides {}
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
/** Import names from \`_quadroFns.gen\` required for this class’s emitted method aliases (avoids unused imports under \`noUnusedLocals\`). */
|
|
187
|
+
function quadroFnTypeNamesForClass(dc) {
|
|
188
|
+
const dcMs = methodsForApply(dc, 'dataClass');
|
|
189
|
+
const emMs = methodsForApply(dc, 'entity');
|
|
190
|
+
const ecMs = methodsForApply(dc, 'entityCollection');
|
|
191
|
+
const out = [];
|
|
192
|
+
if (dcMs.length > 0 || emMs.length > 0 || ecMs.length > 0) {
|
|
193
|
+
out.push('ResolveOverride');
|
|
194
|
+
}
|
|
195
|
+
if (dcMs.length > 0) {
|
|
196
|
+
out.push('QuadroDataClassFn');
|
|
197
|
+
}
|
|
198
|
+
if (emMs.length > 0) {
|
|
199
|
+
out.push('QuadroEntityFn');
|
|
200
|
+
}
|
|
201
|
+
if (ecMs.length > 0) {
|
|
202
|
+
out.push('QuadroEntityCollectionFn');
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
function quadroFnTypeNamesForCatalog(classes, dsNames) {
|
|
207
|
+
const set = new Set();
|
|
208
|
+
for (const c of classes) {
|
|
209
|
+
for (const n of quadroFnTypeNamesForClass(c)) {
|
|
210
|
+
set.add(n);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (dsNames.length > 0) {
|
|
214
|
+
set.add('ResolveOverride');
|
|
215
|
+
set.add('QuadroDatastoreFn');
|
|
216
|
+
}
|
|
217
|
+
const order = [
|
|
218
|
+
'ResolveOverride',
|
|
219
|
+
'QuadroDataClassFn',
|
|
220
|
+
'QuadroEntityFn',
|
|
221
|
+
'QuadroEntityCollectionFn',
|
|
222
|
+
'QuadroDatastoreFn',
|
|
223
|
+
];
|
|
224
|
+
return order.filter((n) => set.has(n));
|
|
225
|
+
}
|
|
226
|
+
function overrideTypeNamesForClass(c) {
|
|
227
|
+
const parts = [];
|
|
228
|
+
if (methodsForApply(c, 'dataClass').length > 0) {
|
|
229
|
+
parts.push(`${c.name}DataclassOverrides`);
|
|
230
|
+
}
|
|
231
|
+
if (methodsForApply(c, 'entity').length > 0) {
|
|
232
|
+
parts.push(`${c.name}EntityOverrides`);
|
|
233
|
+
}
|
|
234
|
+
if (methodsForApply(c, 'entityCollection').length > 0) {
|
|
235
|
+
parts.push(`${c.name}EntityCollectionOverrides`);
|
|
236
|
+
}
|
|
237
|
+
return parts;
|
|
238
|
+
}
|
|
239
|
+
function methodNameLiteralForKey(name) {
|
|
240
|
+
return JSON.stringify(name);
|
|
241
|
+
}
|
|
242
|
+
function buildMethodAliasLines(dc) {
|
|
243
|
+
const dcMs = methodsForApply(dc, 'dataClass');
|
|
244
|
+
const emMs = methodsForApply(dc, 'entity');
|
|
245
|
+
const ecMs = methodsForApply(dc, 'entityCollection');
|
|
246
|
+
const dataclassLines = dcMs.map((m) => {
|
|
247
|
+
const id = sanitizeMethodId(m.name);
|
|
248
|
+
const key = methodNameLiteralForKey(m.name);
|
|
249
|
+
return `export type ${dc.name}_dataclass_${id} = ResolveOverride<${dc.name}DataclassOverrides, ${key}, QuadroDataClassFn>`;
|
|
250
|
+
});
|
|
251
|
+
const entityLines = emMs.map((m) => {
|
|
252
|
+
const id = sanitizeMethodId(m.name);
|
|
253
|
+
const key = methodNameLiteralForKey(m.name);
|
|
254
|
+
return `export type ${dc.name}_entity_${id} = ResolveOverride<${dc.name}EntityOverrides, ${key}, QuadroEntityFn>`;
|
|
255
|
+
});
|
|
256
|
+
const ecLines = ecMs.map((m) => {
|
|
257
|
+
const id = sanitizeMethodId(m.name);
|
|
258
|
+
const key = methodNameLiteralForKey(m.name);
|
|
259
|
+
return `export type ${dc.name}_entityCollection_${id} = ResolveOverride<${dc.name}EntityCollectionOverrides, ${key}, QuadroEntityCollectionFn>`;
|
|
260
|
+
});
|
|
261
|
+
const entityMap = emMs.length > 0
|
|
262
|
+
? `export type ${dc.name}EntityMethodMap = {
|
|
263
|
+
${emMs
|
|
264
|
+
.map((m) => ` ${objectPropertyKey(m.name)}: ${dc.name}_entity_${sanitizeMethodId(m.name)};`)
|
|
265
|
+
.join('\n')}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export type ${dc.name}WithEntityMethods = ${dc.name} & Partial<${dc.name}EntityMethodMap>;
|
|
269
|
+
`
|
|
270
|
+
: '';
|
|
271
|
+
const ecMap = ecMs.length > 0
|
|
272
|
+
? `export type ${dc.name}EntityCollectionMethodMap = {
|
|
273
|
+
${ecMs
|
|
274
|
+
.map((m) => ` ${objectPropertyKey(m.name)}: ${dc.name}_entityCollection_${sanitizeMethodId(m.name)};`)
|
|
275
|
+
.join('\n')}
|
|
276
|
+
};
|
|
277
|
+
`
|
|
278
|
+
: '';
|
|
279
|
+
const methodBlock = [...dataclassLines, ...entityLines, ...ecLines].join('\n');
|
|
280
|
+
const tail = [entityMap, ecMap].filter(Boolean).join('\n');
|
|
281
|
+
return [methodBlock, tail].filter(Boolean).join('\n\n');
|
|
282
|
+
}
|
|
283
|
+
function emitEntityTypeBody(dc, opts) {
|
|
284
|
+
const refs = referencedEntityTypes(dc);
|
|
285
|
+
const fnNames = quadroFnTypeNamesForClass(dc);
|
|
286
|
+
const fnImport = fnNames.length > 0
|
|
287
|
+
? `import type {\n ${fnNames.join(',\n ')},\n} from '${opts.quadroFnImport}';\n`
|
|
288
|
+
: '';
|
|
289
|
+
const ovNames = overrideTypeNamesForClass(dc);
|
|
290
|
+
const ovImport = ovNames.length > 0
|
|
291
|
+
? `import type {\n ${ovNames.join(',\n ')},\n} from '${opts.overridesImport}';\n`
|
|
292
|
+
: '';
|
|
293
|
+
const runtimeImport = `import type { QuadroAttributePaths } from '@quadrokit/client/runtime';
|
|
294
|
+
`;
|
|
295
|
+
const refImports = refs.length > 0
|
|
296
|
+
? `${refs
|
|
297
|
+
.map((r) => `import type { ${r} } from './${r}.gen${EMITTED_TS_IMPORT_SUFFIX}';`)
|
|
298
|
+
.join('\n')}\n`
|
|
299
|
+
: '';
|
|
300
|
+
const iface = emitInterface(dc);
|
|
301
|
+
const pathType = `export type ${dc.name}Path = QuadroAttributePaths<${dc.name}>;
|
|
302
|
+
`;
|
|
303
|
+
const methodAliases = buildMethodAliasLines(dc);
|
|
304
|
+
const blocks = [fnImport, ovImport, runtimeImport, refImports, iface, '', pathType, methodAliases]
|
|
305
|
+
.filter((x) => x !== '')
|
|
306
|
+
.join('\n');
|
|
307
|
+
return blocks;
|
|
308
|
+
}
|
|
309
|
+
function emitEntityTypeFile(dc) {
|
|
310
|
+
const header = `/* eslint-disable */\n/* Auto-generated by @quadrokit/client — do not edit */\n`;
|
|
311
|
+
const quadroPath = `../_quadroFns.gen${EMITTED_TS_IMPORT_SUFFIX}`;
|
|
312
|
+
const body = emitEntityTypeBody(dc, {
|
|
313
|
+
quadroFnImport: quadroPath,
|
|
314
|
+
overridesImport: `./${dc.name}.overrides${EMITTED_TS_IMPORT_SUFFIX}`,
|
|
315
|
+
});
|
|
316
|
+
return `${header}${body}\n`;
|
|
317
|
+
}
|
|
318
|
+
function buildDatastoreMethodAliasLines(catalog) {
|
|
319
|
+
const dsNames = datastoreMethodsExcludingAuthentify(catalog);
|
|
320
|
+
if (dsNames.length === 0) {
|
|
321
|
+
return '';
|
|
322
|
+
}
|
|
323
|
+
return dsNames
|
|
324
|
+
.map((n) => {
|
|
325
|
+
const id = sanitizeMethodId(n);
|
|
326
|
+
const key = methodNameLiteralForKey(n);
|
|
327
|
+
return `export type Datastore_${id} = ResolveOverride<DatastoreOverrides, ${key}, QuadroDatastoreFn>`;
|
|
328
|
+
})
|
|
329
|
+
.join('\n');
|
|
330
|
+
}
|
|
331
|
+
function emitDatastoreTypesFile(catalog) {
|
|
332
|
+
const dsNames = datastoreMethodsExcludingAuthentify(catalog);
|
|
333
|
+
const header = `/* eslint-disable */\n/* Auto-generated by @quadrokit/client — do not edit */\n\n`;
|
|
334
|
+
const imports = `import type {
|
|
335
|
+
ResolveOverride,
|
|
336
|
+
QuadroDatastoreFn,
|
|
337
|
+
} from './_quadroFns.gen${EMITTED_TS_IMPORT_SUFFIX}';
|
|
338
|
+
|
|
339
|
+
import type { DatastoreOverrides } from './datastore.overrides${EMITTED_TS_IMPORT_SUFFIX}';
|
|
340
|
+
|
|
341
|
+
`;
|
|
342
|
+
const base = `/** Default datastore method shape; per-method aliases below respect \`DatastoreOverrides\`. */
|
|
343
|
+
export type QuadroDatastoreMethodFn = QuadroDatastoreFn;
|
|
344
|
+
|
|
345
|
+
`;
|
|
346
|
+
const aliases = dsNames.length > 0 ? `${buildDatastoreMethodAliasLines(catalog)}\n` : '';
|
|
347
|
+
return `${header}${imports}${base}${aliases}`;
|
|
348
|
+
}
|
|
86
349
|
/** Include dataclasses unless explicitly not exposed (`exposed === false`). Omitted `exposed` is treated as true. */
|
|
87
350
|
function exposedDataClasses(catalog) {
|
|
88
351
|
return catalog.dataClasses?.filter((d) => d.exposed !== false) ?? [];
|
|
89
352
|
}
|
|
353
|
+
/** Normalized `applyTo` for catalog method rows (4D: `dataClass`, `entity`, `entityCollection`). */
|
|
354
|
+
const APPLY_TO_KIND = {
|
|
355
|
+
dataClass: 'dataclass',
|
|
356
|
+
entity: 'entity',
|
|
357
|
+
entityCollection: 'entitycollection',
|
|
358
|
+
};
|
|
359
|
+
function normalizeCatalogMethodApplyTo(applyTo) {
|
|
360
|
+
return applyTo?.trim().toLowerCase().replace(/\s+/g, '') ?? '';
|
|
361
|
+
}
|
|
90
362
|
/** ORDA catalog methods by `applyTo` (4D: dataClass, entityCollection, entity). */
|
|
91
|
-
function methodsForApply(dc,
|
|
363
|
+
function methodsForApply(dc, kind) {
|
|
92
364
|
if (!dc) {
|
|
93
365
|
return [];
|
|
94
366
|
}
|
|
95
|
-
|
|
367
|
+
const want = APPLY_TO_KIND[kind];
|
|
368
|
+
return (dc.methods ?? []).filter((m) => normalizeCatalogMethodApplyTo(m.applyTo) === want && m.exposed !== false);
|
|
369
|
+
}
|
|
370
|
+
function sanitizeMethodId(name) {
|
|
371
|
+
return name.replace(/[^\w$]/g, '_').replace(/_+/g, '_');
|
|
372
|
+
}
|
|
373
|
+
/** Top-level ORDA `catalog.methods` entry targeting the datastore (4D: `dataStore`; compare case-insensitively). */
|
|
374
|
+
function catalogMethodApplyToIsDatastore(applyTo) {
|
|
375
|
+
return applyTo?.trim().toLowerCase() === 'datastore';
|
|
96
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* True when the catalog lists an exposed `authentify` datastore method (same rules as other datastore methods).
|
|
379
|
+
* Not emitted in types/runtime when absent or `exposed: false`.
|
|
380
|
+
*/
|
|
97
381
|
function catalogHasAuthentify(catalog) {
|
|
98
|
-
return Boolean(catalog.methods?.some((m) => m.name === 'authentify' &&
|
|
382
|
+
return Boolean(catalog.methods?.some((m) => m.name?.toLowerCase() === 'authentify' &&
|
|
383
|
+
catalogMethodApplyToIsDatastore(m.applyTo) &&
|
|
384
|
+
m.exposed !== false));
|
|
385
|
+
}
|
|
386
|
+
/** Top-level ORDA `methods` with datastore `applyTo`, excluding `authentify` (typed as `authentify.login` when present). */
|
|
387
|
+
function datastoreMethodsExcludingAuthentify(catalog) {
|
|
388
|
+
return (catalog.methods ?? [])
|
|
389
|
+
.filter((m) => Boolean(m.name) &&
|
|
390
|
+
catalogMethodApplyToIsDatastore(m.applyTo) &&
|
|
391
|
+
m.exposed !== false &&
|
|
392
|
+
m.name.toLowerCase() !== 'authentify')
|
|
393
|
+
.map((m) => m.name);
|
|
394
|
+
}
|
|
395
|
+
function isValidJsIdentifier(name) {
|
|
396
|
+
return /^[A-Za-z_$][\w$]*$/.test(name);
|
|
397
|
+
}
|
|
398
|
+
function objectPropertyKey(name) {
|
|
399
|
+
return isValidJsIdentifier(name) ? name : JSON.stringify(name);
|
|
99
400
|
}
|
|
100
401
|
/** Serializable spec consumed by {@link buildQuadroClientFromCatalogSpec} at runtime. */
|
|
101
402
|
export function buildCatalogRuntimeSpec(catalog) {
|
|
@@ -103,6 +404,7 @@ export function buildCatalogRuntimeSpec(catalog) {
|
|
|
103
404
|
const byName = new Map(classes.map((c) => [c.name, c]));
|
|
104
405
|
return {
|
|
105
406
|
hasAuthentify: catalogHasAuthentify(catalog),
|
|
407
|
+
datastoreMethodNames: datastoreMethodsExcludingAuthentify(catalog),
|
|
106
408
|
keyNamesByClass: Object.fromEntries(classes.map((x) => [x.name, [...keyNames(x)]])),
|
|
107
409
|
classes: classes.map((c) => ({
|
|
108
410
|
name: c.name,
|
|
@@ -119,62 +421,148 @@ export function buildCatalogRuntimeSpec(catalog) {
|
|
|
119
421
|
})),
|
|
120
422
|
};
|
|
121
423
|
}
|
|
122
|
-
function emitQuadroClientTypeBlock(catalog, withEntities
|
|
424
|
+
function emitQuadroClientTypeBlock(catalog, withEntities, datastoreFnStyle = 'inline',
|
|
425
|
+
/** When true, method types are referenced as `GenCar.Car_dataclass_*` from per-entity files. */
|
|
426
|
+
splitLayout = false) {
|
|
123
427
|
const classes = exposedDataClasses(catalog);
|
|
124
428
|
const hasAuthentify = catalogHasAuthentify(catalog);
|
|
429
|
+
const dsNames = datastoreMethodsExcludingAuthentify(catalog);
|
|
125
430
|
const authLine = hasAuthentify
|
|
126
431
|
? ` authentify: { login: (body: { email: string; password: string }) => Promise<unknown> };\n`
|
|
127
432
|
: '';
|
|
433
|
+
const dsProps = dsNames.length > 0
|
|
434
|
+
? `${dsNames
|
|
435
|
+
.map((n) => {
|
|
436
|
+
const id = sanitizeMethodId(n);
|
|
437
|
+
const ref = datastoreFnStyle === 'imported' ? `GenDatastore.Datastore_${id}` : `Datastore_${id}`;
|
|
438
|
+
return ` ${objectPropertyKey(n)}: ${ref};`;
|
|
439
|
+
})
|
|
440
|
+
.join('\n')}\n`
|
|
441
|
+
: '';
|
|
128
442
|
const tail = ` rpc: (segments: string[], init?: { method?: 'GET' | 'POST'; body?: unknown }) => Promise<unknown>;
|
|
129
443
|
sessionCookieName: string;
|
|
130
444
|
_http: QuadroHttp;
|
|
131
445
|
};`;
|
|
132
446
|
if (!withEntities || classes.length === 0) {
|
|
133
447
|
return `export type QuadroClient = {
|
|
134
|
-
${authLine}${tail}
|
|
448
|
+
${authLine}${dsProps}${tail}
|
|
135
449
|
`;
|
|
136
450
|
}
|
|
137
|
-
const dcFn = `type QuadroDataClassFn = <R = unknown>(args?: readonly unknown[], init?: ClassFunctionHttpOptions & { unwrapResult?: boolean }) => Promise<R>;
|
|
138
|
-
|
|
139
|
-
`;
|
|
140
451
|
const branches = classes
|
|
141
452
|
.map((c) => {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
453
|
+
const dcMs = methodsForApply(c, 'dataClass');
|
|
454
|
+
if (dcMs.length === 0) {
|
|
455
|
+
return ` ${c.name}: BaseDataClassApi<${c.name}, ${c.name}Path>;`;
|
|
456
|
+
}
|
|
457
|
+
const ns = splitLayout ? entityGenNamespaceImportName(c.name) : null;
|
|
458
|
+
const props = dcMs
|
|
459
|
+
.map((m) => {
|
|
460
|
+
const id = sanitizeMethodId(m.name);
|
|
461
|
+
const ref = ns ? `${ns}.${c.name}_dataclass_${id}` : `${c.name}_dataclass_${id}`;
|
|
462
|
+
return ` ${objectPropertyKey(m.name)}: ${ref};`;
|
|
463
|
+
})
|
|
464
|
+
.join('\n');
|
|
465
|
+
return ` ${c.name}: BaseDataClassApi<${c.name}, ${c.name}Path> & {
|
|
466
|
+
${props}
|
|
467
|
+
};`;
|
|
149
468
|
})
|
|
150
469
|
.join('\n');
|
|
151
|
-
return
|
|
152
|
-
${authLine}${branches}
|
|
470
|
+
return `export type QuadroClient = {
|
|
471
|
+
${authLine}${dsProps}${branches}
|
|
153
472
|
${tail}
|
|
154
473
|
`;
|
|
155
474
|
}
|
|
156
|
-
function
|
|
475
|
+
function emitTypesMonolithic(catalog) {
|
|
157
476
|
const classes = exposedDataClasses(catalog);
|
|
477
|
+
const dsNames = datastoreMethodsExcludingAuthentify(catalog);
|
|
478
|
+
const quadroFnNames = quadroFnTypeNamesForCatalog(classes, dsNames);
|
|
479
|
+
const needsQuadroFns = quadroFnNames.length > 0;
|
|
158
480
|
const interfaces = classes.map((c) => emitInterface(c));
|
|
159
481
|
const pathTypes = classes.map((c) => {
|
|
160
482
|
const name = `${c.name}Path`;
|
|
161
483
|
return `export type ${name} = QuadroAttributePaths<${c.name}>;`;
|
|
162
484
|
});
|
|
485
|
+
const methodAliasBlocks = classes.map((c) => buildMethodAliasLines(c)).join('\n\n');
|
|
486
|
+
const datastoreAliasBlock = dsNames.length > 0 ? `${buildDatastoreMethodAliasLines(catalog)}\n\n` : '';
|
|
163
487
|
const header = `/* eslint-disable */\n/* Auto-generated by @quadrokit/client — do not edit */\n`;
|
|
164
488
|
const pathNote = classes.length > 0
|
|
165
|
-
? `/**\n * *Path types use {@link QuadroAttributePaths} (recursive relation paths, depth-limited for circular graphs).\n * Override depth: \`type DeepEmployeePath = QuadroAttributePaths<Employee, 12>\`.\n */\n`
|
|
489
|
+
? `/**\n * *Path types use {@link QuadroAttributePaths} (recursive relation paths, depth-limited for circular graphs).\n * Override depth: \`type DeepEmployeePath = QuadroAttributePaths<Employee, 12>\`.\n * Per-class REST method typings: \`*_dataclass_*\`, \`*_entity_*\`, \`*_entityCollection_*\`; narrow in \`./*.overrides.ts\`.\n * Datastore methods: \`Datastore_*\` aliases; narrow in \`./datastore.overrides.ts\`.\n */\n`
|
|
490
|
+
: dsNames.length > 0
|
|
491
|
+
? `/**\n * Datastore REST methods: \`Datastore_*\` type aliases; narrow signatures in \`./datastore.overrides.ts\`.\n */\n`
|
|
492
|
+
: '';
|
|
493
|
+
const fnImport = needsQuadroFns
|
|
494
|
+
? `import type {\n ${quadroFnNames.join(',\n ')},\n} from './_quadroFns.gen${EMITTED_TS_IMPORT_SUFFIX}';\n\n`
|
|
166
495
|
: '';
|
|
496
|
+
const datastoreOverrideImport = dsNames.length > 0
|
|
497
|
+
? `import type { DatastoreOverrides } from './datastore.overrides${EMITTED_TS_IMPORT_SUFFIX}';
|
|
498
|
+
|
|
499
|
+
`
|
|
500
|
+
: '';
|
|
501
|
+
const overrideImportLines = classes
|
|
502
|
+
.map((c) => {
|
|
503
|
+
const parts = overrideTypeNamesForClass(c);
|
|
504
|
+
if (parts.length === 0) {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
return `import type { ${parts.join(', ')} } from './${c.name}.overrides${EMITTED_TS_IMPORT_SUFFIX}';`;
|
|
508
|
+
})
|
|
509
|
+
.filter(Boolean);
|
|
510
|
+
const overrideImports = overrideImportLines.length > 0 ? `${overrideImportLines.join('\n')}\n\n` : '';
|
|
167
511
|
const runtimeImport = classes.length > 0
|
|
168
|
-
? `import type { BaseDataClassApi,
|
|
512
|
+
? `import type { BaseDataClassApi, QuadroAttributePaths, QuadroHttp } from '@quadrokit/client/runtime';\n\n`
|
|
169
513
|
: `import type { QuadroHttp } from '@quadrokit/client/runtime';\n\n`;
|
|
514
|
+
if (classes.length === 0) {
|
|
515
|
+
const raw = catalog.dataClasses?.length ?? 0;
|
|
516
|
+
const note = raw > 0
|
|
517
|
+
? `\n/**\n * No entity types: all ${raw} data class(es) in the catalog have exposed: false.\n * Enable REST exposure for tables in 4D, then run quadrokit-client generate again.\n */\n`
|
|
518
|
+
: dsNames.length > 0
|
|
519
|
+
? ''
|
|
520
|
+
: `\n/**\n * No entity types: the catalog has no dataClasses (or an empty list).\n */\n`;
|
|
521
|
+
return `${header}${runtimeImport}${fnImport}${datastoreOverrideImport}${note}${pathNote}${datastoreAliasBlock}${emitQuadroClientTypeBlock(catalog, false, 'inline', false)}`;
|
|
522
|
+
}
|
|
523
|
+
return `${header}${runtimeImport}${fnImport}${overrideImports}${datastoreOverrideImport}${interfaces.join('\n\n')}\n\n${pathNote}${pathTypes.join('\n\n')}\n\n${datastoreAliasBlock}${methodAliasBlocks}\n\n${emitQuadroClientTypeBlock(catalog, true, 'inline', false)}`;
|
|
524
|
+
}
|
|
525
|
+
function emitTypesSplitMain(catalog) {
|
|
526
|
+
const classes = exposedDataClasses(catalog);
|
|
527
|
+
const dsNames = datastoreMethodsExcludingAuthentify(catalog);
|
|
528
|
+
const header = `/* eslint-disable */\n/* Auto-generated by @quadrokit/client — do not edit */\n`;
|
|
529
|
+
const pathNote = classes.length > 0
|
|
530
|
+
? `/**\n * *Path types use {@link QuadroAttributePaths} (recursive relation paths, depth-limited for circular graphs).\n * Override depth: \`type DeepEmployeePath = QuadroAttributePaths<Employee, 12>\`.\n * Entity interfaces and REST method aliases live under \`./entities/*.gen.ts\`; optional signature overrides in \`./entities/*.overrides.ts\`.\n * Datastore: \`./datastore.gen.ts\` and \`./datastore.overrides.ts\` when present.\n */\n`
|
|
531
|
+
: dsNames.length > 0
|
|
532
|
+
? `/**\n * Datastore method typings: \`./datastore.gen.ts\`; narrow signatures in \`./datastore.overrides.ts\`.\n */\n`
|
|
533
|
+
: '';
|
|
534
|
+
const datastoreImport = dsNames.length > 0
|
|
535
|
+
? `import type * as GenDatastore from './datastore.gen${EMITTED_TS_IMPORT_SUFFIX}';\n\n`
|
|
536
|
+
: '';
|
|
537
|
+
const runtimeImport = classes.length > 0
|
|
538
|
+
? `import type { BaseDataClassApi, QuadroHttp } from '@quadrokit/client/runtime';\n\n`
|
|
539
|
+
: `import type { QuadroHttp } from '@quadrokit/client/runtime';\n\n`;
|
|
540
|
+
const entityImports = classes.length > 0
|
|
541
|
+
? `${classes
|
|
542
|
+
.map((c) => `import type { ${c.name}, ${c.name}Path } from './entities/${c.name}.gen${EMITTED_TS_IMPORT_SUFFIX}';`)
|
|
543
|
+
.join('\n')}\n\n`
|
|
544
|
+
: '';
|
|
545
|
+
const genNamespaceImports = classes.length > 0
|
|
546
|
+
? `${classes
|
|
547
|
+
.filter((c) => methodsForApply(c, 'dataClass').length > 0)
|
|
548
|
+
.map((c) => `import type * as ${entityGenNamespaceImportName(c.name)} from './entities/${c.name}.gen${EMITTED_TS_IMPORT_SUFFIX}';`)
|
|
549
|
+
.join('\n')}${classes.some((c) => methodsForApply(c, 'dataClass').length > 0) ? '\n\n' : ''}`
|
|
550
|
+
: '';
|
|
551
|
+
const entityStarExports = classes.length > 0
|
|
552
|
+
? `${classes
|
|
553
|
+
.map((c) => `export * from './entities/${c.name}.gen${EMITTED_TS_IMPORT_SUFFIX}';`)
|
|
554
|
+
.join('\n')}\n\n`
|
|
555
|
+
: '';
|
|
556
|
+
const datastoreStarExport = dsNames.length > 0 ? `export * from './datastore.gen${EMITTED_TS_IMPORT_SUFFIX}';\n\n` : '';
|
|
557
|
+
const datastoreStyle = dsNames.length > 0 ? 'imported' : 'inline';
|
|
170
558
|
if (classes.length === 0) {
|
|
171
559
|
const raw = catalog.dataClasses?.length ?? 0;
|
|
172
560
|
const note = raw > 0
|
|
173
561
|
? `\n/**\n * No entity types: all ${raw} data class(es) in the catalog have exposed: false.\n * Enable REST exposure for tables in 4D, then run quadrokit-client generate again.\n */\n`
|
|
174
562
|
: `\n/**\n * No entity types: the catalog has no dataClasses (or an empty list).\n */\n`;
|
|
175
|
-
return `${header}${runtimeImport}${note}\n${emitQuadroClientTypeBlock(catalog, false)}`;
|
|
563
|
+
return `${header}${runtimeImport}${datastoreImport}${note}${pathNote}\n${datastoreStarExport}${emitQuadroClientTypeBlock(catalog, false, datastoreStyle, false)}`;
|
|
176
564
|
}
|
|
177
|
-
return `${header}${runtimeImport}${
|
|
565
|
+
return `${header}${runtimeImport}${datastoreImport}${entityImports}${genNamespaceImports}${pathNote}${entityStarExports}${datastoreStarExport}\n${emitQuadroClientTypeBlock(catalog, true, datastoreStyle, true)}`;
|
|
178
566
|
}
|
|
179
567
|
function emitClient(catalog) {
|
|
180
568
|
const dbName = catalog.__NAME ?? 'default';
|
|
@@ -226,10 +614,104 @@ export function warnIfCatalogLacksAttributes(catalog) {
|
|
|
226
614
|
}
|
|
227
615
|
console.error('quadrokit: catalog has dataclass names but no attribute details. Fetch the full catalog: GET /rest/$catalog/$all (plain /rest/$catalog omits attributes). See 4D REST docs: $catalog/$all.');
|
|
228
616
|
}
|
|
229
|
-
|
|
617
|
+
/** Writes `contents` only when `filePath` does not exist (used for `*.overrides.ts` stubs). */
|
|
618
|
+
async function writeStubIfMissing(filePath, contents) {
|
|
619
|
+
try {
|
|
620
|
+
await stat(filePath);
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
await writeFile(filePath, contents, 'utf8');
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/** Reads existing `entities/*.overrides.ts` before the entities dir is recreated so user edits are kept. */
|
|
627
|
+
async function readExistingEntityOverrides(entitiesDir) {
|
|
628
|
+
const map = new Map();
|
|
629
|
+
try {
|
|
630
|
+
const st = await stat(entitiesDir);
|
|
631
|
+
if (!st.isDirectory()) {
|
|
632
|
+
return map;
|
|
633
|
+
}
|
|
634
|
+
const names = await readdir(entitiesDir);
|
|
635
|
+
for (const name of names) {
|
|
636
|
+
if (!name.endsWith('.overrides.ts')) {
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
const p = path.join(entitiesDir, name);
|
|
640
|
+
const s = await stat(p);
|
|
641
|
+
if (!s.isFile()) {
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
map.set(name, await readFile(p, 'utf8'));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
// entities dir missing or unreadable
|
|
649
|
+
}
|
|
650
|
+
return map;
|
|
651
|
+
}
|
|
652
|
+
export async function writeGenerated(outDir, catalog, options = {}) {
|
|
653
|
+
const splitTypeFiles = options.splitTypeFiles !== false;
|
|
230
654
|
await mkdir(outDir, { recursive: true });
|
|
655
|
+
const entitiesDir = path.join(outDir, 'entities');
|
|
656
|
+
const datastorePath = path.join(outDir, 'datastore.gen.ts');
|
|
657
|
+
const quadroFnsPath = path.join(outDir, '_quadroFns.gen.ts');
|
|
658
|
+
const classes = exposedDataClasses(catalog);
|
|
659
|
+
const dsNames = datastoreMethodsExcludingAuthentify(catalog);
|
|
660
|
+
if (splitTypeFiles) {
|
|
661
|
+
const preservedEntityOverrides = await readExistingEntityOverrides(entitiesDir);
|
|
662
|
+
await rm(entitiesDir, { recursive: true, force: true });
|
|
663
|
+
const needsQuadroFns = classes.length > 0 || dsNames.length > 0;
|
|
664
|
+
if (needsQuadroFns) {
|
|
665
|
+
await writeFile(quadroFnsPath, emitQuadroFnTypesFile(), 'utf8');
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
await rm(quadroFnsPath, { force: true }).catch(() => { });
|
|
669
|
+
}
|
|
670
|
+
if (classes.length > 0) {
|
|
671
|
+
await mkdir(entitiesDir, { recursive: true });
|
|
672
|
+
for (const c of classes) {
|
|
673
|
+
await writeFile(path.join(entitiesDir, `${c.name}.gen.ts`), emitEntityTypeFile(c), 'utf8');
|
|
674
|
+
const overrideName = `${c.name}.overrides.ts`;
|
|
675
|
+
const overridePath = path.join(entitiesDir, overrideName);
|
|
676
|
+
const previous = preservedEntityOverrides.get(overrideName);
|
|
677
|
+
if (previous !== undefined) {
|
|
678
|
+
await writeFile(overridePath, previous, 'utf8');
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
await writeStubIfMissing(overridePath, emitOverridesStubFile(c.name, `../_quadroFns.gen${EMITTED_TS_IMPORT_SUFFIX}`));
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (dsNames.length > 0) {
|
|
686
|
+
await writeFile(datastorePath, emitDatastoreTypesFile(catalog), 'utf8');
|
|
687
|
+
await writeStubIfMissing(path.join(outDir, 'datastore.overrides.ts'), emitDatastoreOverridesStub(`./_quadroFns.gen${EMITTED_TS_IMPORT_SUFFIX}`));
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
await rm(datastorePath, { force: true }).catch(() => { });
|
|
691
|
+
}
|
|
692
|
+
await writeFile(path.join(outDir, 'types.gen.ts'), emitTypesSplitMain(catalog), 'utf8');
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
await rm(entitiesDir, { recursive: true, force: true });
|
|
696
|
+
await rm(datastorePath, { force: true }).catch(() => { });
|
|
697
|
+
const needsQuadroFns = classes.length > 0 || dsNames.length > 0;
|
|
698
|
+
if (needsQuadroFns) {
|
|
699
|
+
await writeFile(quadroFnsPath, emitQuadroFnTypesFile(), 'utf8');
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
await rm(quadroFnsPath, { force: true }).catch(() => { });
|
|
703
|
+
}
|
|
704
|
+
if (classes.length > 0) {
|
|
705
|
+
for (const c of classes) {
|
|
706
|
+
await writeStubIfMissing(path.join(outDir, `${c.name}.overrides.ts`), emitOverridesStubFile(c.name, `./_quadroFns.gen${EMITTED_TS_IMPORT_SUFFIX}`));
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (dsNames.length > 0) {
|
|
710
|
+
await writeStubIfMissing(path.join(outDir, 'datastore.overrides.ts'), emitDatastoreOverridesStub(`./_quadroFns.gen${EMITTED_TS_IMPORT_SUFFIX}`));
|
|
711
|
+
}
|
|
712
|
+
await writeFile(path.join(outDir, 'types.gen.ts'), emitTypesMonolithic(catalog), 'utf8');
|
|
713
|
+
}
|
|
231
714
|
await writeFile(path.join(outDir, 'catalog.gen.json'), `${JSON.stringify(buildCatalogRuntimeSpec(catalog), null, 2)}\n`, 'utf8');
|
|
232
|
-
await writeFile(path.join(outDir, 'types.gen.ts'), emitTypes(catalog), 'utf8');
|
|
233
715
|
await writeFile(path.join(outDir, 'client.gen.ts'), emitClient(catalog), 'utf8');
|
|
234
716
|
await writeFile(path.join(outDir, 'meta.json'), JSON.stringify({
|
|
235
717
|
__NAME: catalog.__NAME,
|
|
@@ -13,7 +13,10 @@ export interface CatalogClassRuntimeSpec {
|
|
|
13
13
|
export interface CatalogRuntimeSpec {
|
|
14
14
|
classes: readonly CatalogClassRuntimeSpec[];
|
|
15
15
|
keyNamesByClass: Readonly<Record<string, readonly string[]>>;
|
|
16
|
-
|
|
16
|
+
/** Set from the catalog at generate time; omit or `false` when `authentify` is not an exposed datastore method. */
|
|
17
|
+
hasAuthentify?: boolean;
|
|
18
|
+
/** Top-level datastore REST methods from `catalog.methods` (`applyTo: dataStore`), excluding `authentify`. */
|
|
19
|
+
datastoreMethodNames: readonly string[];
|
|
17
20
|
}
|
|
18
21
|
/**
|
|
19
22
|
* Builds the object returned by generated `createClient()` from `catalog.gen.json`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog-builder.d.ts","sourceRoot":"","sources":["../../src/runtime/catalog-builder.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAA;AAExB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C,kEAAkE;AAClE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,2BAA2B,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9C,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,SAAS,EAAE,SAAS,qBAAqB,EAAE,CAAA;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAA;IAC3C,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAA;IAC5D,aAAa,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"catalog-builder.d.ts","sourceRoot":"","sources":["../../src/runtime/catalog-builder.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAA;AAExB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C,kEAAkE;AAClE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,2BAA2B,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9C,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,SAAS,EAAE,SAAS,qBAAqB,EAAE,CAAA;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAA;IAC3C,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAA;IAC5D,mHAAmH;IACnH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,8GAA8G;IAC9G,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAA;CACxC;AAqHD;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE;IAAE,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAClC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA8BzB"}
|
|
@@ -84,11 +84,14 @@ function makeDataClassNamespace(http, c, keyNamesRecord) {
|
|
|
84
84
|
export function buildQuadroClientFromCatalogSpec(http, spec, meta) {
|
|
85
85
|
const keyNamesRecord = spec.keyNamesByClass;
|
|
86
86
|
const out = {};
|
|
87
|
-
if (spec.hasAuthentify) {
|
|
87
|
+
if (spec.hasAuthentify === true) {
|
|
88
88
|
out.authentify = {
|
|
89
89
|
login: (body) => callDatastorePath(http, ['authentify', 'login'], { body }, { operation: 'auth.login' }),
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
+
for (const name of spec.datastoreMethodNames ?? []) {
|
|
93
|
+
out[name] = (body, init) => callDatastorePath(http, [name], { body, method: init?.method }, { operation: 'datastore', methodName: name });
|
|
94
|
+
}
|
|
92
95
|
for (const c of spec.classes) {
|
|
93
96
|
out[c.name] = makeDataClassNamespace(http, c, keyNamesRecord);
|
|
94
97
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../src/runtime/collection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../src/runtime/collection.ts"],"names":[],"mappings":"AACA,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,WAAW,CAAA;AACnF,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,YAAY,CAAA;AAGnB,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,UAAU,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,gEAAgE;IAChE,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sFAAsF;IACtF,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,6FAA6F;IAC7F,gBAAgB,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAA;CACnD;AAED,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC,CAAA;AAsE3C;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,GAAG,EAAE,iBAAiB,EACtB,cAAc,EAAE,iBAAiB,EACjC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAChB,gBAAgB,CAAC,CAAC,CAAC,CA6LrB;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG;IACnD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACvB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB,CAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { QuadroHttpError } from './errors.mjs';
|
|
1
2
|
import { mountPathFromBaseURL, normalizeBaseURL } from './http.mjs';
|
|
2
3
|
import { buildEntitySetCreationParams, buildEntitySetPageParams, buildListSearchParams, } from './query.mjs';
|
|
3
4
|
import { unwrapEntityList } from './unwrap.mjs';
|
|
@@ -111,7 +112,7 @@ export function createCollection(ctx, initialOptions, mapRow) {
|
|
|
111
112
|
});
|
|
112
113
|
const textCreate = await resCreate.text();
|
|
113
114
|
if (!resCreate.ok) {
|
|
114
|
-
throw new
|
|
115
|
+
throw new QuadroHttpError(resCreate.status, textCreate);
|
|
115
116
|
}
|
|
116
117
|
const jsonCreate = textCreate ? JSON.parse(textCreate) : [];
|
|
117
118
|
await parseEntitySetFromResponse(jsonCreate, resCreate);
|
|
@@ -130,7 +131,7 @@ export function createCollection(ctx, initialOptions, mapRow) {
|
|
|
130
131
|
});
|
|
131
132
|
const text = await res.text();
|
|
132
133
|
if (!res.ok) {
|
|
133
|
-
throw new
|
|
134
|
+
throw new QuadroHttpError(res.status, text);
|
|
134
135
|
}
|
|
135
136
|
const json = text ? JSON.parse(text) : [];
|
|
136
137
|
return unwrapEntityList(ctx.className, json);
|
|
@@ -150,7 +151,7 @@ export function createCollection(ctx, initialOptions, mapRow) {
|
|
|
150
151
|
});
|
|
151
152
|
const text = await res.text();
|
|
152
153
|
if (!res.ok) {
|
|
153
|
-
throw new
|
|
154
|
+
throw new QuadroHttpError(res.status, text);
|
|
154
155
|
}
|
|
155
156
|
const json = text ? JSON.parse(text) : [];
|
|
156
157
|
return unwrapEntityList(ctx.className, json);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quadrokit/client",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"description": "Typed 4D REST client and catalog code generator for QuadroKit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"generate:fixture": "bun run src/cli.ts generate --url file://../../assets/catalog.json --out ../../.quadrokit/generated-demo"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@quadrokit/shared": "^0.3.
|
|
36
|
+
"@quadrokit/shared": "^0.3.15",
|
|
37
37
|
"undici": "^6.21.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|