@quadrokit/client 0.3.12 → 0.3.14

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 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
 
@@ -53,7 +54,7 @@ Environment variables (often via `.env` in the project root): `VITE_4D_ORIGIN`,
53
54
  |------|---------|
54
55
  | **`types.gen.ts`** | Entity interfaces; **`*Path`** aliases use **`QuadroAttributePaths<T>`** (recursive dot paths, depth-limited for circular relations); **`QuadroClient`** typing. |
55
56
  | **`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`. |
57
+ | **`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
58
  | **`meta.json`** | `__NAME`, `sessionCookieName` hint for 4D session cookies. |
58
59
 
59
60
  Point your app imports at the generated folder, for example:
@@ -80,10 +81,90 @@ const quadro = createClient({
80
81
  })
81
82
  ```
82
83
 
84
+ Optional **`events: new QuadroEventBus()`** (from **`@quadrokit/client/runtime`**) enables [request lifecycle events](#request-lifecycle-events) on every call through the client.
85
+
83
86
  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
87
 
85
88
  ---
86
89
 
90
+ ## Request lifecycle events
91
+
92
+ 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.).
93
+
94
+ ```ts
95
+ import { createClient } from './.quadrokit/generated/client.gen.js'
96
+ import { QuadroEventBus } from '@quadrokit/client/runtime'
97
+
98
+ const events = new QuadroEventBus()
99
+
100
+ const quadro = createClient({
101
+ baseURL: '/rest',
102
+ events,
103
+ })
104
+
105
+ const unsub = events.subscribe((e) => {
106
+ if (e.type === 'loading') {
107
+ console.debug(e.context.operation, e.path, e.method)
108
+ }
109
+ if (e.type === 'success') {
110
+ console.debug(e.durationMs, e.body)
111
+ }
112
+ if (e.type === 'error') {
113
+ console.warn(e.status, e.message, e.error)
114
+ }
115
+ })
116
+
117
+ // later: unsub()
118
+ ```
119
+
120
+ You can also subscribe on the HTTP instance: **`quadro._http.subscribe(listener)`** returns an unsubscribe function.
121
+
122
+ ### Event shapes (`QuadroClientEvent`)
123
+
124
+ | Field | `loading` | `success` | `error` |
125
+ |-------|-----------|-----------|---------|
126
+ | **`type`** | `'loading'` | `'success'` | `'error'` |
127
+ | **`operationId`** | Correlates one start → one outcome | same | same |
128
+ | **`context`** | **`QuadroRequestContext`**: **`operation`** (**`QuadroOperation`** string), optional **`className`**, **`methodName`**, **`entityKey`**, **`attributes`** | same | same |
129
+ | **`path`** | Request path | same | same |
130
+ | **`method`** | HTTP method | — | — |
131
+ | **`startedAt`** | Timestamp | — | — |
132
+ | **`durationMs`** | — | Elapsed | Elapsed |
133
+ | **`status`** | — | If HTTP response is raw `Response` | If thrown value has HTTP **`status`** (e.g. **`QuadroHttpError`**) |
134
+ | **`body`** | — | Result: parsed JSON, rows, `void`, or a small summary for raw **`Response`** | — |
135
+ | **`message` / `error`** | — | — | User-facing message + original error |
136
+
137
+ ### Operations (`QuadroOperation`)
138
+
139
+ 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`**.
140
+
141
+ ### `QuadroHttp` helpers (advanced)
142
+
143
+ When **`events`** is set, **`json`**, **`void`**, and **`request`** accept an optional trailing **`QuadroRequestContext`** so emissions can be tagged. **`QuadroHttp`** also exposes:
144
+
145
+ - **`rawRequest(path, init)`** — fetch without emitting (used inside multi-step flows).
146
+ - **`runWithEvents(context, path, method, fn)`** — run an async function with the same loading/success/error envelope as the built-in methods.
147
+ - **`events`** getter — the bus instance, if any.
148
+
149
+ ---
150
+
151
+ ## RxJS (`@quadrokit/client/rx`)
152
+
153
+ Install **`rxjs`** in your app, then bridge the bus to an observable:
154
+
155
+ ```ts
156
+ import { quadroEventsObservable } from '@quadrokit/client/rx'
157
+ import { filter } from 'rxjs/operators'
158
+
159
+ const sub = quadroEventsObservable(events)
160
+ .pipe(filter((e) => e.type === 'error'))
161
+ .subscribe((e) => console.warn(e.message))
162
+ ```
163
+
164
+ **`rxjs`** is an **optional peer dependency** of `@quadrokit/client`; you only need it if you import `@quadrokit/client/rx`.
165
+
166
+ ---
167
+
87
168
  ## Dataclass API (generated)
88
169
 
89
170
  For each exposed dataclass, the client exposes a namespace such as `quadro.Agency`, `quadro.Reservation`, etc.
@@ -178,7 +259,8 @@ Import from **`@quadrokit/client/runtime`** when you need primitives without cod
178
259
 
179
260
  | Export | Use |
180
261
  |--------|-----|
181
- | **`QuadroHttp`** | Thin wrapper: `json()`, `void()`, `request()` with base URL + default headers. |
262
+ | **`QuadroHttp`** | Thin wrapper: **`json()`**, **`void()`**, **`request()`** with base URL + default headers; optional **`events`**; **`rawRequest`**, **`runWithEvents`**, **`subscribe`**. |
263
+ | **`QuadroEventBus`**, **`QuadroClientEvent`**, **`QuadroLoadingEvent`**, **`QuadroSuccessEvent`**, **`QuadroErrorEvent`**, **`QuadroRequestContext`**, **`QuadroOperation`** | Lifecycle event types and bus (see [Request lifecycle events](#request-lifecycle-events)). |
182
264
  | **`createCollection`** | Build a `CollectionHandle` from a `CollectionContext` + options + row mapper. |
183
265
  | **`callDataClassFunction`**, **`callEntityFunction`**, **`callEntityCollectionFunction`** | Call ORDA functions by name and path (same REST rules as above). |
184
266
  | **`callDatastorePath`**, **`createClient`’s `rpc`** | Arbitrary segments under the REST root, e.g. datastore or singleton paths. |
@@ -191,7 +273,7 @@ The generated `createClient` is the recommended surface; these are for tooling,
191
273
 
192
274
  ## Errors
193
275
 
194
- Failed HTTP responses throw **`QuadroHttpError`** (status + response body). Handle network errors separately (`fetch` failures).
276
+ 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
277
 
196
278
  ---
197
279
 
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/generate/codegen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsC,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA4GxF,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;CACvB,CAuBA;AA0GD,uIAAuI;AACvI,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAYvE;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBxF"}
1
+ {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/generate/codegen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsC,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA4GxF,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;CACvB,CAuBA;AA6GD,uIAAuI;AACvI,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAYvE;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBxF"}
@@ -193,6 +193,8 @@ export interface QuadroClientConfig {
193
193
  baseURL: string;
194
194
  fetchImpl?: typeof fetch;
195
195
  defaultHeaders?: Record<string, string>;
196
+ /** Optional bus for request lifecycle events (loading / success / error). See {@link QuadroEventBus} from \`@quadrokit/client/runtime\`. */
197
+ events?: import('@quadrokit/client/runtime').QuadroEventBus;
196
198
  }
197
199
  `;
