@outputai/http 0.4.1-dev.56c13a8.0 → 0.4.1-dev.6555a2c.0

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/cost.d.ts CHANGED
@@ -1,16 +1,9 @@
1
1
  import { KyResponse } from 'ky';
2
- export type RequestCost = {
3
- total: number;
4
- components?: Array<{
5
- name: string;
6
- value: number;
7
- }>;
8
- };
9
2
  /**
10
3
  * Attach cost information to the trace of an HTTP Request using the response
11
4
  *
12
5
  * @param response - The response of the HTTP Request to attach the information
13
- * @param cost - The cost information
6
+ * @param value - The price of the HTTP request
14
7
  * @returns
15
8
  */
16
- export declare const addRequestCost: (response: KyResponse | Response, cost: RequestCost) => void;
9
+ export declare const addRequestCost: (response: KyResponse | Response, value: number) => void;
package/dist/cost.js CHANGED
@@ -4,15 +4,16 @@ import { requestIdSymbol } from './consts.js';
4
4
  * Attach cost information to the trace of an HTTP Request using the response
5
5
  *
6
6
  * @param response - The response of the HTTP Request to attach the information
7
- * @param cost - The cost information
7
+ * @param value - The price of the HTTP request
8
8
  * @returns
9
9
  */
10
- export const addRequestCost = (response, cost) => {
10
+ export const addRequestCost = (response, value) => {
11
11
  const eventId = Reflect.get(response, requestIdSymbol);
12
12
  if (!eventId) {
13
13
  console.warn('addRequestCost(): The "response" argument did not originate from @outputai/http, no costs were added.');
14
14
  return;
15
15
  }
16
- Tracing.addEventAttribute({ eventId, name: Tracing.Attribute.COST, value: cost });
17
- emitEvent('cost:http:request', { requestId: eventId, url: response.url, cost });
16
+ const attribute = new Tracing.Attribute.HTTPRequestCost(response.url, eventId, value);
17
+ Tracing.addEventAttribute({ eventId, attribute });
18
+ emitEvent('cost:http:request', attribute);
18
19
  };
package/dist/cost.spec.js CHANGED
@@ -1,14 +1,28 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
2
  import { requestIdSymbol } from './consts.js';
3
- vi.mock('@outputai/core/sdk_activity_integration', () => ({
4
- Tracing: {
5
- addEventAttribute: vi.fn(),
6
- Attribute: {
7
- COST: 'cost'
3
+ vi.mock('@outputai/core/sdk_activity_integration', () => {
4
+ class HTTPRequestCost {
5
+ static TYPE = 'http:request:cost';
6
+ type = HTTPRequestCost.TYPE;
7
+ url;
8
+ requestId;
9
+ total;
10
+ constructor(url, requestId, total) {
11
+ this.url = url;
12
+ this.requestId = requestId;
13
+ this.total = total;
8
14
  }
9
- },
10
- emitEvent: vi.fn()
11
- }));
15
+ }
16
+ return {
17
+ Tracing: {
18
+ addEventAttribute: vi.fn(),
19
+ Attribute: {
20
+ HTTPRequestCost
21
+ }
22
+ },
23
+ emitEvent: vi.fn()
24
+ };
25
+ });
12
26
  import { Tracing, emitEvent } from '@outputai/core/sdk_activity_integration';
13
27
  import { addRequestCost } from './cost.js';
14
28
  const tracing = vi.mocked(Tracing, true);
@@ -24,7 +38,7 @@ describe('addRequestCost', () => {
24
38
  });
25
39
  it('shortcircuits when the response has no http request id', () => {
26
40
  const response = new Response();
27
- const cost = { total: 1 };
41
+ const cost = 1;
28
42
  addRequestCost(response, cost);
29
43
  expect(console.warn).toHaveBeenCalledWith('addRequestCost(): The "response" argument did not originate from @outputai/http, no costs were added.');
30
44
  expect(tracing.addEventAttribute).not.toHaveBeenCalled();
@@ -33,56 +47,36 @@ describe('addRequestCost', () => {
33
47
  it('records cost on the trace event when the response carries the request id', () => {
34
48
  const response = new Response(undefined, { status: 200 });
35
49
  Reflect.set(response, requestIdSymbol, 'evt-cost-1');
36
- const cost = { total: 2.5 };
50
+ const cost = 2.5;
37
51
  addRequestCost(response, cost);
38
52
  expect(console.warn).not.toHaveBeenCalled();
39
53
  expect(tracing.addEventAttribute).toHaveBeenCalledWith({
40
54
  eventId: 'evt-cost-1',
41
- name: Tracing.Attribute.COST,
42
- value: cost
43
- });
44
- expect(emit).toHaveBeenCalledWith('cost:http:request', {
45
- requestId: 'evt-cost-1',
46
- url: response.url,
47
- cost
55
+ attribute: expect.objectContaining({
56
+ type: Tracing.Attribute.HTTPRequestCost.TYPE,
57
+ url: response.url,
58
+ requestId: 'evt-cost-1',
59
+ total: cost
60
+ })
48
61
  });
62
+ const attribute = tracing.addEventAttribute.mock.calls[0][0].attribute;
63
+ expect(emit).toHaveBeenCalledWith('cost:http:request', attribute);
49
64
  });
50
- it('forwards multiple components to tracing', () => {
65
+ it('records zero cost on the trace event', () => {
51
66
  const response = new Response();
52
67
  Reflect.set(response, requestIdSymbol, 'evt-cost-2');
53
- const cost = {
54
- total: 10,
55
- components: [
56
- { name: 'input', value: 3 },
57
- { name: 'output', value: 7 }
58
- ]
59
- };
68
+ const cost = 0;
60
69
  addRequestCost(response, cost);
61
70
  expect(tracing.addEventAttribute).toHaveBeenCalledWith({
62
71
  eventId: 'evt-cost-2',
63
- name: Tracing.Attribute.COST,
64
- value: cost
65
- });
66
- expect(emit).toHaveBeenCalledWith('cost:http:request', {
67
- requestId: 'evt-cost-2',
68
- url: response.url,
69
- cost
70
- });
71
- });
72
- it('forwards an empty components array to tracing', () => {
73
- const response = new Response();
74
- Reflect.set(response, requestIdSymbol, 'evt-cost-3');
75
- const cost = { total: 1, components: [] };
76
- addRequestCost(response, cost);
77
- expect(tracing.addEventAttribute).toHaveBeenCalledWith({
78
- eventId: 'evt-cost-3',
79
- name: Tracing.Attribute.COST,
80
- value: cost
81
- });
82
- expect(emit).toHaveBeenCalledWith('cost:http:request', {
83
- requestId: 'evt-cost-3',
84
- url: response.url,
85
- cost
72
+ attribute: expect.objectContaining({
73
+ type: Tracing.Attribute.HTTPRequestCost.TYPE,
74
+ url: response.url,
75
+ requestId: 'evt-cost-2',
76
+ total: cost
77
+ })
86
78
  });
79
+ const attribute = tracing.addEventAttribute.mock.calls[0][0].attribute;
80
+ expect(emit).toHaveBeenCalledWith('cost:http:request', attribute);
87
81
  });
88
82
  });
@@ -13,6 +13,7 @@ RequestInit };
13
13
  * Behaves the same as any fetch function except:
14
14
  * - Sets a request header called `x-request--trace-id` with a random UUID;
15
15
  * - Sends the request, response, error and/or failure to the Trace system;
16
+ * - Emits a `http:request` event on every call (success, http_error, network_error).
16
17
  *
17
18
  * @see {@link https://fetch.spec.whatwg.org/}
18
19
  * @param input - URL string, URL object or Request object (undici's or Node's)
@@ -15,6 +15,7 @@ export * as undici from 'undici';
15
15
  * Behaves the same as any fetch function except:
16
16
  * - Sets a request header called `x-request--trace-id` with a random UUID;
17
17
  * - Sends the request, response, error and/or failure to the Trace system;
18
+ * - Emits a `http:request` event on every call (success, http_error, network_error).
18
19
  *
19
20
  * @see {@link https://fetch.spec.whatwg.org/}
20
21
  * @param input - URL string, URL object or Request object (undici's or Node's)
@@ -29,20 +30,25 @@ export const fetch = async (input, init) => {
29
30
  const requestId = randomUUID();
30
31
  headers.set('x-request-trace-id', requestId);
31
32
  const request = new undici.Request(base, { headers });
33
+ const method = request.method;
34
+ const url = request.url;
35
+ const startedAt = performance.now();
32
36
  await logRequest({ requestId, request });
33
37
  try {
34
38
  const response = await undici.fetch(request);
39
+ const durationMs = Math.round(performance.now() - startedAt);
35
40
  // This enriches the response of the request id, so it is identifiable later.
36
41
  addRequestIdToResponse(response, requestId);
37
42
  if (response.status > 399) {
38
- await logError({ requestId, response });
43
+ await logError({ requestId, response, method, url, durationMs });
39
44
  return response;
40
45
  }
41
- await logResponse({ requestId, response });
46
+ await logResponse({ requestId, response, method, url, durationMs });
42
47
  return response;
43
48
  }
44
49
  catch (error) {
45
- logFailure({ requestId, error: error });
50
+ const durationMs = Math.round(performance.now() - startedAt);
51
+ logFailure({ requestId, error: error, method, url, durationMs });
46
52
  throw error;
47
53
  }
48
54
  };
@@ -62,8 +62,13 @@ describe('fetch/index', () => {
62
62
  expect(loggerMock.logRequest.mock.calls[0][0].request.method).toBe('GET');
63
63
  expect(loggerMock.logRequest.mock.calls[0][0].request.url).toBe(`${MOCK_ORIGIN}/ok`);
64
64
  expect(loggerMock.logResponse).toHaveBeenCalledTimes(1);
65
- expect(loggerMock.logResponse.mock.calls[0][0].requestId).toBe(FIXED_REQUEST_ID);
66
- expect(loggerMock.logResponse.mock.calls[0][0].response).toBe(response);
65
+ const responseCall = loggerMock.logResponse.mock.calls[0][0];
66
+ expect(responseCall.requestId).toBe(FIXED_REQUEST_ID);
67
+ expect(responseCall.response).toBe(response);
68
+ expect(responseCall.method).toBe('GET');
69
+ expect(responseCall.url).toBe(`${MOCK_ORIGIN}/ok`);
70
+ expect(typeof responseCall.durationMs).toBe('number');
71
+ expect(responseCall.durationMs).toBeGreaterThanOrEqual(0);
67
72
  expect(utilsMock.addRequestIdToResponse).toHaveBeenCalledTimes(1);
68
73
  expect(utilsMock.addRequestIdToResponse).toHaveBeenCalledWith(response, FIXED_REQUEST_ID);
69
74
  expect(loggerMock.logError).not.toHaveBeenCalled();
@@ -12,34 +12,55 @@ export declare const logRequest: ({ requestId, request }: {
12
12
  }) => Promise<void>;
13
13
  /**
14
14
  * Sends the trace error event for an http response with error status
15
+ * and emits a `http:request` event with `outcome: 'http_error'`.
15
16
  *
16
17
  * @param options
17
18
  * @param options.requestId - id of the request
18
19
  * @param options.response - The HTTP Response object
20
+ * @param options.method - HTTP method of the request
21
+ * @param options.url - URL of the request
22
+ * @param options.durationMs - elapsed time from request issuance to response, in milliseconds
19
23
  */
20
- export declare const logError: ({ requestId, response }: {
24
+ export declare const logError: ({ requestId, response, method, url, durationMs }: {
21
25
  requestId: string;
22
26
  response: Response;
27
+ method: string;
28
+ url: string;
29
+ durationMs: number;
23
30
  }) => Promise<void>;
24
31
  /**
25
32
  * Sends the trace end event for an http response
33
+ * and emits a `http:request` event with `outcome: 'success'`.
26
34
  *
27
35
  * @param {object} options
28
36
  * @param options.requestId - id of the request
29
37
  * @param {Response} options.response - The HTTP Response object
38
+ * @param options.method - HTTP method of the request
39
+ * @param options.url - URL of the request
40
+ * @param options.durationMs - elapsed time from request issuance to response, in milliseconds
30
41
  */
31
- export declare const logResponse: ({ requestId, response }: {
42
+ export declare const logResponse: ({ requestId, response, method, url, durationMs }: {
32
43
  requestId: string;
33
44
  response: Response;
45
+ method: string;
46
+ url: string;
47
+ durationMs: number;
34
48
  }) => Promise<void>;
35
49
  /**
36
50
  * Creates the trace error event for a network/connection failure
51
+ * and emits a `http:request` event with `outcome: 'network_error'`.
37
52
  *
38
53
  * @param options
39
54
  * @param options.requestId - id of the request
40
55
  * @param options.error - The error thrown
56
+ * @param options.method - HTTP method of the request
57
+ * @param options.url - URL of the request
58
+ * @param options.durationMs - elapsed time from request issuance to failure, in milliseconds
41
59
  */
42
- export declare const logFailure: ({ requestId, error }: {
60
+ export declare const logFailure: ({ requestId, error, method, url, durationMs }: {
43
61
  requestId: string;
44
62
  error: Error;
63
+ method: string;
64
+ url: string;
65
+ durationMs: number;
45
66
  }) => void;
@@ -1,6 +1,10 @@
1
- import { Tracing } from '@outputai/core/sdk_activity_integration';
1
+ import { Tracing, emitEvent } from '@outputai/core/sdk_activity_integration';
2
2
  import { config } from '../config.js';
3
3
  import { parseBody, redactHeaders, serializeError } from './utils.js';
4
+ /** Single source of truth for the `http:request` event shape. */
5
+ const emitHttpRequestEvent = (payload) => {
6
+ emitEvent('http:request', payload);
7
+ };
4
8
  /**
5
9
  * Sends the trace start event for an http request
6
10
  *
@@ -16,42 +20,69 @@ export const logRequest = async ({ requestId, request }) => {
16
20
  ...(config.logVerbose && { headers: redactHeaders(request.headers), body: await parseBody(request) })
17
21
  }
18
22
  });
19
- Tracing.addEventAttribute({ eventId: requestId, name: 'requestId', value: requestId });
23
+ Tracing.addEventAttribute({ eventId: requestId, attribute: new Tracing.Attribute.HTTPRequestCount(request.url, requestId) });
20
24
  };
21
25
  /**
22
26
  * Sends the trace error event for an http response with error status
27
+ * and emits a `http:request` event with `outcome: 'http_error'`.
23
28
  *
24
29
  * @param options
25
30
  * @param options.requestId - id of the request
26
31
  * @param options.response - The HTTP Response object
32
+ * @param options.method - HTTP method of the request
33
+ * @param options.url - URL of the request
34
+ * @param options.durationMs - elapsed time from request issuance to response, in milliseconds
27
35
  */
28
- export const logError = async ({ requestId, response }) => Tracing.addEventError({
29
- id: requestId, details: {
30
- status: response.status,
31
- statusText: response.statusText,
32
- headers: redactHeaders(response.headers),
33
- body: await parseBody(response)
34
- }
35
- });
36
+ export const logError = async ({ requestId, response, method, url, durationMs }) => {
37
+ await Tracing.addEventError({
38
+ id: requestId, details: {
39
+ status: response.status,
40
+ statusText: response.statusText,
41
+ headers: redactHeaders(response.headers),
42
+ body: await parseBody(response)
43
+ }
44
+ });
45
+ emitHttpRequestEvent({
46
+ requestId, method, url, status: response.status, durationMs, outcome: 'http_error'
47
+ });
48
+ };
36
49
  /**
37
50
  * Sends the trace end event for an http response
51
+ * and emits a `http:request` event with `outcome: 'success'`.
38
52
  *
39
53
  * @param {object} options
40
54
  * @param options.requestId - id of the request
41
55
  * @param {Response} options.response - The HTTP Response object
56
+ * @param options.method - HTTP method of the request
57
+ * @param options.url - URL of the request
58
+ * @param options.durationMs - elapsed time from request issuance to response, in milliseconds
42
59
  */
43
- export const logResponse = async ({ requestId, response }) => Tracing.addEventEnd({
44
- id: requestId, details: {
45
- status: response.status,
46
- statusText: response.statusText,
47
- ...(config.logVerbose && { headers: redactHeaders(response.headers), body: await parseBody(response) })
48
- }
49
- });
60
+ export const logResponse = async ({ requestId, response, method, url, durationMs }) => {
61
+ await Tracing.addEventEnd({
62
+ id: requestId, details: {
63
+ status: response.status,
64
+ statusText: response.statusText,
65
+ ...(config.logVerbose && { headers: redactHeaders(response.headers), body: await parseBody(response) })
66
+ }
67
+ });
68
+ emitHttpRequestEvent({
69
+ requestId, method, url, status: response.status, durationMs, outcome: 'success'
70
+ });
71
+ };
50
72
  /**
51
73
  * Creates the trace error event for a network/connection failure
74
+ * and emits a `http:request` event with `outcome: 'network_error'`.
52
75
  *
53
76
  * @param options
54
77
  * @param options.requestId - id of the request
55
78
  * @param options.error - The error thrown
79
+ * @param options.method - HTTP method of the request
80
+ * @param options.url - URL of the request
81
+ * @param options.durationMs - elapsed time from request issuance to failure, in milliseconds
56
82
  */
57
- export const logFailure = ({ requestId, error }) => Tracing.addEventError({ id: requestId, details: serializeError(error) });
83
+ export const logFailure = ({ requestId, error, method, url, durationMs }) => {
84
+ Tracing.addEventError({ id: requestId, details: serializeError(error) });
85
+ emitHttpRequestEvent({
86
+ requestId, method, url, status: undefined, durationMs, outcome: 'network_error'
87
+ });
88
+ };
@@ -1,15 +1,32 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { Response, Request } from 'undici';
3
- vi.mock('@outputai/core/sdk_activity_integration', () => ({
4
- Tracing: {
5
- addEventStart: vi.fn(),
6
- addEventEnd: vi.fn(),
7
- addEventError: vi.fn(),
8
- addEventAttribute: vi.fn()
3
+ vi.mock('@outputai/core/sdk_activity_integration', () => {
4
+ class HTTPRequestCount {
5
+ static TYPE = 'http:request:count';
6
+ type = HTTPRequestCount.TYPE;
7
+ url;
8
+ requestId;
9
+ constructor(url, requestId) {
10
+ this.url = url;
11
+ this.requestId = requestId;
12
+ }
9
13
  }
10
- }));
11
- import { Tracing } from '@outputai/core/sdk_activity_integration';
14
+ return {
15
+ Tracing: {
16
+ addEventStart: vi.fn(),
17
+ addEventEnd: vi.fn(),
18
+ addEventError: vi.fn(),
19
+ addEventAttribute: vi.fn(),
20
+ Attribute: {
21
+ HTTPRequestCount
22
+ }
23
+ },
24
+ emitEvent: vi.fn()
25
+ };
26
+ });
27
+ import { Tracing, emitEvent } from '@outputai/core/sdk_activity_integration';
12
28
  const tracing = vi.mocked(Tracing, true);
29
+ const emit = vi.mocked(emitEvent, true);
13
30
  /** Loads logger with optional verbose tracing env so `config.js` is evaluated fresh. */
14
31
  async function logLogger(verbose) {
15
32
  vi.resetModules();
@@ -26,14 +43,18 @@ beforeEach(() => {
26
43
  tracing.addEventEnd.mockClear();
27
44
  tracing.addEventError.mockClear();
28
45
  tracing.addEventAttribute.mockClear();
46
+ emit.mockClear();
29
47
  });
30
48
  describe('fetch/logger', () => {
31
49
  describe('logRequest', () => {
32
- const expectRequestIdAttribute = (requestId) => {
50
+ const expectRequestCountAttribute = (requestId, url) => {
33
51
  expect(tracing.addEventAttribute).toHaveBeenCalledWith({
34
52
  eventId: requestId,
35
- name: 'requestId',
36
- value: requestId
53
+ attribute: expect.objectContaining({
54
+ type: Tracing.Attribute.HTTPRequestCount.TYPE,
55
+ url,
56
+ requestId
57
+ })
37
58
  });
38
59
  };
39
60
  it('records minimal details when verbose is off', async () => {
@@ -49,14 +70,14 @@ describe('fetch/logger', () => {
49
70
  url: 'https://api.example.com/r'
50
71
  }
51
72
  });
52
- expectRequestIdAttribute('req-1');
73
+ expectRequestCountAttribute('req-1', 'https://api.example.com/r');
53
74
  });
54
75
  it('defaults method to GET', async () => {
55
76
  const { logRequest } = await logLogger(false);
56
77
  const request = new Request('https://x.test');
57
78
  await logRequest({ requestId: 'r2', request });
58
79
  expect(tracing.addEventStart.mock.calls[0][0].details.method).toBe('GET');
59
- expectRequestIdAttribute('r2');
80
+ expectRequestCountAttribute('r2', 'https://x.test/');
60
81
  });
61
82
  it('includes redacted headers and parsed body when verbose is on', async () => {
62
83
  const { logRequest } = await logLogger(true);
@@ -81,7 +102,7 @@ describe('fetch/logger', () => {
81
102
  body: { x: 1 }
82
103
  }
83
104
  });
84
- expectRequestIdAttribute('req-v');
105
+ expectRequestCountAttribute('req-v', 'https://api.example.com/p');
85
106
  });
86
107
  });
87
108
  describe('logError', () => {
@@ -97,7 +118,9 @@ describe('fetch/logger', () => {
97
118
  'content-type': 'application/json'
98
119
  }
99
120
  });
100
- await logError({ requestId: 'e1', response });
121
+ await logError({
122
+ requestId: 'e1', response, method: 'GET', url: 'https://upstream.test/x', durationMs: 1
123
+ });
101
124
  expect(tracing.addEventError).toHaveBeenCalledWith({
102
125
  id: 'e1',
103
126
  details: {
@@ -120,7 +143,9 @@ describe('fetch/logger', () => {
120
143
  statusText: 'Bad Gateway',
121
144
  headers: { 'content-type': 'text/plain' }
122
145
  });
123
- await logError({ requestId: 'e2', response });
146
+ await logError({
147
+ requestId: 'e2', response, method: 'GET', url: 'https://upstream.test/y', durationMs: 1
148
+ });
124
149
  expect(tracing.addEventError).toHaveBeenCalledWith({
125
150
  id: 'e2',
126
151
  details: {
@@ -140,7 +165,9 @@ describe('fetch/logger', () => {
140
165
  statusText: 'OK',
141
166
  headers: { 'content-type': 'application/json', Authorization: 'x' }
142
167
  });
143
- await logResponse({ requestId: 'lr1', response });
168
+ await logResponse({
169
+ requestId: 'lr1', response, method: 'GET', url: 'https://x.test/a', durationMs: 1
170
+ });
144
171
  expect(tracing.addEventEnd).toHaveBeenCalledWith({
145
172
  id: 'lr1',
146
173
  details: {
@@ -159,7 +186,9 @@ describe('fetch/logger', () => {
159
186
  'Set-Cookie': 'a=b'
160
187
  }
161
188
  });
162
- await logResponse({ requestId: 'lr-v', response });
189
+ await logResponse({
190
+ requestId: 'lr-v', response, method: 'POST', url: 'https://x.test/b', durationMs: 1
191
+ });
163
192
  expect(tracing.addEventEnd).toHaveBeenCalledWith({
164
193
  id: 'lr-v',
165
194
  details: {
@@ -178,7 +207,7 @@ describe('fetch/logger', () => {
178
207
  it('forwards serialized error details (including stack) to Tracing.addEventError', async () => {
179
208
  const { logFailure } = await logLogger(false);
180
209
  const err = new TypeError('network');
181
- logFailure({ requestId: 'f1', error: err });
210
+ logFailure({ requestId: 'f1', error: err, method: 'GET', url: 'https://example.test/x', durationMs: 12 });
182
211
  expect(tracing.addEventError).toHaveBeenCalledWith({
183
212
  id: 'f1',
184
213
  details: {
@@ -191,4 +220,63 @@ describe('fetch/logger', () => {
191
220
  });
192
221
  });
193
222
  });
223
+ describe('http:request event emission', () => {
224
+ it('emits http:request with outcome=success on logResponse', async () => {
225
+ const { logResponse } = await logLogger(false);
226
+ const response = new Response('', { status: 200 });
227
+ await logResponse({
228
+ requestId: 'r-ok',
229
+ response,
230
+ method: 'GET',
231
+ url: 'https://api.example.com/ok',
232
+ durationMs: 42
233
+ });
234
+ expect(emit).toHaveBeenCalledWith('http:request', {
235
+ requestId: 'r-ok',
236
+ method: 'GET',
237
+ url: 'https://api.example.com/ok',
238
+ status: 200,
239
+ durationMs: 42,
240
+ outcome: 'success'
241
+ });
242
+ });
243
+ it('emits http:request with outcome=http_error on logError', async () => {
244
+ const { logError } = await logLogger(false);
245
+ const response = new Response('boom', { status: 500 });
246
+ await logError({
247
+ requestId: 'r-err',
248
+ response,
249
+ method: 'POST',
250
+ url: 'https://api.example.com/err',
251
+ durationMs: 15
252
+ });
253
+ expect(emit).toHaveBeenCalledWith('http:request', {
254
+ requestId: 'r-err',
255
+ method: 'POST',
256
+ url: 'https://api.example.com/err',
257
+ status: 500,
258
+ durationMs: 15,
259
+ outcome: 'http_error'
260
+ });
261
+ });
262
+ it('emits http:request with outcome=network_error on logFailure (status undefined)', async () => {
263
+ const { logFailure } = await logLogger(false);
264
+ const err = new TypeError('network');
265
+ logFailure({
266
+ requestId: 'r-net',
267
+ error: err,
268
+ method: 'GET',
269
+ url: 'https://api.example.com/net',
270
+ durationMs: 9
271
+ });
272
+ expect(emit).toHaveBeenCalledWith('http:request', {
273
+ requestId: 'r-net',
274
+ method: 'GET',
275
+ url: 'https://api.example.com/net',
276
+ status: undefined,
277
+ durationMs: 9,
278
+ outcome: 'network_error'
279
+ });
280
+ });
281
+ });
194
282
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/http",
3
- "version": "0.4.1-dev.56c13a8.0",
3
+ "version": "0.4.1-dev.6555a2c.0",
4
4
  "description": "Framework abstraction to make HTTP calls with tracing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,7 +11,7 @@
11
11
  "dependencies": {
12
12
  "ky": "1.14.3",
13
13
  "undici": "8.1.0",
14
- "@outputai/core": "0.4.1-dev.56c13a8.0"
14
+ "@outputai/core": "0.4.1-dev.6555a2c.0"
15
15
  },
16
16
  "license": "Apache-2.0",
17
17
  "publishConfig": {