@gjsify/fetch 0.0.3 → 0.1.0

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 (88) hide show
  1. package/README.md +27 -2
  2. package/globals.mjs +12 -0
  3. package/lib/body.d.ts +69 -0
  4. package/lib/body.js +375 -0
  5. package/lib/errors/abort-error.d.ts +7 -0
  6. package/lib/errors/abort-error.js +9 -0
  7. package/lib/errors/base.d.ts +6 -0
  8. package/lib/errors/base.js +17 -0
  9. package/lib/errors/fetch-error.d.ts +16 -0
  10. package/lib/errors/fetch-error.js +23 -0
  11. package/lib/esm/body.js +104 -56
  12. package/lib/esm/errors/base.js +3 -1
  13. package/lib/esm/headers.js +116 -131
  14. package/lib/esm/index.js +145 -190
  15. package/lib/esm/request.js +42 -41
  16. package/lib/esm/response.js +19 -4
  17. package/lib/esm/utils/blob-from.js +2 -98
  18. package/lib/esm/utils/data-uri.js +23 -0
  19. package/lib/esm/utils/is.js +7 -3
  20. package/lib/esm/utils/multipart-parser.js +5 -2
  21. package/lib/esm/utils/referrer.js +10 -10
  22. package/lib/esm/utils/soup-helpers.js +22 -0
  23. package/lib/headers.d.ts +33 -0
  24. package/lib/headers.js +195 -0
  25. package/lib/index.d.ts +18 -0
  26. package/lib/index.js +205 -0
  27. package/lib/request.d.ts +101 -0
  28. package/lib/request.js +308 -0
  29. package/lib/response.d.ts +73 -0
  30. package/lib/response.js +158 -0
  31. package/lib/types/index.d.ts +1 -0
  32. package/lib/types/index.js +1 -0
  33. package/lib/types/system-error.d.ts +11 -0
  34. package/lib/types/system-error.js +2 -0
  35. package/lib/utils/blob-from.d.ts +2 -0
  36. package/lib/utils/blob-from.js +4 -0
  37. package/lib/utils/data-uri.d.ts +10 -0
  38. package/lib/utils/data-uri.js +27 -0
  39. package/lib/utils/get-search.d.ts +1 -0
  40. package/lib/utils/get-search.js +8 -0
  41. package/lib/utils/is-redirect.d.ts +7 -0
  42. package/lib/utils/is-redirect.js +10 -0
  43. package/lib/utils/is.d.ts +35 -0
  44. package/lib/utils/is.js +74 -0
  45. package/lib/utils/multipart-parser.d.ts +2 -0
  46. package/lib/utils/multipart-parser.js +396 -0
  47. package/lib/utils/referrer.d.ts +76 -0
  48. package/lib/utils/referrer.js +283 -0
  49. package/lib/utils/soup-helpers.d.ts +12 -0
  50. package/lib/utils/soup-helpers.js +25 -0
  51. package/package.json +23 -27
  52. package/src/body.ts +181 -169
  53. package/src/errors/base.ts +3 -1
  54. package/src/headers.ts +155 -202
  55. package/src/index.spec.ts +268 -3
  56. package/src/index.ts +199 -312
  57. package/src/request.ts +84 -75
  58. package/src/response.ts +48 -18
  59. package/src/test.mts +1 -1
  60. package/src/utils/blob-from.ts +4 -164
  61. package/src/utils/data-uri.ts +29 -0
  62. package/src/utils/is.ts +15 -15
  63. package/src/utils/multipart-parser.ts +3 -3
  64. package/src/utils/referrer.ts +11 -11
  65. package/src/utils/soup-helpers.ts +37 -0
  66. package/tsconfig.json +5 -5
  67. package/tsconfig.tsbuildinfo +1 -0
  68. package/lib/cjs/body.js +0 -255
  69. package/lib/cjs/errors/abort-error.js +0 -9
  70. package/lib/cjs/errors/base.js +0 -17
  71. package/lib/cjs/errors/fetch-error.js +0 -21
  72. package/lib/cjs/headers.js +0 -202
  73. package/lib/cjs/index.js +0 -224
  74. package/lib/cjs/request.js +0 -281
  75. package/lib/cjs/response.js +0 -133
  76. package/lib/cjs/types/index.js +0 -1
  77. package/lib/cjs/types/system-error.js +0 -1
  78. package/lib/cjs/utils/blob-from.js +0 -101
  79. package/lib/cjs/utils/get-search.js +0 -11
  80. package/lib/cjs/utils/is-redirect.js +0 -7
  81. package/lib/cjs/utils/is.js +0 -28
  82. package/lib/cjs/utils/multipart-parser.js +0 -353
  83. package/lib/cjs/utils/referrer.js +0 -153
  84. package/test.gjs.js +0 -34758
  85. package/test.gjs.mjs +0 -53177
  86. package/test.node.js +0 -1226
  87. package/test.node.mjs +0 -6294
  88. package/tsconfig.types.json +0 -8
