@gjsify/fetch 0.3.21 → 0.4.3

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 (45) hide show
  1. package/lib/esm/_virtual/_rolldown/runtime.js +1 -0
  2. package/lib/esm/body.js +1 -1
  3. package/lib/esm/errors/abort-error.js +1 -1
  4. package/lib/esm/errors/base.js +1 -1
  5. package/lib/esm/errors/fetch-error.js +1 -1
  6. package/lib/esm/headers.js +1 -1
  7. package/lib/esm/index.js +1 -1
  8. package/lib/esm/request.js +1 -1
  9. package/lib/esm/response.js +1 -1
  10. package/lib/esm/utils/data-uri.js +1 -1
  11. package/lib/esm/utils/get-search.js +1 -1
  12. package/lib/esm/utils/is-redirect.js +1 -1
  13. package/lib/esm/utils/is.js +1 -1
  14. package/lib/esm/utils/multipart-parser.js +1 -1
  15. package/lib/esm/utils/referrer.js +1 -1
  16. package/lib/esm/utils/soup-helpers.js +1 -1
  17. package/lib/esm/xhr.js +1 -1
  18. package/package.json +61 -58
  19. package/src/body.ts +0 -448
  20. package/src/errors/abort-error.ts +0 -10
  21. package/src/errors/base.ts +0 -22
  22. package/src/errors/fetch-error.ts +0 -26
  23. package/src/headers.ts +0 -232
  24. package/src/index.spec.ts +0 -339
  25. package/src/index.ts +0 -316
  26. package/src/register/fetch.ts +0 -16
  27. package/src/register/xhr.ts +0 -20
  28. package/src/register.ts +0 -5
  29. package/src/request.ts +0 -423
  30. package/src/response.ts +0 -227
  31. package/src/test.browser.mts +0 -130
  32. package/src/test.mts +0 -9
  33. package/src/types/index.ts +0 -1
  34. package/src/types/system-error.ts +0 -11
  35. package/src/utils/blob-from.ts +0 -8
  36. package/src/utils/data-uri.ts +0 -29
  37. package/src/utils/get-search.ts +0 -9
  38. package/src/utils/is-redirect.ts +0 -11
  39. package/src/utils/is.ts +0 -88
  40. package/src/utils/multipart-parser.ts +0 -448
  41. package/src/utils/referrer.ts +0 -350
  42. package/src/utils/soup-helpers.ts +0 -37
  43. package/src/xhr.ts +0 -270
  44. package/tsconfig.json +0 -22
  45. package/tsconfig.tsbuildinfo +0 -1
