@outputai/http 0.10.1-next.f6a7c1a.0 → 0.11.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/fetch/events.d.ts +20 -0
- package/dist/fetch/events.js +4 -0
- package/dist/fetch/events.spec.js +64 -0
- package/dist/fetch/index.d.ts +10 -11
- package/dist/fetch/index.default_dispatcher.spec.d.ts +1 -0
- package/dist/fetch/index.default_dispatcher.spec.js +47 -0
- package/dist/fetch/index.js +32 -19
- package/dist/fetch/index.spec.js +114 -11
- package/dist/fetch/logger.d.ts +16 -66
- package/dist/fetch/logger.js +17 -70
- package/dist/fetch/logger.spec.js +77 -185
- package/dist/fetch/utils.d.ts +3 -45
- package/dist/fetch/utils.js +11 -35
- package/dist/fetch/utils.spec.js +46 -244
- package/dist/index.d.ts +5 -27
- package/dist/index.js +5 -29
- package/dist/ky/index.d.ts +24 -0
- package/dist/ky/index.js +25 -0
- package/dist/ky/index.spec.d.ts +1 -0
- package/dist/ky/index.spec.js +34 -0
- package/package.json +10 -7
- package/dist/index.spec.js +0 -13
- /package/dist/{index.spec.d.ts → fetch/events.spec.d.ts} +0 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { Request, Response } from 'undici';
|
|
3
3
|
vi.mock('@outputai/core/sdk/runtime', () => {
|
|
4
4
|
class HTTPRequestCount {
|
|
5
5
|
static TYPE = 'http:request:count';
|
|
6
6
|
type = HTTPRequestCount.TYPE;
|
|
7
|
-
url;
|
|
8
|
-
requestId;
|
|
9
7
|
constructor(url, requestId) {
|
|
10
8
|
this.url = url;
|
|
11
9
|
this.requestId = requestId;
|
|
@@ -20,179 +18,142 @@ vi.mock('@outputai/core/sdk/runtime', () => {
|
|
|
20
18
|
Attribute: {
|
|
21
19
|
HTTPRequestCount
|
|
22
20
|
}
|
|
23
|
-
},
|
|
24
|
-
Event: {
|
|
25
|
-
emit: vi.fn()
|
|
26
21
|
}
|
|
27
22
|
};
|
|
28
23
|
});
|
|
29
|
-
import { Tracing
|
|
24
|
+
import { Tracing } from '@outputai/core/sdk/runtime';
|
|
25
|
+
import { config } from '../config.js';
|
|
26
|
+
import { logError, logFailure, logRequest, logResponse } from './logger.js';
|
|
30
27
|
const tracing = vi.mocked(Tracing, true);
|
|
31
|
-
const event = vi.mocked(Event, true);
|
|
32
|
-
/** Loads logger with optional verbose tracing env so `config.js` is evaluated fresh. */
|
|
33
|
-
async function logLogger(verbose) {
|
|
34
|
-
vi.resetModules();
|
|
35
|
-
if (verbose) {
|
|
36
|
-
process.env.OUTPUT_TRACE_HTTP_VERBOSE = 'true';
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
delete process.env.OUTPUT_TRACE_HTTP_VERBOSE;
|
|
40
|
-
}
|
|
41
|
-
return import('./logger.js');
|
|
42
|
-
}
|
|
43
28
|
beforeEach(() => {
|
|
29
|
+
config.logVerbose = false;
|
|
44
30
|
tracing.addEventStart.mockClear();
|
|
45
31
|
tracing.addEventEnd.mockClear();
|
|
46
32
|
tracing.addEventError.mockClear();
|
|
47
33
|
tracing.addEventAttribute.mockClear();
|
|
48
|
-
event.emit.mockClear();
|
|
49
34
|
});
|
|
50
|
-
describe('
|
|
35
|
+
describe('instrumented_fetch/logger', () => {
|
|
51
36
|
describe('logRequest', () => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
attribute: expect.objectContaining({
|
|
56
|
-
type: Tracing.Attribute.HTTPRequestCount.TYPE,
|
|
57
|
-
url,
|
|
58
|
-
requestId
|
|
59
|
-
})
|
|
60
|
-
});
|
|
61
|
-
};
|
|
62
|
-
it('records minimal details when verbose is off', async () => {
|
|
63
|
-
const { logRequest } = await logLogger(false);
|
|
64
|
-
const request = new Request('https://api.example.com/r', { method: 'GET' });
|
|
65
|
-
await logRequest({ requestId: 'req-1', request });
|
|
37
|
+
it('records request details and the request count attribute', async () => {
|
|
38
|
+
const request = new Request('https://example.com/users', { method: 'GET' });
|
|
39
|
+
await logRequest({ requestId: 'request-1', request });
|
|
66
40
|
expect(tracing.addEventStart).toHaveBeenCalledWith({
|
|
67
|
-
id: '
|
|
41
|
+
id: 'request-1',
|
|
68
42
|
kind: 'http',
|
|
69
43
|
name: 'request',
|
|
70
44
|
details: {
|
|
71
45
|
method: 'GET',
|
|
72
|
-
url: 'https://
|
|
46
|
+
url: 'https://example.com/users'
|
|
73
47
|
}
|
|
74
48
|
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
49
|
+
expect(tracing.addEventAttribute).toHaveBeenCalledWith({
|
|
50
|
+
eventId: 'request-1',
|
|
51
|
+
attribute: expect.objectContaining({
|
|
52
|
+
type: 'http:request:count',
|
|
53
|
+
url: 'https://example.com/users',
|
|
54
|
+
requestId: 'request-1'
|
|
55
|
+
})
|
|
56
|
+
});
|
|
83
57
|
});
|
|
84
|
-
it('includes redacted headers and parsed body
|
|
85
|
-
|
|
86
|
-
const request = new Request('https://
|
|
58
|
+
it('includes redacted headers and parsed body in verbose mode', async () => {
|
|
59
|
+
config.logVerbose = true;
|
|
60
|
+
const request = new Request('https://example.com/users', {
|
|
87
61
|
method: 'POST',
|
|
88
62
|
headers: {
|
|
89
|
-
authorization: '
|
|
90
|
-
'
|
|
91
|
-
'
|
|
63
|
+
authorization: 'secret',
|
|
64
|
+
'content-type': 'application/json',
|
|
65
|
+
'x-visible': 'visible'
|
|
92
66
|
},
|
|
93
|
-
body: JSON.stringify({
|
|
67
|
+
body: JSON.stringify({ name: 'Ada' })
|
|
94
68
|
});
|
|
95
|
-
await logRequest({ requestId: '
|
|
69
|
+
await logRequest({ requestId: 'request-verbose', request });
|
|
96
70
|
expect(tracing.addEventStart).toHaveBeenCalledWith({
|
|
97
|
-
id: '
|
|
71
|
+
id: 'request-verbose',
|
|
98
72
|
kind: 'http',
|
|
99
73
|
name: 'request',
|
|
100
74
|
details: {
|
|
101
75
|
method: 'POST',
|
|
102
|
-
url: 'https://
|
|
103
|
-
headers: {
|
|
104
|
-
|
|
76
|
+
url: 'https://example.com/users',
|
|
77
|
+
headers: {
|
|
78
|
+
authorization: '[REDACTED]',
|
|
79
|
+
'content-type': 'application/json',
|
|
80
|
+
'x-visible': 'visible'
|
|
81
|
+
},
|
|
82
|
+
body: { name: 'Ada' }
|
|
105
83
|
}
|
|
106
84
|
});
|
|
107
|
-
expectRequestCountAttribute('req-v', 'https://api.example.com/p');
|
|
108
85
|
});
|
|
109
86
|
});
|
|
110
87
|
describe('logError', () => {
|
|
111
|
-
it('
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
statusText: 'Bad Gateway',
|
|
117
|
-
headers: {
|
|
118
|
-
'X-API-Key': 'k',
|
|
119
|
-
Accept: 'text/plain',
|
|
120
|
-
'content-type': 'application/json'
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
await logError({
|
|
124
|
-
requestId: 'e1', response, method: 'GET', url: 'https://upstream.test/x', durationMs: 1
|
|
88
|
+
it('omits headers and body outside verbose mode', async () => {
|
|
89
|
+
const response = new Response('unavailable', {
|
|
90
|
+
status: 503,
|
|
91
|
+
statusText: 'Service Unavailable',
|
|
92
|
+
headers: { 'x-api-key': 'secret' }
|
|
125
93
|
});
|
|
94
|
+
await logError({ requestId: 'request-error', response });
|
|
126
95
|
expect(tracing.addEventError).toHaveBeenCalledWith({
|
|
127
|
-
id: '
|
|
96
|
+
id: 'request-error',
|
|
128
97
|
details: {
|
|
129
|
-
status:
|
|
130
|
-
statusText: '
|
|
131
|
-
headers: {
|
|
132
|
-
'x-api-key': '[REDACTED]',
|
|
133
|
-
accept: 'text/plain',
|
|
134
|
-
'content-type': 'application/json'
|
|
135
|
-
},
|
|
136
|
-
body
|
|
98
|
+
status: 503,
|
|
99
|
+
statusText: 'Service Unavailable'
|
|
137
100
|
}
|
|
138
101
|
});
|
|
139
102
|
});
|
|
140
|
-
it('
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
requestId: 'e2', response, method: 'GET', url: 'https://upstream.test/y', durationMs: 1
|
|
103
|
+
it('includes redacted headers and parsed body in verbose mode', async () => {
|
|
104
|
+
config.logVerbose = true;
|
|
105
|
+
const response = new Response(JSON.stringify({ error: 'unavailable' }), {
|
|
106
|
+
status: 503,
|
|
107
|
+
statusText: 'Service Unavailable',
|
|
108
|
+
headers: {
|
|
109
|
+
'content-type': 'application/json',
|
|
110
|
+
'x-api-key': 'secret'
|
|
111
|
+
}
|
|
150
112
|
});
|
|
113
|
+
await logError({ requestId: 'request-error', response });
|
|
151
114
|
expect(tracing.addEventError).toHaveBeenCalledWith({
|
|
152
|
-
id: '
|
|
115
|
+
id: 'request-error',
|
|
153
116
|
details: {
|
|
154
|
-
status:
|
|
155
|
-
statusText: '
|
|
156
|
-
headers: {
|
|
157
|
-
|
|
117
|
+
status: 503,
|
|
118
|
+
statusText: 'Service Unavailable',
|
|
119
|
+
headers: {
|
|
120
|
+
'content-type': 'application/json',
|
|
121
|
+
'x-api-key': '[REDACTED]'
|
|
122
|
+
},
|
|
123
|
+
body: { error: 'unavailable' }
|
|
158
124
|
}
|
|
159
125
|
});
|
|
160
126
|
});
|
|
161
127
|
});
|
|
162
128
|
describe('logResponse', () => {
|
|
163
|
-
it('
|
|
164
|
-
const
|
|
165
|
-
const response = new Response(JSON.stringify({ a: 1 }), {
|
|
129
|
+
it('omits headers and body outside verbose mode', async () => {
|
|
130
|
+
const response = new Response('ok', {
|
|
166
131
|
status: 200,
|
|
167
132
|
statusText: 'OK',
|
|
168
|
-
headers: {
|
|
169
|
-
});
|
|
170
|
-
await logResponse({
|
|
171
|
-
requestId: 'lr1', response, method: 'GET', url: 'https://x.test/a', durationMs: 1
|
|
133
|
+
headers: { authorization: 'secret' }
|
|
172
134
|
});
|
|
135
|
+
await logResponse({ requestId: 'request-response', response });
|
|
173
136
|
expect(tracing.addEventEnd).toHaveBeenCalledWith({
|
|
174
|
-
id: '
|
|
137
|
+
id: 'request-response',
|
|
175
138
|
details: {
|
|
176
139
|
status: 200,
|
|
177
140
|
statusText: 'OK'
|
|
178
141
|
}
|
|
179
142
|
});
|
|
180
143
|
});
|
|
181
|
-
it('includes redacted headers and parsed body
|
|
182
|
-
|
|
144
|
+
it('includes redacted headers and parsed body in verbose mode', async () => {
|
|
145
|
+
config.logVerbose = true;
|
|
183
146
|
const response = new Response(JSON.stringify({ ok: true }), {
|
|
184
147
|
status: 201,
|
|
185
148
|
statusText: 'Created',
|
|
186
149
|
headers: {
|
|
187
150
|
'content-type': 'application/json',
|
|
188
|
-
'
|
|
151
|
+
'set-cookie': 'session=secret'
|
|
189
152
|
}
|
|
190
153
|
});
|
|
191
|
-
await logResponse({
|
|
192
|
-
requestId: 'lr-v', response, method: 'POST', url: 'https://x.test/b', durationMs: 1
|
|
193
|
-
});
|
|
154
|
+
await logResponse({ requestId: 'request-response-verbose', response });
|
|
194
155
|
expect(tracing.addEventEnd).toHaveBeenCalledWith({
|
|
195
|
-
id: '
|
|
156
|
+
id: 'request-response-verbose',
|
|
196
157
|
details: {
|
|
197
158
|
status: 201,
|
|
198
159
|
statusText: 'Created',
|
|
@@ -206,79 +167,10 @@ describe('fetch/logger', () => {
|
|
|
206
167
|
});
|
|
207
168
|
});
|
|
208
169
|
describe('logFailure', () => {
|
|
209
|
-
it('
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
expect(tracing.addEventError).toHaveBeenCalledWith({
|
|
214
|
-
id: 'f1',
|
|
215
|
-
details: {
|
|
216
|
-
name: 'TypeError',
|
|
217
|
-
message: 'network',
|
|
218
|
-
cause: undefined,
|
|
219
|
-
code: undefined,
|
|
220
|
-
stack: expect.stringMatching(/TypeError:\s*network[\s\S]+at\s+/)
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
describe('http:request event emission', () => {
|
|
226
|
-
it('emits http:request with outcome=success on logResponse', async () => {
|
|
227
|
-
const { logResponse } = await logLogger(false);
|
|
228
|
-
const response = new Response('', { status: 200 });
|
|
229
|
-
await logResponse({
|
|
230
|
-
requestId: 'r-ok',
|
|
231
|
-
response,
|
|
232
|
-
method: 'GET',
|
|
233
|
-
url: 'https://api.example.com/ok',
|
|
234
|
-
durationMs: 42
|
|
235
|
-
});
|
|
236
|
-
expect(event.emit).toHaveBeenCalledWith('http:request', {
|
|
237
|
-
requestId: 'r-ok',
|
|
238
|
-
method: 'GET',
|
|
239
|
-
url: 'https://api.example.com/ok',
|
|
240
|
-
status: 200,
|
|
241
|
-
durationMs: 42,
|
|
242
|
-
outcome: 'success'
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
it('emits http:request with outcome=error on logError', async () => {
|
|
246
|
-
const { logError } = await logLogger(false);
|
|
247
|
-
const response = new Response('boom', { status: 500 });
|
|
248
|
-
await logError({
|
|
249
|
-
requestId: 'r-err',
|
|
250
|
-
response,
|
|
251
|
-
method: 'POST',
|
|
252
|
-
url: 'https://api.example.com/err',
|
|
253
|
-
durationMs: 15
|
|
254
|
-
});
|
|
255
|
-
expect(event.emit).toHaveBeenCalledWith('http:request', {
|
|
256
|
-
requestId: 'r-err',
|
|
257
|
-
method: 'POST',
|
|
258
|
-
url: 'https://api.example.com/err',
|
|
259
|
-
status: 500,
|
|
260
|
-
durationMs: 15,
|
|
261
|
-
outcome: 'error'
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
it('emits http:request with outcome=failure on logFailure (status undefined)', async () => {
|
|
265
|
-
const { logFailure } = await logLogger(false);
|
|
266
|
-
const err = new TypeError('network');
|
|
267
|
-
logFailure({
|
|
268
|
-
requestId: 'r-net',
|
|
269
|
-
error: err,
|
|
270
|
-
method: 'GET',
|
|
271
|
-
url: 'https://api.example.com/net',
|
|
272
|
-
durationMs: 9
|
|
273
|
-
});
|
|
274
|
-
expect(event.emit).toHaveBeenCalledWith('http:request', {
|
|
275
|
-
requestId: 'r-net',
|
|
276
|
-
method: 'GET',
|
|
277
|
-
url: 'https://api.example.com/net',
|
|
278
|
-
status: undefined,
|
|
279
|
-
durationMs: 9,
|
|
280
|
-
outcome: 'failure'
|
|
281
|
-
});
|
|
170
|
+
it('records the error', () => {
|
|
171
|
+
const error = new TypeError('network unavailable');
|
|
172
|
+
logFailure({ requestId: 'request-failure', error });
|
|
173
|
+
expect(tracing.addEventError).toHaveBeenCalledWith({ id: 'request-failure', details: error });
|
|
282
174
|
});
|
|
283
175
|
});
|
|
284
176
|
});
|
package/dist/fetch/utils.d.ts
CHANGED
|
@@ -1,45 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* - name (from constructor.name)
|
|
5
|
-
* - message
|
|
6
|
-
* - stack
|
|
7
|
-
* - code (optional, but present on Node errors)
|
|
8
|
-
* - cause (error chain)
|
|
9
|
-
*
|
|
10
|
-
* @param error Error to serialize
|
|
11
|
-
* @param depth Current recursion depth for the error.cause chain
|
|
12
|
-
* @returns Object
|
|
13
|
-
*/
|
|
14
|
-
export declare const serializeError: (error: Error, depth?: number) => {
|
|
15
|
-
name: string;
|
|
16
|
-
message: string;
|
|
17
|
-
stack: string | undefined;
|
|
18
|
-
code: string | undefined;
|
|
19
|
-
cause: string | object | undefined;
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Redacts sensitive headers for safe logging
|
|
23
|
-
*
|
|
24
|
-
* @param headers
|
|
25
|
-
* @returns Plain object with sensitive headers redacted
|
|
26
|
-
*/
|
|
27
|
-
export declare const redactHeaders: (headers: Headers) => Record<string, unknown>;
|
|
28
|
-
/**
|
|
29
|
-
* Clones a Request or Response object and reads the body as text, then:
|
|
30
|
-
* - non-JSON content-type, or empty body: returns the text as-is
|
|
31
|
-
* - application/json with a non-empty body: returns JSON.parse result, or the raw text if parsing fails
|
|
32
|
-
*
|
|
33
|
-
* @param r
|
|
34
|
-
* @returns Parsed JSON value or raw body string
|
|
35
|
-
*/
|
|
36
|
-
export declare const parseBody: (r: Request | Response) => Promise<string | object>;
|
|
37
|
-
/**
|
|
38
|
-
* Tag a response in place with its request id so downstream code (e.g.
|
|
39
|
-
* `addRequestCost`) can correlate. Stores the id under a private symbol AND
|
|
40
|
-
* patches `clone()` so the tag propagates to clones — ky clones the response
|
|
41
|
-
* before invoking `afterResponse` hooks, and undici headers are immutable on
|
|
42
|
-
* received responses, so a symbol re-attached inside `clone()` is the only
|
|
43
|
-
* path that survives.
|
|
44
|
-
*/
|
|
45
|
-
export declare const addRequestIdToResponse: (response: Response, requestId: string) => void;
|
|
1
|
+
export function redactHeaders(headers: Headers): object;
|
|
2
|
+
export function parseBody(r: Request | Response): object | string;
|
|
3
|
+
export function addRequestIdToResponse(response: Response, requestId: string): void;
|
package/dist/fetch/utils.js
CHANGED
|
@@ -6,10 +6,10 @@ const HEADER_REDACTION_EXEMPT = new Set([
|
|
|
6
6
|
'x-csrf-token',
|
|
7
7
|
'public-key-pins'
|
|
8
8
|
]);
|
|
9
|
-
/** Matches red
|
|
10
|
-
const wordMatcher =
|
|
9
|
+
/** Matches red in "hot-red-pie", but not in "redact" */
|
|
10
|
+
const wordMatcher = term => new RegExp(`(?<![a-z\\d])${term}(?![a-z\\d])`, 'i');
|
|
11
11
|
/** Matches red in "acquired", but not in "redact" */
|
|
12
|
-
const wordEndMatcher =
|
|
12
|
+
const wordEndMatcher = term => new RegExp(`${term}(?![a-z\\d])`, 'i');
|
|
13
13
|
/**
|
|
14
14
|
* Sensitive header patterns for redaction (case-insensitive).
|
|
15
15
|
* Uses alphanumeric boundaries so e.g. `token` does not match inside `tokens`.
|
|
@@ -25,40 +25,13 @@ const SENSITIVE_HEADER_PATTERNS = [
|
|
|
25
25
|
// matches header that contain words ending with these sequences
|
|
26
26
|
wordEndMatcher('key')
|
|
27
27
|
];
|
|
28
|
-
/**
|
|
29
|
-
* Serialize a given error to a plain object keeping main properties:
|
|
30
|
-
* - name (from constructor.name)
|
|
31
|
-
* - message
|
|
32
|
-
* - stack
|
|
33
|
-
* - code (optional, but present on Node errors)
|
|
34
|
-
* - cause (error chain)
|
|
35
|
-
*
|
|
36
|
-
* @param error Error to serialize
|
|
37
|
-
* @param depth Current recursion depth for the error.cause chain
|
|
38
|
-
* @returns Object
|
|
39
|
-
*/
|
|
40
|
-
export const serializeError = (error, depth = 1) => ({
|
|
41
|
-
name: error.constructor.name,
|
|
42
|
-
message: error.message,
|
|
43
|
-
stack: error.stack,
|
|
44
|
-
code: error.code ?? undefined,
|
|
45
|
-
cause: (() => {
|
|
46
|
-
if (depth > 5) {
|
|
47
|
-
return '<Max recursion depth reached>';
|
|
48
|
-
}
|
|
49
|
-
if (error.cause instanceof Error) {
|
|
50
|
-
return serializeError(error.cause, depth + 1);
|
|
51
|
-
}
|
|
52
|
-
return undefined; // eslint-disable-line consistent-return
|
|
53
|
-
})()
|
|
54
|
-
});
|
|
55
28
|
/**
|
|
56
29
|
* Redacts sensitive headers for safe logging
|
|
57
30
|
*
|
|
58
|
-
* @param headers
|
|
59
|
-
* @returns Plain object with sensitive headers redacted
|
|
31
|
+
* @param {Headers} headers
|
|
32
|
+
* @returns {object} Plain object with sensitive headers redacted
|
|
60
33
|
*/
|
|
61
|
-
export const redactHeaders =
|
|
34
|
+
export const redactHeaders = headers => {
|
|
62
35
|
const result = {};
|
|
63
36
|
for (const [key, value] of headers.entries()) {
|
|
64
37
|
const lowerCaseKey = key.toLowerCase();
|
|
@@ -73,8 +46,8 @@ export const redactHeaders = (headers) => {
|
|
|
73
46
|
* - non-JSON content-type, or empty body: returns the text as-is
|
|
74
47
|
* - application/json with a non-empty body: returns JSON.parse result, or the raw text if parsing fails
|
|
75
48
|
*
|
|
76
|
-
* @param r
|
|
77
|
-
* @returns Parsed JSON value or raw body string
|
|
49
|
+
* @param {Request|Response} r
|
|
50
|
+
* @returns {object|string} Parsed JSON value or raw body string
|
|
78
51
|
*/
|
|
79
52
|
export const parseBody = async (r) => {
|
|
80
53
|
const clone = r.clone();
|
|
@@ -97,6 +70,9 @@ export const parseBody = async (r) => {
|
|
|
97
70
|
* before invoking `afterResponse` hooks, and undici headers are immutable on
|
|
98
71
|
* received responses, so a symbol re-attached inside `clone()` is the only
|
|
99
72
|
* path that survives.
|
|
73
|
+
*
|
|
74
|
+
* @param {Response} response
|
|
75
|
+
* @param {string} requestId
|
|
100
76
|
*/
|
|
101
77
|
export const addRequestIdToResponse = (response, requestId) => {
|
|
102
78
|
Object.defineProperty(response, requestIdSymbol, { value: requestId, enumerable: false, configurable: false, writable: false });
|