@nestia/fetcher 2.6.3 → 2.6.4-dev.20240401-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestia/fetcher",
3
- "version": "2.6.3",
3
+ "version": "2.6.4-dev.20240401-2",
4
4
  "description": "Fetcher library of Nestia SDK",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
@@ -1,269 +1,269 @@
1
- import import2 from "import2";
2
-
3
- import { HttpError } from "../HttpError";
4
- import { IConnection } from "../IConnection";
5
- import { IFetchEvent } from "../IFetchEvent";
6
- import { IFetchRoute } from "../IFetchRoute";
7
- import { IPropagation } from "../IPropagation";
8
- import { Singleton } from "./Singleton";
9
-
10
- export namespace FetcherBase {
11
- export interface IProps {
12
- className: string;
13
- encode: (
14
- input: any,
15
- headers: Record<string, IConnection.HeaderValue | undefined>,
16
- ) => string;
17
- decode: (
18
- input: string,
19
- headers: Record<string, IConnection.HeaderValue | undefined>,
20
- ) => any;
21
- }
22
-
23
- export const fetch =
24
- (props: IProps) =>
25
- async <Input, Output>(
26
- connection: IConnection,
27
- route: IFetchRoute<"DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT">,
28
- input?: Input,
29
- stringify?: (input: Input) => string,
30
- ): Promise<Output> => {
31
- const result = await _Propagate("fetch")(props)(
32
- connection,
33
- route,
34
- input,
35
- stringify,
36
- );
37
- if ((result as any).success === false)
38
- throw new HttpError(
39
- route.method,
40
- route.path,
41
- result.status as any as number,
42
- result.headers,
43
- result.data as string,
44
- );
45
- return result.data as Output;
46
- };
47
-
48
- export const propagate =
49
- (props: IProps) =>
50
- async <Input>(
51
- connection: IConnection,
52
- route: IFetchRoute<"DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT">,
53
- input?: Input,
54
- stringify?: (input: Input) => string,
55
- ): Promise<IPropagation<any, any>> =>
56
- _Propagate("propagate")(props)(connection, route, input, stringify);
57
-
58
- /**
59
- * @internal
60
- */
61
- const _Propagate =
62
- (method: string) =>
63
- (props: IProps) =>
64
- async <Input>(
65
- connection: IConnection,
66
- metadata: IFetchRoute<
67
- "DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT"
68
- >,
69
- input?: Input,
70
- stringify?: (input: Input) => string,
71
- ): Promise<IPropagation<any, any>> => {
72
- //----
73
- // REQUEST MESSSAGE
74
- //----
75
- // METHOD & HEADERS
76
- const headers: Record<string, IConnection.HeaderValue | undefined> = {
77
- ...(connection.headers ?? {}),
78
- };
79
- if (input !== undefined)
80
- if (metadata.request?.type === undefined)
81
- throw new Error(
82
- `Error on ${props.className}.fetch(): no content-type being configured.`,
83
- );
84
- else if (metadata.request.type !== "multipart/form-data")
85
- headers["Content-Type"] = metadata.request.type;
86
-
87
- // INIT REQUEST DATA
88
- const init: RequestInit = {
89
- ...(connection.options ?? {}),
90
- method: metadata.method,
91
- headers: (() => {
92
- const output: [string, string][] = [];
93
- for (const [key, value] of Object.entries(headers))
94
- if (value === undefined) continue;
95
- else if (Array.isArray(value))
96
- for (const v of value) output.push([key, String(v)]);
97
- else output.push([key, String(value)]);
98
- return output;
99
- })(),
100
- };
101
-
102
- // CONSTRUCT BODY DATA
103
- if (input !== undefined)
104
- init.body = props.encode(
105
- // BODY TRANSFORM
106
- metadata.request?.type === "application/x-www-form-urlencoded"
107
- ? request_query_body(input)
108
- : metadata.request?.type === "multipart/form-data"
109
- ? request_form_data_body(input as any)
110
- : metadata.request?.type !== "text/plain"
111
- ? (stringify ?? JSON.stringify)(input)
112
- : input,
113
- headers,
114
- );
115
-
116
- //----
117
- // RESPONSE MESSSAGE
118
- //----
119
- // URL SPECIFICATION
120
- const path: string =
121
- connection.host[connection.host.length - 1] !== "/" &&
122
- metadata.path[0] !== "/"
123
- ? `/${metadata.path}`
124
- : metadata.path;
125
- const url: URL = new URL(`${connection.host}${path}`);
126
-
127
- // DO FETCH
128
- const event: IFetchEvent = {
129
- route: metadata,
130
- path,
131
- status: null,
132
- input,
133
- output: undefined,
134
- started_at: new Date(),
135
- respond_at: null,
136
- completed_at: null!,
137
- };
138
- try {
139
- // TRY FETCH
140
- const response: Response = await (
141
- connection.fetch ?? (await polyfill.get())
142
- )(url.href, init);
143
- event.respond_at = new Date();
144
- event.status = response.status;
145
-
146
- // CONSTRUCT RESULT DATA
147
- const result: IPropagation<any, any> = {
148
- success:
149
- response.status === 200 ||
150
- response.status === 201 ||
151
- response.status == metadata.status,
152
- status: response.status,
153
- headers: response_headers_to_object(response.headers),
154
- data: undefined!,
155
- } as any;
156
- if ((result as any).success === false) {
157
- // WHEN FAILED
158
- result.data = await response.text();
159
- const type = response.headers.get("content-type");
160
- if (
161
- method !== "fetch" &&
162
- type &&
163
- type.indexOf("application/json") !== -1
164
- )
165
- try {
166
- result.data = JSON.parse(result.data);
167
- } catch {}
168
- } else {
169
- // WHEN SUCCESS
170
- if (metadata.method === "HEAD") result.data = undefined!;
171
- else if (metadata.response?.type === "application/json") {
172
- const text: string = await response.text();
173
- result.data = text.length ? JSON.parse(text) : undefined;
174
- } else if (
175
- metadata.response?.type === "application/x-www-form-urlencoded"
176
- ) {
177
- const query: URLSearchParams = new URLSearchParams(
178
- await response.text(),
179
- );
180
- result.data = metadata.parseQuery
181
- ? metadata.parseQuery(query)
182
- : query;
183
- } else
184
- result.data = props.decode(await response.text(), result.headers);
185
- }
186
- event.output = result.data;
187
- return result;
188
- } catch (exp) {
189
- throw exp;
190
- } finally {
191
- event.completed_at = new Date();
192
- if (connection.logger)
193
- try {
194
- await connection.logger(event);
195
- } catch {}
196
- }
197
- };
198
- }
199
-
200
- /**
201
- * @internal
202
- */
203
- const polyfill = new Singleton(async (): Promise<typeof fetch> => {
204
- function is_node_process(m: typeof global | null): boolean {
205
- return (
206
- m !== null &&
207
- typeof m.process === "object" &&
208
- m.process !== null &&
209
- typeof m.process.versions === "object" &&
210
- m.process.versions !== null &&
211
- typeof m.process.versions.node !== "undefined"
212
- );
213
- }
214
- if (typeof global === "object" && is_node_process(global)) {
215
- const m: any = global as any;
216
- m.fetch ??= ((await import2("node-fetch")) as any).default;
217
- return (m as any).fetch;
218
- }
219
- return self.fetch;
220
- });
221
-
222
- /**
223
- * @internal
224
- */
225
- const request_query_body = (input: any): URLSearchParams => {
226
- const q: URLSearchParams = new URLSearchParams();
227
- for (const [key, value] of Object.entries(input))
228
- if (value === undefined) continue;
229
- else if (Array.isArray(value))
230
- value.forEach((elem) => q.append(key, String(elem)));
231
- else q.set(key, String(value));
232
- return q;
233
- };
234
-
235
- /**
236
- * @internal
237
- */
238
- const request_form_data_body = (input: Record<string, any>): FormData => {
239
- const encoded: FormData = new FormData();
240
- const append = (key: string) => (value: any) => {
241
- if (value === undefined) return;
242
- else if (value instanceof Blob)
243
- if (value instanceof File) encoded.append(key, value, value.name);
244
- else encoded.append(key, value);
245
- else encoded.append(key, String(value));
246
- };
247
- for (const [key, value] of Object.entries(input))
248
- if (Array.isArray(value)) value.map(append(key));
249
- else append(key)(value);
250
- return encoded;
251
- };
252
-
253
- /**
254
- * @internal
255
- */
256
- const response_headers_to_object = (
257
- headers: Headers,
258
- ): Record<string, string | string[]> => {
259
- const output: Record<string, string | string[]> = {};
260
- headers.forEach((value, key) => {
261
- if (key === "set-cookie") {
262
- output[key] ??= [];
263
- (output[key] as string[]).push(
264
- ...value.split(";").map((str) => str.trim()),
265
- );
266
- } else output[key] = value;
267
- });
268
- return output;
269
- };
1
+ import import2 from "import2";
2
+
3
+ import { HttpError } from "../HttpError";
4
+ import { IConnection } from "../IConnection";
5
+ import { IFetchEvent } from "../IFetchEvent";
6
+ import { IFetchRoute } from "../IFetchRoute";
7
+ import { IPropagation } from "../IPropagation";
8
+ import { Singleton } from "./Singleton";
9
+
10
+ export namespace FetcherBase {
11
+ export interface IProps {
12
+ className: string;
13
+ encode: (
14
+ input: any,
15
+ headers: Record<string, IConnection.HeaderValue | undefined>,
16
+ ) => string;
17
+ decode: (
18
+ input: string,
19
+ headers: Record<string, IConnection.HeaderValue | undefined>,
20
+ ) => any;
21
+ }
22
+
23
+ export const fetch =
24
+ (props: IProps) =>
25
+ async <Input, Output>(
26
+ connection: IConnection,
27
+ route: IFetchRoute<"DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT">,
28
+ input?: Input,
29
+ stringify?: (input: Input) => string,
30
+ ): Promise<Output> => {
31
+ const result = await _Propagate("fetch")(props)(
32
+ connection,
33
+ route,
34
+ input,
35
+ stringify,
36
+ );
37
+ if ((result as any).success === false)
38
+ throw new HttpError(
39
+ route.method,
40
+ route.path,
41
+ result.status as any as number,
42
+ result.headers,
43
+ result.data as string,
44
+ );
45
+ return result.data as Output;
46
+ };
47
+
48
+ export const propagate =
49
+ (props: IProps) =>
50
+ async <Input>(
51
+ connection: IConnection,
52
+ route: IFetchRoute<"DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT">,
53
+ input?: Input,
54
+ stringify?: (input: Input) => string,
55
+ ): Promise<IPropagation<any, any>> =>
56
+ _Propagate("propagate")(props)(connection, route, input, stringify);
57
+
58
+ /**
59
+ * @internal
60
+ */
61
+ const _Propagate =
62
+ (method: string) =>
63
+ (props: IProps) =>
64
+ async <Input>(
65
+ connection: IConnection,
66
+ metadata: IFetchRoute<
67
+ "DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT"
68
+ >,
69
+ input?: Input,
70
+ stringify?: (input: Input) => string,
71
+ ): Promise<IPropagation<any, any>> => {
72
+ //----
73
+ // REQUEST MESSSAGE
74
+ //----
75
+ // METHOD & HEADERS
76
+ const headers: Record<string, IConnection.HeaderValue | undefined> = {
77
+ ...(connection.headers ?? {}),
78
+ };
79
+ if (input !== undefined)
80
+ if (metadata.request?.type === undefined)
81
+ throw new Error(
82
+ `Error on ${props.className}.fetch(): no content-type being configured.`,
83
+ );
84
+ else if (metadata.request.type !== "multipart/form-data")
85
+ headers["Content-Type"] = metadata.request.type;
86
+
87
+ // INIT REQUEST DATA
88
+ const init: RequestInit = {
89
+ ...(connection.options ?? {}),
90
+ method: metadata.method,
91
+ headers: (() => {
92
+ const output: [string, string][] = [];
93
+ for (const [key, value] of Object.entries(headers))
94
+ if (value === undefined) continue;
95
+ else if (Array.isArray(value))
96
+ for (const v of value) output.push([key, String(v)]);
97
+ else output.push([key, String(value)]);
98
+ return output;
99
+ })(),
100
+ };
101
+
102
+ // CONSTRUCT BODY DATA
103
+ if (input !== undefined)
104
+ init.body = props.encode(
105
+ // BODY TRANSFORM
106
+ metadata.request?.type === "application/x-www-form-urlencoded"
107
+ ? request_query_body(input)
108
+ : metadata.request?.type === "multipart/form-data"
109
+ ? request_form_data_body(input as any)
110
+ : metadata.request?.type !== "text/plain"
111
+ ? (stringify ?? JSON.stringify)(input)
112
+ : input,
113
+ headers,
114
+ );
115
+
116
+ //----
117
+ // RESPONSE MESSSAGE
118
+ //----
119
+ // URL SPECIFICATION
120
+ const path: string =
121
+ connection.host[connection.host.length - 1] !== "/" &&
122
+ metadata.path[0] !== "/"
123
+ ? `/${metadata.path}`
124
+ : metadata.path;
125
+ const url: URL = new URL(`${connection.host}${path}`);
126
+
127
+ // DO FETCH
128
+ const event: IFetchEvent = {
129
+ route: metadata,
130
+ path,
131
+ status: null,
132
+ input,
133
+ output: undefined,
134
+ started_at: new Date(),
135
+ respond_at: null,
136
+ completed_at: null!,
137
+ };
138
+ try {
139
+ // TRY FETCH
140
+ const response: Response = await (
141
+ connection.fetch ?? (await polyfill.get())
142
+ )(url.href, init);
143
+ event.respond_at = new Date();
144
+ event.status = response.status;
145
+
146
+ // CONSTRUCT RESULT DATA
147
+ const result: IPropagation<any, any> = {
148
+ success:
149
+ response.status === 200 ||
150
+ response.status === 201 ||
151
+ response.status == metadata.status,
152
+ status: response.status,
153
+ headers: response_headers_to_object(response.headers),
154
+ data: undefined!,
155
+ } as any;
156
+ if ((result as any).success === false) {
157
+ // WHEN FAILED
158
+ result.data = await response.text();
159
+ const type = response.headers.get("content-type");
160
+ if (
161
+ method !== "fetch" &&
162
+ type &&
163
+ type.indexOf("application/json") !== -1
164
+ )
165
+ try {
166
+ result.data = JSON.parse(result.data);
167
+ } catch {}
168
+ } else {
169
+ // WHEN SUCCESS
170
+ if (metadata.method === "HEAD") result.data = undefined!;
171
+ else if (metadata.response?.type === "application/json") {
172
+ const text: string = await response.text();
173
+ result.data = text.length ? JSON.parse(text) : undefined;
174
+ } else if (
175
+ metadata.response?.type === "application/x-www-form-urlencoded"
176
+ ) {
177
+ const query: URLSearchParams = new URLSearchParams(
178
+ await response.text(),
179
+ );
180
+ result.data = metadata.parseQuery
181
+ ? metadata.parseQuery(query)
182
+ : query;
183
+ } else
184
+ result.data = props.decode(await response.text(), result.headers);
185
+ }
186
+ event.output = result.data;
187
+ return result;
188
+ } catch (exp) {
189
+ throw exp;
190
+ } finally {
191
+ event.completed_at = new Date();
192
+ if (connection.logger)
193
+ try {
194
+ await connection.logger(event);
195
+ } catch {}
196
+ }
197
+ };
198
+ }
199
+
200
+ /**
201
+ * @internal
202
+ */
203
+ const polyfill = new Singleton(async (): Promise<typeof fetch> => {
204
+ function is_node_process(m: typeof global | null): boolean {
205
+ return (
206
+ m !== null &&
207
+ typeof m.process === "object" &&
208
+ m.process !== null &&
209
+ typeof m.process.versions === "object" &&
210
+ m.process.versions !== null &&
211
+ typeof m.process.versions.node !== "undefined"
212
+ );
213
+ }
214
+ if (typeof global === "object" && is_node_process(global)) {
215
+ const m: any = global as any;
216
+ m.fetch ??= ((await import2("node-fetch")) as any).default;
217
+ return (m as any).fetch;
218
+ }
219
+ return self.fetch;
220
+ });
221
+
222
+ /**
223
+ * @internal
224
+ */
225
+ const request_query_body = (input: any): URLSearchParams => {
226
+ const q: URLSearchParams = new URLSearchParams();
227
+ for (const [key, value] of Object.entries(input))
228
+ if (value === undefined) continue;
229
+ else if (Array.isArray(value))
230
+ value.forEach((elem) => q.append(key, String(elem)));
231
+ else q.set(key, String(value));
232
+ return q;
233
+ };
234
+
235
+ /**
236
+ * @internal
237
+ */
238
+ const request_form_data_body = (input: Record<string, any>): FormData => {
239
+ const encoded: FormData = new FormData();
240
+ const append = (key: string) => (value: any) => {
241
+ if (value === undefined) return;
242
+ else if (value instanceof Blob)
243
+ if (value instanceof File) encoded.append(key, value, value.name);
244
+ else encoded.append(key, value);
245
+ else encoded.append(key, String(value));
246
+ };
247
+ for (const [key, value] of Object.entries(input))
248
+ if (Array.isArray(value)) value.map(append(key));
249
+ else append(key)(value);
250
+ return encoded;
251
+ };
252
+
253
+ /**
254
+ * @internal
255
+ */
256
+ const response_headers_to_object = (
257
+ headers: Headers,
258
+ ): Record<string, string | string[]> => {
259
+ const output: Record<string, string | string[]> = {};
260
+ headers.forEach((value, key) => {
261
+ if (key === "set-cookie") {
262
+ output[key] ??= [];
263
+ (output[key] as string[]).push(
264
+ ...value.split(";").map((str) => str.trim()),
265
+ );
266
+ } else output[key] = value;
267
+ });
268
+ return output;
269
+ };