@outputai/http 0.10.1-next.69a0f11.0 → 0.10.1-next.af37678.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.
@@ -0,0 +1,20 @@
1
+ export function emitError({ requestId, method, url, status, durationMs }: {
2
+ requestId: any;
3
+ method: any;
4
+ url: any;
5
+ status: any;
6
+ durationMs: any;
7
+ }): boolean;
8
+ export function emitSuccess({ requestId, method, url, status, durationMs }: {
9
+ requestId: any;
10
+ method: any;
11
+ url: any;
12
+ status: any;
13
+ durationMs: any;
14
+ }): boolean;
15
+ export function emitFailure({ requestId, method, url, durationMs }: {
16
+ requestId: any;
17
+ method: any;
18
+ url: any;
19
+ durationMs: any;
20
+ }): boolean;
@@ -0,0 +1,4 @@
1
+ import { Event } from '@outputai/core/sdk/runtime';
2
+ export const emitError = ({ requestId, method, url, status, durationMs }) => Event.emit('http:request', { requestId, method, url, status, durationMs, outcome: 'error' });
3
+ export const emitSuccess = ({ requestId, method, url, status, durationMs }) => Event.emit('http:request', { requestId, method, url, status, durationMs, outcome: 'success' });
4
+ export const emitFailure = ({ requestId, method, url, durationMs }) => Event.emit('http:request', { requestId, method, url, status: undefined, durationMs, outcome: 'failure' });
@@ -0,0 +1,64 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ vi.mock('@outputai/core/sdk/runtime', () => ({
3
+ Event: {
4
+ emit: vi.fn()
5
+ }
6
+ }));
7
+ import { Event } from '@outputai/core/sdk/runtime';
8
+ import { emitError, emitFailure, emitSuccess } from './events.js';
9
+ const event = vi.mocked(Event, true);
10
+ beforeEach(() => {
11
+ event.emit.mockClear();
12
+ });
13
+ describe('instrumented_fetch/events', () => {
14
+ it('emits a successful request', async () => {
15
+ await emitSuccess({
16
+ requestId: 'request-success',
17
+ method: 'GET',
18
+ url: 'https://example.com/success',
19
+ status: 200,
20
+ durationMs: 12
21
+ });
22
+ expect(event.emit).toHaveBeenCalledWith('http:request', {
23
+ requestId: 'request-success',
24
+ method: 'GET',
25
+ url: 'https://example.com/success',
26
+ status: 200,
27
+ durationMs: 12,
28
+ outcome: 'success'
29
+ });
30
+ });
31
+ it('emits an HTTP error', async () => {
32
+ await emitError({
33
+ requestId: 'request-error',
34
+ method: 'POST',
35
+ url: 'https://example.com/error',
36
+ status: 503,
37
+ durationMs: 23
38
+ });
39
+ expect(event.emit).toHaveBeenCalledWith('http:request', {
40
+ requestId: 'request-error',
41
+ method: 'POST',
42
+ url: 'https://example.com/error',
43
+ status: 503,
44
+ durationMs: 23,
45
+ outcome: 'error'
46
+ });
47
+ });
48
+ it('emits a transport failure without a status', () => {
49
+ emitFailure({
50
+ requestId: 'request-failure',
51
+ method: 'DELETE',
52
+ url: 'https://example.com/failure',
53
+ durationMs: 34
54
+ });
55
+ expect(event.emit).toHaveBeenCalledWith('http:request', {
56
+ requestId: 'request-failure',
57
+ method: 'DELETE',
58
+ url: 'https://example.com/failure',
59
+ status: undefined,
60
+ durationMs: 34,
61
+ outcome: 'failure'
62
+ });
63
+ });
64
+ });
@@ -1,17 +1,15 @@
1
- import type { RequestInfo, RequestInit } from 'undici';
2
- /** Re-export undici library for convenience. */
3
- export * as undici from 'undici';
4
- /** Export fetch input types. Also available under in undici.* export. */
5
- export type {
6
- /** Undici's fetch first argument: Either a URL string, a URL object or a undici.Request object. */
7
- RequestInfo,
8
- /** Undici's fetch second argument: A plain object containing HTTP options. */
9
- RequestInit };
1
+ import * as undici from 'undici';
2
+ type NodeRequestInfo = string | URL | globalThis.Request;
3
+ type NodeRequestInit = globalThis.RequestInit & Pick<undici.RequestInit, 'dispatcher'>;
4
+ type OutputFetch = {
5
+ (input: NodeRequestInfo, init?: NodeRequestInit): Promise<Response>;
6
+ (input: undici.RequestInfo, init?: undici.RequestInit): Promise<Response>;
7
+ };
10
8
  /**
11
9
  * A fetch compliant function, that wraps undici's fetch.
12
10
  *
13
11
  * Behaves the same as any fetch function except:
14
- * - Sets a request header called `x-request--trace-id` with a random UUID;
12
+ * - Sets a request header called `x-request-trace-id` with a random UUID;
15
13
  * - Sends the request, response, error and/or failure to the Trace system;
16
14
  * - Emits a `http:request` event on every call (success, error, failure).
17
15
  *
@@ -20,4 +18,5 @@ RequestInit };
20
18
  * @param init - Request options
21
19
  * @returns The HTTP response
22
20
  */
