@quadrokit/client 0.3.12 → 0.3.13

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.
@@ -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":"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,CA+LrB;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"}
@@ -93,29 +93,59 @@ export function createCollection(ctx, initialOptions, mapRow) {
93
93
  }
94
94
  }
95
95
  }
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)}`);
96
+ const listContext = {
97
+ operation: 'collection.list',
98
+ className: ctx.className,
99
+ attributes: {
100
+ page,
101
+ useEntitySet,
102
+ ...(initialOptions.filter !== undefined ? { filter: initialOptions.filter } : {}),
103
+ },
104
+ };
105
+ return ctx.http.runWithEvents(listContext, ctx.path, 'GET', async () => {
106
+ if (useEntitySet && !entitySetUrl) {
107
+ const qsCreate = buildEntitySetCreationParams(initialOptions);
108
+ const createPath = `${ctx.path}${qsCreate}`;
109
+ const resCreate = await ctx.http.rawRequest(createPath, {
110
+ signal: initialOptions.signal,
111
+ });
112
+ const textCreate = await resCreate.text();
113
+ if (!resCreate.ok) {
114
+ throw new Error(`Entity set creation failed ${resCreate.status}: ${textCreate.slice(0, 200)}`);
115
+ }
116
+ const jsonCreate = textCreate ? JSON.parse(textCreate) : [];
117
+ await parseEntitySetFromResponse(jsonCreate, resCreate);
118
+ if (!entitySetUrl) {
119
+ return unwrapEntityList(ctx.className, jsonCreate);
120
+ }
121
+ const qsPage = buildEntitySetPageParams(skip, pageSize, initialOptions.select);
122
+ const base = entitySetUrl;
123
+ const pagePath = qsPage
124
+ ? base.includes('?')
125
+ ? `${base}&${qsPage.slice(1)}`
126
+ : `${base}${qsPage}`
127
+ : base;
128
+ const res = await ctx.http.rawRequest(pagePath, {
129
+ signal: initialOptions.signal,
130
+ });
131
+ const text = await res.text();
132
+ if (!res.ok) {
133
+ throw new Error(`List failed ${res.status}: ${text.slice(0, 200)}`);
134
+ }
135
+ const json = text ? JSON.parse(text) : [];
136
+ return unwrapEntityList(ctx.className, json);
105
137
  }
106
- const jsonCreate = textCreate ? JSON.parse(textCreate) : [];
107
- await parseEntitySetFromResponse(jsonCreate, resCreate);
108
- if (!entitySetUrl) {
109
- return unwrapEntityList(ctx.className, jsonCreate);
138
+ let requestPath;
139
+ if (entitySetUrl && useEntitySet) {
140
+ const qs = buildEntitySetPageParams(skip, pageSize, initialOptions.select);
141
+ const base = entitySetUrl;
142
+ requestPath = qs ? (base.includes('?') ? `${base}&${qs.slice(1)}` : `${base}${qs}`) : base;
110
143
  }
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, {
144
+ else {
145
+ const qs = buildListSearchParams(ctx.className, { ...initialOptions, page }, ctx.relationMap);
146
+ requestPath = `${ctx.path}${qs}`;
147
+ }
148
+ const res = await ctx.http.rawRequest(requestPath, {
119
149
  signal: initialOptions.signal,
120
150
  });
121
151
  const text = await res.text();
@@ -124,26 +154,7 @@ export function createCollection(ctx, initialOptions, mapRow) {
124
154
  }
125
155
  const json = text ? JSON.parse(text) : [];
126
156
  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
157
  });
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
158
  }
148
159
  async function* iteratePages() {
149
160
  let page = initialOptions.page ?? 1;
@@ -190,6 +201,10 @@ export function createCollection(ctx, initialOptions, mapRow) {
190
201
  }
191
202
  await Promise.all(ids.map((id) => ctx.http.void(`${ctx.className}(${encodeURIComponent(String(id))})`, {
192
203
  method: 'DELETE',
204
+ }, {
205
+ operation: 'dataclass.delete',
206
+ className: ctx.className,
207
+ entityKey: id,
193
208
  })));
194
209
  seenIds.clear();
195
210
  },
@@ -201,7 +216,7 @@ export function createCollection(ctx, initialOptions, mapRow) {
201
216
  entitySetUrl = undefined;
202
217
  try {
203
218
  const path = appendReleaseQuery(toRelease);
204
- const res = await ctx.http.request(path, { method: 'GET' });
219
+ const res = await ctx.http.request(path, { method: 'GET' }, { operation: 'collection.release', className: ctx.className });
205
220
  await res.text();
206
221
  }
207
222
  catch {
@@ -1 +1 @@
1
- {"version":3,"file":"data-class.d.ts","sourceRoot":"","sources":["../../src/runtime/data-class.ts"],"names":[],"mappings":"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"}
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.13",
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.13",
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
  }