@passcod/faith 0.0.2 → 0.0.4
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/README.md +292 -74
- package/index.d.ts +534 -31
- package/index.js +57 -52
- package/package.json +15 -14
- package/wrapper.d.ts +341 -95
- package/wrapper.js +283 -275
package/wrapper.js
CHANGED
|
@@ -13,137 +13,134 @@ const { faithFetch } = native;
|
|
|
13
13
|
// Generate ERROR_CODES const enum from native error codes
|
|
14
14
|
// e.g. { InvalidHeader: "InvalidHeader", InvalidMethod: "InvalidMethod", ... }
|
|
15
15
|
const ERROR_CODES = native.errorCodes().reduce((acc, code) => {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
acc[code] = code;
|
|
17
|
+
return acc;
|
|
18
18
|
}, {});
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Response class that provides spec-compliant Fetch API
|
|
22
22
|
*/
|
|
23
23
|
class Response {
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
24
|
+
/** @type {import('./index').FaithResponse} */
|
|
25
|
+
#nativeResponse;
|
|
26
|
+
|
|
27
|
+
constructor(nativeResponse) {
|
|
28
|
+
this.#nativeResponse = nativeResponse;
|
|
29
|
+
|
|
30
|
+
// Create a Headers object from the array of header pairs
|
|
31
|
+
const headers = new Headers();
|
|
32
|
+
const headerPairs = this.#nativeResponse.headers;
|
|
33
|
+
if (Array.isArray(headerPairs)) {
|
|
34
|
+
for (const [name, value] of headerPairs) {
|
|
35
|
+
headers.append(name, value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Object.defineProperty(this, "headers", {
|
|
40
|
+
get: () => headers,
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const nativeProto = Object.getPrototypeOf(this.#nativeResponse);
|
|
46
|
+
const descriptors = Object.getOwnPropertyDescriptors(nativeProto);
|
|
47
|
+
|
|
48
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
49
|
+
if (descriptor.get && key !== "headers") {
|
|
50
|
+
Object.defineProperty(this, key, {
|
|
51
|
+
get: () => this.#nativeResponse[key],
|
|
52
|
+
enumerable: true,
|
|
53
|
+
configurable: true,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Convert response body to text (UTF-8)
|
|
61
|
+
* @returns {Promise<string>}
|
|
62
|
+
*/
|
|
63
|
+
async text() {
|
|
64
|
+
return await this.#nativeResponse.text();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get response body as bytes
|
|
69
|
+
* @returns {Promise<Uint8Array>}
|
|
70
|
+
*/
|
|
71
|
+
async bytes() {
|
|
72
|
+
return await this.#nativeResponse.bytes();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Alias for bytes() that returns ArrayBuffer
|
|
77
|
+
* @returns {Promise<ArrayBuffer>}
|
|
78
|
+
*/
|
|
79
|
+
async arrayBuffer() {
|
|
80
|
+
const buffer = await this.#nativeResponse.bytes();
|
|
81
|
+
return buffer.buffer;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Parse response body as JSON
|
|
86
|
+
* @returns {Promise<any>}
|
|
87
|
+
*/
|
|
88
|
+
async json() {
|
|
89
|
+
return await this.#nativeResponse.json();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get response body as Blob
|
|
94
|
+
* @returns {Promise<Blob>}
|
|
95
|
+
*/
|
|
96
|
+
async blob() {
|
|
97
|
+
const bytes = await this.#nativeResponse.bytes();
|
|
98
|
+
const contentType = this.headers.get("content-type") || "";
|
|
99
|
+
return new Blob([bytes], { type: contentType });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Not supported. Will throw. */
|
|
103
|
+
async formData() {
|
|
104
|
+
throw new Error("not supported");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create a clone of the Response object
|
|
109
|
+
* @returns {Response} A new Response object with the same properties
|
|
110
|
+
* @throws {Error} If response body has already been read
|
|
111
|
+
*/
|
|
112
|
+
clone() {
|
|
113
|
+
return new Response(this.#nativeResponse.clone());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Convert to a Web API Response object
|
|
118
|
+
* @returns {Response} Web API Response object
|
|
119
|
+
* @throws {Error} If response body has been disturbed or Response constructor is not available
|
|
120
|
+
*/
|
|
121
|
+
webResponse() {
|
|
122
|
+
// Check if Web API Response constructor is available
|
|
123
|
+
if (typeof globalThis.Response !== "function") {
|
|
124
|
+
throw new Error(
|
|
125
|
+
"Web API Response constructor not available in this environment",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Create and return a Web API Response object
|
|
130
|
+
return new globalThis.Response(this.body, {
|
|
131
|
+
status: this.status,
|
|
132
|
+
statusText: this.statusText,
|
|
133
|
+
headers: this.headers,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
let defaultAgent;
|
|
134
139
|
|
|
135
140
|
/**
|
|
136
141
|
* Fetch function wrapper
|
|
137
|
-
* @param {string|Request|URL|
|
|
138
|
-
* @param {
|
|
139
|
-
* @param {string} [options.method] - HTTP method
|
|
140
|
-
* @param {Object|Headers} [options.headers] - HTTP headers (Headers object or plain object with {name: value} pairs).
|
|
141
|
-
* Converted to array of tuples for the native binding.
|
|
142
|
-
* @param {Buffer|Array<number>|string|ArrayBuffer|Uint8Array|ReadableStream} [options.body] - Request body
|
|
143
|
-
* @param {number} [options.timeout] - Timeout in milliseconds
|
|
144
|
-
* @param {string} [options.credentials] - Credentials mode: "omit", "same-origin", or "include" (default)
|
|
145
|
-
* @param {string} [options.duplex] - Duplex mode: "half" (required when body is a ReadableStream)
|
|
146
|
-
* @param {AbortSignal} [options.signal] - AbortSignal to cancel the request
|
|
142
|
+
* @param {string|Request|URL|{ toString(): string }} resource - The URL to fetch, a Request object, or an object with stringifier
|
|
143
|
+
* @param {FetchOptions|Request} [options] - Fetch options (when resource is a Request, options override Request properties)
|
|
147
144
|
* @returns {Promise<Response>}
|
|
148
145
|
*
|
|
149
146
|
* When a Request object is provided, all its properties (method, headers, body, mode, credentials,
|
|
@@ -158,163 +155,174 @@ let defaultAgent;
|
|
|
158
155
|
* - null/undefined: treated as no headers
|
|
159
156
|
* - Invalid types: throws TypeError
|
|
160
157
|
*/
|
|
161
|
-
async function fetch(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
158
|
+
async function fetch(resource, options = {}) {
|
|
159
|
+
let url;
|
|
160
|
+
let nativeOptions;
|
|
161
|
+
|
|
162
|
+
// Handle Request object as resource
|
|
163
|
+
if (
|
|
164
|
+
typeof resource === "object" &&
|
|
165
|
+
resource !== null &&
|
|
166
|
+
typeof resource.url === "string"
|
|
167
|
+
) {
|
|
168
|
+
// Extract url separately
|
|
169
|
+
url = resource.url;
|
|
170
|
+
|
|
171
|
+
// Copy all properties from Request object except url and bodyUsed
|
|
172
|
+
const requestOptions = {};
|
|
173
|
+
for (const key in resource) {
|
|
174
|
+
if (key !== "url" && key !== "bodyUsed") {
|
|
175
|
+
const value = resource[key];
|
|
176
|
+
if (value !== undefined && value !== null) {
|
|
177
|
+
requestOptions[key] = value;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Handle body specially - Request.body is a ReadableStream that needs to be consumed
|
|
183
|
+
if (requestOptions.body !== undefined && requestOptions.body !== null) {
|
|
184
|
+
if (typeof resource.arrayBuffer === "function") {
|
|
185
|
+
requestOptions.body = await resource.arrayBuffer();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Merge Request properties with options, options take precedence
|
|
190
|
+
nativeOptions = { ...requestOptions, ...options };
|
|
191
|
+
} else if (typeof resource === "string") {
|
|
192
|
+
url = resource;
|
|
193
|
+
nativeOptions = { ...options };
|
|
194
|
+
} else if (resource && typeof resource.toString === "function") {
|
|
195
|
+
// Handle objects with stringifier (like URL objects)
|
|
196
|
+
url = resource.toString();
|
|
197
|
+
nativeOptions = { ...options };
|
|
198
|
+
} else {
|
|
199
|
+
throw new TypeError(
|
|
200
|
+
"First argument must be a string URL, Request object, or an object with a stringifier",
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Convert headers to native format
|
|
205
|
+
// This is the inverse of what Response does: Request headers go from
|
|
206
|
+
// Headers/Object -> Array<[string, string]>, while Response headers go from
|
|
207
|
+
// Array<[string, string]> -> Headers object
|
|
208
|
+
if (nativeOptions.headers !== undefined && nativeOptions.headers !== null) {
|
|
209
|
+
if (nativeOptions.headers instanceof Headers) {
|
|
210
|
+
// Convert Headers object to array of tuples
|
|
211
|
+
const headersArray = [];
|
|
212
|
+
nativeOptions.headers.forEach((value, name) => {
|
|
213
|
+
headersArray.push([name, value]);
|
|
214
|
+
});
|
|
215
|
+
nativeOptions.headers = headersArray;
|
|
216
|
+
} else if (
|
|
217
|
+
typeof nativeOptions.headers === "object" &&
|
|
218
|
+
!Array.isArray(nativeOptions.headers)
|
|
219
|
+
) {
|
|
220
|
+
// Convert plain object to array of tuples
|
|
221
|
+
const headersArray = [];
|
|
222
|
+
for (const [name, value] of Object.entries(nativeOptions.headers)) {
|
|
223
|
+
headersArray.push([name, value]);
|
|
224
|
+
}
|
|
225
|
+
nativeOptions.headers = headersArray;
|
|
226
|
+
} else {
|
|
227
|
+
throw new TypeError(
|
|
228
|
+
"headers must be a Headers object or a plain object",
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
} else if (nativeOptions.headers === null) {
|
|
232
|
+
// Convert null to undefined so Rust treats it as None
|
|
233
|
+
delete nativeOptions.headers;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Convert body to Buffer if needed
|
|
237
|
+
// Native binding handles: string, Buffer, Uint8Array
|
|
238
|
+
// We convert: ArrayBuffer, Array<number>, ReadableStream
|
|
239
|
+
// Validate ReadableStream bodies require duplex option
|
|
240
|
+
if (nativeOptions.body !== undefined && nativeOptions.body !== null) {
|
|
241
|
+
// Check if body is a ReadableStream
|
|
242
|
+
if (
|
|
243
|
+
typeof nativeOptions.body === "object" &&
|
|
244
|
+
typeof nativeOptions.body.getReader === "function"
|
|
245
|
+
) {
|
|
246
|
+
// ReadableStream body requires duplex option
|
|
247
|
+
if (!nativeOptions.duplex) {
|
|
248
|
+
throw new TypeError(
|
|
249
|
+
"RequestInit's body is a ReadableStream and duplex option is not set",
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Consume the ReadableStream into a Buffer
|
|
254
|
+
const reader = nativeOptions.body.getReader();
|
|
255
|
+
const chunks = [];
|
|
256
|
+
try {
|
|
257
|
+
while (true) {
|
|
258
|
+
const { done, value } = await reader.read();
|
|
259
|
+
if (done) break;
|
|
260
|
+
chunks.push(value);
|
|
261
|
+
}
|
|
262
|
+
} finally {
|
|
263
|
+
reader.releaseLock();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Concatenate all chunks into a single Buffer
|
|
267
|
+
const totalLength = chunks.reduce(
|
|
268
|
+
(acc, chunk) => acc + chunk.length,
|
|
269
|
+
0,
|
|
270
|
+
);
|
|
271
|
+
const result = new Uint8Array(totalLength);
|
|
272
|
+
let offset = 0;
|
|
273
|
+
for (const chunk of chunks) {
|
|
274
|
+
result.set(chunk, offset);
|
|
275
|
+
offset += chunk.length;
|
|
276
|
+
}
|
|
277
|
+
nativeOptions.body = Buffer.from(result);
|
|
278
|
+
} else if (nativeOptions.body instanceof ArrayBuffer) {
|
|
279
|
+
nativeOptions.body = Buffer.from(nativeOptions.body);
|
|
280
|
+
} else if (Array.isArray(nativeOptions.body)) {
|
|
281
|
+
nativeOptions.body = Buffer.from(nativeOptions.body);
|
|
282
|
+
}
|
|
283
|
+
} else if (nativeOptions.body === null) {
|
|
284
|
+
// Remove null body
|
|
285
|
+
delete nativeOptions.body;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Attach to the default agent if none is provided
|
|
289
|
+
if (!nativeOptions.agent) {
|
|
290
|
+
if (!defaultAgent) {
|
|
291
|
+
defaultAgent = new native.Agent();
|
|
292
|
+
}
|
|
293
|
+
nativeOptions.agent = defaultAgent;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Extract signal to pass as separate parameter
|
|
297
|
+
const signal = nativeOptions.signal;
|
|
298
|
+
delete nativeOptions.signal;
|
|
299
|
+
|
|
300
|
+
// Check if signal is already aborted
|
|
301
|
+
if (signal && signal.aborted) {
|
|
302
|
+
const error = new Error(
|
|
303
|
+
"Aborted: the request was aborted before it could start",
|
|
304
|
+
);
|
|
305
|
+
error.name = "AbortError";
|
|
306
|
+
error.code = ERROR_CODES.Aborted;
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const nativeResponse = await faithFetch(url, nativeOptions, signal);
|
|
311
|
+
return new Response(nativeResponse);
|
|
310
312
|
}
|
|
311
313
|
|
|
312
314
|
module.exports = {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
315
|
+
Agent: native.Agent,
|
|
316
|
+
CacheMode: native.CacheMode,
|
|
317
|
+
CacheStore: native.CacheStore,
|
|
318
|
+
Credentials: native.CredentialsOption,
|
|
319
|
+
Duplex: native.DuplexOption,
|
|
320
|
+
ERROR_CODES,
|
|
321
|
+
FAITH_VERSION: native.FAITH_VERSION,
|
|
322
|
+
fetch,
|
|
323
|
+
Http3Congestion: native.Http3Congestion,
|
|
324
|
+
Redirect: native.Redirect,
|
|
325
|
+
REQWEST_VERSION: native.REQWEST_VERSION,
|
|
326
|
+
Response,
|
|
327
|
+
USER_AGENT: native.USER_AGENT,
|
|
320
328
|
};
|