23
- export declare const fetch: (input: RequestInfo | Request, init?: RequestInit) => Promise<Response>;
21
+ export declare const outputFetch: OutputFetch;
22
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ const undiciMock = vi.hoisted(() => ({
3
+ dispatcher: { dispatch: vi.fn() },
4
+ envHttpProxyAgent: vi.fn(),
5
+ fetch: vi.fn()
6
+ }));
7
+ vi.mock('undici', async (importOriginal) => {
8
+ const actual = await importOriginal();
9
+ class EnvHttpProxyAgent {
10
+ constructor(options) {
11
+ undiciMock.envHttpProxyAgent(options);
12
+ return undiciMock.dispatcher;
13
+ }
14
+ }
15
+ return {
16
+ ...actual,
17
+ EnvHttpProxyAgent,
18
+ fetch: undiciMock.fetch
19
+ };
20
+ });
21
+ vi.mock('./logger.js', () => ({
22
+ logRequest: vi.fn(),
23
+ logResponse: vi.fn(),
24
+ logError: vi.fn(),
25
+ logFailure: vi.fn()
26
+ }));
27
+ vi.mock('./events.js', () => ({
28
+ emitSuccess: vi.fn(),
29
+ emitError: vi.fn(),
30
+ emitFailure: vi.fn()
31
+ }));
32
+ vi.mock('./utils.js', () => ({
33
+ addRequestIdToResponse: vi.fn()
34
+ }));
35
+ import { Request, Response } from 'undici';
36
+ import { outputFetch } from './index.js';
37
+ describe('outputFetch default dispatcher', () => {
38
+ beforeEach(() => {
39
+ undiciMock.fetch.mockReset();
40
+ undiciMock.fetch.mockResolvedValue(new Response('ok'));
41
+ });
42
+ it('uses an EnvHttpProxyAgent when dispatcher is omitted', async () => {
43
+ await outputFetch('https://example.com');
44
+ expect(undiciMock.envHttpProxyAgent).toHaveBeenCalledWith({ allowH2: false });
45
+ expect(undiciMock.fetch).toHaveBeenCalledWith(expect.any(Request), { dispatcher: undiciMock.dispatcher });
46
+ });
47
+ });
@@ -1,21 +1,29 @@
1
- import * as undici from 'undici';
2
1
  import { randomUUID } from 'node:crypto';
3
2
  import { logRequest, logResponse, logError, logFailure } from './logger.js';
3
+ import { emitSuccess, emitError, emitFailure } from './events.js';
4
4
  import { addRequestIdToResponse } from './utils.js';
5
+ import * as undici from 'undici';
5
6
  /* Ignore HTTP/2. Check: https://github.com/growthxai/output/issues/299 */
6
7
  const customDispatcher = new undici.EnvHttpProxyAgent({ allowH2: false });
