@gjsify/fetch 0.0.2

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.
Files changed (58) hide show
  1. package/README.md +9 -0
  2. package/lib/cjs/body.js +284 -0
  3. package/lib/cjs/errors/abort-error.js +28 -0
  4. package/lib/cjs/errors/base.js +36 -0
  5. package/lib/cjs/errors/fetch-error.js +40 -0
  6. package/lib/cjs/headers.js +231 -0
  7. package/lib/cjs/index.js +246 -0
  8. package/lib/cjs/request.js +306 -0
  9. package/lib/cjs/response.js +162 -0
  10. package/lib/cjs/types/index.js +17 -0
  11. package/lib/cjs/types/system-error.js +16 -0
  12. package/lib/cjs/utils/blob-from.js +124 -0
  13. package/lib/cjs/utils/get-search.js +30 -0
  14. package/lib/cjs/utils/is-redirect.js +26 -0
  15. package/lib/cjs/utils/is.js +47 -0
  16. package/lib/cjs/utils/multipart-parser.js +372 -0
  17. package/lib/cjs/utils/referrer.js +172 -0
  18. package/lib/esm/body.js +255 -0
  19. package/lib/esm/errors/abort-error.js +9 -0
  20. package/lib/esm/errors/base.js +17 -0
  21. package/lib/esm/errors/fetch-error.js +21 -0
  22. package/lib/esm/headers.js +202 -0
  23. package/lib/esm/index.js +224 -0
  24. package/lib/esm/request.js +281 -0
  25. package/lib/esm/response.js +133 -0
  26. package/lib/esm/types/index.js +1 -0
  27. package/lib/esm/types/system-error.js +1 -0
  28. package/lib/esm/utils/blob-from.js +101 -0
  29. package/lib/esm/utils/get-search.js +11 -0
  30. package/lib/esm/utils/is-redirect.js +7 -0
  31. package/lib/esm/utils/is.js +28 -0
  32. package/lib/esm/utils/multipart-parser.js +353 -0
  33. package/lib/esm/utils/referrer.js +153 -0
  34. package/package.json +53 -0
  35. package/src/body.ts +415 -0
  36. package/src/errors/abort-error.ts +10 -0
  37. package/src/errors/base.ts +20 -0
  38. package/src/errors/fetch-error.ts +26 -0
  39. package/src/headers.ts +279 -0
  40. package/src/index.spec.ts +13 -0
  41. package/src/index.ts +367 -0
  42. package/src/request.ts +396 -0
  43. package/src/response.ts +197 -0
  44. package/src/test.mts +6 -0
  45. package/src/types/index.ts +1 -0
  46. package/src/types/system-error.ts +11 -0
  47. package/src/utils/blob-from.ts +168 -0
  48. package/src/utils/get-search.ts +9 -0
  49. package/src/utils/is-redirect.ts +11 -0
  50. package/src/utils/is.ts +88 -0
  51. package/src/utils/multipart-parser.ts +448 -0
  52. package/src/utils/referrer.ts +350 -0
  53. package/test.gjs.js +34758 -0
  54. package/test.gjs.mjs +53177 -0
  55. package/test.node.js +1226 -0
  56. package/test.node.mjs +6294 -0
  57. package/tsconfig.json +19 -0
  58. package/tsconfig.types.json +8 -0
