@nestia/fetcher 2.0.0-dev.20230906 → 2.0.0-dev.20230907

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.
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Resolved type erased every methods.
3
+ *
4
+ * `Resolved` is a type of TMP (Type Meta Programming) type which converts
5
+ * its argument as a resolved type that erased every method properties.
6
+ *
7
+ * If the target argument is a built-in class which returns its origin primitive type
8
+ * through the `valueOf()` method like the `String` or `Number`, its return type would
9
+ * be the `string` or `number`. Otherwise, the built-in class does not have the
10
+ * `valueOf()` method, the return type would be same with the target argument.
11
+ *
12
+ * Otherwise, the target argument is a type of custom class, all of its custom methods
13
+ * would be erased and its prototype would be changed to the primitive `object`.
14
+ * Therefore, return type of the TMP type finally be the resolved object.
15
+ *
16
+ * Before | After
17
+ * ------------------------|----------------------------------------
18
+ * `Boolean` | `boolean`
19
+ * `Number` | `number`
20
+ * `BigInt` | `bigint`
21
+ * `String` | `string`
22
+ * `Class` | `interface`
23
+ * Native Class or Others | No change
24
+ *
25
+ * @template Instance Target argument type.
26
+ * @author Jeongho Nam - https://github.com/samchon
27
+ * @author Kyungsu Kang - https://github.com/kakasoo
28
+ */
29
+ export type Resolved<T> = Equal<T, ResolvedMain<T>> extends true
30
+ ? T
31
+ : ResolvedMain<T>;
32
+
33
+ type Equal<X, Y> = X extends Y ? (Y extends X ? true : false) : false;
34
+
35
+ type ResolvedMain<Instance> = Instance extends [never]
36
+ ? never // (special trick for jsonable | null) type
37
+ : ValueOf<Instance> extends boolean | number | bigint | string
38
+ ? ValueOf<Instance>
39
+ : Instance extends Function
40
+ ? never
41
+ : Instance extends object
42
+ ? ResolvedObject<Instance>
43
+ : ValueOf<Instance>;
44
+
45
+ type ResolvedObject<Instance extends object> = Instance extends Array<infer T>
46
+ ? IsTuple<Instance> extends true
47
+ ? ResolvedTuple<Instance>
48
+ : ResolvedMain<T>[]
49
+ : Instance extends Set<infer U>
50
+ ? Set<ResolvedMain<U>>
51
+ : Instance extends Map<infer K, infer V>
52
+ ? Map<ResolvedMain<K>, ResolvedMain<V>>
53
+ : Instance extends WeakSet<any> | WeakMap<any, any>
54
+ ? never
55
+ : Instance extends
56
+ | Date
57
+ | Uint8Array
58
+ | Uint8ClampedArray
59
+ | Uint16Array
60
+ | Uint32Array
61
+ | BigUint64Array
62
+ | Int8Array
63
+ | Int16Array
64
+ | Int32Array
65
+ | BigInt64Array
66
+ | Float32Array
67
+ | Float64Array
68
+ | ArrayBuffer
69
+ | SharedArrayBuffer
70
+ | DataView
71
+ ? Instance
72
+ : {
73
+ [P in keyof Instance]: ResolvedMain<Instance[P]>;
74
+ };
75
+
76
+ type ResolvedTuple<T extends readonly any[]> = T extends []
77
+ ? []
78
+ : T extends [infer F]
79
+ ? [ResolvedMain<F>]
80
+ : T extends [infer F, ...infer Rest extends readonly any[]]
81
+ ? [ResolvedMain<F>, ...ResolvedTuple<Rest>]
82
+ : T extends [(infer F)?]
83
+ ? [ResolvedMain<F>?]
84
+ : T extends [(infer F)?, ...infer Rest extends readonly any[]]
85
+ ? [ResolvedMain<F>?, ...ResolvedTuple<Rest>]
86
+ : [];
87
+
88
+ type ValueOf<Instance> = IsValueOf<Instance, Boolean> extends true
89
+ ? boolean
90
+ : IsValueOf<Instance, Number> extends true
91
+ ? number
92
+ : IsValueOf<Instance, String> extends true
93
+ ? string
94
+ : Instance;
95
+
96
+ type IsTuple<T extends readonly any[] | { length: number }> = [T] extends [
97
+ never,
98
+ ]
99
+ ? false
100
+ : T extends readonly any[]
101
+ ? number extends T["length"]
102
+ ? false
103
+ : true
104
+ : false;
105
+
106
+ type IsValueOf<Instance, Object extends IValueOf<any>> = Instance extends Object
107
+ ? Object extends IValueOf<infer Primitive>
108
+ ? Instance extends Primitive
109
+ ? false
110
+ : true // not Primitive, but Object
111
+ : false // cannot be
112
+ : false;
113
+
114
+ interface IValueOf<T> {
115
+ valueOf(): T;
116
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from "./IConnection";
2
2
  export * from "./IEncryptionPassword";
3
+ export * from "./IPropagation";
3
4
  export * from "./IRandomGenerator";
4
- export * from "./Primitive";
5
5
  export * from "./HttpError";
6
+ export * from "./Primitive";
7
+ export * from "./Resolved";
@@ -4,6 +4,7 @@ import { Primitive } from "../Primitive";
4
4
  import { IFetchRoute } from "./IFetchRoute";
5
5
  import { Singleton } from "./Singleton";
6
6
  import { HttpError } from "../HttpError";
7
+ import { IPropagation } from "../IPropagation";
7
8
 
8
9
  export namespace FetcherBase {
9
10
  export interface IProps {
@@ -28,6 +29,49 @@ export namespace FetcherBase {
28
29
  input?: Input,
29
30
  stringify?: (input: Input) => string,
30
31
  ): Promise<Primitive<Output>> => {
32
+ const result = await _Propagate("fetch")(props)(
33
+ connection,
34
+ route,
35
+ input,
36
+ stringify,
37
+ );
38
+ if (result.success === false)
39
+ throw new HttpError(
40
+ route.method,
41
+ route.path,
42
+ result.status,
43
+ result.headers,
44
+ result.data as string,
45
+ );
46
+ return result.data as Primitive<Output>;
47
+ };
48
+
49
+ export const propagate =
50
+ (props: IProps) =>
51
+ async <Input>(
52
+ connection: IConnection,
53
+ route: IFetchRoute<
54
+ "DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT"
55
+ >,
56
+ input?: Input,
57
+ stringify?: (input: Input) => string,
58
+ ): Promise<IPropagation<any, any, any>> =>
59
+ _Propagate("propagate")(props)(connection, route, input, stringify);
60
+
61
+ /**
62
+ * @internal
63
+ */
64
+ const _Propagate =
65
+ (method: string) =>
66
+ (props: IProps) =>
67
+ async <Input>(
68
+ connection: IConnection,
69
+ route: IFetchRoute<
70
+ "DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT"
71
+ >,
72
+ input?: Input,
73
+ stringify?: (input: Input) => string,
74
+ ): Promise<IPropagation<any, any, any>> => {
31
75
  //----
32
76
  // REQUEST MESSSAGE
33
77
  //----
@@ -86,46 +130,47 @@ export namespace FetcherBase {
86
130
  await polyfill.get()
87
131
  )(url.href, init);
88
132
 
89
- // VALIDATE RESPONSE
90
- if (
91
- (route.status !== null && response.status !== route.status) ||
92
- (route.status === null &&
93
- response.status !== 200 &&
94
- response.status !== 201)
95
- )
96
- throw new HttpError(
97
- route.method,
98
- path,
99
- response.status,
100
- await response.text(),
101
- );
102
- else if (
103
- route.response?.type &&
104
- (
105
- response.headers.get("content-type") ?? route.response.type
106
- )?.indexOf(route.response.type) === -1
107
- )
108
- throw new Error(
109
- `Error on ${
110
- props.className
111
- }.fetch(): response type mismatched - expected ${
112
- route.response.type
113
- }, but ${response.headers.get("content-type")}.`,
114
- );
115
-
116
- // RETURNS
117
- if (route.method === "HEAD") return undefined!;
118
- else if (route.response?.type === "application/json") {
119
- const text: string = await response.text();
120
- return text.length ? JSON.parse(text) : undefined;
133
+ // CONSTRUCT RESULT DATA
134
+ const result: IPropagation<any, any, any> = {
135
+ success:
136
+ response.status === 200 ||
137
+ response.status === 201 ||
138
+ response.status == route.status,
139
+ status: route.status,
140
+ headers: response_headers_to_object(response.headers),
141
+ data: undefined!,
142
+ };
143
+ if (result.success === false) {
144
+ // WHEN FAILED
145
+ result.data = await response.text();
146
+ const type = response.headers.get("content-type");
147
+ if (
148
+ method !== "fetch" &&
149
+ type &&
150
+ type.indexOf("application/json") !== -1
151
+ )
152
+ try {
153
+ result.data = JSON.parse(result.data);
154
+ } catch {}
155
+ } else {
156
+ // WHEN SUCCESS
157
+ if (route.method === "HEAD") result.data = undefined!;
158
+ else if (route.response?.type === "application/json") {
159
+ const text: string = await response.text();
160
+ result.data = text.length ? JSON.parse(text) : undefined;
161
+ } else
162
+ result.data = props.decode(
163
+ await response.text(),
164
+ result.headers,
165
+ );
121
166
  }
122
- return props.decode(
123
- await response.text(),
124
- response_headers_to_object(response.headers),
125
- ) as Primitive<Output>;
167
+ return result;
126
168
  };
127
169
  }
128
170
 
171
+ /**
172
+ * @internal
173
+ */
129
174
  const polyfill = new Singleton(async (): Promise<typeof fetch> => {
130
175
  if (
131
176
  typeof global === "object" &&
@@ -134,16 +179,19 @@ const polyfill = new Singleton(async (): Promise<typeof fetch> => {
134
179
  typeof global.process.versions.node !== undefined
135
180
  ) {
136
181
  if (global.fetch === undefined)
137
- global.fetch = ((await import2("node-fetch")) as any).default;
182
+ global.fetch ??= ((await import2("node-fetch")) as any).default;
138
183
  return (global as any).fetch;
139
184
  }
140
185
  return window.fetch;
141
186
  });
142
187
 
188
+ /**
189
+ * @internal
190
+ */
143
191
  const response_headers_to_object = (
144
192
  headers: Headers,
145
- ): Record<string, IConnection.HeaderValue | undefined> => {
146
- const output: Record<string, IConnection.HeaderValue> = {};
193
+ ): Record<string, string | string[]> => {
194
+ const output: Record<string, string | string[]> = {};
147
195
  headers.forEach((value, key) => {
148
196
  if (key === "set-cookie") {
149
197
  output[key] ??= [];