@passcod/faith 0.0.2 → 0.0.5

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