@topolo/sdk 0.1.1 → 0.1.2

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/dist/index.cjs CHANGED
@@ -119,6 +119,7 @@ var TopoloClient = class {
119
119
  requireConfirmForWrites;
120
120
  timeoutMs;
121
121
  fetchImpl;
122
+ debug;
122
123
  constructor(options) {
123
124
  if (!options.credential) throw new TopoloAuthError("credential is required");
124
125
  if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
@@ -128,6 +129,14 @@ var TopoloClient = class {
128
129
  this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
129
130
  this.timeoutMs = options.timeoutMs ?? 3e4;
130
131
  this.fetchImpl = options.fetch ?? fetch;
132
+ this.debug = options.debug;
133
+ }
134
+ emit(event) {
135
+ if (!this.debug) return;
136
+ try {
137
+ this.debug(event);
138
+ } catch {
139
+ }
131
140
  }
132
141
  /**
133
142
  * Low-level JSON request. Prefer the typed module helpers (identity, crm, ...)
@@ -154,27 +163,56 @@ var TopoloClient = class {
154
163
  const platformServiceId = PLATFORM_SERVICE_IDS[opts.service];
155
164
  if (platformServiceId) headers.set("X-Service-ID", platformServiceId);
156
165
  applyAuthHeaders(headers, this.credential);
157
- applyAuditHeaders(headers, this.agent, generateRequestId());
166
+ const requestId = generateRequestId();
167
+ applyAuditHeaders(headers, this.agent, requestId);
158
168
  if (opts.headers) {
159
169
  for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
160
170
  }
161
171
  const controller = new AbortController();
162
172
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
163
173
  const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
174
+ const urlStr = url.toString();
175
+ const startedAt = Date.now();
176
+ this.emit({ phase: "request", method, service: opts.service, path: opts.path, url: urlStr, requestId });
164
177
  let res;
165
178
  try {
166
- res = await this.fetchImpl(url.toString(), {
179
+ res = await this.fetchImpl(urlStr, {
167
180
  method,
168
181
  headers,
169
182
  body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
170
183
  signal
171
184
  });
185
+ } catch (err) {
186
+ this.emit({
187
+ phase: "error",
188
+ method,
189
+ service: opts.service,
190
+ path: opts.path,
191
+ url: urlStr,
192
+ requestId,
193
+ durationMs: Date.now() - startedAt,
194
+ error: err instanceof Error ? err.message : String(err)
195
+ });
196
+ throw err;
172
197
  } finally {
173
198
  clearTimeout(timeoutId);
174
199
  }
175
200
  const contentType = res.headers.get("Content-Type") ?? "";
176
201
  const parsed = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => null);
177
202
  if (!res.ok) {
203
+ const durationMs = Date.now() - startedAt;
204
+ const message = describeError(parsed, `HTTP ${res.status}`);
205
+ this.emit({
206
+ phase: "error",
207
+ method,
208
+ service: opts.service,
209
+ path: opts.path,
210
+ url: urlStr,
211
+ requestId,
212
+ durationMs,
213
+ error: message,
214
+ status: res.status
215
+ });
178
216
  if (res.status === 401) throw new TopoloAuthError(describeError(parsed, "Unauthorized"));
179
217
  if (res.status === 403) {
180
218
  throw new TopoloPermissionError(
@@ -184,6 +222,16 @@ var TopoloClient = class {
184
222
  }
185
223
  throw new TopoloHttpError(opts.service, opts.path, res.status, parsed);
186
224
  }
225
+ this.emit({
226
+ phase: "response",
227
+ method,
228
+ service: opts.service,
229
+ path: opts.path,
230
+ url: urlStr,
231
+ requestId,
232
+ status: res.status,
233
+ durationMs: Date.now() - startedAt
234
+ });
187
235
  return parsed;
188
236
  }
189
237
  /**
package/dist/index.d.cts CHANGED
@@ -65,7 +65,41 @@ interface TopoloClientOptions {
65
65
  timeoutMs?: number;
66
66
  /** Injected fetch, for testing. Defaults to global fetch. */
67
67
  fetch?: typeof fetch;
68
+ /**
69
+ * Observability hook. Fires once per request lifecycle (`request` at start,
70
+ * then exactly one of `response` or `error`). Safe to leave unset — off by
71
+ * default. Intended for CLI/MCP hosts to surface request-level diagnostics
72
+ * and for consumers building their own logging/tracing.
73
+ */
74
+ debug?: (event: TopoloDebugEvent) => void;
68
75
  }
76
+ type TopoloDebugEvent = {
77
+ phase: 'request';
78
+ method: string;
79
+ service: ServiceId;
80
+ path: string;
81
+ url: string;
82
+ requestId: string;
83
+ } | {
84
+ phase: 'response';
85
+ method: string;
86
+ service: ServiceId;
87
+ path: string;
88
+ url: string;
89
+ requestId: string;
90
+ status: number;
91
+ durationMs: number;
92
+ } | {
93
+ phase: 'error';
94
+ method: string;
95
+ service: ServiceId;
96
+ path: string;
97
+ url: string;
98
+ requestId: string;
99
+ durationMs: number;
100
+ error: string;
101
+ status?: number;
102
+ };
69
103
  interface RequestOptions {
70
104
  service: ServiceId;
71
105
  path: string;
@@ -85,7 +119,9 @@ declare class TopoloClient {
85
119
  private readonly requireConfirmForWrites;
86
120
  private readonly timeoutMs;
87
121
  private readonly fetchImpl;
122
+ private readonly debug?;
88
123
  constructor(options: TopoloClientOptions);
124
+ private emit;
89
125
  /**
90
126
  * Low-level JSON request. Prefer the typed module helpers (identity, crm, ...)
91
127
  * for anything a caller would reach for; this stays exported for escape-hatch
@@ -274,4 +310,4 @@ declare function createTopolo(options: TopoloClientOptions): {
274
310
  };
275
311
  type Topolo = ReturnType<typeof createTopolo>;
276
312
 
277
- export { type AgentIdentity, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, PLATFORM_SERVICE_IDS, type RequestOptions, type ServiceId, type TokenResponse, type Topolo, TopoloAuthError, TopoloClient, type TopoloClientOptions, type TopoloCredential, TopoloHttpError, TopoloOAuth, TopoloPermissionError, TopoloSdkError, createTopolo, resolveServiceUrl };
313
+ export { type AgentIdentity, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, PLATFORM_SERVICE_IDS, type RequestOptions, type ServiceId, type TokenResponse, type Topolo, TopoloAuthError, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, TopoloOAuth, TopoloPermissionError, TopoloSdkError, createTopolo, resolveServiceUrl };
package/dist/index.d.ts CHANGED
@@ -65,7 +65,41 @@ interface TopoloClientOptions {
65
65
  timeoutMs?: number;
66
66
  /** Injected fetch, for testing. Defaults to global fetch. */
67
67
  fetch?: typeof fetch;
68
+ /**
69
+ * Observability hook. Fires once per request lifecycle (`request` at start,
70
+ * then exactly one of `response` or `error`). Safe to leave unset — off by
71
+ * default. Intended for CLI/MCP hosts to surface request-level diagnostics
72
+ * and for consumers building their own logging/tracing.
73
+ */
74
+ debug?: (event: TopoloDebugEvent) => void;
68
75
  }
76
+ type TopoloDebugEvent = {
77
+ phase: 'request';
78
+ method: string;
79
+ service: ServiceId;
80
+ path: string;
81
+ url: string;
82
+ requestId: string;
83
+ } | {
84
+ phase: 'response';
85
+ method: string;
86
+ service: ServiceId;
87
+ path: string;
88
+ url: string;
89
+ requestId: string;
90
+ status: number;
91
+ durationMs: number;
92
+ } | {
93
+ phase: 'error';
94
+ method: string;
95
+ service: ServiceId;
96
+ path: string;
97
+ url: string;
98
+ requestId: string;
99
+ durationMs: number;
100
+ error: string;
101
+ status?: number;
102
+ };
69
103
  interface RequestOptions {
70
104
  service: ServiceId;
71
105
  path: string;
@@ -85,7 +119,9 @@ declare class TopoloClient {
85
119
  private readonly requireConfirmForWrites;
86
120
  private readonly timeoutMs;
87
121
  private readonly fetchImpl;
122
+ private readonly debug?;
88
123
  constructor(options: TopoloClientOptions);
124
+ private emit;
89
125
  /**
90
126
  * Low-level JSON request. Prefer the typed module helpers (identity, crm, ...)
91
127
  * for anything a caller would reach for; this stays exported for escape-hatch
@@ -274,4 +310,4 @@ declare function createTopolo(options: TopoloClientOptions): {
274
310
  };
275
311
  type Topolo = ReturnType<typeof createTopolo>;
276
312
 
277
- export { type AgentIdentity, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, PLATFORM_SERVICE_IDS, type RequestOptions, type ServiceId, type TokenResponse, type Topolo, TopoloAuthError, TopoloClient, type TopoloClientOptions, type TopoloCredential, TopoloHttpError, TopoloOAuth, TopoloPermissionError, TopoloSdkError, createTopolo, resolveServiceUrl };
313
+ export { type AgentIdentity, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, PLATFORM_SERVICE_IDS, type RequestOptions, type ServiceId, type TokenResponse, type Topolo, TopoloAuthError, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, TopoloOAuth, TopoloPermissionError, TopoloSdkError, createTopolo, resolveServiceUrl };
package/dist/index.js CHANGED
@@ -82,6 +82,7 @@ var TopoloClient = class {
82
82
  requireConfirmForWrites;
83
83
  timeoutMs;
84
84
  fetchImpl;
85
+ debug;
85
86
  constructor(options) {
86
87
  if (!options.credential) throw new TopoloAuthError("credential is required");
87
88
  if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
@@ -91,6 +92,14 @@ var TopoloClient = class {
91
92
  this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
92
93
  this.timeoutMs = options.timeoutMs ?? 3e4;
93
94
  this.fetchImpl = options.fetch ?? fetch;
95
+ this.debug = options.debug;
96
+ }
97
+ emit(event) {
98
+ if (!this.debug) return;
99
+ try {
100
+ this.debug(event);
101
+ } catch {
102
+ }
94
103
  }
95
104
  /**
96
105
  * Low-level JSON request. Prefer the typed module helpers (identity, crm, ...)
@@ -117,27 +126,56 @@ var TopoloClient = class {
117
126
  const platformServiceId = PLATFORM_SERVICE_IDS[opts.service];
118
127
  if (platformServiceId) headers.set("X-Service-ID", platformServiceId);
119
128
  applyAuthHeaders(headers, this.credential);
120
- applyAuditHeaders(headers, this.agent, generateRequestId());
129
+ const requestId = generateRequestId();
130
+ applyAuditHeaders(headers, this.agent, requestId);
121
131
  if (opts.headers) {
122
132
  for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
123
133
  }
124
134
  const controller = new AbortController();
125
135
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
126
136
  const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
137
+ const urlStr = url.toString();
138
+ const startedAt = Date.now();
139
+ this.emit({ phase: "request", method, service: opts.service, path: opts.path, url: urlStr, requestId });
127
140
  let res;
128
141
  try {
129
- res = await this.fetchImpl(url.toString(), {
142
+ res = await this.fetchImpl(urlStr, {
130
143
  method,
131
144
  headers,
132
145
  body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
133
146
  signal
134
147
  });
148
+ } catch (err) {
149
+ this.emit({
150
+ phase: "error",
151
+ method,
152
+ service: opts.service,
153
+ path: opts.path,
154
+ url: urlStr,
155
+ requestId,
156
+ durationMs: Date.now() - startedAt,
157
+ error: err instanceof Error ? err.message : String(err)
158
+ });
159
+ throw err;
135
160
  } finally {
136
161
  clearTimeout(timeoutId);
137
162
  }
138
163
  const contentType = res.headers.get("Content-Type") ?? "";
139
164
  const parsed = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => null);
140
165
  if (!res.ok) {
166
+ const durationMs = Date.now() - startedAt;
167
+ const message = describeError(parsed, `HTTP ${res.status}`);
168
+ this.emit({
169
+ phase: "error",
170
+ method,
171
+ service: opts.service,
172
+ path: opts.path,
173
+ url: urlStr,
174
+ requestId,
175
+ durationMs,
176
+ error: message,
177
+ status: res.status
178
+ });
141
179
  if (res.status === 401) throw new TopoloAuthError(describeError(parsed, "Unauthorized"));
142
180
  if (res.status === 403) {
143
181
  throw new TopoloPermissionError(
@@ -147,6 +185,16 @@ var TopoloClient = class {
147
185
  }
148
186
  throw new TopoloHttpError(opts.service, opts.path, res.status, parsed);
149
187
  }
188
+ this.emit({
189
+ phase: "response",
190
+ method,
191
+ service: opts.service,
192
+ path: opts.path,
193
+ url: urlStr,
194
+ requestId,
195
+ status: res.status,
196
+ durationMs: Date.now() - startedAt
197
+ });
150
198
  return parsed;
151
199
  }
152
200
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topolo/sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -167,5 +167,89 @@ describe('TopoloClient.request', () => {
167
167
  });
168
168
  });
169
169
 
170
+ describe('TopoloClient.debug hook', () => {
171
+ it('fires request → response on a successful call with stable requestId + duration', async () => {
172
+ const { impl } = stubFetch([{ body: { ok: true } }]);
173
+ const events: unknown[] = [];
174
+ const client = new TopoloClient({
175
+ credential: { kind: 'api_key', apiKey: 'x' },
176
+ agent: AGENT,
177
+ fetch: impl,
178
+ debug: (e) => events.push(e),
179
+ });
180
+ await client.request({ service: 'auth', path: '/api/me' });
181
+ expect(events).toHaveLength(2);
182
+ const typed = events as Array<{
183
+ phase: string;
184
+ method: string;
185
+ service: string;
186
+ path: string;
187
+ url: string;
188
+ requestId: string;
189
+ status?: number;
190
+ durationMs?: number;
191
+ }>;
192
+ const req = typed[0]!;
193
+ const res = typed[1]!;
194
+ expect(req.phase).toBe('request');
195
+ expect(req.method).toBe('GET');
196
+ expect(req.service).toBe('auth');
197
+ expect(req.path).toBe('/api/me');
198
+ expect(req.requestId).toMatch(/.+/);
199
+ expect(res.phase).toBe('response');
200
+ expect(res.status).toBe(200);
201
+ expect(res.requestId).toBe(req.requestId);
202
+ expect(typeof res.durationMs).toBe('number');
203
+ });
204
+
205
+ it('fires request → error on 4xx with the unwrapped message and status', async () => {
206
+ const { impl } = stubFetch([
207
+ { status: 401, body: { error: { code: 'auth_error', message: 'Expired' } } },
208
+ ]);
209
+ const events: Array<{ phase: string; error?: string; status?: number }> = [];
210
+ const client = new TopoloClient({
211
+ credential: { kind: 'api_key', apiKey: 'x' },
212
+ agent: AGENT,
213
+ fetch: impl,
214
+ debug: (e) => events.push(e),
215
+ });
216
+ await expect(client.request({ service: 'auth', path: '/api/me' })).rejects.toThrow(TopoloAuthError);
217
+ expect(events.map((e) => e.phase)).toEqual(['request', 'error']);
218
+ expect(events[1]!.status).toBe(401);
219
+ expect(events[1]!.error).toBe('Expired');
220
+ });
221
+
222
+ it('fires request → error on a thrown fetch (network failure)', async () => {
223
+ const impl = (vi.fn(async () => {
224
+ throw new Error('ECONNREFUSED');
225
+ }) as unknown) as typeof fetch;
226
+ const events: Array<{ phase: string; error?: string; status?: number }> = [];
227
+ const client = new TopoloClient({
228
+ credential: { kind: 'api_key', apiKey: 'x' },
229
+ agent: AGENT,
230
+ fetch: impl,
231
+ debug: (e) => events.push(e),
232
+ });
233
+ await expect(client.request({ service: 'auth', path: '/api/me' })).rejects.toThrow('ECONNREFUSED');
234
+ expect(events.map((e) => e.phase)).toEqual(['request', 'error']);
235
+ expect(events[1]!.error).toBe('ECONNREFUSED');
236
+ expect(events[1]!.status).toBeUndefined();
237
+ });
238
+
239
+ it('swallows throws from the debug callback without affecting the request path', async () => {
240
+ const { impl } = stubFetch([{ body: { ok: true } }]);
241
+ const client = new TopoloClient({
242
+ credential: { kind: 'api_key', apiKey: 'x' },
243
+ agent: AGENT,
244
+ fetch: impl,
245
+ debug: () => {
246
+ throw new Error('callback blew up');
247
+ },
248
+ });
249
+ // Returns normally despite the callback throwing on both events.
250
+ await expect(client.request({ service: 'auth', path: '/api/me' })).resolves.toBeDefined();
251
+ });
252
+ });
253
+
170
254
  // Silence unused-import warning if the bundler later tree-shakes these
171
255
  void TopoloHttpError;
package/src/client.ts CHANGED
@@ -26,8 +26,46 @@ export interface TopoloClientOptions {
26
26
  timeoutMs?: number;
27
27
  /** Injected fetch, for testing. Defaults to global fetch. */
28
28
  fetch?: typeof fetch;
29
+ /**
30
+ * Observability hook. Fires once per request lifecycle (`request` at start,
31
+ * then exactly one of `response` or `error`). Safe to leave unset — off by
32
+ * default. Intended for CLI/MCP hosts to surface request-level diagnostics
33
+ * and for consumers building their own logging/tracing.
34
+ */
35
+ debug?: (event: TopoloDebugEvent) => void;
29
36
  }
30
37
 
38
+ export type TopoloDebugEvent =
39
+ | {
40
+ phase: 'request';
41
+ method: string;
42
+ service: ServiceId;
43
+ path: string;
44
+ url: string;
45
+ requestId: string;
46
+ }
47
+ | {
48
+ phase: 'response';
49
+ method: string;
50
+ service: ServiceId;
51
+ path: string;
52
+ url: string;
53
+ requestId: string;
54
+ status: number;
55
+ durationMs: number;
56
+ }
57
+ | {
58
+ phase: 'error';
59
+ method: string;
60
+ service: ServiceId;
61
+ path: string;
62
+ url: string;
63
+ requestId: string;
64
+ durationMs: number;
65
+ error: string;
66
+ status?: number;
67
+ };
68
+
31
69
  export interface RequestOptions {
32
70
  service: ServiceId;
33
71
  path: string;
@@ -50,6 +88,7 @@ export class TopoloClient {
50
88
  private readonly requireConfirmForWrites: boolean;
51
89
  private readonly timeoutMs: number;
52
90
  private readonly fetchImpl: typeof fetch;
91
+ private readonly debug?: (event: TopoloDebugEvent) => void;
53
92
 
54
93
  constructor(options: TopoloClientOptions) {
55
94
  if (!options.credential) throw new TopoloAuthError('credential is required');
@@ -60,6 +99,16 @@ export class TopoloClient {
60
99
  this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
61
100
  this.timeoutMs = options.timeoutMs ?? 30_000;
62
101
  this.fetchImpl = options.fetch ?? fetch;
102
+ this.debug = options.debug;
103
+ }
104
+
105
+ private emit(event: TopoloDebugEvent): void {
106
+ if (!this.debug) return;
107
+ try {
108
+ this.debug(event);
109
+ } catch {
110
+ // Never let a debug consumer crash the request path.
111
+ }
63
112
  }
64
113
 
65
114
  /**
@@ -91,7 +140,8 @@ export class TopoloClient {
91
140
  if (platformServiceId) headers.set('X-Service-ID', platformServiceId);
92
141
 
93
142
  applyAuthHeaders(headers, this.credential);
94
- applyAuditHeaders(headers, this.agent, generateRequestId());
143
+ const requestId = generateRequestId();
144
+ applyAuditHeaders(headers, this.agent, requestId);
95
145
 
96
146
  if (opts.headers) {
97
147
  for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
@@ -103,14 +153,30 @@ export class TopoloClient {
103
153
  ? mergeSignals(opts.signal, controller.signal)
104
154
  : controller.signal;
105
155
 
156
+ const urlStr = url.toString();
157
+ const startedAt = Date.now();
158
+ this.emit({ phase: 'request', method, service: opts.service, path: opts.path, url: urlStr, requestId });
159
+
106
160
  let res: Response;
107
161
  try {
108
- res = await this.fetchImpl(url.toString(), {
162
+ res = await this.fetchImpl(urlStr, {
109
163
  method,
110
164
  headers,
111
165
  body: opts.body !== undefined ? JSON.stringify(opts.body) : null,
112
166
  signal,
113
167
  });
168
+ } catch (err) {
169
+ this.emit({
170
+ phase: 'error',
171
+ method,
172
+ service: opts.service,
173
+ path: opts.path,
174
+ url: urlStr,
175
+ requestId,
176
+ durationMs: Date.now() - startedAt,
177
+ error: err instanceof Error ? err.message : String(err),
178
+ });
179
+ throw err;
114
180
  } finally {
115
181
  clearTimeout(timeoutId);
116
182
  }
@@ -121,6 +187,19 @@ export class TopoloClient {
121
187
  : await res.text().catch(() => null);
122
188
 
123
189
  if (!res.ok) {
190
+ const durationMs = Date.now() - startedAt;
191
+ const message = describeError(parsed, `HTTP ${res.status}`);
192
+ this.emit({
193
+ phase: 'error',
194
+ method,
195
+ service: opts.service,
196
+ path: opts.path,
197
+ url: urlStr,
198
+ requestId,
199
+ durationMs,
200
+ error: message,
201
+ status: res.status,
202
+ });
124
203
  if (res.status === 401) throw new TopoloAuthError(describeError(parsed, 'Unauthorized'));
125
204
  if (res.status === 403) {
126
205
  throw new TopoloPermissionError(
@@ -131,6 +210,17 @@ export class TopoloClient {
131
210
  throw new TopoloHttpError(opts.service, opts.path, res.status, parsed);
132
211
  }
133
212
 
213
+ this.emit({
214
+ phase: 'response',
215
+ method,
216
+ service: opts.service,
217
+ path: opts.path,
218
+ url: urlStr,
219
+ requestId,
220
+ status: res.status,
221
+ durationMs: Date.now() - startedAt,
222
+ });
223
+
134
224
  return parsed as T;
135
225
  }
136
226
 
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export {
3
3
  type TopoloClientOptions,
4
4
  type RequestOptions,
5
5
  type CredentialIntrospection,
6
+ type TopoloDebugEvent,
6
7
  } from './client.js';
7
8
  export type { TopoloCredential, AgentIdentity } from './auth.js';
8
9
  export {