-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathhttp.js
108 lines (103 loc) · 2.76 KB
/
http.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import * as API from '@ucanto/interface'
/**
* @typedef {{
* ok: boolean
* arrayBuffer():API.Await<ArrayBuffer>
* headers: {
* entries?: () => Iterable<[string, string]>
* } | Headers
* status?: number
* statusText?: string
* url?: string
* }} FetchResponse
* @typedef {(url:string, init:API.HTTPRequest) => API.Await<FetchResponse>} Fetcher
*/
/**
* @template S
* @param {object} options
* @param {URL} options.url
* @param {(url:string, init:API.HTTPRequest) => API.Await<FetchResponse>} [options.fetch]
* @param {string} [options.method]
* @returns {API.Channel<S>}
*/
export const open = ({ url, method = 'POST', fetch }) => {
/* c8 ignore next 9 */
if (!fetch) {
if (typeof globalThis.fetch !== 'undefined') {
fetch = globalThis.fetch.bind(globalThis)
} else {
throw new TypeError(
`ucanto HTTP transport got undefined \`fetch\`. Try passing in a \`fetch\` implementation explicitly.`
)
}
}
return new Channel({ url, method, fetch })
}
/**
* @template {Record<string, any>} S
* @implements {API.Channel<S>}
*/
class Channel {
/**
* @param {object} options
* @param {URL} options.url
* @param {Fetcher} options.fetch
* @param {string} [options.method]
*/
constructor({ url, fetch, method }) {
this.fetch = fetch
this.method = method
this.url = url
}
/**
* @template {API.Tuple<API.ServiceInvocation<API.Capability, S>>} I
* @param {API.HTTPRequest<API.AgentMessage<{ In: API.InferInvocations<I>, Out: API.Tuple<API.Receipt> }>>} request
* @returns {Promise<API.HTTPResponse<API.AgentMessage<{ Out: API.InferReceipts<I, S>, In: API.Tuple<API.Invocation> }>>>}
*/
async request({ headers, body }) {
const response = await this.fetch(this.url.href, {
headers,
body,
method: this.method,
})
const buffer = response.ok
? await response.arrayBuffer()
: HTTPError.throw(`HTTP Request failed. ${this.method} ${this.url.href} → ${response.status}`, response)
return {
headers: response.headers.entries
? Object.fromEntries(response.headers.entries())
: /* c8 ignore next */
{},
body: new Uint8Array(buffer),
}
}
}
/**
* @typedef {{
* status?: number
* statusText?: string
* url?: string
* }} Options
*/
class HTTPError extends Error {
/**
* @param {string} message
* @param {Options} options
* @returns {never}
*/
static throw(message, options) {
throw new this(message, options)
}
/**
* @param {string} message
* @param {Options} options
*/
constructor(message, { url, status = 500, statusText = 'Server error' }) {
super(message)
/** @type {'HTTPError'} */
this.name = 'HTTPError'
this.url = url
this.status = status
this.statusText = statusText
}
}