package/src/request.ts ADDED
@@ -0,0 +1,396 @@
1
+ import '@girs/gjs';
2
+ import '@girs/gio-2.0';
3
+
4
+ import GLib from '@girs/glib-2.0';
5
+ import Soup from '@girs/soup-3.0';
6
+ import Gio from '@girs/gio-2.0';
7
+ import * as GioExt from '@gjsify/gio-2.0';
8
+ import * as SoupExt from '@gjsify/soup-3.0';
9
+
10
+ import { URL } from '@gjsify/deno-runtime/ext/url/00_url';
11
+ import { Blob } from '@gjsify/deno-runtime/ext/web/09_file';
12
+
13
+ import { Readable } from 'stream';
14
+
15
+ import Headers from './headers.js';
16
+ import Body, {clone, extractContentType, getTotalBytes} from './body.js';
17
+ import {isAbortSignal} from './utils/is.js';
18
+ // import { getSearch } from './utils/get-search.js';
19
+ import {
20
+ validateReferrerPolicy, determineRequestsReferrer, DEFAULT_REFERRER_POLICY
21
+ } from './utils/referrer.js';
22
+
23
+ const INTERNALS = Symbol('Request internals');
24
+
25
+ /**
26
+ * Check if `obj` is an instance of Request.
27
+ */
28
+ const isRequest = (obj: RequestInfo | URL) => {
29
+ return (
30
+ typeof obj === 'object' &&
31
+ typeof (obj as Request).url === 'string'
32
+ );
33
+ };
34
+
35
+ // @ts-ignore
36
+ export interface Request extends globalThis.Request {}
37
+
38
+ /** This Fetch API interface represents a resource request. */
39
+ export class Request extends Body {
40
+ /** Returns the cache mode associated with request, which is a string indicating how the request will interact with the browser's cache when fetching. */
41
+ readonly cache: RequestCache;
42
+ /** Returns the credentials mode associated with request, which is a string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. */
43
+ readonly credentials: RequestCredentials;
44
+ /** Returns the kind of resource requested by request, e.g., "document" or "script". */
45
+ readonly destination: RequestDestination;
46
+ /** Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header. */
47
+ get headers(): Headers {
48
+ return this[INTERNALS].headers;
49
+ }
50
+ /** Returns request's subresource integrity metadata, which is a cryptographic hash of the resource being fetched. Its value consists of multiple hashes separated by whitespace. [SRI] */
51
+ readonly integrity: string;
52
+ /** Returns a boolean indicating whether or not request can outlive the global in which it was created. */
53
+ readonly keepalive: boolean;
54
+ /** Returns request's HTTP method, which is "GET" by default. */
55
+ get method(): string {
56
+ return this[INTERNALS].method;
57
+ }
58
+ /** Returns the mode associated with request, which is a string indicating whether the request will use CORS, or will be restricted to same-origin URLs. */
59
+ readonly mode: RequestMode;
60
+ /** Returns the redirect mode associated with request, which is a string indicating how redirects for the request will be handled during fetching. A request will follow redirects by default. */
61
+ get redirect(): RequestRedirect {
62
+ return this[INTERNALS].redirect;
63
+ }
64
+ /**
65
+ * Returns the referrer of request.
66
+ * Its value can be a same-origin URL if explicitly set in init, the empty string to indicate no referrer, and "about:client" when defaulting to the global's default.
67
+ * This is used during fetching to determine the value of the `Referer` header of the request being made.
68
+ * @see https://fetch.spec.whatwg.org/#dom-request-referrer
69
+ **/
70
+ get referrer(): string {
71
+ if (this[INTERNALS].referrer === 'no-referrer') {
72
+ return '';
73
+ }
74
+
75
+ if (this[INTERNALS].referrer === 'client') {
76
+ return 'about:client';
77
+ }
78
+
79
+ if (this[INTERNALS].referrer) {
80
+ return this[INTERNALS].referrer.toString();
81
+ }
82
+
83
+ return undefined;
84
+ }
85
+ /** Returns the referrer policy associated with request. This is used during fetching to compute the value of the request's referrer. */
86
+ get referrerPolicy(): ReferrerPolicy {
87
+ return this[INTERNALS].referrerPolicy;
88
+ }
89
+ set referrerPolicy(referrerPolicy) {
90
+ this[INTERNALS].referrerPolicy = validateReferrerPolicy(referrerPolicy);
91
+ }
92
+ /** Returns the signal associated with request, which is an AbortSignal object indicating whether or not request has been aborted, and its abort event handler. */
93
+ get signal(): AbortSignal {
94
+ return this[INTERNALS].signal;
95
+ }
96
+ /** Returns the URL of request as a string. */
97
+ get url(): string {
98
+ return this[INTERNALS].parsedURL.toString();
99
+ }
100
+
101
+ get _uri() {
102
+ return GLib.Uri.parse(this.url, GLib.UriFlags.NONE);
103
+ }
104
+
105
+ get _session() {
106
+ return this[INTERNALS].session;
107
+ }
108
+
109
+ get _message() {
110
+ return this[INTERNALS].message;
111
+ }
112
+
113
+ get _inputStream() {
114
+ return this[INTERNALS].inputStream;
115
+ }
116
+
117
+ get [Symbol.toStringTag]() {
118
+ return 'Request';
119
+ }
120
+
121
+ [INTERNALS]: {
122
+ // node-fetch
123
+ method: string;
124
+ redirect: RequestRedirect;
125
+ headers: Headers;
126
+ parsedURL: URL;
127
+ signal: AbortSignal;
128
+ referrer: string | URL;
129
+ referrerPolicy: ReferrerPolicy;
130
+ // Gjsify
131
+ session: SoupExt.ExtSession & Soup.Session;
132
+ message: Soup.Message;
133
+ inputStream?: Gio.InputStream & GioExt.ExtInputStream<Gio.InputStream>;
134
+ readable?: Readable
135
+ };
136
+
137
+ // Node-fetch-only options
138
+ follow: number;
139
+ compress = false;
140
+ counter = 0
141
+ agent: string | ((url: URL) => string) = ''
142
+ highWaterMark = 16384;
143
+ insecureHTTPParser = false;
144
+
145
+ constructor(input: RequestInfo | URL, init?: RequestInit) {
146
+ let parsedURL: URL;
147
+ let requestObj: Partial<Request> = {};
148
+
149
+ if(isRequest(input)) {
150
+ parsedURL = new URL((input as Request).url);
151
+ requestObj = input as Request;
152
+ } else {
153
+ parsedURL = new URL(input as string | URL);
154
+ }
155
+
156
+ if (parsedURL.username !== '' || parsedURL.password !== '') {
157
+ throw new TypeError(`${parsedURL} is an url with embedded credentials.`);
158
+ }
159
+
160
+ let method = init.method || requestObj.method || 'GET';
161
+ if (/^(delete|get|head|options|post|put)$/i.test(method)) {
162
+ method = method.toUpperCase();
163
+ }
164
+
165
+ if ((init.body != null || (isRequest(input) && (input as Request).body !== null)) &&
166
+ (method === 'GET' || method === 'HEAD')) {
167
+ throw new TypeError('Request with GET/HEAD method cannot have body');
168
+ }
169
+
170
+ const inputBody = init.body ? init.body : (isRequest(input) && (input as Request).body !== null ? clone(input as Request & Body) : null);
171
+
172
+ super(inputBody, {
173
+ size: (init as Request).size || (init as any).size || 0
174
+ });
175
+
176
+ const headers = new Headers((init.headers || (input as Request).headers || {}) as HeadersInit);
177
+
178
+ if (inputBody !== null && !headers.has('Content-Type')) {
179
+ const contentType = extractContentType(inputBody, this);
180
+ if (contentType) {
181
+ headers.set('Content-Type', contentType);
182
+ }
183
+ }
184
+
185
+ let signal = isRequest(input) ?
186
+ (input as Request).signal :
187
+ null;
188
+ if ('signal' in init) {
189
+ signal = init.signal;
190
+ }
191
+
192
+ if (signal != null && !isAbortSignal(signal)) {
193
+ throw new TypeError('Expected signal to be an instanceof AbortSignal or EventTarget');
194
+ }
195
+
196
+ // §5.4, Request constructor steps, step 15.1
197
+ let referrer: string | URL = init.referrer == null ? (input as Request).referrer : init.referrer;
198
+ if (referrer === '') {
199
+ // §5.4, Request constructor steps, step 15.2
200
+ referrer = 'no-referrer';
201
+ } else if (referrer) {
202
+ // §5.4, Request constructor steps, step 15.3.1, 15.3.2
203
+ const parsedReferrer = new URL(referrer);
204
+ // §5.4, Request constructor steps, step 15.3.3, 15.3.4
205
+ referrer = /^about:(\/\/)?client$/.test(parsedReferrer.toString()) ? 'client' : parsedReferrer;
206
+ } else {
207
+ referrer = undefined;
208
+ }
209
+
210
+ const session = SoupExt.ExtSession.new();
211
+ const message = new Soup.Message({
212
+ method,
213
+ uri: this._uri,
214
+ });
215
+
216
+ this[INTERNALS] = {
217
+ method,
218
+ redirect: init.redirect || (input as Request).redirect || 'follow',
219
+ headers,
220
+ parsedURL,
221
+ signal,
222
+ referrer,
223
+ referrerPolicy: '',
224
+ session,
225
+ message,
226
+ };
227
+
228
+ // Node-fetch-only options
229
+ this.follow = (init as Request).follow === undefined ? ((input as Request).follow === undefined ? 20 : (input as Request).follow) : (init as Request).follow;
230
+ this.compress = (init as Request).compress === undefined ? ((input as Request).compress === undefined ? true : (input as Request).compress) : (init as Request).compress;
231
+ this.counter = (init as Request).counter || (input as Request).counter || 0;
232
+ this.agent = (init as Request).agent || (input as Request).agent;
233
+ this.highWaterMark = (init as Request).highWaterMark || (input as Request).highWaterMark || 16384;
234
+ this.insecureHTTPParser = (init as Request).insecureHTTPParser || (input as Request).insecureHTTPParser || false;
235
+
236
+ // §5.4, Request constructor steps, step 16.
237
+ // Default is empty string per https://fetch.spec.whatwg.org/#concept-request-referrer-policy
238
+ this.referrerPolicy = init.referrerPolicy || (input as Request).referrerPolicy || '';
239
+ }
240
+
241
+ /**
242
+ * Custom send method using Soup, used in fetch to send the request
243
+ * @param options
244
+ * @returns
245
+ */
246
+ async _send(options: { headers: Headers }) {
247
+ options.headers._appendToSoupMessage(this._message);
248
+
249
+ const cancellable = new Gio.Cancellable();
250
+
251
+ this[INTERNALS].inputStream = await this._session.sendAsync(this._message, GLib.PRIORITY_DEFAULT, cancellable);
252
+
253
+ this[INTERNALS].readable = this[INTERNALS].inputStream.toReadable({});
254
+
255
+ return {
256
+ inputStream: this[INTERNALS].inputStream,
257
+ readable: this[INTERNALS].readable,
258
+ cancellable
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Clone this request
264
+ */
265
+ clone(): Request {
266
+ // @ts-ignores
267
+ return new Request(this);
268
+ }
269
+
270
+ async arrayBuffer(): Promise<ArrayBuffer> {
271
+ return super.arrayBuffer();
272
+ }
273
+ async blob(): Promise<Blob> {
274
+ return super.blob();
275
+ }
276
+ async formData(): Promise<FormData> {
277
+ return super.formData();
278
+ }
279
+ async json(): Promise<any> {
280
+ return super.json();
281
+ }
282
+ async text(): Promise<string> {
283
+ return super.text();
284
+ }
285
+ }
286
+
287
+ Object.defineProperties(Request.prototype, {
288
+ method: {enumerable: true},
289
+ url: {enumerable: true},
290
+ headers: {enumerable: true},
291
+ redirect: {enumerable: true},
292
+ clone: {enumerable: true},
293
+ signal: {enumerable: true},
294
+ referrer: {enumerable: true},
295
+ referrerPolicy: {enumerable: true}
296
+ });
297
+
298
+ export default Request;
299
+
300
+ /**
301
+ * Convert a Request to Soup request options.
302
+ *
303
+ * @param request - A Request instance
304
+ * @return The options object to be passed to http.request
305
+ */
306
+ export const getSoupRequestOptions = (request: Request) => {
307
+ const { parsedURL } = request[INTERNALS];
308
+ const headers = new Headers(request[INTERNALS].headers);
309
+
310
+ // Fetch step 1.3
311
+ if (!headers.has('Accept')) {
312
+ headers.set('Accept', '*/*');
313
+ }
314
+
315
+ // HTTP-network-or-cache fetch steps 2.4-2.7
316
+ let contentLengthValue = null;
317
+ if (request.body === null && /^(post|put)$/i.test(request.method)) {
318
+ contentLengthValue = '0';
319
+ }
320
+
321
+ if (request.body !== null) {
322
+ const totalBytes = getTotalBytes(request);
323
+ // Set Content-Length if totalBytes is a number (that is not NaN)
324
+ if (typeof totalBytes === 'number' && !Number.isNaN(totalBytes)) {
325
+ contentLengthValue = String(totalBytes);
326
+ }
327
+ }
328
+
329
+ if (contentLengthValue) {
330
+ headers.set('Content-Length', contentLengthValue);
331
+ }
332
+
333
+ // 4.1. Main fetch, step 2.6
334
+ // > If request's referrer policy is the empty string, then set request's referrer policy to the
335
+ // > default referrer policy.
336
+ if (request.referrerPolicy === '') {
337
+ request.referrerPolicy = DEFAULT_REFERRER_POLICY;
338
+ }
339
+
340
+ // 4.1. Main fetch, step 2.7
341
+ // > If request's referrer is not "no-referrer", set request's referrer to the result of invoking
342
+ // > determine request's referrer.
343
+ if (request.referrer && request.referrer !== 'no-referrer') {
344
+ request[INTERNALS].referrer = determineRequestsReferrer(request);
345
+ } else {
346
+ request[INTERNALS].referrer = 'no-referrer';
347
+ }
348
+
349
+ // 4.5. HTTP-network-or-cache fetch, step 6.9
350
+ // > If httpRequest's referrer is a URL, then append `Referer`/httpRequest's referrer, serialized
351
+ // > and isomorphic encoded, to httpRequest's header list.
352
+ if (request[INTERNALS].referrer instanceof URL) {
353
+ headers.set('Referer', request.referrer);
354
+ }
355
+
356
+ // HTTP-network-or-cache fetch step 2.11
357
+ if (!headers.has('User-Agent')) {
358
+ headers.set('User-Agent', 'node-fetch');
359
+ }
360
+
361
+ // HTTP-network-or-cache fetch step 2.15
362
+ if (request.compress && !headers.has('Accept-Encoding')) {
363
+ headers.set('Accept-Encoding', 'gzip, deflate, br');
364
+ }
365
+
366
+ let { agent } = request;
367
+ if (typeof agent === 'function') {
368
+ agent = agent(parsedURL);
369
+ }
370
+
371
+ if (!headers.has('Connection') && !agent) {
372
+ headers.set('Connection', 'close');
373
+ }
374
+
375
+ // HTTP-network fetch step 4.2
376
+ // chunked encoding is handled by Node.js
377
+
378
+ // const search = getSearch(parsedURL);
379
+
380
+ // Pass the full URL directly to request(), but overwrite the following
381
+ // options:
382
+ const options = {
383
+ // Overwrite search to retain trailing ? (issue #776)
384
+ // path: parsedURL.pathname + search,
385
+ // The following options are not expressed in the URL
386
+ // method: request.method,
387
+ headers,
388
+ // insecureHTTPParser: request.insecureHTTPParser,
389
+ // agent
390
+ };
391
+
392
+ return {
393
+ parsedURL,
394
+ options
395
+ };
396
+ };
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Response.js
3
+ *
4
+ * Response class provides content decoding
5
+ */
6
+
7
+ import GLib from '@girs/glib-2.0';
8
+ import Gio from '@girs/gio-2.0';
9
+
10
+ import Headers from './headers.js';
11
+ import Body, { clone, extractContentType } from './body.js';
12
+ import { isRedirect } from './utils/is-redirect.js';
13
+
14
+ import { URL } from '@gjsify/deno-runtime/ext/url/00_url';
15
+ import { Blob } from '@gjsify/deno-runtime/ext/web/09_file';
16
+
17
+ import type { Readable } from 'stream';
18
+
19
+ const INTERNALS = Symbol('Response internals');
20
+
21
+ interface ResponseInit extends globalThis.ResponseInit {
22
+ type?: ResponseType;
23
+ url?: string;
24
+ counter?: number;
25
+ highWaterMark?: number;
26
+ ok?: boolean;
27
+ redirected?: boolean;
28
+ size?: number;
29
+ }
30
+
31
+ /**
32
+ * Response class
33
+ *
34
+ * Ref: https://fetch.spec.whatwg.org/#response-class
35
+ *
36
+ * @param body Readable stream
37
+ * @param opts Response options
38
+ */
39
+ export class Response extends Body implements globalThis.Response {
40
+
41
+ [INTERNALS]: {
42
+ type: ResponseType;
43
+ url: string;
44
+ status: number;
45
+ statusText: string;
46
+ headers: Headers;
47
+ counter: number;
48
+ highWaterMark: number;
49
+ };
50
+
51
+ _inputStream: Gio.InputStream | null = null;
52
+
53
+ constructor(body: BodyInit | Readable | Blob | Buffer | null = null, options: ResponseInit = {}) {
54
+ super(body, options);
55
+
56
+ // eslint-disable-next-line no-eq-null, eqeqeq, no-negated-condition
57
+ const status = options.status != null ? options.status : 200;
58
+
59
+ const headers = new Headers(options.headers);
60
+
61
+ if (body !== null && !headers.has('Content-Type')) {
62
+ const contentType = extractContentType(body, this);
63
+ if (contentType) {
64
+ headers.append('Content-Type', contentType);
65
+ }
66
+ }
67
+
68
+ this[INTERNALS] = {
69
+ type: 'default',
70
+ url: options.url,
71
+ status,
72
+ statusText: options.statusText || '',
73
+ headers,
74
+ counter: options.counter,
75
+ highWaterMark: options.highWaterMark
76
+ };
77
+ }
78
+
79
+ get type() {
80
+ return this[INTERNALS].type;
81
+ }
82
+
83
+ get url() {
84
+ return this[INTERNALS].url || '';
85
+ }
86
+
87
+ get status() {
88
+ return this[INTERNALS].status;
89
+ }
90
+
91
+ /**
92
+ * Convenience property representing if the request ended normally
93
+ */
94
+ get ok() {
95
+ return this[INTERNALS].status >= 200 && this[INTERNALS].status < 300;
96
+ }
97
+
98
+ get redirected() {
99
+ return this[INTERNALS].counter > 0;
100
+ }
101
+
102
+ get statusText() {
103
+ return this[INTERNALS].statusText;
104
+ }
105
+
106
+ get headers() {
107
+ return this[INTERNALS].headers;
108
+ }
109
+
110
+ get highWaterMark() {
111
+ return this[INTERNALS].highWaterMark;
112
+ }
113
+
114
+ /**
115
+ * Clone this response
116
+ *
117
+ * @return Response
118
+ */
119
+ // @ts-ignore
120
+ clone() {
121
+ return new Response(clone(this, this.highWaterMark), {
122
+ type: this.type,
123
+ url: this.url,
124
+ status: this.status,
125
+ statusText: this.statusText,
126
+ headers: this.headers,
127
+ ok: this.ok,
128
+ redirected: this.redirected,
129
+ size: this.size,
130
+ highWaterMark: this.highWaterMark
131
+ });
132
+ }
133
+
134
+ /**
135
+ * @param url The URL that the new response is to originate from.
136
+ * @param status An optional status code for the response (e.g., 302.)
137
+ * @returns A Response object.
138
+ */
139
+ static redirect(url: string, status = 302) {
140
+ if (!isRedirect(status)) {
141
+ throw new RangeError('Failed to execute "redirect" on "response": Invalid status code');
142
+ }
143
+
144
+ return new Response(null, {
145
+ headers: {
146
+ location: new URL(url).toString()
147
+ },
148
+ status
149
+ });
150
+ }
151
+
152
+ static error() {
153
+ const response = new Response(null, { status: 0, statusText: '' });
154
+ response[INTERNALS].type = 'error';
155
+ return response;
156
+ }
157
+
158
+ get [Symbol.toStringTag]() {
159
+ return 'Response';
160
+ }
161
+
162
+ async text(): Promise<string> {
163
+ if (!this._inputStream) {
164
+ return super.text();
165
+ }
166
+
167
+ const outputStream = Gio.MemoryOutputStream.new_resizable();
168
+
169
+ await new Promise<number>((resolve, reject) => {
170
+ outputStream.splice_async(this._inputStream, Gio.OutputStreamSpliceFlags.CLOSE_TARGET | Gio.OutputStreamSpliceFlags.CLOSE_SOURCE, GLib.PRIORITY_DEFAULT, null, (self, res) => {
171
+ try {
172
+ resolve(outputStream.splice_finish(res));
173
+ } catch (error) {
174
+ reject(error);
175
+ }
176
+ });
177
+ });
178
+
179
+
180
+ const bytes = outputStream.steal_as_bytes();
181
+
182
+ return new TextDecoder().decode(bytes.toArray());
183
+ }
184
+ }
185
+
186
+ Object.defineProperties(Response.prototype, {
187
+ type: { enumerable: true },
188
+ url: { enumerable: true },
189
+ status: { enumerable: true },
190
+ ok: { enumerable: true },
191
+ redirected: { enumerable: true },
192
+ statusText: { enumerable: true },
193
+ headers: { enumerable: true },
194
+ clone: { enumerable: true }
195
+ });
196
+
197
+ export default Response;
package/src/test.mts ADDED
@@ -0,0 +1,6 @@
1
+
2
+ import { run } from '@gjsify/unit';
3
+
4
+ import testSuite from './index.spec.js';
5
+
6
+ run({ testSuite });
@@ -0,0 +1 @@
1
+ export * from './system-error.js';
@@ -0,0 +1,11 @@
1
+ export interface SystemError {
2
+ address?: string;
3
+ code: string;
4
+ dest?: string;
5
+ errno: number;
6
+ info?: object;
7
+ message: string;
8
+ path?: string;
9
+ port?: number;
10
+ syscall: string
11
+ };