@ai-sdk/provider-utils 4.0.5 → 4.0.6
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/CHANGELOG.md +8 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +4 -2
- package/src/__snapshots__/schema.test.ts.snap +346 -0
- package/src/add-additional-properties-to-json-schema.test.ts +289 -0
- package/src/add-additional-properties-to-json-schema.ts +53 -0
- package/src/combine-headers.ts +11 -0
- package/src/convert-async-iterator-to-readable-stream.test.ts +78 -0
- package/src/convert-async-iterator-to-readable-stream.ts +47 -0
- package/src/convert-image-model-file-to-data-uri.test.ts +85 -0
- package/src/convert-image-model-file-to-data-uri.ts +19 -0
- package/src/convert-to-form-data.test.ts +167 -0
- package/src/convert-to-form-data.ts +61 -0
- package/src/create-tool-name-mapping.test.ts +163 -0
- package/src/create-tool-name-mapping.ts +66 -0
- package/src/delay.test.ts +212 -0
- package/src/delay.ts +47 -0
- package/src/delayed-promise.test.ts +132 -0
- package/src/delayed-promise.ts +61 -0
- package/src/download-blob.test.ts +145 -0
- package/src/download-blob.ts +31 -0
- package/src/download-error.ts +39 -0
- package/src/extract-response-headers.ts +9 -0
- package/src/fetch-function.ts +4 -0
- package/src/generate-id.test.ts +31 -0
- package/src/generate-id.ts +57 -0
- package/src/get-error-message.ts +15 -0
- package/src/get-from-api.test.ts +199 -0
- package/src/get-from-api.ts +97 -0
- package/src/get-runtime-environment-user-agent.test.ts +47 -0
- package/src/get-runtime-environment-user-agent.ts +24 -0
- package/src/handle-fetch-error.ts +39 -0
- package/src/index.ts +67 -0
- package/src/inject-json-instruction.test.ts +404 -0
- package/src/inject-json-instruction.ts +63 -0
- package/src/is-abort-error.ts +8 -0
- package/src/is-async-iterable.ts +3 -0
- package/src/is-non-nullable.ts +12 -0
- package/src/is-url-supported.test.ts +282 -0
- package/src/is-url-supported.ts +40 -0
- package/src/load-api-key.ts +45 -0
- package/src/load-optional-setting.ts +30 -0
- package/src/load-setting.ts +62 -0
- package/src/maybe-promise-like.ts +3 -0
- package/src/media-type-to-extension.test.ts +26 -0
- package/src/media-type-to-extension.ts +22 -0
- package/src/normalize-headers.test.ts +64 -0
- package/src/normalize-headers.ts +38 -0
- package/src/parse-json-event-stream.ts +33 -0
- package/src/parse-json.test.ts +191 -0
- package/src/parse-json.ts +122 -0
- package/src/parse-provider-options.ts +32 -0
- package/src/post-to-api.ts +166 -0
- package/src/provider-tool-factory.ts +125 -0
- package/src/remove-undefined-entries.test.ts +57 -0
- package/src/remove-undefined-entries.ts +12 -0
- package/src/resolve.test.ts +125 -0
- package/src/resolve.ts +17 -0
- package/src/response-handler.test.ts +89 -0
- package/src/response-handler.ts +187 -0
- package/src/schema.test-d.ts +11 -0
- package/src/schema.test.ts +502 -0
- package/src/schema.ts +267 -0
- package/src/secure-json-parse.test.ts +59 -0
- package/src/secure-json-parse.ts +92 -0
- package/src/test/convert-array-to-async-iterable.ts +9 -0
- package/src/test/convert-array-to-readable-stream.ts +15 -0
- package/src/test/convert-async-iterable-to-array.ts +9 -0
- package/src/test/convert-readable-stream-to-array.ts +14 -0
- package/src/test/convert-response-stream-to-array.ts +9 -0
- package/src/test/index.ts +7 -0
- package/src/test/is-node-version.ts +4 -0
- package/src/test/mock-id.ts +8 -0
- package/src/to-json-schema/zod3-to-json-schema/LICENSE +16 -0
- package/src/to-json-schema/zod3-to-json-schema/README.md +24 -0
- package/src/to-json-schema/zod3-to-json-schema/get-relative-path.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/index.ts +1 -0
- package/src/to-json-schema/zod3-to-json-schema/options.ts +98 -0
- package/src/to-json-schema/zod3-to-json-schema/parse-def.test.ts +224 -0
- package/src/to-json-schema/zod3-to-json-schema/parse-def.ts +109 -0
- package/src/to-json-schema/zod3-to-json-schema/parse-types.ts +57 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/any.ts +5 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/array.test.ts +98 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/array.ts +38 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.test.ts +51 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.ts +44 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/boolean.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/branded.test.ts +16 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/branded.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/catch.test.ts +15 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/catch.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/date.test.ts +97 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/date.ts +64 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/default.test.ts +54 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/default.ts +14 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/effects.test.ts +41 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/effects.ts +14 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/enum.ts +13 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.test.ts +92 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.ts +52 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/literal.ts +29 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/map.test.ts +48 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/map.ts +47 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.test.ts +102 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.ts +31 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/never.ts +9 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/null.ts +9 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.test.ts +67 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.ts +42 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/number.test.ts +65 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/number.ts +44 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/object.test.ts +149 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/object.ts +88 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/optional.test.ts +147 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/optional.ts +23 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/pipe.test.ts +35 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/pipeline.ts +29 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/promise.test.ts +15 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/promise.ts +11 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.test.ts +20 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/record.test.ts +108 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/record.ts +71 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/set.test.ts +20 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/set.ts +35 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/string.test.ts +438 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/string.ts +426 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.test.ts +33 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.ts +61 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/undefined.ts +11 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/union.test.ts +226 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/union.ts +144 -0
- package/src/to-json-schema/zod3-to-json-schema/parsers/unknown.ts +7 -0
- package/src/to-json-schema/zod3-to-json-schema/refs.test.ts +919 -0
- package/src/to-json-schema/zod3-to-json-schema/refs.ts +39 -0
- package/src/to-json-schema/zod3-to-json-schema/select-parser.ts +115 -0
- package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.test.ts +862 -0
- package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.ts +93 -0
- package/src/types/assistant-model-message.ts +39 -0
- package/src/types/content-part.ts +379 -0
- package/src/types/data-content.ts +4 -0
- package/src/types/execute-tool.ts +27 -0
- package/src/types/index.ts +40 -0
- package/src/types/model-message.ts +14 -0
- package/src/types/provider-options.ts +9 -0
- package/src/types/system-model-message.ts +20 -0
- package/src/types/tool-approval-request.ts +16 -0
- package/src/types/tool-approval-response.ts +27 -0
- package/src/types/tool-call.ts +31 -0
- package/src/types/tool-model-message.ts +23 -0
- package/src/types/tool-result.ts +35 -0
- package/src/types/tool.test-d.ts +193 -0
- package/src/types/tool.ts +324 -0
- package/src/types/user-model-message.ts +22 -0
- package/src/uint8-utils.ts +26 -0
- package/src/validate-types.test.ts +105 -0
- package/src/validate-types.ts +81 -0
- package/src/version.ts +6 -0
- package/src/with-user-agent-suffix.test.ts +84 -0
- package/src/with-user-agent-suffix.ts +27 -0
- package/src/without-trailing-slash.ts +3 -0
- package/LICENSE +0 -13
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { resolve, Resolvable } from './resolve';
|
|
3
|
+
|
|
4
|
+
describe('resolve', () => {
|
|
5
|
+
// Test raw values
|
|
6
|
+
it('should resolve raw values', async () => {
|
|
7
|
+
const value: Resolvable<number> = 42;
|
|
8
|
+
expect(await resolve(value)).toBe(42);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should resolve raw objects', async () => {
|
|
12
|
+
const value: Resolvable<object> = { foo: 'bar' };
|
|
13
|
+
expect(await resolve(value)).toEqual({ foo: 'bar' });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Test promises
|
|
17
|
+
it('should resolve promises', async () => {
|
|
18
|
+
const value: Resolvable<string> = Promise.resolve('hello');
|
|
19
|
+
expect(await resolve(value)).toBe('hello');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should resolve rejected promises', async () => {
|
|
23
|
+
const value: Resolvable<string> = Promise.reject(new Error('test error'));
|
|
24
|
+
await expect(resolve(value)).rejects.toThrow('test error');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Test synchronous functions
|
|
28
|
+
it('should resolve synchronous functions', async () => {
|
|
29
|
+
const value: Resolvable<number> = () => 42;
|
|
30
|
+
expect(await resolve(value)).toBe(42);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should resolve synchronous functions returning objects', async () => {
|
|
34
|
+
const value: Resolvable<object> = () => ({ foo: 'bar' });
|
|
35
|
+
expect(await resolve(value)).toEqual({ foo: 'bar' });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Test async functions
|
|
39
|
+
it('should resolve async functions', async () => {
|
|
40
|
+
const value: Resolvable<string> = async () => 'hello';
|
|
41
|
+
expect(await resolve(value)).toBe('hello');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should resolve async functions returning promises', async () => {
|
|
45
|
+
const value: Resolvable<number> = () => Promise.resolve(42);
|
|
46
|
+
expect(await resolve(value)).toBe(42);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should handle async function rejections', async () => {
|
|
50
|
+
const value: Resolvable<string> = async () => {
|
|
51
|
+
throw new Error('async error');
|
|
52
|
+
};
|
|
53
|
+
await expect(resolve(value)).rejects.toThrow('async error');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Test edge cases
|
|
57
|
+
it('should handle null', async () => {
|
|
58
|
+
const value: Resolvable<null> = null;
|
|
59
|
+
expect(await resolve(value)).toBe(null);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle undefined', async () => {
|
|
63
|
+
const value: Resolvable<undefined> = undefined;
|
|
64
|
+
expect(await resolve(value)).toBe(undefined);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Test with complex objects
|
|
68
|
+
it('should resolve nested objects', async () => {
|
|
69
|
+
const value: Resolvable<{ nested: { value: number } }> = {
|
|
70
|
+
nested: { value: 42 },
|
|
71
|
+
};
|
|
72
|
+
expect(await resolve(value)).toEqual({ nested: { value: 42 } });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Test resolving objects as frequently used in headers as a common example
|
|
76
|
+
describe('resolve headers', () => {
|
|
77
|
+
it('should resolve header objects', async () => {
|
|
78
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
79
|
+
expect(await resolve(headers)).toEqual(headers);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should resolve header functions', async () => {
|
|
83
|
+
const headers = () => ({ Authorization: 'Bearer token' });
|
|
84
|
+
expect(await resolve(headers)).toEqual({ Authorization: 'Bearer token' });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should resolve async header functions', async () => {
|
|
88
|
+
const headers = async () => ({ 'X-Custom': 'value' });
|
|
89
|
+
expect(await resolve(headers)).toEqual({ 'X-Custom': 'value' });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should resolve header promises', async () => {
|
|
93
|
+
const headers = Promise.resolve({ Accept: 'application/json' });
|
|
94
|
+
expect(await resolve(headers)).toEqual({ Accept: 'application/json' });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should call async header functions each time when resolved multiple times', async () => {
|
|
98
|
+
let counter = 0;
|
|
99
|
+
const headers = async () => ({ 'X-Request-Number': String(++counter) });
|
|
100
|
+
|
|
101
|
+
// Resolve the same headers function multiple times
|
|
102
|
+
expect(await resolve(headers)).toEqual({ 'X-Request-Number': '1' });
|
|
103
|
+
expect(await resolve(headers)).toEqual({ 'X-Request-Number': '2' });
|
|
104
|
+
expect(await resolve(headers)).toEqual({ 'X-Request-Number': '3' });
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Test type inference
|
|
109
|
+
it('should maintain type information', async () => {
|
|
110
|
+
interface User {
|
|
111
|
+
id: number;
|
|
112
|
+
name: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const userPromise: Resolvable<User> = Promise.resolve({
|
|
116
|
+
id: 1,
|
|
117
|
+
name: 'Test User',
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const result = await resolve(userPromise);
|
|
121
|
+
// TypeScript should recognize result as User type
|
|
122
|
+
expect(result.id).toBe(1);
|
|
123
|
+
expect(result.name).toBe('Test User');
|
|
124
|
+
});
|
|
125
|
+
});
|
package/src/resolve.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MaybePromiseLike } from './maybe-promise-like';
|
|
2
|
+
|
|
3
|
+
export type Resolvable<T> = MaybePromiseLike<T> | (() => MaybePromiseLike<T>);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolves a value that could be a raw value, a Promise, a function returning a value,
|
|
7
|
+
* or a function returning a Promise.
|
|
8
|
+
*/
|
|
9
|
+
export async function resolve<T>(value: Resolvable<T>): Promise<T> {
|
|
10
|
+
// If it's a function, call it to get the value/promise
|
|
11
|
+
if (typeof value === 'function') {
|
|
12
|
+
value = (value as Function)();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Otherwise just resolve whatever we got (value or promise)
|
|
16
|
+
return Promise.resolve(value as T);
|
|
17
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
3
|
+
import {
|
|
4
|
+
createBinaryResponseHandler,
|
|
5
|
+
createJsonResponseHandler,
|
|
6
|
+
createStatusCodeErrorResponseHandler,
|
|
7
|
+
} from './response-handler';
|
|
8
|
+
|
|
9
|
+
describe('createJsonResponseHandler', () => {
|
|
10
|
+
it('should return both parsed value and rawValue', async () => {
|
|
11
|
+
const responseSchema = z.object({
|
|
12
|
+
name: z.string(),
|
|
13
|
+
age: z.number(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const rawData = {
|
|
17
|
+
name: 'John',
|
|
18
|
+
age: 30,
|
|
19
|
+
extraField: 'ignored',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const response = new Response(JSON.stringify(rawData));
|
|
23
|
+
const handler = createJsonResponseHandler(responseSchema);
|
|
24
|
+
|
|
25
|
+
const result = await handler({
|
|
26
|
+
url: 'test-url',
|
|
27
|
+
requestBodyValues: {},
|
|
28
|
+
response,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(result.value).toEqual({
|
|
32
|
+
name: 'John',
|
|
33
|
+
age: 30,
|
|
34
|
+
});
|
|
35
|
+
expect(result.rawValue).toEqual(rawData);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('createBinaryResponseHandler', () => {
|
|
40
|
+
it('should handle binary response successfully', async () => {
|
|
41
|
+
const binaryData = new Uint8Array([1, 2, 3, 4]);
|
|
42
|
+
const response = new Response(binaryData);
|
|
43
|
+
const handler = createBinaryResponseHandler();
|
|
44
|
+
|
|
45
|
+
const result = await handler({
|
|
46
|
+
url: 'test-url',
|
|
47
|
+
requestBodyValues: {},
|
|
48
|
+
response,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(result.value).toBeInstanceOf(Uint8Array);
|
|
52
|
+
expect(result.value).toEqual(binaryData);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should throw APICallError when response body is null', async () => {
|
|
56
|
+
const response = new Response(null);
|
|
57
|
+
const handler = createBinaryResponseHandler();
|
|
58
|
+
|
|
59
|
+
await expect(
|
|
60
|
+
handler({
|
|
61
|
+
url: 'test-url',
|
|
62
|
+
requestBodyValues: {},
|
|
63
|
+
response,
|
|
64
|
+
}),
|
|
65
|
+
).rejects.toThrow('Response body is empty');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('createStatusCodeErrorResponseHandler', () => {
|
|
70
|
+
it('should create error with status text and response body', async () => {
|
|
71
|
+
const response = new Response('Error message', {
|
|
72
|
+
status: 404,
|
|
73
|
+
statusText: 'Not Found',
|
|
74
|
+
});
|
|
75
|
+
const handler = createStatusCodeErrorResponseHandler();
|
|
76
|
+
|
|
77
|
+
const result = await handler({
|
|
78
|
+
url: 'test-url',
|
|
79
|
+
requestBodyValues: { some: 'data' },
|
|
80
|
+
response,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(result.value.message).toBe('Not Found');
|
|
84
|
+
expect(result.value.statusCode).toBe(404);
|
|
85
|
+
expect(result.value.responseBody).toBe('Error message');
|
|
86
|
+
expect(result.value.url).toBe('test-url');
|
|
87
|
+
expect(result.value.requestBodyValues).toEqual({ some: 'data' });
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { APICallError, EmptyResponseBodyError } from '@ai-sdk/provider';
|
|
2
|
+
import { ZodType } from 'zod/v4';
|
|
3
|
+
import { extractResponseHeaders } from './extract-response-headers';
|
|
4
|
+
import { parseJSON, ParseResult, safeParseJSON } from './parse-json';
|
|
5
|
+
import { parseJsonEventStream } from './parse-json-event-stream';
|
|
6
|
+
import { FlexibleSchema } from './schema';
|
|
7
|
+
|
|
8
|
+
export type ResponseHandler<RETURN_TYPE> = (options: {
|
|
9
|
+
url: string;
|
|
10
|
+
requestBodyValues: unknown;
|
|
11
|
+
response: Response;
|
|
12
|
+
}) => PromiseLike<{
|
|
13
|
+
value: RETURN_TYPE;
|
|
14
|
+
rawValue?: unknown;
|
|
15
|
+
responseHeaders?: Record<string, string>;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
export const createJsonErrorResponseHandler =
|
|
19
|
+
<T>({
|
|
20
|
+
errorSchema,
|
|
21
|
+
errorToMessage,
|
|
22
|
+
isRetryable,
|
|
23
|
+
}: {
|
|
24
|
+
errorSchema: FlexibleSchema<T>;
|
|
25
|
+
errorToMessage: (error: T) => string;
|
|
26
|
+
isRetryable?: (response: Response, error?: T) => boolean;
|
|
27
|
+
}): ResponseHandler<APICallError> =>
|
|
28
|
+
async ({ response, url, requestBodyValues }) => {
|
|
29
|
+
const responseBody = await response.text();
|
|
30
|
+
const responseHeaders = extractResponseHeaders(response);
|
|
31
|
+
|
|
32
|
+
// Some providers return an empty response body for some errors:
|
|
33
|
+
if (responseBody.trim() === '') {
|
|
34
|
+
return {
|
|
35
|
+
responseHeaders,
|
|
36
|
+
value: new APICallError({
|
|
37
|
+
message: response.statusText,
|
|
38
|
+
url,
|
|
39
|
+
requestBodyValues,
|
|
40
|
+
statusCode: response.status,
|
|
41
|
+
responseHeaders,
|
|
42
|
+
responseBody,
|
|
43
|
+
isRetryable: isRetryable?.(response),
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// resilient parsing in case the response is not JSON or does not match the schema:
|
|
49
|
+
try {
|
|
50
|
+
const parsedError = await parseJSON({
|
|
51
|
+
text: responseBody,
|
|
52
|
+
schema: errorSchema,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
responseHeaders,
|
|
57
|
+
value: new APICallError({
|
|
58
|
+
message: errorToMessage(parsedError),
|
|
59
|
+
url,
|
|
60
|
+
requestBodyValues,
|
|
61
|
+
statusCode: response.status,
|
|
62
|
+
responseHeaders,
|
|
63
|
+
responseBody,
|
|
64
|
+
data: parsedError,
|
|
65
|
+
isRetryable: isRetryable?.(response, parsedError),
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
} catch (parseError) {
|
|
69
|
+
return {
|
|
70
|
+
responseHeaders,
|
|
71
|
+
value: new APICallError({
|
|
72
|
+
message: response.statusText,
|
|
73
|
+
url,
|
|
74
|
+
requestBodyValues,
|
|
75
|
+
statusCode: response.status,
|
|
76
|
+
responseHeaders,
|
|
77
|
+
responseBody,
|
|
78
|
+
isRetryable: isRetryable?.(response),
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const createEventSourceResponseHandler =
|
|
85
|
+
<T>(
|
|
86
|
+
chunkSchema: FlexibleSchema<T>,
|
|
87
|
+
): ResponseHandler<ReadableStream<ParseResult<T>>> =>
|
|
88
|
+
async ({ response }: { response: Response }) => {
|
|
89
|
+
const responseHeaders = extractResponseHeaders(response);
|
|
90
|
+
|
|
91
|
+
if (response.body == null) {
|
|
92
|
+
throw new EmptyResponseBodyError({});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
responseHeaders,
|
|
97
|
+
value: parseJsonEventStream({
|
|
98
|
+
stream: response.body,
|
|
99
|
+
schema: chunkSchema,
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const createJsonResponseHandler =
|
|
105
|
+
<T>(responseSchema: FlexibleSchema<T>): ResponseHandler<T> =>
|
|
106
|
+
async ({ response, url, requestBodyValues }) => {
|
|
107
|
+
const responseBody = await response.text();
|
|
108
|
+
|
|
109
|
+
const parsedResult = await safeParseJSON({
|
|
110
|
+
text: responseBody,
|
|
111
|
+
schema: responseSchema,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const responseHeaders = extractResponseHeaders(response);
|
|
115
|
+
|
|
116
|
+
if (!parsedResult.success) {
|
|
117
|
+
throw new APICallError({
|
|
118
|
+
message: 'Invalid JSON response',
|
|
119
|
+
cause: parsedResult.error,
|
|
120
|
+
statusCode: response.status,
|
|
121
|
+
responseHeaders,
|
|
122
|
+
responseBody,
|
|
123
|
+
url,
|
|
124
|
+
requestBodyValues,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
responseHeaders,
|
|
130
|
+
value: parsedResult.value,
|
|
131
|
+
rawValue: parsedResult.rawValue,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const createBinaryResponseHandler =
|
|
136
|
+
(): ResponseHandler<Uint8Array> =>
|
|
137
|
+
async ({ response, url, requestBodyValues }) => {
|
|
138
|
+
const responseHeaders = extractResponseHeaders(response);
|
|
139
|
+
|
|
140
|
+
if (!response.body) {
|
|
141
|
+
throw new APICallError({
|
|
142
|
+
message: 'Response body is empty',
|
|
143
|
+
url,
|
|
144
|
+
requestBodyValues,
|
|
145
|
+
statusCode: response.status,
|
|
146
|
+
responseHeaders,
|
|
147
|
+
responseBody: undefined,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const buffer = await response.arrayBuffer();
|
|
153
|
+
return {
|
|
154
|
+
responseHeaders,
|
|
155
|
+
value: new Uint8Array(buffer),
|
|
156
|
+
};
|
|
157
|
+
} catch (error) {
|
|
158
|
+
throw new APICallError({
|
|
159
|
+
message: 'Failed to read response as array buffer',
|
|
160
|
+
url,
|
|
161
|
+
requestBodyValues,
|
|
162
|
+
statusCode: response.status,
|
|
163
|
+
responseHeaders,
|
|
164
|
+
responseBody: undefined,
|
|
165
|
+
cause: error,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const createStatusCodeErrorResponseHandler =
|
|
171
|
+
(): ResponseHandler<APICallError> =>
|
|
172
|
+
async ({ response, url, requestBodyValues }) => {
|
|
173
|
+
const responseHeaders = extractResponseHeaders(response);
|
|
174
|
+
const responseBody = await response.text();
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
responseHeaders,
|
|
178
|
+
value: new APICallError({
|
|
179
|
+
message: response.statusText,
|
|
180
|
+
url,
|
|
181
|
+
requestBodyValues: requestBodyValues as Record<string, unknown>,
|
|
182
|
+
statusCode: response.status,
|
|
183
|
+
responseHeaders,
|
|
184
|
+
responseBody,
|
|
185
|
+
}),
|
|
186
|
+
};
|
|
187
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
2
|
+
import { InferSchema, StandardSchema } from './schema';
|
|
3
|
+
|
|
4
|
+
describe('InferSchema type', () => {
|
|
5
|
+
it('should work with StandardSchema', () => {
|
|
6
|
+
type MySchema = StandardSchema<number>;
|
|
7
|
+
type Result = InferSchema<MySchema>;
|
|
8
|
+
|
|
9
|
+
expectTypeOf<Result>().toMatchTypeOf<number>();
|
|
10
|
+
});
|
|
11
|
+
});
|