package/src/headers.ts CHANGED
@@ -1,176 +1,108 @@
1
- /**
2
- * Headers.js
3
- *
4
- * Headers class offers convenient helpers
5
- */
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
6
5
 
7
6
  import Soup from '@girs/soup-3.0';
8
- import { URLSearchParams } from '@gjsify/deno-runtime/ext/url/00_url';
7
+ import { validateHeaderName, validateHeaderValue } from '@gjsify/http';
9
8
 
10
- import { types } from 'util';
11
- import * as http from 'http';
12
- import type { IncomingMessage } from 'http';
9
+ const _headers = Symbol('Headers.headers');
13
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
+ }
14
20
 
15
- /* c8 ignore next 9 */
21
+ export default class Headers implements Iterable<[string, string]> {
22
+ [_headers]: Map<string, string[]>;
16
23
 
17
- const validateHeaderName = http.validateHeaderName;
18
- const validateHeaderValue = http.validateHeaderValue;
24
+ constructor(init?: HeadersInit | Headers | null) {
25
+ this[_headers] = new Map();
26
+
27
+ if (init == null) {
28
+ return;
29
+ }
19
30
 
20
- /**
21
- * This Fetch API interface allows you to perform various actions on HTTP request and response headers.
22
- * These actions include retrieving, setting, adding to, and removing.
23
- * A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs.
24
- * You can add to this using methods like append() (see Examples.)
25
- * In all methods of this interface, header names are matched by case-insensitive byte sequence.
26
- *
27
- */
28
- export default class Headers extends URLSearchParams implements globalThis.Headers, Iterable<[string, string]> {
29
- /**
30
- * Headers class
31
- *
32
- * @constructor
33
- * @param init Response headers
34
- */
35
- constructor(init?: HeadersInit) {
36
- // Validate and normalize init object in [name, value(s)][]
37
- let result: string[][] = [];
38
31
  if (init instanceof Headers) {
39
- const raw = init.raw();
40
- for (const [name, values] of Object.entries(raw)) {
41
- result.push(...values.map(value => [name, value]));
32
+ for (const [name, values] of init[_headers]) {
33
+ this[_headers].set(name, [...values]);
42
34
  }
43
- } else if (init == null) { // eslint-disable-line no-eq-null, eqeqeq
44
- // No op
45
- } else if (typeof init === 'object' && !types.isBoxedPrimitive(init)) {
46
- const method = init[Symbol.iterator];
47
- // eslint-disable-next-line no-eq-null, eqeqeq
35
+ return;
36
+ }
37
+
38
+ if (typeof init === 'object' && !isBoxedPrimitive(init)) {
39
+ const method = (init as Iterable<string[]>)[Symbol.iterator];
48
40
  if (method == null) {
49
- // Record<ByteString, ByteString>
50
- result.push(...Object.entries(init));
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
+ }
51
47
  } else {
52
48
  if (typeof method !== 'function') {
53
49
  throw new TypeError('Header pairs must be iterable');
54
50
  }
55
51
 
56
- // Sequence<sequence<ByteString>>
57
- // Note: per spec we have to first exhaust the lists then process them
58
- result = [...(init as string[][])] // TODO check if this works with Objects
59
- .map(pair => {
60
- if (
61
- typeof pair !== 'object' || types.isBoxedPrimitive(pair)
62
- ) {
63
- throw new TypeError('Each header pair must be an iterable object');
64
- }
65
-
66
- return [...pair];
67
- }).map(pair => {
68
- if (pair.length !== 2) {
69
- throw new TypeError('Each header pair must be a name/value tuple');
70
- }
71
-
72
- return [...pair];
73
- });
74
- }
75
- } else {
76
- throw new TypeError('Failed to construct \'Headers\': The provided value is not of type \'(sequence<sequence<ByteString>> or record<ByteString, ByteString>)');
77
- }
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
+ }
78
56
 
79
- // Validate and lowercase
80
- result =
81
- result.length > 0 ?
82
- result.map(([name, value]) => {
83
- validateHeaderName(name);
84
- validateHeaderValue(name, String(value));
85
- return [String(name).toLowerCase(), String(value)];
86
- }) :
87
- undefined;
88
-
89
- super(result);
90
-
91
- // Returning a Proxy that will lowercase key names, validate parameters and sort keys
92
- // eslint-disable-next-line no-constructor-return
93
- return new Proxy(this, {
94
- get(target, p, receiver) {
95
- switch (p) {
96
- case 'append':
97
- case 'set':
98
- return (name: string, value: any) => {
99
- validateHeaderName(name);
100
- validateHeaderValue(name, String(value));
101
- return URLSearchParams.prototype[p].call(
102
- target,
103
- String(name).toLowerCase(),
104
- String(value)
105
- );
106
- };
107
-
108
- case 'delete':
109
- case 'has':
110
- case 'getAll':
111
- return (name: string) => {
112
- validateHeaderName(name);
113
- return URLSearchParams.prototype[p].call(
114
- target,
115
- String(name).toLowerCase()
116
- );
117
- };
118
-
119
- case 'keys':
120
- return () => {
121
- target.sort();
122
- return new Set(URLSearchParams.prototype.keys.call(target)).keys();
123
- };
124
-
125
- default:
126
- return Reflect.get(target, p, receiver);
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]));
127
65
  }
128
66
  }
129
- });
130
- /* c8 ignore next */
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
+ }
131
73
  }