package/src/headers.ts DELETED
@@ -1,232 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- // Adapted from node-fetch (https://github.com/node-fetch/node-fetch/blob/main/src/headers.js)
3
- // Copyright (c) node-fetch contributors. MIT license.
4
- // Modifications: Standalone implementation using internal Map, libsoup integration
5
-
6
- import Soup from '@girs/soup-3.0';
7
- import { validateHeaderName, validateHeaderValue } from '@gjsify/http/validators';
8
-
9
- const _headers = Symbol('Headers.headers');
10
-
11
- function isBoxedPrimitive(val: unknown): boolean {
12
- return (
13
- val instanceof String ||
14
- val instanceof Number ||
15
- val instanceof Boolean ||
16
- (typeof Symbol !== 'undefined' && val instanceof Symbol) ||
17
- (typeof BigInt !== 'undefined' && val instanceof (BigInt as unknown as typeof Number))
18
- );
19
- }
20
-
21
- export default class Headers implements Iterable<[string, string]> {
22
- [_headers]: Map<string, string[]>;
23
-
24
- constructor(init?: HeadersInit | Headers | null) {
25
- this[_headers] = new Map();
26
-
27
- if (init == null) {
28
- return;
29
- }
30
-
31
- if (init instanceof Headers) {
32
- for (const [name, values] of init[_headers]) {
33
- this[_headers].set(name, [...values]);
34
- }
35
- return;
36
- }
37
-
38
- if (typeof init === 'object' && !isBoxedPrimitive(init)) {
39
- const method = (init as Iterable<string[]>)[Symbol.iterator];
40
- if (method == null) {
41
- // Record<string, string>
42
- for (const [name, value] of Object.entries(init)) {
43
- validateHeaderName(name);
44
- validateHeaderValue(name, String(value));
45
- this.append(name, String(value));
46
- }
47
- } else {
48
- if (typeof method !== 'function') {
49
- throw new TypeError('Header pairs must be iterable');
50
- }
51
-
52
- for (const pair of init as Iterable<string[]>) {
53
- if (typeof pair !== 'object' || isBoxedPrimitive(pair)) {
54
- throw new TypeError('Each header pair must be an iterable object');
55
- }
56
-
57
- const arr = [...pair];
58
- if (arr.length !== 2) {
59
- throw new TypeError('Each header pair must be a name/value tuple');
60
- }
61
-
62
- validateHeaderName(arr[0]);
63
- validateHeaderValue(arr[0], String(arr[1]));
64
- this.append(arr[0], String(arr[1]));
65
- }
66
- }
67
- } else {
68
- throw new TypeError(
69
- 'Failed to construct \'Headers\': The provided value is not of type ' +
70
- '\'(sequence<sequence<ByteString>> or record<ByteString, ByteString>)\''
71
- );
72
- }
73
- }
74
-
75
- append(name: string, value: string): void {
76
- validateHeaderName(name);
77
- validateHeaderValue(name, value);
78
- const lowerName = String(name).toLowerCase();
79
- const strValue = String(value);
80
- const existing = this[_headers].get(lowerName);
81
- if (existing) {
82
- existing.push(strValue);
83
- } else {
84
- this[_headers].set(lowerName, [strValue]);
85
- }
86
- }
87
-
88
- set(name: string, value: string): void {
89
- validateHeaderName(name);
90
- validateHeaderValue(name, value);
91
- const lowerName = String(name).toLowerCase();
92
- this[_headers].set(lowerName, [String(value)]);
93
- }
94
-
95
- delete(name: string): void {
96
- this[_headers].delete(String(name).toLowerCase());
97
- }
98
-
99
- has(name: string): boolean {
100
- return this[_headers].has(String(name).toLowerCase());
101
- }
102
-
103
- get(name: string): string | null {
104
- const values = this[_headers].get(String(name).toLowerCase());
105
- if (!values || values.length === 0) {
106
- return null;
107
- }
108
-
109
- let value = values.join(', ');
110
- if (/^content-encoding$/i.test(name)) {
111
- value = value.toLowerCase();
112
- }
113
-
114
- return value;
115
- }
116
-
117
- getAll(name: string): string[] {
118
- return this[_headers].get(String(name).toLowerCase()) ?? [];
119
- }
120
-
121
- getSetCookie(): string[] {
122
- return this[_headers].get('set-cookie') ?? [];
123
- }
124
-
125
- forEach(callback: (value: string, name: string, parent: Headers) => void, thisArg?: unknown): void {
126
- for (const name of this.keys()) {
127
- Reflect.apply(callback, thisArg, [this.get(name), name, this]);
128
- }
129
- }
130
-
131
- *keys(): IterableIterator<string> {
132
- const sorted = [...this[_headers].keys()].sort();
133
- const seen = new Set<string>();
134
- for (const key of sorted) {
135
- if (!seen.has(key)) {
136
- seen.add(key);
137
- yield key;
138
- }
139
- }
140
- }
141
-
142
- *values(): IterableIterator<string> {
143
- for (const name of this.keys()) {
144
- yield this.get(name)!;
145
- }
146
- }
147
-
148
- *entries(): IterableIterator<[string, string]> {
149
- for (const name of this.keys()) {
150
- yield [name, this.get(name)!];
151
- }
152
- }
153
-
154
- [Symbol.iterator](): IterableIterator<[string, string]> {
155
- return this.entries();
156
- }
157
-
158
- get [Symbol.toStringTag](): string {
159
- return 'Headers';
160
- }
161
-
162
- toString(): string {
163
- return Object.prototype.toString.call(this);
164
- }
165
-
166
- /**
167
- * Node-fetch non-spec method: return all headers and their values as arrays.
168
- */
169
- raw(): Record<string, string[]> {
170
- const result: Record<string, string[]> = {};
171
- for (const name of this.keys()) {
172
- result[name] = this.getAll(name);
173
- }
174
- return result;
175
- }
176
-
177
- /**
178
- * Append all headers to a Soup.Message for sending.
179
- */
180
- _appendToSoupMessage(message?: Soup.Message, type = Soup.MessageHeadersType.REQUEST): Soup.MessageHeaders {
181
- const soupHeaders = message ? message.get_request_headers() : new Soup.MessageHeaders(type);
182
- for (const [name, value] of this.entries()) {
183
- soupHeaders.append(name, value);
184
- }
185
- return soupHeaders;
186
- }
187
-
188
- /**
189
- * Create a Headers instance from a Soup.Message's headers.
190
- */
191
- static _newFromSoupMessage(message: Soup.Message, type: Soup.MessageHeadersType = Soup.MessageHeadersType.RESPONSE): Headers {
192
- const headers = new Headers();
193
- let soupHeaders: Soup.MessageHeaders;
194
-
195
- if (type === Soup.MessageHeadersType.RESPONSE) {
196
- soupHeaders = message.get_response_headers();
197
- } else {
198
- soupHeaders = message.get_request_headers();
199
- }
200
-
201
- // Soup.MessageHeaders.foreach iterates all header name/value pairs
202
- soupHeaders.foreach((name: string, value: string) => {
203
- headers.append(name, value);
204
- });
205
-
206
- return headers;
207
- }
208
-
209
- /**
210
- * For better console.log(headers)
211
- */
212
- [Symbol.for('nodejs.util.inspect.custom')]() {
213
- const result: Record<string, string | string[]> = {};
214
- for (const key of this.keys()) {
215
- const values = this.getAll(key);
216
- if (key === 'host') {
217
- result[key] = values[0];
218
- } else {
219
- result[key] = values.length > 1 ? values : values[0];
220
- }
221
- }
222
- return result;
223
- }
224
- }
225
-
226
- Object.defineProperties(
227
- Headers.prototype,
228
- ['get', 'entries', 'forEach', 'values'].reduce((result: PropertyDescriptorMap, property) => {
229
- result[property] = { enumerable: true };
230
- return result;
231
- }, {})
232
- );
package/src/index.spec.ts DELETED
@@ -1,339 +0,0 @@
1
- import { describe, it, expect, on } from '@gjsify/unit';
2
-
3
- // fetch / Headers / Request / Response / FormData are accessed off globalThis:
4
- // on Node they are native, on GJS they are installed by
5
- // `@gjsify/fetch/register` (pulled in automatically by `--globals auto`).
6
- // XMLHttpRequest is GJS-only and is likewise read from globalThis inside an
7
- // on('Gjs', …) gate further below.
8
-
9
- export default async () => {
10
-
11
- await describe('fetch', async () => {
12
- await it('fetch should be a function', async () => {
13
- expect(typeof fetch).toBe("function");
14
- });
15
- });
16
-
17
- await describe('Headers', async () => {
18
- await it('should create empty headers', async () => {
19
- const h = new Headers();
20
- expect([...h.entries()].length).toBe(0);
21
- });
22
-
23
- await it('should set and get headers', async () => {
24
- const h = new Headers();
25
- h.set('Content-Type', 'text/plain');
26
- expect(h.get('content-type')).toBe('text/plain');
27
- });
28
-
29
- await it('should be case-insensitive', async () => {
30
- const h = new Headers({ 'X-Custom': 'value' });
31
- expect(h.get('x-custom')).toBe('value');
32
- expect(h.has('X-CUSTOM')).toBe(true);
33
- });
34
-
35
- await it('should append multiple values', async () => {
36
- const h = new Headers();
37
- h.append('Set-Cookie', 'a=1');
38
- h.append('Set-Cookie', 'b=2');
39
- expect(h.get('set-cookie')).toBe('a=1, b=2');
40
- });
41
-
42
- await it('should delete headers', async () => {
43
- const h = new Headers({ 'X-Remove': 'yes' });
44
- h.delete('x-remove');
45
- expect(h.has('x-remove')).toBe(false);
46
- });
47
-
48
- await it('should construct from array pairs', async () => {
49
- const h = new Headers([['a', '1'], ['b', '2']]);
50
- expect(h.get('a')).toBe('1');
51
- expect(h.get('b')).toBe('2');
52
- });
53
-
54
- await it('should construct from another Headers', async () => {
55
- const h1 = new Headers({ 'x-foo': 'bar' });
56
- const h2 = new Headers(h1);
57
- expect(h2.get('x-foo')).toBe('bar');
58
- });
59
-
60
- await it('should iterate entries in sorted order', async () => {
61
- const h = new Headers({ 'z-header': '1', 'a-header': '2' });
62
- const keys = [...h.keys()];
63
- expect(keys[0]).toBe('a-header');
64
- expect(keys[1]).toBe('z-header');
65
- });
66
-
67
- // raw() is a node-fetch extension, not part of the standard Web API
68
- await on('Gjs', async () => {
69
- await it('should support raw() method', async () => {
70
- const h = new Headers();
71
- h.append('x-multi', 'a');
72
- h.append('x-multi', 'b');
73
- const raw = (h as any).raw();
74
- expect(raw['x-multi'].length).toBe(2);
75
- expect(raw['x-multi'][0]).toBe('a');
76
- expect(raw['x-multi'][1]).toBe('b');
77
- });
78
- });
79
-
80
- await it('should have correct Symbol.toStringTag', async () => {
81
- const h = new Headers();
82
- expect(Object.prototype.toString.call(h)).toBe('[object Headers]');
83
- });
84
- });
85
-
86
- await describe('Headers forEach', async () => {
87
- await it('should iterate all headers with forEach', async () => {
88
- const h = new Headers({ 'a': '1', 'b': '2' });
89
- const collected: string[] = [];
90
- h.forEach((value, key) => { collected.push(`${key}:${value}`); });
91
- expect(collected.length).toBe(2);
92
- expect(collected[0]).toBe('a:1');
93
- expect(collected[1]).toBe('b:2');
94
- });
95
-
96
- await it('should return correct values() iterator', async () => {
97
- const h = new Headers({ 'x': 'hello', 'y': 'world' });
98
- const values = [...h.values()];
99
- expect(values.length).toBe(2);
100
- });
101
-
102
- await it('should be iterable with for...of', async () => {
103
- const h = new Headers({ 'content-type': 'text/plain', 'x-custom': 'val' });
104
- const entries: [string, string][] = [];
105
- for (const [k, v] of h) entries.push([k, v]);
106
- expect(entries.length).toBe(2);
107
- expect(entries[0][0]).toBe('content-type');
108
- expect(entries[1][0]).toBe('x-custom');
109
- });
110
-
111
- await it('should support spread operator', async () => {
112
- const h = new Headers({ 'a': '1' });
113
- const arr = [...h];
114
- expect(arr.length).toBe(1);
115
- expect(arr[0][0]).toBe('a');
116
- expect(arr[0][1]).toBe('1');
117
- });
118
-
119
- await it('should throw on invalid header name in set()', async () => {
120
- const h = new Headers();
121
- expect(() => h.set('invalid header', 'value')).toThrow();
122
- });
123
-
124
- await it('should throw on invalid header name in append()', async () => {
125
- const h = new Headers();
126
- expect(() => h.append('invalid header', 'value')).toThrow();
127
- });
128
-
129
- await it('should support entries() for destructuring', async () => {
130
- const h = new Headers({ 'host': 'example.com', 'accept': '*/*' });
131
- const obj: Record<string, string> = {};
132
- for (const [k, v] of h.entries()) obj[k] = v;
133
- expect(obj['accept']).toBe('*/*');
134
- expect(obj['host']).toBe('example.com');
135
- });
136
- });
137
-
138
- await describe('Request', async () => {
139
- await it('should create a request with URL string', async () => {
140
- const r = new Request('https://example.com');
141
- expect(r.url).toBe('https://example.com/');
142
- expect(r.method).toBe('GET');
143
- });
144
-
145
- await it('should normalize method to uppercase', async () => {
146
- const r = new Request('https://example.com', { method: 'post' });
147
- expect(r.method).toBe('POST');
148
- });
149
-
150
- await it('should set custom headers', async () => {
151
- const r = new Request('https://example.com', {
152
- headers: { 'X-Custom': 'test' }
153
- });
154
- expect(r.headers.get('x-custom')).toBe('test');
155
- });
156
-
157
- await it('should default redirect to "follow"', async () => {
158
- const r = new Request('https://example.com');
159
- expect(r.redirect).toBe('follow');
160
- });
161
-
162
- await it('should have a signal property', async () => {
163
- const r = new Request('https://example.com');
164
- expect(r.signal).toBeDefined();
165
- });
166
-
167
- await it('should clone a request', async () => {
168
- const r = new Request('https://example.com', {
169
- method: 'POST',
170
- headers: { 'X-Test': 'value' },
171
- });
172
- const clone = r.clone();
173
- expect(clone.url).toBe(r.url);
174
- expect(clone.method).toBe('POST');
175
- expect(clone.headers.get('x-test')).toBe('value');
176
- });
177
-
178
- // Firefox returns a non-null body for new Request(url, { body: null }) — likely
179
- // an empty ReadableStream — contrary to the spec (body: null → r.body === null).
180
- // Guard to Node.js + GJS where spec-correct behavior is verified.
181
- await on(['Node.js', 'Gjs'], async () => {
182
- await it('should create request with null body', async () => {
183
- const r = new Request('https://example.com', { body: null });
184
- expect(r.body).toBeNull();
185
- });
186
- });
187
- });
188
-
189
- await describe('Response', async () => {
190
- await it('should create a response with defaults', async () => {
191
- const r = new Response();
192
- expect(r.status).toBe(200);
193
- expect(r.ok).toBe(true);
194
- });
195
-
196
- await it('should create a response with custom status', async () => {
197
- const r = new Response(null, { status: 404 });
198
- expect(r.status).toBe(404);
199
- expect(r.ok).toBe(false);
200
- });
201
-
202
- await it('should parse text body', async () => {
203
- const r = new Response('hello world');
204
- const text = await r.text();
205
- expect(text).toBe('hello world');
206
- });
207
-
208
- await it('should parse json body', async () => {
209
- const r = new Response('{"key": "value"}');
210
- const json = await r.json() as { key: string };
211
- expect(json.key).toBe('value');
212
- });
213
-
214
- await it('should parse arrayBuffer body', async () => {
215
- const r = new Response('test');
216
- const ab = await r.arrayBuffer();
217
- expect(ab.byteLength).toBe(4);
218
- });
219
-
220
- await it('should track bodyUsed', async () => {
221
- const r = new Response('data');
222
- expect(r.bodyUsed).toBe(false);
223
- await r.text();
224
- expect(r.bodyUsed).toBe(true);
225
- });
226
-
227
- await it('Response.error() should return error response', async () => {
228
- const r = Response.error();
229
- expect(r.type).toBe('error');
230
- expect(r.status).toBe(0);
231
- });
232
-
233
- await it('Response.redirect() should create redirect', async () => {
234
- const r = Response.redirect('https://example.com', 301);
235
- expect(r.status).toBe(301);
236
- expect(r.headers.get('location')).toBe('https://example.com/');
237
- });
238
-
239
- await it('Response.json() should create JSON response', async () => {
240
- const r = Response.json({ message: 'ok' });
241
- expect(r.status).toBe(200);
242
- const ct = r.headers.get('content-type') || '';
243
- expect(ct.includes('application/json')).toBe(true);
244
- const data = await r.json() as { message: string };
245
- expect(data.message).toBe('ok');
246
- });
247
-
248
- await it('should clone a response', async () => {
249
- const r = new Response('clone test');
250
- const c = r.clone();
251
- const text1 = await r.text();
252
- const text2 = await c.text();
253
- expect(text1).toBe('clone test');
254
- expect(text2).toBe('clone test');
255
- });
256
-
257
- await it('should have correct statusText', async () => {
258
- const r = new Response(null, { status: 201, statusText: 'Created' });
259
- expect(r.statusText).toBe('Created');
260
- });
261
-
262
- await it('should have correct type for normal response', async () => {
263
- const r = new Response();
264
- expect(r.type).toBe('default');
265
- });
266
-
267
- await it('should have correct headers property', async () => {
268
- const r = new Response(null, { headers: { 'X-Test': 'yes' } });
269
- expect(r.headers.get('x-test')).toBe('yes');
270
- });
271
- });
272
-
273
- await describe('data: URI', async () => {
274
- await it('should fetch a data: URI with text', async () => {
275
- const r = await fetch('data:text/plain,hello%20world');
276
- expect(r.status).toBe(200);
277
- const text = await r.text();
278
- expect(text).toBe('hello world');
279
- });
280
-
281
- await it('should fetch a base64 data: URI', async () => {
282
- const r = await fetch('data:text/plain;base64,aGVsbG8=');
283
- const text = await r.text();
284
- expect(text).toBe('hello');
285
- });
286
- });
287
-
288
- await on('Gjs', async () => {
289
- await describe('XMLHttpRequest responseType', async () => {
290
- // Regression: Excalibur.js sets responseType='arraybuffer' for audio and
291
- // 'blob' for images. Before this fix XHR ignored responseType and always
292
- // returned text, which crashed gst_memory_new_wrapped on audio decode and
293
- // broke URL.createObjectURL on image load.
294
- //
295
- // XMLHttpRequest is accessed off globalThis (not imported). On GJS it's
296
- // registered by `@gjsify/fetch/register/xhr` (pulled in by --globals
297
- // auto when the detector sees `new XMLHttpRequest()`); on Node there
298
- // is no native XMLHttpRequest, so the suite is gated with on('Gjs', …).
299
- const XHR = (globalThis as any).XMLHttpRequest;
300
-
301
- const runXhr = (init: (xhr: any) => void) => new Promise<any>((resolve, reject) => {
302
- const xhr = new XHR();
303
- xhr.open('GET', 'data:text/plain;base64,aGVsbG8=');
304
- init(xhr);
305
- xhr.onload = () => resolve(xhr);
306
- xhr.onerror = () => reject(new Error('xhr error'));
307
- xhr.send();
308
- });
309
-
310
- await it('responseType="arraybuffer" yields ArrayBuffer', async () => {
311
- const xhr = await runXhr((x) => { x.responseType = 'arraybuffer'; });
312
- expect(xhr.response instanceof ArrayBuffer).toBe(true);
313
- expect((xhr.response as ArrayBuffer).byteLength).toBe(5);
314
- });
315
-
316
- await it('responseType="text" yields decoded string', async () => {
317
- const xhr = await runXhr((x) => { x.responseType = 'text'; });
318
- expect(xhr.response).toBe('hello');
319
- expect(xhr.responseText).toBe('hello');
320
- });
321
-
322
- await it('default responseType "" yields text', async () => {
323
- const xhr = await runXhr(() => { /* responseType left at "" */ });
324
- expect(xhr.response).toBe('hello');
325
- expect(xhr.responseText).toBe('hello');
326
- });
327
-
328
- await it('responseType="blob" attaches _tmpPath for URL.createObjectURL', async () => {
329
- const xhr = await runXhr((x) => { x.responseType = 'blob'; });
330
- const blob = xhr.response as Blob & { _tmpPath?: string };
331
- expect(blob instanceof Blob).toBe(true);
332
- expect(typeof blob._tmpPath).toBe('string');
333
- const url = URL.createObjectURL(blob);
334
- expect(url.startsWith('file://')).toBe(true);
335
- URL.revokeObjectURL(url);
336
- });
337
- });
338
- });
339
- };