7
- /*
8
- * Unifies undici and nodes realms
9
- * https://github.com/nodejs/undici#keep-fetch-and-formdata-together
10
- */
11
- undici.install();
12
- /** Re-export undici library for convenience. */
13
- export * as undici from 'undici';
8
+ const createUndiciRequest = (input, init) => {
9
+ const isNodeRequest = input instanceof globalThis.Request;
10
+ const hasNodeFormData = init?.body instanceof globalThis.FormData;
11
+ const isUndiciRequest = input instanceof undici.Request;
12
+ const hasUndiciFormData = init?.body instanceof undici.FormData;
13
+ if ((isNodeRequest && hasUndiciFormData) || (isUndiciRequest && hasNodeFormData)) {
14
+ throw new TypeError('Cannot mix Node and Undici Request/FormData realms.');
15
+ }
16
+ if (!isNodeRequest && !hasNodeFormData) {
17
+ return new undici.Request(input, init);
18
+ }
19
+ const request = new globalThis.Request(input, init);
20
+ return new undici.Request(request.url, request);
21
+ };
14
22
  /**
15
23
  * A fetch compliant function, that wraps undici's fetch.
16
24
  *
17
25
  * Behaves the same as any fetch function except:
18
- * - Sets a request header called `x-request--trace-id` with a random UUID;
26
+ * - Sets a request header called `x-request-trace-id` with a random UUID;
19
27
  * - Sends the request, response, error and/or failure to the Trace system;
20
28
  * - Emits a `http:request` event on every call (success, error, failure).
21
29
  *
@@ -24,9 +32,10 @@ export * as undici from 'undici';
24
32
  * @param init - Request options
25
33
  * @returns The HTTP response
26
34
  */