132
74
 
133
- get [Symbol.toStringTag]() {
134
- return this.constructor.name;
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
+ }
135
86
  }
136
87
 
137
- toString() {
138
- return Object.prototype.toString.call(this);
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)]);
139
93
  }
140
94
 
141
- _appendToSoupMessage(message?: Soup.Message, type = Soup.MessageHeadersType.REQUEST) {
142
- const soupHeaders = message ? message.get_request_headers() : new Soup.MessageHeaders(type);
143
- for (const header in this.entries()) {
144
- soupHeaders.append(header, this.get(header));
145
- }
146
- return soupHeaders;
95
+ delete(name: string): void {
96
+ this[_headers].delete(String(name).toLowerCase());
147
97
  }
148
98
 
149
-
150
- static _newFromSoupMessage(message: Soup.Message, type: Soup.MessageHeadersType = Soup.MessageHeadersType.RESPONSE) {
151
- let soupHeaders: Soup.MessageHeaders;
152
- const headers = new Headers();
153
-
154
- if (type === Soup.MessageHeadersType.RESPONSE) {
155
- soupHeaders = message.get_response_headers();
156
- } else if(type === Soup.MessageHeadersType.REQUEST) {
157
- soupHeaders = message.get_request_headers();
158
- } else {
159
- for (const header in message.get_request_headers()) {
160
- headers.append(header, soupHeaders[header]);
161
- }
162
- soupHeaders = message.get_response_headers();
163
- }
164
-
165
- for (const header in soupHeaders) {
166
- headers.append(header, soupHeaders[header]);
167
- }
168
- return headers;
99
+ has(name: string): boolean {
100
+ return this[_headers].has(String(name).toLowerCase());
169
101
  }
170
102
 
171
- get(name: string) {
172
- const values = this.getAll(name);
173
- if (values.length === 0) {
103
+ get(name: string): string | null {
104
+ const values = this[_headers].get(String(name).toLowerCase());
105
+ if (!values || values.length === 0) {
174
106
  return null;
175
107
  }
176
108
 
@@ -182,98 +114,119 @@ export default class Headers extends URLSearchParams implements globalThis.Heade
182
114
  return value;
183
115
  }
184
116
 
185
- forEach(callback, thisArg = undefined) {
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 {
186
126
  for (const name of this.keys()) {
187
127
  Reflect.apply(callback, thisArg, [this.get(name), name, this]);
188
128
  }
189
129
  }
190
130
 
191
- * values() {
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]> {
192
149
  for (const name of this.keys()) {
193
- yield this.get(name);
150
+ yield [name, this.get(name)!];
194
151
  }
195
152
  }
196
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
+
197
166
  /**
198
- *
167
+ * Node-fetch non-spec method: return all headers and their values as arrays.
199
168
  */
200
- * entries(): IterableIterator<[string, string]> {
169
+ raw(): Record<string, string[]> {
170
+ const result: Record<string, string[]> = {};
201
171
  for (const name of this.keys()) {
202
- yield [name, this.get(name)];
172
+ result[name] = this.getAll(name);
203
173
  }
174
+ return result;
204
175
  }
205
176
 
206
- [Symbol.iterator]() {
207
- return this.entries();
208
- }
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
+ }
209
187
 
210
188
  /**
211
- * Node-fetch non-spec method
212
- * returning all headers and their values as array
189
+ * Create a Headers instance from a Soup.Message's headers.
213
190
  */
214
- raw(): Record<string, string[]> {
215
- return [...this.keys()].reduce((result, key) => {
216
- result[key] = this.getAll(key);
217
- return result;
218
- }, {});
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;
219
207
  }
220
208
 
221
209
  /**
222
- * For better console.log(headers) and also to convert Headers into Node.js Request compatible format
210
+ * For better console.log(headers)
223
211
  */
224
212
  [Symbol.for('nodejs.util.inspect.custom')]() {
225
- return [...this.keys()].reduce((result, key) => {
213
+ const result: Record<string, string | string[]> = {};
214
+ for (const key of this.keys()) {
226
215
  const values = this.getAll(key);
227
- // Http.request() only supports string as Host header.
228
- // This hack makes specifying custom Host header possible.
229
216
  if (key === 'host') {
230
217
  result[key] = values[0];
231
218
  } else {
232
219
  result[key] = values.length > 1 ? values : values[0];
233
220
  }
234
-
235
- return result;
236
- }, {});
221
+ }
222
+ return result;
237
223
  }
238
224
  }
239
225
 
240
- /**
241
- * Re-shaping object for Web IDL tests
242
- * Only need to do it for overridden methods
243
- */
244
226
  Object.defineProperties(
245
227
  Headers.prototype,
246
- ['get', 'entries', 'forEach', 'values'].reduce((result, property) => {
228
+ ['get', 'entries', 'forEach', 'values'].reduce((result: PropertyDescriptorMap, property) => {
247
229
  result[property] = { enumerable: true };
248
230
  return result;
249
231
  }, {})
250
232
  );
251
-
252
- /**
253
- * Create a Headers object from an http.IncomingMessage.rawHeaders, ignoring those that do
254
- * not conform to HTTP grammar productions.
255
- * @param headers
256
- */
257
- export function fromRawHeaders(headers: IncomingMessage['rawHeaders'] = []) {
258
- return new Headers(
259
- headers
260
- // Split into pairs
261
- .reduce((result, value, index, array) => {
262
- if (index % 2 === 0) {
263
- result.push(array.slice(index, index + 2));
264
- }
265
-
266
- return result;
267
- }, [])
268
- .filter(([name, value]) => {
269
- try {
270
- validateHeaderName(name);
271
- validateHeaderValue(name, String(value));
272
- return true;
273
- } catch {
274
- return false;
275
- }
276
- })
277
-
278
- );
279
- }