@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
@@ -0,0 +1,255 @@
1
+ import { URLSearchParams } from "@gjsify/deno-runtime/ext/url/00_url";
2
+ import { Blob } from "@gjsify/deno-runtime/ext/web/09_file";
3
+ import { PassThrough, pipeline as pipelineCb, Readable, Stream } from "stream";
4
+ import { ReadableStream as StreamWebReadableStream } from "stream/web";
5
+ import { types, deprecate, promisify } from "util";
6
+ import { Buffer } from "buffer";
7
+ import { FormData, formDataToBlob } from "formdata-polyfill/esm.min.js";
8
+ import { FetchError } from "./errors/fetch-error.js";
9
+ import { FetchBaseError } from "./errors/base.js";
10
+ import { isBlob, isURLSearchParameters } from "./utils/is.js";
11
+ const pipeline = promisify(pipelineCb);
12
+ const INTERNALS = Symbol("Body internals");
13
+ class Body {
14
+ [INTERNALS] = {
15
+ body: null,
16
+ stream: null,
17
+ boundary: "",
18
+ disturbed: false,
19
+ error: null
20
+ };
21
+ size = 0;
22
+ constructor(body, options = { size: 0 }) {
23
+ this.size = options.size || 0;
24
+ if (body === null) {
25
+ this[INTERNALS].body = null;
26
+ } else if (isURLSearchParameters(body)) {
27
+ this[INTERNALS].body = Buffer.from(body.toString());
28
+ } else if (isBlob(body)) {
29
+ } else if (Buffer.isBuffer(body)) {
30
+ } else if (types.isAnyArrayBuffer(body)) {
31
+ this[INTERNALS].body = Buffer.from(body);
32
+ } else if (ArrayBuffer.isView(body)) {
33
+ this[INTERNALS].body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
34
+ } else if (body instanceof Readable) {
35
+ this[INTERNALS].body = body;
36
+ } else if (body instanceof ReadableStream || body instanceof StreamWebReadableStream) {
37
+ this[INTERNALS].body = Readable.fromWeb(body);
38
+ } else if (body instanceof FormData) {
39
+ this[INTERNALS].body = formDataToBlob(body);
40
+ this[INTERNALS].boundary = this[INTERNALS].body.type.split("=")[1];
41
+ } else if (typeof body === "string") {
42
+ this[INTERNALS].body = Buffer.from(body);
43
+ } else if (body instanceof URLSearchParams) {
44
+ this[INTERNALS].body = Buffer.from(body.toString());
45
+ } else {
46
+ console.warn(`Unknown body type "${typeof body}", try to parse the body to string!`);
47
+ this[INTERNALS].body = Readable.from(typeof body.toString === "function" ? body.toString() : body);
48
+ }
49
+ if (Buffer.isBuffer(body)) {
50
+ this[INTERNALS].stream = Readable.from(body);
51
+ } else if (isBlob(body)) {
52
+ this[INTERNALS].stream = Readable.from(body.stream());
53
+ } else if (body instanceof Readable) {
54
+ this[INTERNALS].stream = body;
55
+ }
56
+ if (body instanceof Stream) {
57
+ body.on("error", (error_) => {
58
+ const error = error_ instanceof FetchBaseError ? error_ : new FetchError(`Invalid response body while trying to fetch ${this.url}: ${error_.message}`, "system", error_);
59
+ this[INTERNALS].error = error;
60
+ });
61
+ }
62
+ }
63
+ get body() {
64
+ return Readable.toWeb(this[INTERNALS].stream);
65
+ }
66
+ get _stream() {
67
+ return this[INTERNALS].stream;
68
+ }
69
+ get bodyUsed() {
70
+ return this[INTERNALS].disturbed;
71
+ }
72
+ /**
73
+ * Decode response as ArrayBuffer
74
+ *
75
+ * @return Promise
76
+ */
77
+ async arrayBuffer() {
78
+ const { buffer, byteOffset, byteLength } = await consumeBody(this);
79
+ return buffer.slice(byteOffset, byteOffset + byteLength);
80
+ }
81
+ async formData() {
82
+ const ct = this.headers?.get("content-type");
83
+ if (ct.startsWith("application/x-www-form-urlencoded")) {
84
+ const formData = new FormData();
85
+ const parameters = new URLSearchParams(await this.text());
86
+ for (const [name, value] of parameters) {
87
+ formData.append(name, value);
88
+ }
89
+ return formData;
90
+ }
91
+ const { toFormData } = await import("./utils/multipart-parser.js");
92
+ return toFormData(this.body, ct);
93
+ }
94
+ /**
95
+ * Return raw response as Blob
96
+ *
97
+ * @return Promise
98
+ */
99
+ // @ts-ignore
100
+ async blob() {
101
+ const ct = this.headers?.get("content-type") || this[INTERNALS].body && this[INTERNALS].body.type || "";
102
+ const buf = await this.arrayBuffer();
103
+ return new Blob([buf], {
104
+ type: ct
105
+ });
106
+ }
107
+ /**
108
+ * Decode response as json
109
+ *
110
+ * @return Promise
111
+ */
112
+ async json() {
113
+ const text = await this.text();
114
+ return JSON.parse(text);
115
+ }
116
+ /**
117
+ * Decode response as text
118
+ *
119
+ * @return Promise
120
+ */
121
+ async text() {
122
+ const buffer = await consumeBody(this);
123
+ return new TextDecoder().decode(buffer);
124
+ }
125
+ }
126
+ Object.defineProperties(Body.prototype, {
127
+ body: { enumerable: true },
128
+ bodyUsed: { enumerable: true },
129
+ arrayBuffer: { enumerable: true },
130
+ blob: { enumerable: true },
131
+ json: { enumerable: true },
132
+ text: { enumerable: true },
133
+ data: { get: deprecate(
134
+ () => {
135
+ },
136
+ "data doesn't exist, use json(), text(), arrayBuffer(), or body instead",
137
+ "https://github.com/node-fetch/node-fetch/issues/1000 (response)"
138
+ ) }
139
+ });
140
+ async function consumeBody(data) {
141
+ if (data[INTERNALS].disturbed) {
142
+ throw new TypeError(`body used already for: ${data.url}`);
143
+ }
144
+ data[INTERNALS].disturbed = true;
145
+ if (data[INTERNALS].error) {
146
+ throw data[INTERNALS].error;
147
+ }
148
+ const { _stream: body } = data;
149
+ if (body === null) {
150
+ return Buffer.alloc(0);
151
+ }
152
+ if (!(body instanceof Stream)) {
153
+ return Buffer.alloc(0);
154
+ }
155
+ const accum = [];
156
+ let accumBytes = 0;
157
+ try {
158
+ for await (const chunk of body) {
159
+ if (data.size > 0 && accumBytes + chunk.length > data.size) {
160
+ const error = new FetchError(`content size at ${data.url} over limit: ${data.size}`, "max-size");
161
+ body.destroy(error);
162
+ throw error;
163
+ }
164
+ accumBytes += chunk.length;
165
+ accum.push(chunk);
166
+ }
167
+ } catch (error) {
168
+ const error_ = error instanceof FetchBaseError ? error : new FetchError(`Invalid response body while trying to fetch ${data.url}: ${error.message}`, "system", error);
169
+ throw error_;
170
+ }
171
+ if (body.readableEnded === true || body._readableState.ended === true) {
172
+ try {
173
+ if (accum.every((c) => typeof c === "string")) {
174
+ return Buffer.from(accum.join(""));
175
+ }
176
+ return Buffer.concat(accum, accumBytes);
177
+ } catch (error) {
178
+ throw new FetchError(`Could not create Buffer from response body for ${data.url}: ${error.message}`, "system", error);
179
+ }
180
+ } else {
181
+ throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`);
182
+ }
183
+ }
184
+ const clone = (instance, highWaterMark) => {
185
+ let p1;
186
+ let p2;
187
+ let { body } = instance[INTERNALS];
188
+ if (instance.bodyUsed) {
189
+ throw new Error("cannot clone body after it is used");
190
+ }
191
+ if (body instanceof Stream && typeof body.getBoundary !== "function") {
192
+ p1 = new PassThrough({ highWaterMark });
193
+ p2 = new PassThrough({ highWaterMark });
194
+ body.pipe(p1);
195
+ body.pipe(p2);
196
+ instance[INTERNALS].stream = p1;
197
+ body = p2;
198
+ }
199
+ return body;
200
+ };
201
+ const extractContentType = (body, request) => {
202
+ if (body === null) {
203
+ return null;
204
+ }
205
+ if (typeof body === "string") {
206
+ return "text/plain;charset=UTF-8";
207
+ }
208
+ if (isURLSearchParameters(body)) {
209
+ return "application/x-www-form-urlencoded;charset=UTF-8";
210
+ }
211
+ if (isBlob(body)) {
212
+ return body.type || null;
213
+ }
214
+ if (Buffer.isBuffer(body) || types.isAnyArrayBuffer(body) || ArrayBuffer.isView(body)) {
215
+ return null;
216
+ }
217
+ if (body instanceof FormData) {
218
+ return `multipart/form-data; boundary=${request[INTERNALS].boundary}`;
219
+ }
220
+ if (body instanceof Stream) {
221
+ return null;
222
+ }
223
+ return "text/plain;charset=UTF-8";
224
+ };
225
+ const getTotalBytes = (request) => {
226
+ const { body } = request[INTERNALS];
227
+ if (body === null) {
228
+ return 0;
229
+ }
230
+ if (isBlob(body)) {
231
+ return body.size;
232
+ }
233
+ if (Buffer.isBuffer(body)) {
234
+ return body.length;
235
+ }
236
+ if (body && typeof body.getLengthSync === "function") {
237
+ const anyBody = body;
238
+ return anyBody.hasKnownLength && anyBody.hasKnownLength() ? anyBody.getLengthSync() : null;
239
+ }
240
+ return null;
241
+ };
242
+ const writeToStream = async (dest, { body }) => {
243
+ if (body === null) {
244
+ dest.end();
245
+ } else {
246
+ await pipeline(body, dest);
247
+ }
248
+ };
249
+ export {
250
+ clone,
251
+ Body as default,
252
+ extractContentType,
253
+ getTotalBytes,
254
+ writeToStream
255
+ };
@@ -0,0 +1,9 @@
1
+ import { FetchBaseError } from "./base.js";
2
+ class AbortError extends FetchBaseError {
3
+ constructor(message, type = "aborted") {
4
+ super(message, type);
5
+ }
6
+ }
7
+ export {
8
+ AbortError
9
+ };
@@ -0,0 +1,17 @@
1
+ class FetchBaseError extends Error {
2
+ type;
3
+ constructor(message, type) {
4
+ super(message);
5
+ Error.captureStackTrace(this, this.constructor);
6
+ this.type = type;
7
+ }
8
+ get name() {
9
+ return this.constructor.name;
10
+ }
11
+ get [Symbol.toStringTag]() {
12
+ return this.constructor.name;
13
+ }
14
+ }
15
+ export {
16
+ FetchBaseError
17
+ };
@@ -0,0 +1,21 @@
1
+ import { FetchBaseError } from "./base.js";
2
+ class FetchError extends FetchBaseError {
3
+ code;
4
+ errno;
5
+ erroredSysCall;
6
+ /**
7
+ * @param message Error message for human
8
+ * @param type Error type for machine
9
+ * @param systemError For Node.js system error
10
+ */
11
+ constructor(message, type, systemError) {
12
+ super(message, type);
13
+ if (systemError) {
14
+ this.code = this.errno = systemError.code;
15
+ this.erroredSysCall = systemError.syscall;
16
+ }
17
+ }
18
+ }
19
+ export {
20
+ FetchError
21
+ };
@@ -0,0 +1,202 @@
1
+ import Soup from "@girs/soup-3.0";
2
+ import { URLSearchParams } from "@gjsify/deno-runtime/ext/url/00_url";
3
+ import { types } from "util";
4
+ import * as http from "http";
5
+ const validateHeaderName = http.validateHeaderName;
6
+ const validateHeaderValue = http.validateHeaderValue;
7
+ class Headers extends URLSearchParams {
8
+ /**
9
+ * Headers class
10
+ *
11
+ * @constructor
12
+ * @param init Response headers
13
+ */
14
+ constructor(init) {
15
+ let result = [];
16
+ if (init instanceof Headers) {
17
+ const raw = init.raw();
18
+ for (const [name, values] of Object.entries(raw)) {
19
+ result.push(...values.map((value) => [name, value]));
20
+ }
21
+ } else if (init == null) {
22
+ } else if (typeof init === "object" && !types.isBoxedPrimitive(init)) {
23
+ const method = init[Symbol.iterator];
24
+ if (method == null) {
25
+ result.push(...Object.entries(init));
26
+ } else {
27
+ if (typeof method !== "function") {
28
+ throw new TypeError("Header pairs must be iterable");
29
+ }
30
+ result = [...init].map((pair) => {
31
+ if (typeof pair !== "object" || types.isBoxedPrimitive(pair)) {
32
+ throw new TypeError("Each header pair must be an iterable object");
33
+ }
34
+ return [...pair];
35
+ }).map((pair) => {
36
+ if (pair.length !== 2) {
37
+ throw new TypeError("Each header pair must be a name/value tuple");
38
+ }
39
+ return [...pair];
40
+ });
41
+ }
42
+ } else {
43
+ throw new TypeError("Failed to construct 'Headers': The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)");
44
+ }
45
+ result = result.length > 0 ? result.map(([name, value]) => {
46
+ validateHeaderName(name);
47
+ validateHeaderValue(name, String(value));
48
+ return [String(name).toLowerCase(), String(value)];
49
+ }) : void 0;
50
+ super(result);
51
+ return new Proxy(this, {
52
+ get(target, p, receiver) {
53
+ switch (p) {
54
+ case "append":
55
+ case "set":
56
+ return (name, value) => {
57
+ validateHeaderName(name);
58
+ validateHeaderValue(name, String(value));
59
+ return URLSearchParams.prototype[p].call(
60
+ target,
61
+ String(name).toLowerCase(),
62
+ String(value)
63
+ );
64
+ };
65
+ case "delete":
66
+ case "has":
67
+ case "getAll":
68
+ return (name) => {
69
+ validateHeaderName(name);
70
+ return URLSearchParams.prototype[p].call(
71
+ target,
72
+ String(name).toLowerCase()
73
+ );
74
+ };
75
+ case "keys":
76
+ return () => {
77
+ target.sort();
78
+ return new Set(URLSearchParams.prototype.keys.call(target)).keys();
79
+ };
80
+ default:
81
+ return Reflect.get(target, p, receiver);
82
+ }
83
+ }
84
+ });
85
+ }
86
+ get [Symbol.toStringTag]() {
87
+ return this.constructor.name;
88
+ }
89
+ toString() {
90
+ return Object.prototype.toString.call(this);
91
+ }
92
+ _appendToSoupMessage(message, type = Soup.MessageHeadersType.REQUEST) {
93
+ const soupHeaders = message ? message.get_request_headers() : new Soup.MessageHeaders(type);
94
+ for (const header in this.entries()) {
95
+ soupHeaders.append(header, this.get(header));
96
+ }
97
+ return soupHeaders;
98
+ }
99
+ static _newFromSoupMessage(message, type = Soup.MessageHeadersType.RESPONSE) {
100
+ let soupHeaders;
101
+ const headers = new Headers();
102
+ if (type === Soup.MessageHeadersType.RESPONSE) {
103
+ soupHeaders = message.get_response_headers();
104
+ } else if (type === Soup.MessageHeadersType.REQUEST) {
105
+ soupHeaders = message.get_request_headers();
106
+ } else {
107
+ for (const header in message.get_request_headers()) {
108
+ headers.append(header, soupHeaders[header]);
109
+ }
110
+ soupHeaders = message.get_response_headers();
111
+ }
112
+ for (const header in soupHeaders) {
113
+ headers.append(header, soupHeaders[header]);
114
+ }
115
+ return headers;
116
+ }
117
+ get(name) {
118
+ const values = this.getAll(name);
119
+ if (values.length === 0) {
120
+ return null;
121
+ }
122
+ let value = values.join(", ");
123
+ if (/^content-encoding$/i.test(name)) {
124
+ value = value.toLowerCase();
125
+ }
126
+ return value;
127
+ }
128
+ forEach(callback, thisArg = void 0) {
129
+ for (const name of this.keys()) {
130
+ Reflect.apply(callback, thisArg, [this.get(name), name, this]);
131
+ }
132
+ }
133
+ *values() {
134
+ for (const name of this.keys()) {
135
+ yield this.get(name);
136
+ }
137
+ }
138
+ /**
139
+ *
140
+ */
141
+ *entries() {
142
+ for (const name of this.keys()) {
143
+ yield [name, this.get(name)];
144
+ }
145
+ }
146
+ [Symbol.iterator]() {
147
+ return this.entries();
148
+ }
149
+ /**
150
+ * Node-fetch non-spec method
151
+ * returning all headers and their values as array
152
+ */
153
+ raw() {
154
+ return [...this.keys()].reduce((result, key) => {
155
+ result[key] = this.getAll(key);
156
+ return result;
157
+ }, {});
158
+ }
159
+ /**
160
+ * For better console.log(headers) and also to convert Headers into Node.js Request compatible format
161
+ */
162
+ [Symbol.for("nodejs.util.inspect.custom")]() {
163
+ return [...this.keys()].reduce((result, key) => {
164
+ const values = this.getAll(key);
165
+ if (key === "host") {
166
+ result[key] = values[0];
167
+ } else {
168
+ result[key] = values.length > 1 ? values : values[0];
169
+ }
170
+ return result;
171
+ }, {});
172
+ }
173
+ }
174
+ Object.defineProperties(
175
+ Headers.prototype,
176
+ ["get", "entries", "forEach", "values"].reduce((result, property) => {
177
+ result[property] = { enumerable: true };
178
+ return result;
179
+ }, {})
180
+ );
181
+ function fromRawHeaders(headers = []) {
182
+ return new Headers(
183
+ headers.reduce((result, value, index, array) => {
184
+ if (index % 2 === 0) {
185
+ result.push(array.slice(index, index + 2));
186
+ }
187
+ return result;
188
+ }, []).filter(([name, value]) => {
189
+ try {
190
+ validateHeaderName(name);
191
+ validateHeaderValue(name, String(value));
192
+ return true;
193
+ } catch {
194
+ return false;
195
+ }
196
+ })
197
+ );
198
+ }
199
+ export {
200
+ Headers as default,
201
+ fromRawHeaders
202
+ };