27
- export const fetch = async (input, init) => {
35
+ export const outputFetch = async (input, init) => {
36
+ const { dispatcher: inputDispatcher, ...requestInit } = (init ?? {});
28
37
  // Creates a Request object with the many shapes RequestInfo can have
29
- const base = new undici.Request(input, init);
38
+ const base = createUndiciRequest(input, requestInit);
30
39
  // Creates a headers object with the many shapes Request.Headers can have (object, array, Headers)
31
40
  const headers = new undici.Headers(base.headers);
32
41
  const requestId = randomUUID();
@@ -34,25 +43,29 @@ export const fetch = async (input, init) => {
34
43
  const request = new undici.Request(base, { headers });
35
44
  const method = request.method;
36
45
  const url = request.url;
37
- const startedAt = performance.now();
46
+ const startedAt = Date.now();
38
47
  await logRequest({ requestId, request });
39
48
  // this allows for users not only to override the dispatcher but also to define it as undefined and remove it altogether.
40
- const dispatcher = Object.hasOwn(init ?? {}, 'dispatcher') ? init?.dispatcher : customDispatcher;
49
+ const dispatcher = Object.hasOwn(init ?? {}, 'dispatcher') ? inputDispatcher : customDispatcher;
41
50
  try {
42
51
  const response = await undici.fetch(request, dispatcher ? { dispatcher } : undefined);
43
- const durationMs = performance.now() - startedAt;
52
+ const durationMs = Date.now() - startedAt;
53
+ const { status } = response;
44
54
  // This enriches the response of the request id, so it is identifiable later.
45
55
  addRequestIdToResponse(response, requestId);
46
- if (response.status > 399) {
47
- await logError({ requestId, response, method, url, durationMs });
56
+ if (status > 399) {
57
+ await logError({ requestId, response });
58
+ emitError({ requestId, method, url, status, durationMs });
48
59
  return response;
49
60
  }
50
- await logResponse({ requestId, response, method, url, durationMs });
61
+ await logResponse({ requestId, response });
62
+ emitSuccess({ requestId, method, url, status, durationMs });
51
63
  return response;
52
64
  }
53
65
  catch (error) {
54
- const durationMs = performance.now() - startedAt;
55
- logFailure({ requestId, error: error, method, url, durationMs });
66
+ const durationMs = Date.now() - startedAt;
67
+ logFailure({ requestId, error });
68
+ emitFailure({ requestId, method, url, durationMs });
56
69
  throw error;
57
70
  }
58
71
  };
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { getGlobalDispatcher, Headers, MockAgent, Request, setGlobalDispatcher } from 'undici';
2
+ import { FormData as UndiciFormData, getGlobalDispatcher, Headers, MockAgent, Request, setGlobalDispatcher } from 'undici';
3
3
  const FIXED_REQUEST_ID = 'aaaaaaaa-bbbb-4ccc-dddd-eeeeeeeeeeee';
4
4
  const randomUUIDMock = vi.hoisted(() => vi.fn(() => FIXED_REQUEST_ID));
5
5
  const loggerMock = vi.hoisted(() => ({
@@ -11,6 +11,11 @@ const loggerMock = vi.hoisted(() => ({
11
11
  const utilsMock = vi.hoisted(() => ({
12
12
  addRequestIdToResponse: vi.fn()
13
13
  }));
14
+ const eventsMock = vi.hoisted(() => ({
15
+ emitSuccess: vi.fn(),
16
+ emitError: vi.fn(),
17
+ emitFailure: vi.fn()
18
+ }));
14
19
  vi.mock('node:crypto', () => ({
15
20
  randomUUID: () => randomUUIDMock()
16
21
  }));
@@ -23,14 +28,19 @@ vi.mock('./logger.js', () => ({
23
28
  vi.mock('./utils.js', () => ({
24
29
  addRequestIdToResponse: utilsMock.addRequestIdToResponse
25
30
  }));
26
- import { fetch } from './index.js';
31
+ vi.mock('./events.js', () => ({
32
+ emitSuccess: eventsMock.emitSuccess,
33
+ emitError: eventsMock.emitError,
34
+ emitFailure: eventsMock.emitFailure
35
+ }));
36
+ import { outputFetch } from './index.js';
27
37
  const MOCK_ORIGIN = 'https://fetch-index.undici.test';
28
- describe('fetch/index', () => {
38
+ describe('instrumented_fetch/index', () => {
29
39
  const undiciCtx = {
30
40
  mockAgent: undefined,
31
41
  previousDispatcher: undefined
32
42
  };
33
- const fetchWithMockDispatcher = (input, init) => fetch(input, { ...init, dispatcher: undiciCtx.mockAgent });
43
+ const fetchWithMockDispatcher = (input, init) => outputFetch(input, { ...init, dispatcher: undiciCtx.mockAgent });
34
44
  beforeEach(() => {
35
45
  undiciCtx.mockAgent = new MockAgent();
36
46
  undiciCtx.mockAgent.disableNetConnect();
@@ -41,6 +51,9 @@ describe('fetch/index', () => {
41
51
  loggerMock.logError.mockClear();
42
52
  loggerMock.logFailure.mockClear();
43
53
  utilsMock.addRequestIdToResponse.mockClear();
54
+ eventsMock.emitSuccess.mockClear();
55
+ eventsMock.emitError.mockClear();
56
+ eventsMock.emitFailure.mockClear();
44
57
  randomUUIDMock.mockClear();
45
58
  randomUUIDMock.mockImplementation(() => FIXED_REQUEST_ID);
46
59
  });
@@ -66,14 +79,17 @@ describe('fetch/index', () => {
66
79
  const responseCall = loggerMock.logResponse.mock.calls[0][0];
67
80
  expect(responseCall.requestId).toBe(FIXED_REQUEST_ID);
68
81
  expect(responseCall.response).toBe(response);
69
- expect(responseCall.method).toBe('GET');
70
- expect(responseCall.url).toBe(`${MOCK_ORIGIN}/ok`);
71
- expect(typeof responseCall.durationMs).toBe('number');
72
- expect(responseCall.durationMs).toBeGreaterThanOrEqual(0);
73
82
  expect(utilsMock.addRequestIdToResponse).toHaveBeenCalledTimes(1);
74
83
  expect(utilsMock.addRequestIdToResponse).toHaveBeenCalledWith(response, FIXED_REQUEST_ID);
75
84
  expect(loggerMock.logError).not.toHaveBeenCalled();
76
85
  expect(loggerMock.logFailure).not.toHaveBeenCalled();
86
+ expect(eventsMock.emitSuccess).toHaveBeenCalledWith({
87
+ requestId: FIXED_REQUEST_ID,
88
+ method: 'GET',
89
+ url: `${MOCK_ORIGIN}/ok`,
90
+ status: 200,
91
+ durationMs: expect.any(Number)
92
+ });
77
93
  });
78
94
  it('uses logError for status > 399 without logging response end', async () => {
79
95
  undiciCtx.mockAgent.get(MOCK_ORIGIN).intercept({ path: '/missing', method: 'GET' }).reply(404, 'nope', { headers: { 'content-type': 'text/plain' } });
@@ -86,6 +102,13 @@ describe('fetch/index', () => {
86
102
  expect(loggerMock.logError.mock.calls[0][0].response).toBe(response);
87
103
  expect(utilsMock.addRequestIdToResponse).toHaveBeenCalledTimes(1);
88
104
  expect(utilsMock.addRequestIdToResponse).toHaveBeenCalledWith(response, FIXED_REQUEST_ID);
105
+ expect(eventsMock.emitError).toHaveBeenCalledWith({
106
+ requestId: FIXED_REQUEST_ID,
107
+ method: 'GET',
108
+ url: `${MOCK_ORIGIN}/missing`,
109
+ status: 404,
110
+ durationMs: expect.any(Number)
111
+ });
89
112
  });
90
113
  it('treats status 399 as success (logs response end, not HTTP error)', async () => {
91
114
  undiciCtx.mockAgent.get(MOCK_ORIGIN).intercept({ path: '/edge', method: 'GET' }).reply(399, '', { headers: { 'content-type': 'text/plain' } });
@@ -142,6 +165,12 @@ describe('fetch/index', () => {
142
165
  expect(failure.message).toBe('fetch failed');
143
166
  expect(failure.cause).toBeInstanceOf(Error);
144
167
  expect(failure.cause.message).toBe('simulated network failure');
168
+ expect(eventsMock.emitFailure).toHaveBeenCalledWith({
169
+ requestId: FIXED_REQUEST_ID,
170
+ method: 'GET',
171
+ url: `${MOCK_ORIGIN}/boom`,
172
+ durationMs: expect.any(Number)
173
+ });
145
174
  });
146
175
  it('fails when no mock matches (disabled net)', async () => {
147
176
  await expect(fetchWithMockDispatcher(`${MOCK_ORIGIN}/unmocked`)).rejects.toThrow();
@@ -168,7 +197,7 @@ describe('fetch/index', () => {
168
197
  expect(loggerMock.logRequest.mock.calls[0][0].request.method).toBe('GET');
169
198
  });
170
199
  });
171
- describe('fetch RequestInfo / RequestInit shapes', () => {
200
+ describe('outputFetch RequestInfo / RequestInit shapes', () => {
172
201
  it('accepts a URL object as the first argument', async () => {
173
202
  undiciCtx.mockAgent.get(MOCK_ORIGIN).intercept({ path: '/from-url', method: 'GET' }).reply(200, 'url-ok', { headers: { 'content-type': 'text/plain' } });
174
203
  const href = new URL('/from-url', `${MOCK_ORIGIN}/`);
@@ -187,6 +216,80 @@ describe('fetch/index', () => {
187
216
  expect(request.method).toBe('GET');
188
217
  expect(request.url).toBe(`${MOCK_ORIGIN}/req-only`);
189
218
  });
219
+ it('accepts Node built-in Request and Headers instances', async () => {
220
+ undiciCtx.mockAgent.get(MOCK_ORIGIN).intercept({
221
+ path: '/node-request',
222
+ method: 'POST',
223
+ headers: {
224
+ 'content-type': 'application/json',
225
+ 'x-node-header': 'node-value',
226
+ 'x-request-trace-id': FIXED_REQUEST_ID
227
+ }
228
+ }).reply(201, JSON.stringify({ created: true }), {
229
+ headers: { 'content-type': 'application/json' }
230
+ });
231
+ const headers = new globalThis.Headers({
232
+ 'content-type': 'application/json',
233
+ 'x-node-header': 'node-value'
234
+ });
235
+ const input = new globalThis.Request(`${MOCK_ORIGIN}/node-request`, {
236
+ method: 'POST',
237
+ headers,
238
+ body: JSON.stringify({ name: 'node' })
239
+ });
240
+ const response = await fetchWithMockDispatcher(input);
241
+ const nodeResponse = response;
242
+ expect(input).not.toBeInstanceOf(Request);
243
+ expect(nodeResponse.status).toBe(201);
244
+ await expect(nodeResponse.json()).resolves.toEqual({ created: true });
245
+ expect(loggerMock.logRequest.mock.calls[0][0].request).toBeInstanceOf(Request);
246
+ expect(loggerMock.logRequest.mock.calls[0][0].request.method).toBe('POST');
247
+ });
248
+ it('encodes Node built-in FormData before entering the Undici realm', async () => {
249
+ undiciCtx.mockAgent.get(MOCK_ORIGIN).intercept({
250
+ path: '/node-form-data',
251
+ method: 'POST'
252
+ }).reply(200, 'ok');
253
+ const body = new globalThis.FormData();
254
+ body.set('name', 'node');
255
+ loggerMock.logRequest.mockImplementationOnce(async ({ request }) => {
256
+ expect(request).toBeInstanceOf(Request);
257
+ expect(request.headers.get('content-type')).toMatch(/^multipart\/form-data; boundary=/);
258
+ await expect(request.clone().text()).resolves.toContain('name="name"\r\n\r\nnode');
259
+ });
260
+ const response = await fetchWithMockDispatcher(`${MOCK_ORIGIN}/node-form-data`, {
261
+ method: 'POST',
262
+ body
263
+ });
264
+ expect(response.status).toBe(200);
265
+ });
266
+ it('rejects mixed Request and FormData realms', async () => {
267
+ const input = new globalThis.Request(`${MOCK_ORIGIN}/mixed-realms`, { method: 'POST', body: 'node' });
268
+ const body = new UndiciFormData();
269
+ body.set('name', 'undici');
270
+ await expect(fetchWithMockDispatcher(input, {
271
+ method: 'POST',
272
+ body: body
273
+ })).rejects.toThrow('Cannot mix Node and Undici Request/FormData realms.');
274
+ });
275
+ it('accepts Node built-in RequestInit with a string input', async () => {
276
+ undiciCtx.mockAgent.get(MOCK_ORIGIN).intercept({
277
+ path: '/node-init',
278
+ method: 'PATCH',
279
+ headers: {
280
+ 'x-node-init': 'yes',
281
+ 'x-request-trace-id': FIXED_REQUEST_ID
282
+ }
283
+ }).reply(204);
284
+ const init = {
285
+ method: 'PATCH',
286
+ headers: new globalThis.Headers({ 'x-node-init': 'yes' })
287
+ };
288
+ const response = await fetchWithMockDispatcher(`${MOCK_ORIGIN}/node-init`, init);
289
+ expect(response.status).toBe(204);
290
+ expect(loggerMock.logRequest.mock.calls[0][0].request.method).toBe('PATCH');
291
+ expect(loggerMock.logRequest.mock.calls[0][0].request.headers.get('x-node-init')).toBe('yes');
292
+ });
190
293
  it('accepts Request plus init that overrides method and body', async () => {
191
294
  undiciCtx.mockAgent.get(MOCK_ORIGIN).intercept({ path: '/req-plus-init', method: 'POST' }).reply(201, JSON.stringify({ saved: true }), { headers: { 'content-type': 'application/json' } });
192
295
  const input = new Request(`${MOCK_ORIGIN}/req-plus-init`, { method: 'GET' });
@@ -232,7 +335,7 @@ describe('fetch/index', () => {
232
335
  'x-custom': 'custom-value'
233
336
  }
234
337
  }).reply(200, 'ok');
235
- const response = await fetch(`${MOCK_ORIGIN}/custom-dispatcher`, {
338
+ const response = await outputFetch(`${MOCK_ORIGIN}/custom-dispatcher`, {
236
339
  method: 'POST',
237
340
  headers: {
238
341
  'Content-Type': 'application/json',
@@ -253,7 +356,7 @@ describe('fetch/index', () => {
253
356
  method: 'GET',
254
357
  headers: { 'x-request-trace-id': FIXED_REQUEST_ID }
255
358
  }).reply(200, 'ok');
256
- const response = await fetch(`${MOCK_ORIGIN}/undefined-dispatcher`, {
359
+ const response = await outputFetch(`${MOCK_ORIGIN}/undefined-dispatcher`, {
257
360
  dispatcher: undefined
258
361
  });
259
362
  expect(response.status).toBe(200);
@@ -1,66 +1,16 @@
1
- import type { Request, Response } from 'undici';
2
- /**
3
- * Sends the trace start event for an http request
4
- *
5
- * @param options
6
- * @param options.requestId - id of the request
7
- * @param options.request - The HTTP Request object
8
- */
9
- export declare const logRequest: ({ requestId, request }: {
10
- requestId: string;
11
- request: Request;
12
- }) => Promise<void>;
13
- /**
14
- * Sends the trace error event for an http response with error status
15
- * and emits a `http:request` event with `outcome: 'error'`.
16
- *
17
- * @param options
18
- * @param options.requestId - id of the request
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
23
- */
24
- export declare const logError: ({ requestId, response, method, url, durationMs }: {
25
- requestId: string;
26
- response: Response;
27
- method: string;
28
- url: string;
29
- durationMs: number;
30
- }) => Promise<void>;
31
- /**
32
- * Sends the trace end event for an http response
33
- * and emits a `http:request` event with `outcome: 'success'`.
34
- *
35
- * @param {object} options
36
- * @param options.requestId - id of the request
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
41
- */
42
- export declare const logResponse: ({ requestId, response, method, url, durationMs }: {
43
- requestId: string;
44
- response: Response;
45
- method: string;
46
- url: string;
47
- durationMs: number;
48
- }) => Promise<void>;
49
- /**
50
- * Creates the trace error event for a network/connection failure
51
- * and emits a `http:request` event with `outcome: 'failure'`.
52
- *
53
- * @param options
54
- * @param options.requestId - id of the request
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
59
- */
60
- export declare const logFailure: ({ requestId, error, method, url, durationMs }: {
61
- requestId: string;
62
- error: Error;
63
- method: string;
64
- url: string;
65
- durationMs: number;
66
- }) => void;
1
+ export function logRequest({ requestId, request }: {
2
+ requestId: any;
3
+ request: any;
4
+ }): Promise<void>;
5
+ export function logError({ requestId, response }: {
6
+ requestId: any;
7
+ response: any;
8
+ }): Promise<void>;
9
+ export function logResponse({ requestId, response }: {
10
+ requestId: any;
11
+ response: any;
12
+ }): Promise<void>;
13
+ export function logFailure({ requestId, error }: {
14
+ requestId: any;
15
+ error: any;
16
+ }): void;
@@ -1,10 +1,6 @@
1
- import { Tracing, Event } from '@outputai/core/sdk/runtime';
1
+ import { Tracing } from '@outputai/core/sdk/runtime';
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
- Event.emit('http:request', payload);
7
- };
8
4
  /**
9
5
  * Sends the trace start event for an http request
10
6
  *
@@ -22,67 +18,18 @@ export const logRequest = async ({ requestId, request }) => {
22
18
  });
23
19
  Tracing.addEventAttribute({ eventId: requestId, attribute: new Tracing.Attribute.HTTPRequestCount(request.url, requestId) });
24
20
  };
25
- /**
26
- * Sends the trace error event for an http response with error status
27
- * and emits a `http:request` event with `outcome: 'error'`.
28
- *
29
- * @param options
30
- * @param options.requestId - id of the request
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
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: 'error'
47
- });
48
- };
49
- /**
50
- * Sends the trace end event for an http response
51
- * and emits a `http:request` event with `outcome: 'success'`.
52
- *
53
- * @param {object} options
54
- * @param options.requestId - id of the request
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
59
- */
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
- };
72
- /**
73
- * Creates the trace error event for a network/connection failure
74
- * and emits a `http:request` event with `outcome: 'failure'`.
75
- *
76
- * @param options
77
- * @param options.requestId - id of the request
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
82
- */
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: 'failure'
87
- });
88
- };
21
+ export const logError = async ({ requestId, response }) => Tracing.addEventError({
22
+ id: requestId, details: {
23
+ status: response.status,
24
+ statusText: response.statusText,
25
+ ...(config.logVerbose && { headers: redactHeaders(response.headers), body: await parseBody(response) })
26
+ }
27
+ });
28
+ export const logResponse = async ({ requestId, response }) => Tracing.addEventEnd({
29
+ id: requestId, details: {
30
+ status: response.status,
31
+ statusText: response.statusText,
32
+ ...(config.logVerbose && { headers: redactHeaders(response.headers), body: await parseBody(response) })
33
+ }
34
+ });
35
+ export const logFailure = ({ requestId, error }) => Tracing.addEventError({ id: requestId, details: serializeError(error) });