@nestia/fetcher 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.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Primitive type.
3
+ *
4
+ * `Primitive` is a type of TMP (Type Meta Programming) type who converts its argument as a
5
+ * primitive type.
6
+ *
7
+ * If the target argument is a built-in class who returns its origin primitive type through
8
+ * the `valueOf()` method like the `String` or `Number`, its return type would be the
9
+ * `string` or `number`.
10
+ *
11
+ * Otherwise, the target argument is a type of custom class, all of its custom method would
12
+ * be erased and its prototype would be changed to the primitive `object`. Therefore, return
13
+ * type of the TMP type finally be the primitive object.
14
+ *
15
+ * In addition, if the target argument is a type of custom class and it has a special
16
+ * method `toJSON()`, return type of this `Primitive` would be not `Primitive<Instance>`
17
+ * but `Primitive<ReturnType<Instance.toJSON>>`.
18
+ *
19
+ * Before | After
20
+ * ------------------------|----------------------------------------
21
+ * `Boolean` | `boolean`
22
+ * `Number` | `number`
23
+ * `String` | `string`
24
+ * `Class` | `object`
25
+ * `Class` with `toJSON()` | `Primitive<ReturnType<Class.toJSON>>`
26
+ * Others | No change
27
+ *
28
+ * @template Instance Target argument type.
29
+ * @author Jenogho Nam - https://github.com/samchon
30
+ */
31
+ export declare type Primitive<Instance> = _Equal<Instance, _Primitive<Instance>> extends true ? Instance : _Primitive<Instance>;
32
+ declare type _Equal<X, Y> = X extends Y ? (Y extends X ? true : false) : false;
33
+ declare type _Primitive<Instance> = _ValueOf<Instance> extends object ? Instance extends object ? Instance extends IJsonable<infer Raw> ? _ValueOf<Raw> extends object ? Raw extends object ? _PrimitiveObject<Raw> : never : _ValueOf<Raw> : _PrimitiveObject<Instance> : never : _ValueOf<Instance>;
34
+ declare type _PrimitiveObject<Instance extends object> = Instance extends Array<infer T> ? _Primitive<T>[] : {
35
+ [P in keyof Instance]: Instance[P] extends Function ? never : _Primitive<Instance[P]>;
36
+ };
37
+ declare type _ValueOf<Instance> = _IsValueOf<Instance, Boolean> extends true ? boolean : _IsValueOf<Instance, Number> extends true ? number : _IsValueOf<Instance, String> extends true ? string : Instance;
38
+ declare type _IsValueOf<Instance, Object extends IValueOf<any>> = Instance extends Object ? Object extends IValueOf<infer Primitive> ? Instance extends Primitive ? false : true : false : false;
39
+ interface IValueOf<T> {
40
+ valueOf(): T;
41
+ }
42
+ interface IJsonable<T> {
43
+ toJSON(): T;
44
+ }
45
+ export {};
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=Primitive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Primitive.js","sourceRoot":"","sources":["../src/Primitive.ts"],"names":[],"mappings":""}
package/lib/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./IConnection";
2
+ export * from "./IEncryptionPassword";
3
+ export * from "./Primitive";
4
+ export * from "./AesPkcs5";
5
+ export * from "./Fetcher";
6
+ export * from "./HttpError";
package/lib/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./IConnection"), exports);
18
+ __exportStar(require("./IEncryptionPassword"), exports);
19
+ __exportStar(require("./Primitive"), exports);
20
+ __exportStar(require("./AesPkcs5"), exports);
21
+ __exportStar(require("./Fetcher"), exports);
22
+ __exportStar(require("./HttpError"), exports);
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gDAA8B;AAC9B,wDAAsC;AACtC,8CAA4B;AAE5B,6CAA2B;AAC3B,4CAA0B;AAC1B,8CAA4B"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Singleton = void 0;
4
+ /**
5
+ * @internal
6
+ */
7
+ var Singleton = /** @class */ (function () {
8
+ function Singleton(closure_) {
9
+ this.closure_ = closure_;
10
+ this.value_ = NOT_MOUNTED_YET;
11
+ }
12
+ Singleton.prototype.get = function () {
13
+ if (this.value_ === NOT_MOUNTED_YET)
14
+ this.value_ = this.closure_();
15
+ return this.value_;
16
+ };
17
+ return Singleton;
18
+ }());
19
+ exports.Singleton = Singleton;
20
+ /**
21
+ * @internal
22
+ */
23
+ var NOT_MOUNTED_YET = {};
24
+ //# sourceMappingURL=Singleton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Singleton.js","sourceRoot":"","sources":["../../src/internal/Singleton.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH;IAGI,mBAAoC,QAAiB;QAAjB,aAAQ,GAAR,QAAQ,CAAS;QACjD,IAAI,CAAC,MAAM,GAAG,eAAe,CAAC;IAClC,CAAC;IAEM,uBAAG,GAAV;QACI,IAAI,IAAI,CAAC,MAAM,KAAK,eAAe;YAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnE,OAAO,IAAI,CAAC,MAAW,CAAC;IAC5B,CAAC;IACL,gBAAC;AAAD,CAAC,AAXD,IAWC;AAXY,8BAAS;AAatB;;GAEG;AACH,IAAM,eAAe,GAAG,EAAE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@nestia/fetcher",
3
+ "version": "0.1.0",
4
+ "description": "Fetcher library of Nestia SDK",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
7
+ "scripts": {
8
+ "build": "rimraf lib && tsc",
9
+ "dev": "npm run build -- --watch",
10
+ "eslint": "eslint src",
11
+ "eslint:fix": "eslint src --fix",
12
+ "prettier": "prettier src --write"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/samchon/nestia"
17
+ },
18
+ "keywords": [
19
+ "nestia",
20
+ "fetcher",
21
+ "sdk"
22
+ ],
23
+ "author": "Jeongho Nam",
24
+ "license": "MIT",
25
+ "bugs": {
26
+ "url": "https://github.com/samchon/nestia/issues"
27
+ },
28
+ "homepage": "https://github.com/samchon/nestia/packages/fetcher",
29
+ "devDependencies": {
30
+ "@types/node": "^18.11.14",
31
+ "@typescript-eslint/eslint-plugin": "^5.46.1",
32
+ "@typescript-eslint/parser": "^5.46.1",
33
+ "prettier": "^2.8.1",
34
+ "rimraf": "^3.0.2",
35
+ "typescript": "^4.9.4"
36
+ },
37
+ "dependencies": {
38
+ "import2": "^1.0.3",
39
+ "node-fetch": "^3.3.0"
40
+ },
41
+ "files": [
42
+ "README.md",
43
+ "LICENSE",
44
+ "package.json",
45
+ "lib",
46
+ "src"
47
+ ]
48
+ }
@@ -0,0 +1,51 @@
1
+ import crypto from "crypto";
2
+
3
+ /**
4
+ * Utility class for the AES-128/256 encryption.
5
+ *
6
+ * - AES-128/256
7
+ * - CBC mode
8
+ * - PKCS#5 Padding
9
+ * - Base64 Encoding
10
+ *
11
+ * @author Jeongho Nam - https://github.com/samchon
12
+ */
13
+ export namespace AesPkcs5 {
14
+ /**
15
+ * Encrypt data
16
+ *
17
+ * @param data Target data
18
+ * @param key Key value of the encryption.
19
+ * @param iv Initializer Vector for the encryption
20
+ * @return Encrypted data
21
+ */
22
+ export function encrypt(data: string, key: string, iv: string): string {
23
+ const bytes: number = key.length * 8;
24
+ const cipher: crypto.Cipher = crypto.createCipheriv(
25
+ `AES-${bytes}-CBC`,
26
+ key,
27
+ iv,
28
+ );
29
+
30
+ return cipher.update(data, "utf8", "base64") + cipher.final("base64");
31
+ }
32
+
33
+ /**
34
+ * Decrypt data.
35
+ *
36
+ * @param data Target data
37
+ * @param key Key value of the decryption.
38
+ * @param iv Initializer Vector for the decryption
39
+ * @return Decrypted data.
40
+ */
41
+ export function decrypt(data: string, key: string, iv: string): string {
42
+ const bytes: number = key.length * 8;
43
+ const decipher: crypto.Decipher = crypto.createDecipheriv(
44
+ `AES-${bytes}-CBC`,
45
+ key,
46
+ iv,
47
+ );
48
+
49
+ return decipher.update(data, "base64", "utf8") + decipher.final("utf8");
50
+ }
51
+ }
package/src/Fetcher.ts ADDED
@@ -0,0 +1,241 @@
1
+ import import2 from "import2";
2
+
3
+ import { IConnection } from "./IConnection";
4
+ import { IEncryptionPassword } from "./IEncryptionPassword";
5
+ import { Primitive } from "./Primitive";
6
+
7
+ import { AesPkcs5 } from "./AesPkcs5";
8
+ import { HttpError } from "./HttpError";
9
+ import { Singleton } from "./internal/Singleton";
10
+
11
+ /**
12
+ * Fetcher, utility class for the [**Nestia**](https://github.com/samchon/nestia) fetch.
13
+ *
14
+ * `Fetcher` is a utility class providing the {@link Fetcher.fetch} functions who're being
15
+ * used by all of the SDK libraries, interacting with the remote HTTP servers, who are
16
+ * generated by the [**Nestia**](https://github.com/samchon/nestia).
17
+ *
18
+ * As this `Fetcher` be used only by the [**Nestia**](https://github.com/samchon/nestia)
19
+ * generated SDK libraries, you don't need to handle this class directly. It may only be
20
+ * appeared in the source codes of the [**Nestia**](https://github.com/samchon/nestia)
21
+ * generated SDK libraries.
22
+ *
23
+ * @author Jeongho Nam - https://github.com/samchon
24
+ */
25
+ export class Fetcher {
26
+ /**
27
+ * Fetch function for the `GET` or `DELETE` methods.
28
+ *
29
+ * @param connection Connection information for the remote HTTP server
30
+ * @param encrypted Whether the request/response body be encrypted or not
31
+ * @param method Method of the HTTP request
32
+ * @param path Path of the HTTP request
33
+ * @return Response body data from the remote HTTP server
34
+ */
35
+ public static fetch<Output>(
36
+ connection: IConnection,
37
+ encrypted: Fetcher.IEncrypted,
38
+ method: "GET" | "DELETE",
39
+ path: string,
40
+ ): Promise<Primitive<Output>>;
41
+
42
+ /**
43
+ * Fetch function for the `POST`, `PUT` and `PATCH` methods.
44
+ *
45
+ * @param connection Connection information for the remote HTTP server
46
+ * @param encrypted Whether the request/response body be encrypted or not
47
+ * @param method Method of the HTTP request
48
+ * @param path Path of the HTTP request
49
+ * @param input Request body data for the HTTP request
50
+ * @param stringify JSON string conversion function, default is the `JSON.stringify`
51
+ * @return Response body data from the remote HTTP server
52
+ */
53
+ public static fetch<Input, Output>(
54
+ connection: IConnection,
55
+ encrypted: Fetcher.IEncrypted,
56
+ method: "POST" | "PUT" | "PATCH",
57
+ path: string,
58
+ input: Input,
59
+ stringify?: (input: Input) => string,
60
+ ): Promise<Primitive<Output>>;
61
+
62
+ public static async fetch<Output>(
63
+ connection: IConnection,
64
+ encrypted: Fetcher.IEncrypted,
65
+ method: "GET" | "DELETE" | "POST" | "PUT" | "PATCH",
66
+ path: string,
67
+ input?: object,
68
+ stringify?: (input: object) => string,
69
+ ): Promise<Primitive<Output>> {
70
+ if (encrypted.request === true || encrypted.response === true)
71
+ if (connection.encryption === undefined)
72
+ throw new Error(
73
+ "Error on nestia.Fetcher.encrypt(): the encryption password has not been configured.",
74
+ );
75
+
76
+ //----
77
+ // REQUEST MESSSAGE
78
+ //----
79
+ // METHOD & HEADERS
80
+ const init: RequestInit = {
81
+ method,
82
+ headers:
83
+ encrypted.request === false &&
84
+ input !== undefined &&
85
+ typeof input === "object"
86
+ ? {
87
+ ...connection.headers,
88
+ "Content-Type": "application/json",
89
+ }
90
+ : connection.headers,
91
+ };
92
+
93
+ // REQUEST BODY (WITH ENCRYPTION)
94
+ if (input !== undefined) {
95
+ let body: string = (stringify || JSON.stringify)(input);
96
+ if (encrypted.request === true) {
97
+ const headers: Singleton<Record<string, string>> =
98
+ new Singleton(() => init.headers as Record<string, string>);
99
+ const password:
100
+ | IEncryptionPassword
101
+ | IEncryptionPassword.Closure =
102
+ connection.encryption instanceof Function
103
+ ? connection.encryption!(
104
+ { headers: headers.get(), body },
105
+ true,
106
+ )
107
+ : connection.encryption!;
108
+ if (is_disabled(password, headers, body, true) === false)
109
+ body = AesPkcs5.encrypt(body, password.key, password.iv);
110
+ }
111
+ init.body = body;
112
+ }
113
+
114
+ //----
115
+ // RESPONSE MESSAGE
116
+ //----
117
+ // URL SPECIFICATION
118
+ if (
119
+ connection.host[connection.host.length - 1] !== "/" &&
120
+ path[0] !== "/"
121
+ )
122
+ path = "/" + path;
123
+
124
+ const url: URL = new URL(`${connection.host}${path}`);
125
+
126
+ // DO FETCH
127
+ const response: Response = await (await polyfill.get())(url.href, init);
128
+ let body: string = await response.text();
129
+ if (!body) return undefined!;
130
+
131
+ // CHECK THE STATUS CODE
132
+ if (response.status !== 200 && response.status !== 201)
133
+ throw new HttpError(method, path, response.status, body);
134
+
135
+ // FINALIZATION (WITH DECODING)
136
+ if (encrypted.response === true) {
137
+ const headers: Singleton<Record<string, string>> = new Singleton(
138
+ () => headers_to_object(response.headers),
139
+ );
140
+ const password: IEncryptionPassword | IEncryptionPassword.Closure =
141
+ connection.encryption instanceof Function
142
+ ? connection.encryption!(
143
+ { headers: headers.get(), body },
144
+ false,
145
+ )
146
+ : connection.encryption!;
147
+ if (is_disabled(password, headers, body, false) === false)
148
+ body = AesPkcs5.decrypt(body, password.key, password.iv);
149
+ }
150
+
151
+ //----
152
+ // OUTPUT
153
+ //----
154
+ let ret: { __set_headers__: Record<string, any> } & Primitive<Output> =
155
+ body as any;
156
+ try {
157
+ // PARSE RESPONSE BODY
158
+ ret = JSON.parse(ret as any);
159
+
160
+ // FIND __SET_HEADERS__ FIELD
161
+ if (
162
+ ret.__set_headers__ !== undefined &&
163
+ typeof ret.__set_headers__ === "object"
164
+ ) {
165
+ if (connection.headers === undefined) connection.headers = {};
166
+ Object.assign(connection.headers, ret.__set_headers__);
167
+ }
168
+ } catch {}
169
+
170
+ // RETURNS
171
+ return ret;
172
+ }
173
+ }
174
+
175
+ export namespace Fetcher {
176
+ /**
177
+ * Whether be encrypted or not.
178
+ *
179
+ * `Fetcher.IEncrypted` is a type of interface who represents whether the HTTP request
180
+ * and response body must be encrypted or not.
181
+ *
182
+ * Like the {@link Fetcher} who are being used by all of the SDK libraries that are
183
+ * generated by the [Nestia](https://github.com/samchon/nestia), this `IEncrypted`
184
+ * interface would be used by the [Nestia](https://github.com/samchon/nestia) generated
185
+ * SDK libaries.
186
+ *
187
+ * As this `Fetcher` be used only by the [**Nestia**](https://github.com/samchon/nestia)
188
+ * generated SDK libraries, you don't need to handle this class directly. It may only be
189
+ * appeared in the source codes of the [**Nestia**](https://github.com/samchon/nestia)
190
+ * generated SDK libraries.
191
+ */
192
+ export interface IEncrypted {
193
+ /**
194
+ * Whether the request body be encrypted or not.
195
+ */
196
+ request?: boolean;
197
+
198
+ /**
199
+ * Whether the response body be encrypted or not.
200
+ */
201
+ response: boolean;
202
+ }
203
+ }
204
+
205
+ const polyfill = new Singleton(async (): Promise<typeof fetch> => {
206
+ if (
207
+ typeof global === "object" &&
208
+ typeof global.process === "object" &&
209
+ typeof global.process.versions === "object" &&
210
+ typeof global.process.versions.node !== undefined
211
+ ) {
212
+ if (global.fetch === undefined)
213
+ global.fetch = ((await import2("node-fetch")) as any).default;
214
+ return (global as any).fetch;
215
+ }
216
+ return window.fetch;
217
+ });
218
+
219
+ function is_disabled(
220
+ password: IEncryptionPassword,
221
+ headers: Singleton<Record<string, string>>,
222
+ body: string,
223
+ encoded: boolean,
224
+ ): boolean {
225
+ if (password.disabled === undefined) return false;
226
+ if (typeof password.disabled === "function")
227
+ return password.disabled(
228
+ {
229
+ headers: headers.get(),
230
+ body,
231
+ },
232
+ encoded,
233
+ );
234
+ return password.disabled;
235
+ }
236
+
237
+ function headers_to_object(headers: Headers): Record<string, string> {
238
+ const output: Record<string, string> = {};
239
+ headers.forEach((value, key) => (output[key] = value));
240
+ return output;
241
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * HTTP Error.
3
+ *
4
+ * `HttpError` is a type of error class who've been thrown by the remote HTTP server.
5
+ *
6
+ * @author Jeongho Nam - https://github.com/samchon
7
+ */
8
+ export class HttpError extends Error {
9
+ /**
10
+ * Initializer Constructor.
11
+ *
12
+ * @param method Method of the HTTP request.
13
+ * @param path Path of the HTTP request.
14
+ * @param status Status code from the remote HTTP server.
15
+ * @param message Error message from the remote HTTP server.
16
+ */
17
+ public constructor(
18
+ public readonly method: "GET" | "DELETE" | "POST" | "PUT" | "PATCH",
19
+ public readonly path: string,
20
+ public readonly status: number,
21
+ message: string,
22
+ ) {
23
+ super(message);
24
+
25
+ // INHERITANCE POLYFILL
26
+ const proto: HttpError = new.target.prototype;
27
+ if (Object.setPrototypeOf) Object.setPrototypeOf(this, proto);
28
+ else (this as any).__proto__ = proto;
29
+ }
30
+ }
@@ -0,0 +1,32 @@
1
+ import { IEncryptionPassword } from "./IEncryptionPassword";
2
+
3
+ /**
4
+ * Connection information.
5
+ *
6
+ * `IConnection` is a type of interface who represents connection information of the remote
7
+ * HTTP server. You can target the remote HTTP server by wring the {@link IConnection.host}
8
+ * variable down. Also, you can configure special header values by specializing the
9
+ * {@link IConnection.headers} variable.
10
+ *
11
+ * If the remote HTTP server encrypts or decrypts its body data through the AES-128/256
12
+ * algorithm, specify the {@link IConnection.encryption} with {@link IEncryptionPassword}
13
+ * or {@link IEncryptionPassword.Closure} variable.
14
+ *
15
+ * @author Jenogho Nam - https://github.com/samchon
16
+ */
17
+ export interface IConnection {
18
+ /**
19
+ * Host address of the remote HTTP server.
20
+ */
21
+ host: string;
22
+
23
+ /**
24
+ * Header values delivered to the remote HTTP server.
25
+ */
26
+ headers?: Record<string, string>;
27
+
28
+ /**
29
+ * Encryption password of its closure function.
30
+ */
31
+ encryption?: IEncryptionPassword | IEncryptionPassword.Closure;
32
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Encryption password.
3
+ *
4
+ * `IEncryptionPassword` is a type of interface who represents encryption password used by
5
+ * the {@link Fetcher} with AES-128/256 algorithm. If your encryption password is not fixed
6
+ * but changes according to the input content, you can utilize the
7
+ * {@link IEncryptionPassword.Closure} function type.
8
+ *
9
+ * @author Jeongho Nam - https://github.com/samchon
10
+ */
11
+ export interface IEncryptionPassword {
12
+ /**
13
+ * Secret key.
14
+ */
15
+ key: string;
16
+
17
+ /**
18
+ * Initialization vector.
19
+ */
20
+ iv: string;
21
+
22
+ /**
23
+ * Disable encryption to let content as plain.
24
+ *
25
+ * When you configure this `disabled` variable to be `false`, encryption and decryption
26
+ * algorithm would be disabled. Therefore, content like request or response body
27
+ * would be considered as a plain text instead.
28
+ *
29
+ * Default is `false`.
30
+ */
31
+ disabled?:
32
+ | boolean
33
+ | ((
34
+ param: IEncryptionPassword.IParameter,
35
+ encoded: boolean,
36
+ ) => boolean);
37
+ }
38
+ export namespace IEncryptionPassword {
39
+ /**
40
+ * Type of a closure function returning the {@link IEncryptionPassword} object.
41
+ *
42
+ * `IEncryptionPassword.Closure` is a type of closure function who are returning the
43
+ * {@link IEncryptionPassword} object. It would be used when your encryption password
44
+ * be changed according to the input content.
45
+ */
46
+ export interface Closure {
47
+ /**
48
+ * Encryption password getter.
49
+ *
50
+ * @param param Request or response headers and body content
51
+ * @param encoded Be encoded or to be decoded
52
+ * @returns Encryption password
53
+ */
54
+ (param: IParameter, encoded: boolean): IEncryptionPassword;
55
+ }
56
+
57
+ /**
58
+ * Parameter for the closure.
59
+ */
60
+ export interface IParameter {
61
+ headers: Record<string, string>;
62
+ body: string;
63
+ }
64
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Primitive type.
3
+ *
4
+ * `Primitive` is a type of TMP (Type Meta Programming) type who converts its argument as a
5
+ * primitive type.
6
+ *
7
+ * If the target argument is a built-in class who returns its origin primitive type through
8
+ * the `valueOf()` method like the `String` or `Number`, its return type would be the
9
+ * `string` or `number`.
10
+ *
11
+ * Otherwise, the target argument is a type of custom class, all of its custom method would
12
+ * be erased and its prototype would be changed to the primitive `object`. Therefore, return
13
+ * type of the TMP type finally be the primitive object.
14
+ *
15
+ * In addition, if the target argument is a type of custom class and it has a special
16
+ * method `toJSON()`, return type of this `Primitive` would be not `Primitive<Instance>`
17
+ * but `Primitive<ReturnType<Instance.toJSON>>`.
18
+ *
19
+ * Before | After
20
+ * ------------------------|----------------------------------------
21
+ * `Boolean` | `boolean`
22
+ * `Number` | `number`
23
+ * `String` | `string`
24
+ * `Class` | `object`
25
+ * `Class` with `toJSON()` | `Primitive<ReturnType<Class.toJSON>>`
26
+ * Others | No change
27
+ *
28
+ * @template Instance Target argument type.
29
+ * @author Jenogho Nam - https://github.com/samchon
30
+ */
31
+ export type Primitive<Instance> = _Equal<
32
+ Instance,
33
+ _Primitive<Instance>
34
+ > extends true
35
+ ? Instance
36
+ : _Primitive<Instance>;
37
+
38
+ type _Equal<X, Y> = X extends Y ? (Y extends X ? true : false) : false;
39
+
40
+ type _Primitive<Instance> = _ValueOf<Instance> extends object
41
+ ? Instance extends object
42
+ ? Instance extends IJsonable<infer Raw>
43
+ ? _ValueOf<Raw> extends object
44
+ ? Raw extends object
45
+ ? _PrimitiveObject<Raw> // object would be primitified
46
+ : never // cannot be
47
+ : _ValueOf<Raw> // atomic value
48
+ : _PrimitiveObject<Instance> // object would be primitified
49
+ : never // cannot be
50
+ : _ValueOf<Instance>;
51
+
52
+ type _PrimitiveObject<Instance extends object> = Instance extends Array<infer T>
53
+ ? _Primitive<T>[]
54
+ : {
55
+ [P in keyof Instance]: Instance[P] extends Function
56
+ ? never
57
+ : _Primitive<Instance[P]>;
58
+ };
59
+
60
+ type _ValueOf<Instance> = _IsValueOf<Instance, Boolean> extends true
61
+ ? boolean
62
+ : _IsValueOf<Instance, Number> extends true
63
+ ? number
64
+ : _IsValueOf<Instance, String> extends true
65
+ ? string
66
+ : Instance;
67
+
68
+ type _IsValueOf<
69
+ Instance,
70
+ Object extends IValueOf<any>,
71
+ > = Instance extends Object
72
+ ? Object extends IValueOf<infer Primitive>
73
+ ? Instance extends Primitive
74
+ ? false
75
+ : true // not Primitive, but Object
76
+ : false // cannot be
77
+ : false;
78
+
79
+ interface IValueOf<T> {
80
+ valueOf(): T;
81
+ }
82
+
83
+ interface IJsonable<T> {
84
+ toJSON(): T;
85
+ }