198
200
  const body = `
@@ -201,6 +203,7 @@ export function createClient(config: QuadroClientConfig): QuadroClient {
201
203
  baseURL: config.baseURL,
202
204
  fetchImpl: config.fetchImpl,
203
205
  defaultHeaders: config.defaultHeaders,
206
+ events: config.events,
204
207
  });
205
208
  return buildQuadroClientFromCatalogSpec(
206
209
  http,
@@ -86,7 +86,7 @@ export function buildQuadroClientFromCatalogSpec(http, spec, meta) {
86
86
  const out = {};
87
87
  if (spec.hasAuthentify) {
88
88
  out.authentify = {
89
- login: (body) => callDatastorePath(http, ['authentify', 'login'], { body }),
89
+ login: (body) => callDatastorePath(http, ['authentify', 'login'], { body }, { operation: 'auth.login' }),
90
90
  };
91
91
  }
92
92
  for (const c of spec.classes) {
@@ -1,7 +1,3 @@
1
- /**
2
- * ORDA class functions over REST ([4D docs](https://developer.4d.com/docs/REST/classFunctions.html)):
3
- * dataclass, entity selection (entityCollection), and entity methods.
4
- */
5
1
  import type { QuadroHttp } from './http.js';
6
2
  import { type MethodSelectionQuery } from './query.js';
7
3
  export type ClassFunctionHttpOptions = {
@@ -1 +1 @@
1
- {"version":3,"file":"class-function.d.ts","sourceRoot":"","sources":["../../src/runtime/class-function.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAA6B,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEjF,MAAM,MAAM,wBAAwB,GAAG;IACrC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,CAAA;AAED,uGAAuG;AACvG,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,GAAG,CAAC,CAK7D;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,SAAS,OAAO,EAAE,EACxB,OAAO,CAAC,EAAE,wBAAwB,GAAG;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9D,OAAO,CAAC,CAAC,CAAC,CAoBZ;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EACxC,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,MAAM,EAC1B,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,SAAS,OAAO,EAAE,EACxB,OAAO,CAAC,EAAE,wBAAwB,GAAG;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9D,OAAO,CAAC,CAAC,CAAC,CAqBZ;AAED,MAAM,MAAM,6BAA6B,GAAG,wBAAwB,GAAG;IACrE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,oBAAoB,CAAA;IAChC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAAC,CAAC,EAClD,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,SAAS,OAAO,EAAE,EACxB,OAAO,CAAC,EAAE,6BAA6B,GACtC,OAAO,CAAC,CAAC,CAAC,CA2BZ"}
1
+ {"version":3,"file":"class-function.d.ts","sourceRoot":"","sources":["../../src/runtime/class-function.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAA6B,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEjF,MAAM,MAAM,wBAAwB,GAAG;IACrC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,CAAA;AAED,uGAAuG;AACvG,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,GAAG,CAAC,CAK7D;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,SAAS,OAAO,EAAE,EACxB,OAAO,CAAC,EAAE,wBAAwB,GAAG;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9D,OAAO,CAAC,CAAC,CAAC,CAkCZ;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EACxC,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,MAAM,EAC1B,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,SAAS,OAAO,EAAE,EACxB,OAAO,CAAC,EAAE,wBAAwB,GAAG;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9D,OAAO,CAAC,CAAC,CAAC,CAoCZ;AAED,MAAM,MAAM,6BAA6B,GAAG,wBAAwB,GAAG;IACrE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,oBAAoB,CAAA;IAChC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAAC,CAAC,EAClD,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,SAAS,OAAO,EAAE,EACxB,OAAO,CAAC,EAAE,6BAA6B,GACtC,OAAO,CAAC,CAAC,CAAC,CA4CZ"}
@@ -14,6 +14,12 @@ export async function callDataClassFunction(http, dataClass, functionName, args,
14
14
  const method = options?.method ?? 'POST';
15
15
  const unwrap = options?.unwrapResult !== false;
16
16
  const base = `/${dataClass}/${functionName}`;
17
+ const ctx = {
18
+ operation: 'function.dataclass',
19
+ className: dataClass,
20
+ methodName: functionName,
21
+ attributes: { argCount: args.length },
22
+ };
17
23
  let body;
18
24
  if (method === 'GET') {
19
25
  const params = new URLSearchParams();
@@ -21,14 +27,14 @@ export async function callDataClassFunction(http, dataClass, functionName, args,
21
27
  body = await http.json(`${base}?${params.toString()}`, {
22
28
  method: 'GET',
23
29
  signal: options?.signal,
24
- });
30
+ }, ctx);
25
31
  }
26
32
  else {
27
33
  body = await http.json(base, {
28
34
  method: 'POST',
29
35
  body: JSON.stringify([...args]),
30
36
  signal: options?.signal,
31
- });
37
+ }, ctx);
32
38
  }
33
39
  return (unwrap ? unwrapClassFunctionResult(body) : body);
34
40
  }
@@ -40,6 +46,13 @@ export async function callEntityFunction(http, dataClass, entityKey, functionNam
40
46
  const unwrap = options?.unwrapResult !== false;
41
47
  const key = encodeURIComponent(String(entityKey));
42
48
  const base = `/${dataClass}(${key})/${functionName}`;
49
+ const ctx = {
50
+ operation: 'function.entity',
51
+ className: dataClass,
52
+ methodName: functionName,
53
+ entityKey,
54
+ attributes: { argCount: args.length },
55
+ };
43
56
  let body;
44
57
  if (method === 'GET') {
45
58
  const params = new URLSearchParams();
@@ -47,14 +60,14 @@ export async function callEntityFunction(http, dataClass, entityKey, functionNam
47
60
  body = await http.json(`${base}?${params.toString()}`, {
48
61
  method: 'GET',
49
62
  signal: options?.signal,
50
- });
63
+ }, ctx);
51
64
  }
52
65
  else {
53
66
  body = await http.json(base, {
54
67
  method: 'POST',
55
68
  body: JSON.stringify([...args]),
56
69
  signal: options?.signal,
57
- });
70
+ }, ctx);
58
71
  }
59
72
  return (unwrap ? unwrapClassFunctionResult(body) : body);
60
73
  }
@@ -72,6 +85,15 @@ export async function callEntityCollectionFunction(http, dataClass, relationMap,
72
85
  path += `/$entityset/${encodeURIComponent(id)}`;
73
86
  }
74
87
  path += buildMethodSelectionQuery(dataClass, relationMap, options?.selection);
88
+ const ctx = {
89
+ operation: 'function.entityCollection',
90
+ className: dataClass,
91
+ methodName: functionName,
92
+ attributes: {
93
+ argCount: args.length,
94
+ ...(options?.entitySet ? { entitySet: options.entitySet } : {}),
95
+ },
96
+ };
75
97
  let body;
76
98
  if (method === 'GET') {
77
99
  const params = new URLSearchParams();
@@ -80,14 +102,14 @@ export async function callEntityCollectionFunction(http, dataClass, relationMap,
80
102
  body = await http.json(`${path}${join}${params.toString()}`, {
81
103
  method: 'GET',
82
104
  signal: options?.signal,
83
- });
105
+ }, ctx);
84
106
  }
85
107
  else {
86
108
  body = await http.json(path, {
87
109
  method: 'POST',
88
110
  body: JSON.stringify([...args]),
89
111
  signal: options?.signal,
90
- });
112
+ }, ctx);
91
113
  }
92
114
  return (unwrap ? unwrapClassFunctionResult(body) : body);
93
115
  }
@@ -1 +1 @@
1
- {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../src/runtime/collection.ts"],"names":[],"mappings":"AAAA,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,CAmKrB;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
+ {"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';
@@ -93,57 +94,68 @@ export function createCollection(ctx, initialOptions, mapRow) {
93
94
  }
94
95
  }
95
96
  }
96
- if (useEntitySet && !entitySetUrl) {
97
- const qsCreate = buildEntitySetCreationParams(initialOptions);
98
- const createPath = `${ctx.path}${qsCreate}`;
99
- const resCreate = await ctx.http.request(createPath, {
100
- signal: initialOptions.signal,
101
- });
102
- const textCreate = await resCreate.text();
103
- if (!resCreate.ok) {
104
- throw new Error(`Entity set creation failed ${resCreate.status}: ${textCreate.slice(0, 200)}`);
97
+ const listContext = {
98
+ operation: 'collection.list',
99
+ className: ctx.className,
100
+ attributes: {
101
+ page,
102
+ useEntitySet,
103
+ ...(initialOptions.filter !== undefined ? { filter: initialOptions.filter } : {}),
104
+ },
105
+ };
106
+ return ctx.http.runWithEvents(listContext, ctx.path, 'GET', async () => {
107
+ if (useEntitySet && !entitySetUrl) {
108
+ const qsCreate = buildEntitySetCreationParams(initialOptions);
109
+ const createPath = `${ctx.path}${qsCreate}`;
110
+ const resCreate = await ctx.http.rawRequest(createPath, {
111
+ signal: initialOptions.signal,
112
+ });
113
+ const textCreate = await resCreate.text();
114
+ if (!resCreate.ok) {
115
+ throw new QuadroHttpError(resCreate.status, textCreate);
116
+ }
117
+ const jsonCreate = textCreate ? JSON.parse(textCreate) : [];
118
+ await parseEntitySetFromResponse(jsonCreate, resCreate);
119
+ if (!entitySetUrl) {
120
+ return unwrapEntityList(ctx.className, jsonCreate);
121
+ }
122
+ const qsPage = buildEntitySetPageParams(skip, pageSize, initialOptions.select);
123
+ const base = entitySetUrl;
124
+ const pagePath = qsPage
125
+ ? base.includes('?')
126
+ ? `${base}&${qsPage.slice(1)}`
127
+ : `${base}${qsPage}`
128
+ : base;
129
+ const res = await ctx.http.rawRequest(pagePath, {
130
+ signal: initialOptions.signal,
131
+ });
132
+ const text = await res.text();
133
+ if (!res.ok) {
134
+ throw new QuadroHttpError(res.status, text);
135
+ }
136
+ const json = text ? JSON.parse(text) : [];
137
+ return unwrapEntityList(ctx.className, json);
105
138
  }
106
- const jsonCreate = textCreate ? JSON.parse(textCreate) : [];
107
- await parseEntitySetFromResponse(jsonCreate, resCreate);
108
- if (!entitySetUrl) {
109
- return unwrapEntityList(ctx.className, jsonCreate);
139
+ let requestPath;
140
+ if (entitySetUrl && useEntitySet) {
141
+ const qs = buildEntitySetPageParams(skip, pageSize, initialOptions.select);
142
+ const base = entitySetUrl;
143
+ requestPath = qs ? (base.includes('?') ? `${base}&${qs.slice(1)}` : `${base}${qs}`) : base;
110
144
  }
111
- const qsPage = buildEntitySetPageParams(skip, pageSize, initialOptions.select);
112
- const base = entitySetUrl;
113
- const pagePath = qsPage
114
- ? base.includes('?')
115
- ? `${base}&${qsPage.slice(1)}`
116
- : `${base}${qsPage}`
117
- : base;
118
- const res = await ctx.http.request(pagePath, {
145
+ else {
146
+ const qs = buildListSearchParams(ctx.className, { ...initialOptions, page }, ctx.relationMap);
147
+ requestPath = `${ctx.path}${qs}`;
148
+ }
149
+ const res = await ctx.http.rawRequest(requestPath, {
119
150
  signal: initialOptions.signal,
120
151
  });
121
152
  const text = await res.text();
122
153
  if (!res.ok) {
123
- throw new Error(`List failed ${res.status}: ${text.slice(0, 200)}`);
154
+ throw new QuadroHttpError(res.status, text);
124
155
  }
125
156
  const json = text ? JSON.parse(text) : [];
126
157
  return unwrapEntityList(ctx.className, json);
127
- }
128
- let requestPath;
129
- if (entitySetUrl && useEntitySet) {
130
- const qs = buildEntitySetPageParams(skip, pageSize, initialOptions.select);
131
- const base = entitySetUrl;
132
- requestPath = qs ? (base.includes('?') ? `${base}&${qs.slice(1)}` : `${base}${qs}`) : base;
133
- }
134
- else {
135
- const qs = buildListSearchParams(ctx.className, { ...initialOptions, page }, ctx.relationMap);
136
- requestPath = `${ctx.path}${qs}`;
137
- }
138
- const res = await ctx.http.request(requestPath, {
139
- signal: initialOptions.signal,
140
158
  });
141
- const text = await res.text();
142
- if (!res.ok) {
143
- throw new Error(`List failed ${res.status}: ${text.slice(0, 200)}`);
144
- }
145
- const json = text ? JSON.parse(text) : [];
146
- return unwrapEntityList(ctx.className, json);
147
159
  }
148
160
  async function* iteratePages() {
149
161
  let page = initialOptions.page ?? 1;
@@ -190,6 +202,10 @@ export function createCollection(ctx, initialOptions, mapRow) {
190
202
  }
191
203
  await Promise.all(ids.map((id) => ctx.http.void(`${ctx.className}(${encodeURIComponent(String(id))})`, {
192
204
  method: 'DELETE',
205
+ }, {
206
+ operation: 'dataclass.delete',
207
+ className: ctx.className,
208
+ entityKey: id,
193
209
  })));
194
210
  seenIds.clear();
195
211
  },
@@ -201,7 +217,7 @@ export function createCollection(ctx, initialOptions, mapRow) {
201
217
  entitySetUrl = undefined;
202
218
  try {
203
219
  const path = appendReleaseQuery(toRelease);
204
- const res = await ctx.http.request(path, { method: 'GET' });
220
+ const res = await ctx.http.request(path, { method: 'GET' }, { operation: 'collection.release', className: ctx.className });
205
221
  await res.text();
206
222
  }
207
223
  catch {
@@ -1 +1 @@
1
- {"version":3,"file":"data-class.d.ts","sourceRoot":"","sources":["../../src/runtime/data-class.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,iBAAiB,EAAoB,MAAM,iBAAiB,CAAA;AACjG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C,OAAO,EAAgB,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE5D,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,UAAU,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,wGAAwG;IACxG,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CAC5B;AAED,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,sBAAsB;QAIjE,CAAC,SAAS,SAAS,MAAM,EAAE,0BACnB,iBAAiB,GAAG;QAAE,MAAM,CAAC,EAAE,CAAC,CAAA;KAAE,GAC3C,gBAAgB,CAAC,CAAC,SAAS,SAAS,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;QAc7C,CAAC,SAAS,SAAS,MAAM,EAAE,oBAC/B,MAAM,GAAG,MAAM,YACT;QAAE,MAAM,CAAC,EAAE,CAAC,CAAA;KAAE,GACvB,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;eASH,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;UAU7C,CAAC,SAAS,SAAS,MAAM,EAAE,wBACvB,MAAM,YACJ,iBAAiB,GAAG;QAAE,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,CAAC,CAAA;KAAE,GAC/D,gBAAgB,CAAC,CAAC,SAAS,SAAS,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;EAe1D;AA6BD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,UAAU,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAA;CAC5C;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,2BAA2B,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAChD;AAED,6FAA6F;AAC7F,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,MAAM,EAC1D,MAAM,EAAE,CAAC,EACT,GAAG,EAAE,IAAI,CAAC,sBAAsB,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,EACvE,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,CAAC,CAiBH;AAgBD,iEAAiE;AACjE,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,IAAI,CAAC,sBAAsB,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,GAAG,UAAU,CAAC,EACpF,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,IAAI,CAmBN;AAED,yEAAyE;AACzE,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,sBAAsB,EAC3B,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,2BAA2B,CAAC,EAAE,SAAS,MAAM,EAAE;SAKxC,CAAC,SAAS,SAAS,MAAM,EAAE,0BACpB,iBAAiB,GAAG;QAAE,MAAM,CAAC,EAAE,CAAC,CAAA;KAAE,GAC3C,gBAAgB,CAAC,OAAO,CAAC;EAmB/B;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,SAAS,qBAAqB,EAAE,GAC1C,IAAI,CAYN;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
1
+ {"version":3,"file":"data-class.d.ts","sourceRoot":"","sources":["../../src/runtime/data-class.ts"],"names":[],"mappings":"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"}
@@ -19,13 +19,21 @@ export function makeDataClassApi(cfg) {
19
19
  ? buildEntityParams(cfg.className, options.select, cfg.relationMap)
20
20
  : '';
21
21
  const key = encodeURIComponent(String(id));
22
- const body = await cfg.http.json(`${cfg.className}(${key})${qs}`);
22
+ const body = await cfg.http.json(`${cfg.className}(${key})${qs}`, {}, {
23
+ operation: 'dataclass.get',
24
+ className: cfg.className,
25
+ entityKey: id,
26
+ });
23
27
  return unwrapEntity(cfg.className, body);
24
28
  },
25
29
  async delete(id) {
26
30
  const key = encodeURIComponent(String(id));
27
31
  try {
28
- await cfg.http.void(`${cfg.className}(${key})`, { method: 'DELETE' });
32
+ await cfg.http.void(`${cfg.className}(${key})`, { method: 'DELETE' }, {
33
+ operation: 'dataclass.delete',
34
+ className: cfg.className,
35
+ entityKey: id,
36
+ });
29
37
  return true;
30
38
  }
31
39
  catch {
@@ -1,8 +1,9 @@
1
+ import type { QuadroRequestContext } from './events.js';
1
2
  import type { QuadroHttp } from './http.js';
2
3
  /** Call nested datastore paths like `authentify/login` (POST by default). */
3
4
  export declare function callDatastorePath(http: QuadroHttp, segments: readonly string[], init?: {
4
5
  method?: 'GET' | 'POST';
5
6
  body?: unknown;
6
- }): Promise<unknown>;
7
+ }, context?: QuadroRequestContext): Promise<unknown>;
7
8
  export declare function createDatastoreNamespace(_http: QuadroHttp, tree: Record<string, unknown>): Record<string, unknown>;
8
9
  //# sourceMappingURL=datastore.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"datastore.d.ts","sourceRoot":"","sources":["../../src/runtime/datastore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C,6EAA6E;AAC7E,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GACjD,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB"}
1
+ {"version":3,"file":"datastore.d.ts","sourceRoot":"","sources":["../../src/runtime/datastore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C,6EAA6E;AAC7E,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,EAClD,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,OAAO,CAAC,CAoBlB;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB"}
@@ -1,14 +1,19 @@
1
1
  /** Call nested datastore paths like `authentify/login` (POST by default). */
2
- export async function callDatastorePath(http, segments, init) {
2
+ export async function callDatastorePath(http, segments, init, context) {
3
3
  const path = `/${segments.join('/')}`;
4
4
  const method = init?.method ?? 'POST';
5
+ const ctx = context ??
6
+ {
7
+ operation: 'datastore',
8
+ methodName: segments.join('/'),
9
+ };
5
10
  if (method === 'GET') {
6
- return http.json(path, { method: 'GET' });
11
+ return http.json(path, { method: 'GET' }, ctx);
7
12
  }
8
13
  return http.json(path, {
9
14
  method: 'POST',
10
15
  body: init?.body !== undefined ? JSON.stringify(init.body) : undefined,
11
- });
16
+ }, ctx);
12
17
  }
13
18
  export function createDatastoreNamespace(_http, tree) {
14
19
  return tree;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Structured lifecycle events for all Quadro HTTP-backed operations.
3
+ * Use {@link QuadroEventBus} with {@link QuadroHttp} options, or bridge to RxJS via `@quadrokit/client/rx`.
4
+ */
5
+ /** What the client is doing (for filtering and UI). */
6
+ export type QuadroOperation = 'http.json' | 'http.void' | 'http.request' | 'collection.list' | 'collection.release' | 'dataclass.get' | 'dataclass.delete' | 'function.dataclass' | 'function.entity' | 'function.entityCollection' | 'datastore' | 'auth.login';
7
+ /** Optional metadata for an operation (class, method, entity key, arbitrary tags). */
8
+ export type QuadroRequestContext = {
9
+ operation: QuadroOperation;
10
+ /** Dataclass name when applicable. */
11
+ className?: string;
12
+ /** 4D method / function name for `function.*` operations. */
13
+ methodName?: string;
14
+ /** Primary key for entity-scoped calls. */
15
+ entityKey?: string | number;
16
+ /** Extra structured data (page, filter summary, datastore path, …). */
17
+ attributes?: Record<string, unknown>;
18
+ };
19
+ export type QuadroLoadingEvent = {
20
+ type: 'loading';
21
+ operationId: string;
22
+ context: QuadroRequestContext;
23
+ path: string;
24
+ method: string;
25
+ startedAt: number;
26
+ };
27
+ export type QuadroSuccessEvent = {
28
+ type: 'success';
29
+ operationId: string;
30
+ context: QuadroRequestContext;
31
+ path: string;
32
+ durationMs: number;
33
+ /** HTTP status when applicable. */
34
+ status?: number;
35
+ /** Result payload: parsed JSON, rows, void, or a summary for raw `Response`. */
36
+ body?: unknown;
37
+ };
38
+ export type QuadroErrorEvent = {
39
+ type: 'error';
40
+ operationId: string;
41
+ context: QuadroRequestContext;
42
+ path: string;
43
+ durationMs: number;
44
+ message: string;
45
+ error: unknown;
46
+ /** HTTP status when the failure came from the transport. */
47
+ status?: number;
48
+ };
49
+ export type QuadroClientEvent = QuadroLoadingEvent | QuadroSuccessEvent | QuadroErrorEvent;
50
+ /**
51
+ * Subscribe to {@link QuadroClientEvent} from a {@link QuadroHttp} client.
52
+ * Pass `new QuadroEventBus()` as `events` in {@link QuadroFetchOptions}.
53
+ */
54
+ export declare class QuadroEventBus {
55
+ private readonly listeners;
56
+ subscribe(listener: (e: QuadroClientEvent) => void): () => void;
57
+ /** @internal */
58
+ emit(event: QuadroClientEvent): void;
59
+ /**
60
+ * Run an async operation with loading → success | error events.
61
+ * @internal
62
+ */
63
+ runWithEvents<T>(context: QuadroRequestContext, path: string, method: string, fn: () => Promise<T>): Promise<T>;
64
+ }
65
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/runtime/events.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,uDAAuD;AACvD,MAAM,MAAM,eAAe,GACvB,WAAW,GACX,WAAW,GACX,cAAc,GACd,iBAAiB,GACjB,oBAAoB,GACpB,eAAe,GACf,kBAAkB,GAClB,oBAAoB,GACpB,iBAAiB,GACjB,2BAA2B,GAC3B,WAAW,GACX,YAAY,CAAA;AAEhB,sFAAsF;AACtF,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,eAAe,CAAA;IAC1B,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3B,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,SAAS,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,oBAAoB,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,SAAS,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,oBAAoB,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gFAAgF;IAChF,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,OAAO,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,oBAAoB,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,OAAO,CAAA;IACd,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG,kBAAkB,GAAG,gBAAgB,CAAA;AAM1F;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4C;IAEtE,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI;IAO/D,gBAAgB;IAChB,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAUpC;;;OAGG;IACH,aAAa,CAAC,CAAC,EACb,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC;CAmDd"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Structured lifecycle events for all Quadro HTTP-backed operations.
3
+ * Use {@link QuadroEventBus} with {@link QuadroHttp} options, or bridge to RxJS via `@quadrokit/client/rx`.
4
+ */
5
+ function newOperationId() {
6
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
7
+ }
8
+ /**
9
+ * Subscribe to {@link QuadroClientEvent} from a {@link QuadroHttp} client.
10
+ * Pass `new QuadroEventBus()` as `events` in {@link QuadroFetchOptions}.
11
+ */
12
+ export class QuadroEventBus {
13
+ listeners = new Set();
14
+ subscribe(listener) {
15
+ this.listeners.add(listener);
16
+ return () => {
17
+ this.listeners.delete(listener);
18
+ };
19
+ }
20
+ /** @internal */
21
+ emit(event) {
22
+ for (const fn of this.listeners) {
23
+ try {
24
+ fn(event);
25
+ }
26
+ catch {
27
+ /* listener errors must not break the client */
28
+ }
29
+ }
30
+ }
31
+ /**
32
+ * Run an async operation with loading → success | error events.
33
+ * @internal
34
+ */
35
+ runWithEvents(context, path, method, fn) {
36
+ const operationId = newOperationId();
37
+ const startedAt = Date.now();
38
+ this.emit({ type: 'loading', operationId, context, path, method, startedAt });
39
+ return (async () => {
40
+ try {
41
+ const result = await fn();
42
+ const durationMs = Date.now() - startedAt;
43
+ let body = result;
44
+ if (result instanceof Response) {
45
+ body = {
46
+ kind: 'response',
47
+ status: result.status,
48
+ ok: result.ok,
49
+ url: result.url,
50
+ };
51
+ }
52
+ this.emit({
53
+ type: 'success',
54
+ operationId,
55
+ context,
56
+ path,
57
+ durationMs,
58
+ status: result instanceof Response ? result.status : undefined,
59
+ body,
60
+ });
61
+ return result;
62
+ }
63
+ catch (error) {
64
+ const durationMs = Date.now() - startedAt;
65
+ const message = error instanceof Error ? error.message : String(error);
66
+ const status = error &&
67
+ typeof error === 'object' &&
68
+ 'status' in error &&
69
+ typeof error.status === 'number'
70
+ ? error.status
71
+ : undefined;
72
+ this.emit({
73
+ type: 'error',
74
+ operationId,
75
+ context,
76
+ path,
77
+ durationMs,
78
+ message,
79
+ error,
80
+ status,
81
+ });
82
+ throw error;
83
+ }
84
+ })();
85
+ }
86
+ }
@@ -1,8 +1,14 @@
1
+ import type { QuadroClientEvent, QuadroEventBus, QuadroRequestContext } from './events.js';
1
2
  export interface QuadroFetchOptions {
2
3
  baseURL: string;
3
4
  fetchImpl?: typeof fetch;
4
5
  /** Extra headers on every request (e.g. Authorization for generate). */
5
6
  defaultHeaders?: Record<string, string>;
7
+ /**
8
+ * Optional bus for {@link QuadroClientEvent}: loading, success, error per logical operation.
9
+ * Create with `new QuadroEventBus()` and pass here; subscribe before calling the client.
10
+ */
11
+ events?: QuadroEventBus;
6
12
  }
7
13
  export declare function normalizeBaseURL(baseURL: string): string;
8
14
  /**
@@ -14,8 +20,23 @@ export declare class QuadroHttp {
14
20
  private readonly opts;
15
21
  constructor(opts: QuadroFetchOptions);
16
22
  get baseURL(): string;
17
- request(path: string, init?: RequestInit): Promise<Response>;
18
- json<T>(path: string, init?: RequestInit): Promise<T>;
19
- void(path: string, init?: RequestInit): Promise<void>;
23
+ /** Optional event bus from constructor options. */
24
+ get events(): QuadroEventBus | undefined;
25
+ /**
26
+ * Run a logical operation with loading / success / error events (when `events` is set).
27
+ * Use from collection code and other multi-step flows that do not use {@link json}.
28
+ */
29
+ runWithEvents<T>(context: QuadroRequestContext, path: string, method: string, fn: () => Promise<T>): Promise<T>;
30
+ /** Subscribe without reaching into options: `quadro._http.subscribe(cb)`. */
31
+ subscribe(listener: (e: QuadroClientEvent) => void): () => void;
32
+ /** @internal Raw fetch without events (used inside {@link runWithEvents}). */
33
+ rawRequest(path: string, init?: RequestInit): Promise<Response>;
34
+ /**
35
+ * Low-level request. When `events` is configured, emits loading/success/error unless
36
+ * you pass `context: undefined` and use {@link rawRequest} for manual control.
37
+ */
38
+ request(path: string, init?: RequestInit, context?: QuadroRequestContext): Promise<Response>;
39
+ json<T>(path: string, init?: RequestInit, context?: QuadroRequestContext): Promise<T>;
40
+ void(path: string, init?: RequestInit, context?: QuadroRequestContext): Promise<void>;
20
41
  }
21
42
  //# sourceMappingURL=http.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/runtime/http.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAa5D;AAED,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,kBAAkB;IAErD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAEK,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwBhE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;IAYzD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;CAOhE"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/runtime/http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAE1F,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC;;;OAGG;IACH,MAAM,CAAC,EAAE,cAAc,CAAA;CACxB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAa5D;AAED,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,kBAAkB;IAErD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,mDAAmD;IACnD,IAAI,MAAM,IAAI,cAAc,GAAG,SAAS,CAEvC;IAED;;;OAGG;IACH,aAAa,CAAC,CAAC,EACb,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC;IAQb,6EAA6E;IAC7E,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI;IAI/D,8EAA8E;IACxE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwBzE;;;OAGG;IACG,OAAO,CACX,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,WAAgB,EACtB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,QAAQ,CAAC;IAUd,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,CAAC,CAAC;IAqBzF,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;CAgBhG"}
@@ -29,7 +29,27 @@ export class QuadroHttp {
29
29
  get baseURL() {
30
30
  return normalizeBaseURL(this.opts.baseURL);
31
31
  }
32
- async request(path, init = {}) {
32
+ /** Optional event bus from constructor options. */
33
+ get events() {
34
+ return this.opts.events;
35
+ }
36
+ /**
37
+ * Run a logical operation with loading / success / error events (when `events` is set).
38
+ * Use from collection code and other multi-step flows that do not use {@link json}.
39
+ */
40
+ runWithEvents(context, path, method, fn) {
41
+ const bus = this.opts.events;
42
+ if (!bus) {
43
+ return fn();
44
+ }
45
+ return bus.runWithEvents(context, path, method, fn);
46
+ }
47
+ /** Subscribe without reaching into options: `quadro._http.subscribe(cb)`. */
48
+ subscribe(listener) {
49
+ return this.opts.events?.subscribe(listener) ?? (() => { });
50
+ }
51
+ /** @internal Raw fetch without events (used inside {@link runWithEvents}). */
52
+ async rawRequest(path, init = {}) {
33
53
  const url = path.startsWith('http')
34
54
  ? path
35
55
  : `${this.baseURL}${path.startsWith('/') ? '' : '/'}${path}`;
@@ -52,22 +72,53 @@ export class QuadroHttp {
52
72
  headers,
53
73
  });
54
74
  }
55
- async json(path, init = {}) {
56
- const res = await this.request(path, init);
57
- const text = await res.text();
58
- if (!res.ok) {
59
- throw new QuadroHttpError(res.status, text);
60
- }
61
- if (!text) {
62
- return undefined;
75
+ /**
76
+ * Low-level request. When `events` is configured, emits loading/success/error unless
77
+ * you pass `context: undefined` and use {@link rawRequest} for manual control.
78
+ */
79
+ async request(path, init = {}, context) {
80
+ const ctx = context ?? { operation: 'http.request' };
81
+ const method = (init.method ?? 'GET').toUpperCase();
82
+ const bus = this.opts.events;
83
+ if (!bus) {
84
+ return this.rawRequest(path, init);
63
85
  }
64
- return JSON.parse(text);
86
+ return bus.runWithEvents(ctx, path, method, () => this.rawRequest(path, init));
65
87
  }
66
- async void(path, init = {}) {
67
- const res = await this.request(path, init);
68
- if (!res.ok) {
88
+ async json(path, init = {}, context) {
89
+ const ctx = context ?? { operation: 'http.json' };
90
+ const method = (init.method ?? 'GET').toUpperCase();
91
+ const bus = this.opts.events;
92
+ const exec = async () => {
93
+ const res = await this.rawRequest(path, init);
69
94
  const text = await res.text();
70
- throw new QuadroHttpError(res.status, text);
95
+ if (!res.ok) {
96
+ throw new QuadroHttpError(res.status, text);
97
+ }
98
+ if (!text) {
99
+ return undefined;
100
+ }
101
+ return JSON.parse(text);
102
+ };
103
+ if (!bus) {
104
+ return exec();
105
+ }
106
+ return bus.runWithEvents(ctx, path, method, exec);
107
+ }
108
+ async void(path, init = {}, context) {
109
+ const ctx = context ?? { operation: 'http.void' };
110
+ const method = (init.method ?? 'GET').toUpperCase();
111
+ const bus = this.opts.events;
112
+ const exec = async () => {
113
+ const res = await this.rawRequest(path, init);
114
+ if (!res.ok) {
115
+ const text = await res.text();
116
+ throw new QuadroHttpError(res.status, text);
117
+ }
118
+ };
119
+ if (!bus) {
120
+ return exec();
71
121
  }
122
+ return bus.runWithEvents(ctx, path, method, exec);
72
123
  }
73
124
  }
@@ -5,6 +5,7 @@ export { type CollectionContext, type CollectionHandle, type CollectionOptions,
5
5
  export { attachEntityClassMethods, attachRelatedApis, bindEntityCollectionMethods, type DataClassRuntimeConfig, type EntityNavigationConfig, makeDataClassApi, makeRelatedCollectionApi, type RelatedNavigationSpec, } from './data-class.js';
6
6
  export { callDatastorePath, createDatastoreNamespace } from './datastore.js';
7
7
  export { QuadroHttpError } from './errors.js';
8
+ export { type QuadroClientEvent, type QuadroErrorEvent, QuadroEventBus, type QuadroLoadingEvent, type QuadroOperation, type QuadroRequestContext, type QuadroSuccessEvent, } from './events.js';
8
9
  export { mountPathFromBaseURL, normalizeBaseURL, type QuadroFetchOptions, QuadroHttp, } from './http.js';
9
10
  export type { Paths1, QuadroAttributePaths, SelectedEntity } from './paths.js';
10
11
  export { buildEntitySetCreationParams, buildEntitySetPageParams, buildListSearchParams, buildMethodSelectionQuery, type ListQueryParams, type MethodSelectionQuery, } from './query.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,EAChC,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,KAAK,wBAAwB,EAC7B,qBAAqB,EACrB,4BAA4B,EAC5B,kBAAkB,EAClB,KAAK,6BAA6B,EAClC,yBAAyB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,gBAAgB,EAChB,wBAAwB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,UAAU,GACX,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,MAAM,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC9E,OAAO,EACL,4BAA4B,EAC5B,wBAAwB,EACxB,qBAAqB,EACrB,yBAAyB,EACzB,KAAK,eAAe,EACpB,KAAK,oBAAoB,GAC1B,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,EAChC,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,KAAK,wBAAwB,EAC7B,qBAAqB,EACrB,4BAA4B,EAC5B,kBAAkB,EAClB,KAAK,6BAA6B,EAClC,yBAAyB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,gBAAgB,EAChB,wBAAwB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,UAAU,GACX,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,MAAM,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC9E,OAAO,EACL,4BAA4B,EAC5B,wBAAwB,EACxB,qBAAqB,EACrB,yBAAyB,EACzB,KAAK,eAAe,EACpB,KAAK,oBAAoB,GAC1B,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA"}
@@ -4,6 +4,7 @@ export { createCollection, } from './collection.mjs';
4
4
  export { attachEntityClassMethods, attachRelatedApis, bindEntityCollectionMethods, makeDataClassApi, makeRelatedCollectionApi, } from './data-class.mjs';
5
5
  export { callDatastorePath, createDatastoreNamespace } from './datastore.mjs';
6
6
  export { QuadroHttpError } from './errors.mjs';
7
+ export { QuadroEventBus, } from './events.mjs';
7
8
  export { mountPathFromBaseURL, normalizeBaseURL, QuadroHttp, } from './http.mjs';
8
9
  export { buildEntitySetCreationParams, buildEntitySetPageParams, buildListSearchParams, buildMethodSelectionQuery, } from './query.mjs';
9
10
  export { unwrapEntity, unwrapEntityList } from './unwrap.mjs';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * RxJS bridge for {@link QuadroEventBus}. Install `rxjs` as a dependency in your app.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { QuadroEventBus } from '@quadrokit/client/runtime'
7
+ * import { quadroEventsObservable } from '@quadrokit/client/rx'
8
+ * import { filter } from 'rxjs/operators'
9
+ *
10
+ * const bus = new QuadroEventBus()
11
+ * const quadro = createClient({ baseURL: '/rest', events: bus })
12
+ * quadroEventsObservable(bus)
13
+ * .pipe(filter((e) => e.type === 'error'))
14
+ * .subscribe(console.warn)
15
+ * ```
16
+ */
17
+ import { Observable } from 'rxjs';
18
+ import type { QuadroClientEvent, QuadroEventBus } from './events.js';
19
+ export declare function quadroEventsObservable(bus: QuadroEventBus): Observable<QuadroClientEvent>;
20
+ //# sourceMappingURL=rxjs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rxjs.d.ts","sourceRoot":"","sources":["../../src/runtime/rxjs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AACjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEpE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,UAAU,CAAC,iBAAiB,CAAC,CASzF"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * RxJS bridge for {@link QuadroEventBus}. Install `rxjs` as a dependency in your app.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { QuadroEventBus } from '@quadrokit/client/runtime'
7
+ * import { quadroEventsObservable } from '@quadrokit/client/rx'
8
+ * import { filter } from 'rxjs/operators'
9
+ *
10
+ * const bus = new QuadroEventBus()
11
+ * const quadro = createClient({ baseURL: '/rest', events: bus })
12
+ * quadroEventsObservable(bus)
13
+ * .pipe(filter((e) => e.type === 'error'))
14
+ * .subscribe(console.warn)
15
+ * ```
16
+ */
17
+ import { Observable } from 'rxjs';
18
+ export function quadroEventsObservable(bus) {
19
+ return new Observable((subscriber) => {
20
+ const unsub = bus.subscribe((e) => {
21
+ subscriber.next(e);
22
+ });
23
+ return () => {
24
+ unsub();
25
+ };
26
+ });
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quadrokit/client",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "Typed 4D REST client and catalog code generator for QuadroKit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -17,6 +17,10 @@
17
17
  "./runtime": {
18
18
  "types": "./dist/runtime/index.d.ts",
19
19
  "import": "./dist/runtime/index.mjs"
20
+ },
21
+ "./rx": {
22
+ "types": "./dist/runtime/rxjs.d.ts",
23
+ "import": "./dist/runtime/rxjs.mjs"
20
24
  }
21
25
  },
22
26
  "files": [
@@ -29,15 +33,22 @@
29
33
  "generate:fixture": "bun run src/cli.ts generate --url file://../../assets/catalog.json --out ../../.quadrokit/generated-demo"
30
34
  },
31
35
  "dependencies": {
32
- "@quadrokit/shared": "^0.3.12",
36
+ "@quadrokit/shared": "^0.3.14",
33
37
  "undici": "^6.21.0"
34
38
  },
35
39
  "peerDependencies": {
40
+ "rxjs": ">=7.8.0",
36
41
  "typescript": ">=5.4"
37
42
  },
43
+ "peerDependenciesMeta": {
44
+ "rxjs": {
45
+ "optional": true
46
+ }
47
+ },
38
48
  "devDependencies": {
39
49
  "@types/bun": "^1.3.11",
40
50
  "@types/node": "^25.5.0",
51
+ "rxjs": "^7.8.2",
41
52
  "typescript": "^5.9.3"
42
53
  }
43
54
  }