nestia 0.4.1 → 1.2.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,3 @@
1
+ # These are supported funding model platforms
2
+
3
+ open_collective: nestia
package/README.md CHANGED
@@ -7,8 +7,14 @@ Automatic SDK generator for the NestJS.
7
7
  [![Build Status](https://github.com/samchon/nestia/workflows/build/badge.svg)](https://github.com/samchon/nestia/actions?query=workflow%3Abuild)
8
8
 
9
9
  ```bash
10
+ # INSTALL NESTIA
10
11
  npm install --save-dev nestia
12
+
13
+ # WHEN ALL OF THE CONTROLLRES ARE GATHERED INTO A DIRECTORY
11
14
  npx nestia sdk "src/controller" --out "src/api"
15
+
16
+ # REGULAR NESTJS PATTERN
17
+ npx nestia sdk "src/**/*.controller.ts" --out "src/api"
12
18
  ```
13
19
 
14
20
  Don't write any `swagger` comment. Just deliver the SDK.
@@ -60,54 +66,69 @@ Just type the `npm install --save-dev nestia` command in your NestJS backend pro
60
66
  ```bash
61
67
  npx nestia sdk <source_controller_directory> --out <output_sdk_directory>
62
68
 
69
+ npx nestia sdk "src/**/*.controller.ts" --out "src/api"
63
70
  npx nestia sdk "src/controllers" --out "src/api"
64
- npx nestia sdk "src/controllers/consumers" "src/controllers/sellers" --out "src/api
71
+ npx nestia sdk "src/controllers/consumers" "src/controllers/sellers" --out "src/api"
72
+ npx nestia sdk "src/controllers" --exclude "src/**/Fake*.ts" --out "src/api"
65
73
  ```
66
74
 
67
75
  To generate a SDK library through the **Nestia** is very easy.
68
76
 
69
- Just type the `nestia sdk <input> --out <output>` command in the console. If there're multiple source directories containing the NestJS controller classes, type all of them separating by a `space` word.
77
+ Just type the `nestia sdk <input> --out <output>` command in the console. When there're multiple source directories containing the NestJS controller classes, type all of them separating by a `space` word. If you want to exclude some directories or files from the SDK generation, the `--exclude` option would be useful.
70
78
 
71
79
  Also, when generating a SDK using the cli options, `compilerOptions` would follow the `tsconfig.json`, that is configured for the backend server. If no `tsconfig.json` file exists in your project, the configuration would be default option (`ES5` with `strict` mode). If you want to use different `compilerOptions` with the `tsconfig.json`, you should configure the [nestia.config.ts](#nestiaconfigts).
72
80
 
73
- ```bash
74
- npx nestia install
75
- ```
76
-
77
81
  ### Dependencies
78
- SDK library generated by the **Nestia** has some dependencies like below.
79
-
80
- When you type the `nestia install` command in the console, those dependencies would be automatically installed and enrolled to the `dependencies` and `devDependencies` fields in the `package.json`
82
+ SDK library generated by the **Nestia** requires the [nestia-fetcher](https://github.com/samchon/nestia-fetcher) module.
81
83
 
82
- - [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node)
83
- - [node-fetch](https://github.com/node-fetch/node-fetch)
84
+ Therefore, when you publish an SDK library generated by this **Nestia**, you have to write the [nestia-fetcher](https://github.com/samchon/nestia-fetcher) module into the `dependencies` property of the `package.json` file like below. You also can configure the `dependencies` property of the `package.json` file by typing the `npm install --save nestia-fetcher` command in the console, too.
84
85
 
86
+ ```json
87
+ {
88
+ "name": "payments-server-api",
89
+ "dependencies": {
90
+ "nestia-fetcher": "^1.0.2"
91
+ }
92
+ }
93
+ ```
85
94
 
86
95
 
87
96
 
88
97
  ## Advanced
89
98
  ### `nestia.config.ts`
90
99
  ```typescript
91
- export namespace NestiaApplication
100
+ export interface IConfiguration
92
101
  {
93
- export interface IConfiguration
94
- {
95
- /**
96
- * List of directories containing the NestJS controller classes.
97
- */
98
- input: string | string[];
102
+ /**
103
+ * List of files or directories containing the NestJS controller classes.
104
+ */
105
+ input: string | string[] | IConfiguration.IInput;
106
+
107
+ /**
108
+ * Output directory that SDK would be placed in.
109
+ */
110
+ output: string;
99
111
 
112
+ /**
113
+ * Compiler options for the TypeScript.
114
+ *
115
+ * If omitted, the configuration would follow the `tsconfig.json`.
116
+ */
117
+ compilerOptions?: tsc.CompilerOptions
118
+ }
119
+ export namespace IConfiguration
120
+ {
121
+ export interface IInput
122
+ {
100
123
  /**
101
- * Output directory that SDK would be placed in.
124
+ * List of files or directories containing the NestJS controller classes.
102
125
  */
103
- output: string;
126
+ include: string[];
104
127
 
105
128
  /**
106
- * Compiler options for the TypeScript.
107
- *
108
- * If omitted, the configuration would follow the `tsconfig.json`.
129
+ * List of files or directories to be excluded.
109
130
  */
110
- compilerOptions?: tsc.CompilerOptions
131
+ exclude: string[];
111
132
  }
112
133
  }
113
134
  ```
@@ -118,11 +139,24 @@ Write below content as the `nestia.config.ts` file and place it onto the root di
118
139
 
119
140
  ```typescript
120
141
  export = {
121
- input: "src/controllers`",
142
+ input: "src/controllers",
122
143
  output: "src/api"
123
144
  };
124
145
  ```
125
146
 
147
+ > Alternative options for the regular NestJS project:
148
+ >
149
+ > ```typescript
150
+ > export = {
151
+ > input: "src/**/*.controller.ts",
152
+ > /* input: {
153
+ > include: ["src/controllers/*.controller.ts"],
154
+ > exclude: ["src/controllers/fake_*.controller.ts"]
155
+ > },*/
156
+ > output: "src/api"
157
+ > }
158
+ > ```
159
+
126
160
 
127
161
 
128
162
  ### Recommended Structures
@@ -213,18 +247,15 @@ export function store
213
247
  connection: IConnection,
214
248
  section: string,
215
249
  saleId: number,
216
- input: store.Input
250
+ input: Primitive<store.Input>
217
251
  ): Promise<store.Output>
218
252
  {
219
253
  return Fetcher.fetch
220
254
  (
221
255
  connection,
222
- {
223
- input_encrypted: false,
224
- output_encrypted: false
225
- },
226
- "POST",
227
- `/consumers/${section}/sales/${saleId}/questions/`,
256
+ store.ENCRYPTED,
257
+ store.METHOD,
258
+ store.path(section, saleId),
228
259
  input
229
260
  );
230
261
  }
@@ -232,6 +263,18 @@ export namespace store
232
263
  {
233
264
  export type Input = Primitive<ISaleInquiry.IStore>;
234
265
  export type Output = Primitive<ISaleInquiry<ISaleArticle.IContent>>;
266
+
267
+ export const METHOD = "POST" as const;
268
+ export const PATH: string = "/consumers/:section/sales/:saleId/questions";
269
+ export const ENCRYPTED: Fetcher.IEncrypted = {
270
+ request: true,
271
+ response: true,
272
+ };
273
+
274
+ export function path(section: string, saleId: number): string
275
+ {
276
+ return `/consumers/${section}/sales/${saleId}/questions`;
277
+ }
235
278
  }
236
279
  ```
237
280
 
@@ -239,26 +282,55 @@ export namespace store
239
282
 
240
283
 
241
284
  ## Appendix
242
- ### Safe-TypeORM
243
- https://github.com/samchon/safe-typeorm
285
+ ### Template Project
286
+ https://github.com/samchon/backend
244
287
 
245
- [safe-typeorm](https://github.com/samchon/safe-typeorm) is another library that what I've developed, helping typeorm in the compilation level and optimizes DB performance automatically without any extra dedication.
288
+ I support template backend project using this **Nestia*** library, [backend](https://github.com/samchon/backend).
246
289
 
247
- Therefore, this **Nestia** makes you to be much convenient in the API interaction level and safe-typeorm helps you to be much convenient in the DB interaction level. With those **Nestia** and [safe-typeorm](https://github.com/samchon/safe-typeorm), let's implement the backend server much easily and conveniently.
290
+ Also, reading the README content of the [backend](https://github.com/samchon/backend) template repository, you can find lots of example backend projects who've been generated from the [backend](https://github.com/samchon/backend). Furthermore, the example projects guide how to generate SDK library from the **Nestia** and how to distribute the SDK library thorugh the NPM module.
248
291
 
249
- ### Technial Support
250
- samchon.github@gmail.com
292
+ Therefore, if you're planning to compose your own backend project using this **Nestia**, I recommend you to create the repository and learn from the [backend](https://github.com/samchon/backend) template project.
251
293
 
252
- I can provide technical support about those **Nestia** and [safe-typeorm](https://github.com/samchon/safe-typeorm).
294
+ ### Nestia-Helper
295
+ https://github.com/samchon/nestia-helper
253
296
 
254
- Therefore, if you have any question or need help, feel free to contact me. If you want to adapt those **Nestia** and [safe-typeorm](https://github.com/samchon/safe-typeorm) in your commercial project, I can provide you the best guidance.
297
+ Helper library of the `NestJS` with **Nestia**.
255
298
 
256
- I also can help your backend project in the entire development level. If you're suffering by DB architecture design or API structure design, just contact me and get help. I'll help you with my best effort.
299
+ [nestia-helper](https://github.com/samchon/nestia-helper) is a type of helper library for `Nestia` by enhancing decorator functions. Also, all of the decorator functions provided by this [nestia-helper](https://github.com/samchon/nestia-helper) are all fully compatible with the **Nestia**, who can generate SDK library by analyzing NestJS controller classes in the compilation level.
300
+
301
+ Of course, this [nestia-helper](https://github.com/samchon/nestia-helper) is not essential for utilizing the `NestJS` and **Nestia**. You can generate SDK library of your NestJS developed backend server without this [nestia-helper](https://github.com/samchon/nestia-helper). However, as decorator functions of this [nestia-helper](https://github.com/samchon/nestia-helper) is enough strong, I recommend you to adapt this [nestia-helper](https://github.com/samchon/nestia-helper) when using `NestJS` and **Nestia**.
302
+
303
+ - Supported decorator functions
304
+ - [EncryptedController](https://github.com/samchon/nestia-helper#encryptedcontroller), [EncryptedModule](https://github.com/samchon/nestia-helper#encryptedmodule)
305
+ - [TypedRoute](https://github.com/samchon/nestia-helper#typedroute), [EncryptedRoute](https://github.com/samchon/nestia-helper#encryptedroute)
306
+ - [TypedParam](https://github.com/samchon/nestia-helper#typedparam), [EncryptedBody](https://github.com/samchon/nestia-helper#encryptedbody), [PlainBody](https://github.com/samchon/nestia-helper#plainbody)
307
+ - [ExceptionManager](https://github.com/samchon/nestia-helper#exceptionmanager)
308
+
309
+ ### Safe-TypeORM
310
+ https://github.com/samchon/safe-typeorm
311
+
312
+ [safe-typeorm](https://github.com/samchon/safe-typeorm) is another library that what I've developed, helping `TypeORM` in the compilation level and optimizes DB performance automatically without any extra dedication.
313
+
314
+ Therefore, this **Nestia** makes you to be much convenient in the API interaction level and safe-typeorm helps you to be much convenient in the DB interaction level. With those **Nestia** and [safe-typeorm](https://github.com/samchon/safe-typeorm), let's implement the backend server much easily and conveniently.
315
+
316
+ - When writing [**SQL query**](https://github.com/samchon/safe-typeorm#safe-query-builder),
317
+ - Errors would be detected in the **compilation** level
318
+ - **Auto Completion** would be provided
319
+ - **Type Hint** would be supported
320
+ - You can implement [**App-join**](https://github.com/samchon/safe-typeorm#app-join-builder) very conveniently
321
+ - When [**SELECT**ing for **JSON** conversion](https://github.com/samchon/safe-typeorm#json-select-builder)
322
+ - [**App-Join**](https://github.com/samchon/safe-typeorm#app-join-builder) with the related entities would be automatically done
323
+ - Exact JSON **type** would be automatically **deduced**
324
+ - The **performance** would be **automatically tuned**
325
+ - When [**INSERT**](https://github.com/samchon/safe-typeorm#insert-collection)ing records
326
+ - Sequence of tables would be automatically sorted by analyzing dependencies
327
+ - The **performance** would be **automatically tuned**
328
+
329
+ ![Safe-TypeORM Demo](https://raw.githubusercontent.com/samchon/safe-typeorm/master/assets/demonstrations/safe-query-builder.gif)
257
330
 
258
331
  ### Archidraw
259
332
  https://www.archisketch.com/
260
333
 
261
334
  I have special thanks to the Archidraw, where I'm working for.
262
335
 
263
- The Archidraw is a great IT company developing 3D interior editor and lots of solutions based on the 3D assets. Also, the Archidraw is the first company who had adopted this **Nestia** on their commercial backend project, even this **Nestia** was in the alpha level.
264
-
336
+ The Archidraw is a great IT company developing 3D interior editor and lots of solutions based on the 3D assets. Also, the Archidraw is the first company who had adopted this **Nestia** on their commercial backend project, even this **Nestia** was in the alpha level.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestia",
3
- "version": "0.4.1",
3
+ "version": "1.2.0",
4
4
  "description": "Automatic SDK and Document generator for the NestJS",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -30,17 +30,19 @@
30
30
  "homepage": "https://github.com/samchon/nestia#readme",
31
31
  "dependencies": {
32
32
  "@types/cli": "^0.11.19",
33
+ "@types/glob": "^7.2.0",
33
34
  "@types/node": "^14.14.22",
34
35
  "@types/reflect-metadata": "^0.1.0",
35
36
  "cli": "^1.0.1",
36
37
  "del": "^6.0.0",
38
+ "glob": "^7.2.0",
37
39
  "ts-node": "^9.1.1",
38
- "tstl": "^2.5.0",
39
- "ttypescript": "^1.5.12",
40
- "typescript": "^4.3.2"
40
+ "tsconfig-paths": "^3.14.1",
41
+ "tstl": "^2.5.3",
42
+ "typescript": "^4.6.3"
41
43
  },
42
44
  "devDependencies": {
43
- "encrypted-nestjs": "^0.1.15",
45
+ "nestia-helper": "^1.2.0-dev.20220411",
44
46
  "rimraf": "^3.0.2"
45
47
  }
46
48
  }
@@ -0,0 +1,16 @@
1
+ import type tsc from "typescript";
2
+
3
+ export interface IConfiguration
4
+ {
5
+ input: string | string[] | IConfiguration.IInput;
6
+ output: string;
7
+ compilerOptions?: tsc.CompilerOptions;
8
+ }
9
+ export namespace IConfiguration
10
+ {
11
+ export interface IInput
12
+ {
13
+ include: string[];
14
+ exclude?: string[];
15
+ }
16
+ }
@@ -9,16 +9,17 @@ import { ReflectAnalyzer } from "./analyses/ReflectAnalyzer";
9
9
  import { SourceFinder } from "./analyses/SourceFinder";
10
10
  import { SdkGenerator } from "./generates/SdkGenerator";
11
11
 
12
+ import { IConfiguration } from "./IConfiguration";
12
13
  import { IController } from "./structures/IController";
13
14
  import { IRoute } from "./structures/IRoute";
14
15
  import { ArrayUtil } from "./utils/ArrayUtil";
15
16
 
16
17
  export class NestiaApplication
17
18
  {
18
- private readonly config_: NestiaApplication.IConfiguration;
19
+ private readonly config_: IConfiguration;
19
20
  private readonly bundle_checker_: Singleton<Promise<(str: string) => boolean>>;
20
21
 
21
- public constructor(config: NestiaApplication.IConfiguration)
22
+ public constructor(config: IConfiguration)
22
23
  {
23
24
  this.config_ = config;
24
25
  this.bundle_checker_ = new Singleton(async () =>
@@ -47,18 +48,16 @@ export class NestiaApplication
47
48
  public async generate(): Promise<void>
48
49
  {
49
50
  // LOAD CONTROLLER FILES
50
- const fileList: string[] = [];
51
- const inputList: string[] = this.config_.input instanceof Array
52
- ? this.config_.input
53
- : [this.config_.input];
54
-
55
- for (const file of inputList.map(str => path.resolve(str)))
56
- {
57
- const found: string[] = await SourceFinder.find(file);
58
- const filtered: string[] = await ArrayUtil.asyncFilter(found, file => this.is_not_excluded(file));
59
-
60
- fileList.push(...filtered);
61
- }
51
+ const input: IConfiguration.IInput = this.config_.input instanceof Array
52
+ ? { include: this.config_.input }
53
+ : typeof this.config_.input === "string"
54
+ ? { include: [ this.config_.input ] }
55
+ : this.config_.input;
56
+ const fileList: string[] = await ArrayUtil.asyncFilter
57
+ (
58
+ await SourceFinder.find(input),
59
+ file => this.is_not_excluded(file)
60
+ );
62
61
 
63
62
  // ANALYZE REFLECTS
64
63
  const unique: WeakSet<any> = new WeakSet();
@@ -94,14 +93,4 @@ export class NestiaApplication
94
93
  return file.indexOf(`${this.config_.output}${path.sep}functional`) === -1
95
94
  && (await this.bundle_checker_.get())(file) === false;
96
95
  }
97
- }
98
-
99
- export namespace NestiaApplication
100
- {
101
- export interface IConfiguration
102
- {
103
- input: string | string[];
104
- output: string;
105
- compilerOptions?: tsc.CompilerOptions;
106
- }
107
96
  }
@@ -1,33 +1,73 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
1
+ import fs from "fs";
2
+ import glob from "glob";
3
+ import path from "path";
4
+
5
+ import { IConfiguration } from "../IConfiguration";
3
6
 
4
7
  export namespace SourceFinder
5
8
  {
6
- export async function find(directory: string): Promise<string[]>
9
+ export async function find(input: IConfiguration.IInput): Promise<string[]>
7
10
  {
8
- const output: string[] = [];
9
- await gather(output, directory);
11
+ const dict: Set<string> = new Set();
12
+ await decode(input.include, str => dict.add(str));
13
+ if (input.exclude)
14
+ await decode(input.exclude, str => dict.delete(str));
15
+
16
+ return [...dict];
17
+ }
10
18
 
11
- return output;
19
+ async function decode
20
+ (
21
+ input: string[],
22
+ closure: (location: string) => void,
23
+ ): Promise<void>
24
+ {
25
+ for (const pattern of input)
26
+ for (const location of await _Glob(path.resolve(pattern)))
27
+ {
28
+ const stats: fs.Stats = await fs.promises.stat(location);
29
+ if (stats.isDirectory() === true)
30
+ await iterate(closure, location);
31
+ else if (stats.isFile() && _Is_ts_file(location))
32
+ closure(location);
33
+ }
12
34
  }
13
35
 
14
- async function gather(output: string[], directory: string): Promise<void>
36
+ async function iterate
37
+ (
38
+ closure: (location: string) => void,
39
+ location: string
40
+ ): Promise<void>
15
41
  {
16
- const children: string[] = await fs.promises.readdir(directory);
17
- for (const file of children)
42
+ const directory: string[] = await fs.promises.readdir(location);
43
+ for (const file of directory)
18
44
  {
19
- const current: string = `${directory}${path.sep}${file}`;
20
- const stats: fs.Stats = await fs.promises.lstat(current);
45
+ const next: string = path.resolve(`${location}/${file}`);
46
+ const stats: fs.Stats = await fs.promises.stat(next);
21
47
 
22
48
  if (stats.isDirectory() === true)
23
- {
24
- await gather(output, current);
25
- continue;
26
- }
27
- else if (file.substr(-3) !== ".ts" || file.substr(-5) === ".d.ts")
28
- continue;
29
-
30
- output.push(current);
49
+ await iterate(closure, next);
50
+ else if (stats.isFile() && _Is_ts_file(file))
51
+ closure(next);
31
52
  }
32
53
  }
54
+
55
+ function _Glob(pattern: string): Promise<string[]>
56
+ {
57
+ return new Promise((resolve, reject) =>
58
+ {
59
+ glob(pattern, (err, matches) =>
60
+ {
61
+ if (err)
62
+ reject(err);
63
+ else
64
+ resolve(matches.map(str => path.resolve(str)));
65
+ });
66
+ });
67
+ }
68
+
69
+ function _Is_ts_file(file: string): boolean
70
+ {
71
+ return file.substr(-3) === ".ts" && file.substr(-5) !== ".d.ts";
72
+ }
33
73
  }
package/src/bin/nestia.ts CHANGED
@@ -1,88 +1,82 @@
1
- #!/usr/bin/env ts-node-script
1
+ #!/usr/bin/env ts-node
2
2
 
3
- import cli from "cli";
3
+ import cp from "child_process";
4
4
  import fs from "fs";
5
- import path from "path";
6
5
  import tsc from "typescript";
7
6
 
8
- import { NestiaApplication } from "../NestiaApplication";
9
-
10
- import { Terminal } from "../utils/Terminal";
11
7
  import { stripJsonComments } from "../utils/stripJsonComments";
12
8
 
13
- interface ICommand
9
+ function install(): void
14
10
  {
15
- out: string | null;
11
+ const command: string = "npm install --save nestia-fetcher";
12
+ cp.execSync(command, { stdio: "inherit" });
16
13
  }
17
14
 
18
- async function sdk(input: string[], command: ICommand): Promise<void>
15
+ function sdk(alias: boolean): void
19
16
  {
20
- let compilerOptions: tsc.CompilerOptions | undefined = {};
17
+ const parameters: string[] = [
18
+ alias ? "npx ts-node -r tsconfig-paths/register" : "npx ts-node",
19
+ __dirname + "/../executable/sdk",
20
+ ...process.argv.slice(3)
21
+ ];
22
+ const command: string = parameters.join(" ");
23
+ cp.execSync(command, { stdio: "inherit" });
24
+ }
21
25
 
22
- //----
23
- // NESTIA.CONFIG.TS
24
- //----
25
- if (fs.existsSync("nestia.config.ts") === true)
26
+ async function tsconfig(task: (alias: boolean) => void): Promise<void>
27
+ {
28
+ // NO TSCONFIG.JSON?
29
+ if (fs.existsSync("tsconfig.json") === false)
26
30
  {
27
- const config: NestiaApplication.IConfiguration = await import(path.resolve("nestia.config.ts"));
28
- compilerOptions = config.compilerOptions;
29
- input = config.input instanceof Array ? config.input : [config.input];
30
- command.out = config.output;
31
+ task(false);
32
+ return;
31
33
  }
32
-
33
- //----
34
- // VALIDATIONS
35
- //----
36
- // CHECK OUTPUT
37
- if (command.out === null)
38
- throw new Error(`Output directory is not specified. Add the "--out <output_directory>" option.`);
39
34
 
40
- // CHECK PARENT DIRECTORY
41
- const parentPath: string = path.resolve(command.out + "/..");
42
- const parentStats: fs.Stats = await fs.promises.stat(parentPath);
35
+ const content: string = await fs.promises.readFile("tsconfig.json", "utf8");
36
+ const json: any = JSON.parse(stripJsonComments(content));
37
+ const options: tsc.CompilerOptions = json.compilerOptions;
43
38
 
44
- if (parentStats.isDirectory() === false)
45
- throw new Error(`Unable to find parent directory of the output path: "${parentPath}".`);
46
-
47
- // CHECK INPUTS
48
- for (const path of input)
39
+ // NO ALIAS PATHS
40
+ if (!options.paths || !Object.entries(options.paths).length)
49
41
  {
50
- const inputStats: fs.Stats = await fs.promises.stat(path);
51
- if (inputStats.isDirectory() === false)
52
- throw new Error(`Target "${path}" is not a directory.`);
42
+ task(false);
43
+ return;
53
44
  }
54
45
 
55
- //----
56
- // GENERATION
57
- //----
58
- if (fs.existsSync("tsconfig.json") === true)
59
- {
60
- const content: string = await fs.promises.readFile("tsconfig.json", "utf8");
61
- const options: tsc.CompilerOptions = JSON.parse(stripJsonComments(content)).compilerOptions;
46
+ let closer: null | (() => Promise<void>) = null;
47
+ let error: Error | null = null;
62
48
 
63
- compilerOptions = compilerOptions
64
- ? { ...options, ...compilerOptions }
65
- : options;
49
+ if (!options.baseUrl)
50
+ {
51
+ options.baseUrl = "./";
52
+ await fs.promises.writeFile
53
+ (
54
+ "tsconfig.json",
55
+ JSON.stringify(json, null, 2),
56
+ "utf8"
57
+ );
58
+
59
+ closer = () => fs.promises.writeFile
60
+ (
61
+ "tsconfig.json",
62
+ content,
63
+ "utf8"
64
+ );
66
65
  }
67
66
 
68
- // CHECK NESTIA.CONFIG.TS
69
-
70
- // CALL THE APP.GENERATE()
71
- const app: NestiaApplication = new NestiaApplication({
72
- output: command.out,
73
- input,
74
- compilerOptions,
75
- });
76
- await app.generate();
77
- }
78
-
79
- async function install(): Promise<void>
80
- {
81
- for (const module of ["@types/node", "node-fetch"])
67
+ try
82
68
  {
83
- console.log(`installing ${module}...`);
84
- await Terminal.execute(`npm install --save-dev ${module}`);
69
+ task(true);
85
70
  }
71
+ catch (exp)
72
+ {
73
+ error = exp as Error;
74
+ }
75
+
76
+ if (closer)
77
+ await closer();
78
+ if (error)
79
+ throw error;
86
80
  }
87
81
 
88
82
  async function main(): Promise<void>
@@ -90,32 +84,12 @@ async function main(): Promise<void>
90
84
  if (process.argv[2] === "install")
91
85
  await install();
92
86
  else if (process.argv[2] === "sdk")
93
- {
94
- const command: ICommand = cli.parse({
95
- out: ["o", "Output path of the SDK files", "string", null],
96
- });
97
-
98
- try
99
- {
100
- const inputs: string[] = [];
101
- for (const arg of process.argv.slice(3))
102
- {
103
- if (arg[0] === "-")
104
- break;
105
- inputs.push(arg);
106
- }
107
- await sdk(inputs, command);
108
- }
109
- catch (exp)
110
- {
111
- console.log(exp);
112
- process.exit(-1);
113
- }
114
- }
87
+ await tsconfig(sdk);
115
88
  else
116
- {
117
- console.log(`nestia supports only two commands; install and sdk, however you typed ${process.argv[2]}`);
118
- process.exit(-1);
119
- }
89
+ throw new Error(`nestia supports only two commands; install and sdk, however you typed ${process.argv[2]}`);
120
90
  }
121
- main();
91
+ main().catch(exp =>
92
+ {
93
+ console.log(exp.message);
94
+ process.exit(-1);
95
+ });
@@ -1,33 +1 @@
1
- /**
2
- * @packageDocumentation
3
- * @module api
4
- */
5
- //================================================================
6
- /**
7
- * HTTP Error from the backend server.
8
- *
9
- * @author Jeongho Nam - https://github.com/samchon
10
- */
11
- export class HttpError extends Error
12
- {
13
- public readonly method: string;
14
- public readonly path: string;
15
- public readonly status: number;
16
-
17
- public constructor(method: string, path: string, status: number, message: string)
18
- {
19
- super(message);
20
-
21
- // INHERITANCE POLYFILL
22
- const proto: HttpError = new.target.prototype;
23
- if (Object.setPrototypeOf)
24
- Object.setPrototypeOf(this, proto);
25
- else
26
- (this as any).__proto__ = proto;
27
-
28
- // ASSIGN MEMBERS
29
- this.method = method;
30
- this.path = path;
31
- this.status = status;
32
- }
33
- }
1
+ export { HttpError } from "nestia-fetcher";
@@ -1,26 +1 @@
1
- /**
2
- * @packageDocumentation
3
- * @module api
4
- */
5
- //================================================================
6
- export interface IConnection
7
- {
8
- host: string;
9
- headers?: Record<string, string>;
10
- encryption?: IConnection.IEncyptionPassword | IConnection.EncryptionClosure;
11
- path?: (path: string) => string;
12
- }
13
-
14
- export namespace IConnection
15
- {
16
- export interface IEncyptionPassword
17
- {
18
- key: string;
19
- iv: string;
20
- }
21
-
22
- export interface EncryptionClosure
23
- {
24
- (content: string, isEncode: boolean): IEncyptionPassword;
25
- }
26
- }
1
+ export { IConnection } from "nestia-fetcher";
@@ -1,113 +1 @@
1
- /**
2
- * @packageDocumentation
3
- * @module api
4
- */
5
- //================================================================
6
- /**
7
- * 객체의 원시 타입.
8
- *
9
- * `Primitive` 는 대상 인스턴스의 모든 메서드를 제거하여, 그 타입을 pritimive object 의 형태로
10
- * 바꾸어주는 TMP (Type Meta Programming) 타입이다.
11
- *
12
- * @template Instance 대상 인스턴스
13
- * @author Samchon
14
- */
15
- export type Primitive<Instance> = value_of<Instance> extends object
16
- ? Instance extends object
17
- ? Instance extends IJsonable<infer Raw>
18
- ? value_of<Raw> extends object
19
- ? Raw extends object
20
- ? PrimitiveObject<Raw> // object would be primitified
21
- : never // cannot be
22
- : value_of<Raw> // atomic value
23
- : PrimitiveObject<Instance> // object would be primitified
24
- : never // cannot be
25
- : value_of<Instance>;
26
-
27
- export namespace Primitive
28
- {
29
- /**
30
- * Primitive object 하드 카피.
31
- *
32
- * `Primitive.clone()` 은 파라미터 인스턴스를 원시 오브젝트 형태로 hard copy 하는 함수이다.
33
- *
34
- * @param instance 복사 대상 인스턴스
35
- * @return 복사된 객체
36
- */
37
- export function clone<Instance>(instance: Instance): Primitive<Instance>
38
- {
39
- return JSON.parse(JSON.stringify(instance));
40
- }
41
-
42
- /**
43
- * @todo
44
- */
45
- export function equal_to<Instance>(x: Instance, y: Instance): boolean
46
- {
47
- return JSON.stringify(x) === JSON.stringify(y) || recursive_equal_to(x, y);
48
- }
49
- }
50
-
51
- type PrimitiveObject<Instance extends object> = Instance extends Array<infer T>
52
- ? Primitive<T>[]
53
- :
54
- {
55
- [P in keyof Instance]: Instance[P] extends Function
56
- ? never
57
- : Primitive<Instance[P]>
58
- };
59
-
60
- type value_of<Instance> =
61
- is_value_of<Instance, Boolean> extends true ? boolean
62
- : is_value_of<Instance, Number> extends true ? number
63
- : is_value_of<Instance, String> extends true ? string
64
- : Instance;
65
-
66
- type is_value_of<Instance, Object extends IValueOf<any>> =
67
- Instance extends Object
68
- ? Object extends IValueOf<infer Primitive>
69
- ? Instance extends Primitive
70
- ? false
71
- : true // not Primitive, but Object
72
- : false // cannot be
73
- : false;
74
-
75
- interface IValueOf<T>
76
- {
77
- valueOf(): T;
78
- }
79
-
80
- interface IJsonable<T>
81
- {
82
- toJSON(): T;
83
- }
84
-
85
- function object_equal_to<T extends object>(x: T, y: T): boolean
86
- {
87
- for (const key in x)
88
- if (recursive_equal_to(x[key], y[key]) === false)
89
- return false;
90
- return true;
91
- }
92
-
93
- function array_equal_to<T>(x: T[], y: T[]): boolean
94
- {
95
- if (x.length !== y.length)
96
- return false;
97
-
98
- return x.every((value, index) => recursive_equal_to(value, y[index]));
99
- }
100
-
101
- function recursive_equal_to<T>(x: T, y: T): boolean
102
- {
103
- const type = typeof x;
104
- if (type !== typeof y)
105
- return false;
106
- else if (type === "object")
107
- if (x instanceof Array)
108
- return array_equal_to(x, y as typeof x);
109
- else
110
- return object_equal_to(<any>x as object, <any>y as object);
111
- else
112
- return x === y;
113
- }
1
+ export { Primitive } from "nestia-fetcher";
@@ -1,51 +1 @@
1
- /**
2
- * @packageDocumentation
3
- * @module api.__internal
4
- */
5
- //================================================================
6
- import * as crypto from "crypto";
7
-
8
- /**
9
- * Utility class for AES Encryption.
10
- *
11
- * - AES-128/256
12
- * - CBC mode
13
- * - PKCS#5 Padding
14
- * - Base64 Encoding
15
- *
16
- * @author Jeongho Nam - https://github.com/samchon
17
- */
18
- export namespace AesPkcs5
19
- {
20
- /**
21
- * Encode data
22
- *
23
- * @param data Target data
24
- * @param key Key value of the encryption.
25
- * @param iv Initializer Vector for the encryption
26
- * @return Encoded data
27
- */
28
- export function encode(data: string, key: string, iv: string): string
29
- {
30
- const bytes: number = key.length * 8;
31
- const cipher: crypto.Cipher = crypto.createCipheriv(`AES-${bytes}-CBC`, key, iv);
32
-
33
- return cipher.update(data, "utf8", "base64") + cipher.final("base64");
34
- }
35
-
36
- /**
37
- * Decode data.
38
- *
39
- * @param data Target data
40
- * @param key Key value of the decryption.
41
- * @param iv Initializer Vector for the decryption
42
- * @return Decoded data.
43
- */
44
- export function decode(data: string, key: string, iv: string): string
45
- {
46
- const bytes: number = key.length * 8;
47
- const decipher: crypto.Decipher = crypto.createDecipheriv(`AES-${bytes}-CBC`, key, iv);
48
-
49
- return decipher.update(data, "base64", "utf8") + decipher.final("utf8");
50
- }
51
- }
1
+ export { AesPkcs5 } from "nestia-fetcher";
@@ -1,126 +1 @@
1
- /**
2
- * @packageDocumentation
3
- * @module api.__internal
4
- */
5
- //================================================================
6
- import { AesPkcs5 } from "./AesPkcs5";
7
- import { HttpError } from "../HttpError";
8
- import { IConnection } from "../IConnection";
9
- import { Primitive } from "../Primitive";
10
-
11
- // POLYFILL FOR NODE
12
- if (typeof global === "object"
13
- && typeof global.process === "object"
14
- && typeof global.process.versions === "object"
15
- && typeof global.process.versions.node !== undefined)
16
- (global as any).fetch = require("node-fetch");
17
-
18
- export class Fetcher
19
- {
20
- public static fetch<Output>(connection: IConnection, config: Fetcher.IConfig, method: "GET" | "DELETE", path: string): Promise<Primitive<Output>>;
21
- public static fetch<Input, Output>(connection: IConnection, config: Fetcher.IConfig, method: "POST" | "PUT" | "PATCH", path: string, input: Input): Promise<Primitive<Output>>;
22
-
23
- public static async fetch<Output>
24
- (
25
- connection: IConnection,
26
- config: Fetcher.IConfig,
27
- method: string,
28
- path: string,
29
- input?: object
30
- ): Promise<Primitive<Output>>
31
- {
32
- if (config.input_encrypted === true || config.output_encrypted === true)
33
- if (connection.encryption === undefined)
34
- throw new Error("Error on nestia.Fetcher.encrypt(): the encryption password has not been configured.");
35
-
36
- //----
37
- // REQUEST MESSSAGE
38
- //----
39
- // METHOD & HEADERS
40
- const init: RequestInit = {
41
- method,
42
- headers: config.input_encrypted === false && input !== undefined && typeof input === "object"
43
- ? {
44
- ...connection.headers,
45
- "Content-Type": "application/json"
46
- }
47
- : connection.headers
48
- };
49
-
50
- // REQUEST BODY (WITH ENCRYPTION)
51
- if (input !== undefined)
52
- {
53
- let content: string = JSON.stringify(input);
54
- if (config.input_encrypted === true)
55
- {
56
- const password: IConnection.IEncyptionPassword = connection.encryption instanceof Function
57
- ? connection.encryption!(content, true)
58
- : connection.encryption!;
59
- content = AesPkcs5.encode(content, password.key, password.iv);
60
- }
61
- init.body = content;
62
- }
63
-
64
- //----
65
- // RESPONSE MESSAGE
66
- //----
67
- // URL SPECIFICATION
68
- if (connection.host[connection.host.length - 1] !== "/" && path[0] !== "/")
69
- path = "/" + path;
70
- if (connection.path)
71
- path = connection.path(path);
72
-
73
- const url: URL = new URL(`${connection.host}${path}`);
74
-
75
- // DO FETCH
76
- const response: Response = await fetch(url.href, init);
77
- let content: string = await response.text();
78
-
79
- if (!content)
80
- return undefined!;
81
-
82
- // CHECK THE STATUS CODE
83
- if (response.status !== 200 && response.status !== 201)
84
- throw new HttpError(method, path, response.status, content);
85
-
86
- // FINALIZATION (WITH DECODING)
87
- if (config.output_encrypted === true)
88
- {
89
- const password: IConnection.IEncyptionPassword = connection.encryption instanceof Function
90
- ? connection.encryption!(content, false)
91
- : connection.encryption!;
92
- content = AesPkcs5.decode(content, password.key, password.iv);
93
- }
94
-
95
- //----
96
- // OUTPUT
97
- //----
98
- let ret: { __set_headers__: Record<string, any> } & Primitive<Output> = content as any;
99
- try
100
- {
101
- // PARSE RESPONSE BODY
102
- ret = JSON.parse(ret as any);
103
-
104
- // FIND __SET_HEADERS__ FIELD
105
- if (ret.__set_headers__ !== undefined && typeof ret.__set_headers__ === "object")
106
- {
107
- if (connection.headers === undefined)
108
- connection.headers = {};
109
- Object.assign(connection.headers, ret.__set_headers__);
110
- }
111
- }
112
- catch {}
113
-
114
- // RETURNS
115
- return ret;
116
- }
117
- }
118
-
119
- export namespace Fetcher
120
- {
121
- export interface IConfig
122
- {
123
- input_encrypted?: boolean;
124
- output_encrypted: boolean;
125
- }
126
- }
1
+ export { Fetcher } from "nestia-fetcher";
@@ -0,0 +1,89 @@
1
+ import cli from "cli";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import tsc from "typescript";
5
+ import { Primitive } from "nestia-fetcher";
6
+
7
+ import { IConfiguration } from "../IConfiguration";
8
+ import { NestiaApplication } from "../NestiaApplication";
9
+ import { stripJsonComments } from "../utils/stripJsonComments";
10
+
11
+ interface ICommand
12
+ {
13
+ exclude: string | null;
14
+ out: string | null;
15
+ }
16
+
17
+ async function sdk(include: string[], command: ICommand): Promise<void>
18
+ {
19
+ // CONFIGURATION
20
+ let config: IConfiguration;
21
+ if (fs.existsSync("nestia.config.ts") === true)
22
+ config = Primitive.clone
23
+ (
24
+ await import(path.resolve("nestia.config.ts"))
25
+ );
26
+ else
27
+ {
28
+ if (command.out === null)
29
+ throw new Error(`Output directory is not specified. Add the "--out <output_directory>" option.`);
30
+ config = {
31
+ input: {
32
+ include,
33
+ exclude: command.exclude
34
+ ? [command.exclude]
35
+ : undefined
36
+ },
37
+ output: command.out
38
+ };
39
+ }
40
+
41
+ // VALIDATE OUTPUT DIRECTORY
42
+ const parentPath: string = path.resolve(config.output + "/..");
43
+ const parentStats: fs.Stats = await fs.promises.stat(parentPath);
44
+
45
+ if (parentStats.isDirectory() === false)
46
+ throw new Error(`Unable to find parent directory of the output path: "${parentPath}".`);
47
+
48
+ // GENERATION
49
+ if (fs.existsSync("tsconfig.json") === true)
50
+ {
51
+ const content: string = await fs.promises.readFile("tsconfig.json", "utf8");
52
+ const options: tsc.CompilerOptions = JSON.parse(stripJsonComments(content)).compilerOptions;
53
+
54
+ config.compilerOptions = {
55
+ ...options,
56
+ ...(config.compilerOptions || {})
57
+ };
58
+ }
59
+
60
+ // CALL THE APP.GENERATE()
61
+ const app: NestiaApplication = new NestiaApplication(config);
62
+ await app.generate();
63
+ }
64
+
65
+ async function main(): Promise<void>
66
+ {
67
+ const command: ICommand = cli.parse({
68
+ exclude: ["e", "Something to exclude", "string", null],
69
+ out: ["o", "Output path of the SDK files", "string", null],
70
+ });
71
+
72
+ try
73
+ {
74
+ const inputs: string[] = [];
75
+ for (const arg of process.argv.slice(2))
76
+ {
77
+ if (arg[0] === "-")
78
+ break;
79
+ inputs.push(arg);
80
+ }
81
+ await sdk(inputs, command);
82
+ }
83
+ catch (exp)
84
+ {
85
+ console.log(exp);
86
+ process.exit(-1);
87
+ }
88
+ }
89
+ main();
@@ -2,7 +2,6 @@ import * as fs from "fs";
2
2
  import { HashMap } from "tstl/container/HashMap";
3
3
 
4
4
  import { IRoute } from "../structures/IRoute";
5
- import { DirectoryUtil } from "../utils/DirectoryUtil";
6
5
  import { ImportDictionary } from "../utils/ImportDictionary";
7
6
  import { FunctionGenerator } from "./FunctionGenerator";
8
7
 
@@ -21,14 +20,8 @@ export namespace FileGenerator
21
20
  // RELOCATE FOR ONLY ONE CONTROLLER METHOD IN AN URL CASE
22
21
  relocate(root);
23
22
 
24
- const defaultImportDict: ImportDictionary = new ImportDictionary();
25
- defaultImportDict.emplace(`${outDir}/__internal/AesPkcs5.ts`, true, "AesPkcs5");
26
- defaultImportDict.emplace(`${outDir}/__internal/Fetcher.ts`, true, "Fetcher");
27
- defaultImportDict.emplace(`${outDir}/Primitive.ts`, true, "Primitive");
28
- defaultImportDict.emplace(`${outDir}/IConnection.ts`, false, "IConnection");
29
-
30
- await DirectoryUtil.remove(outDir + "/functional");
31
- await iterate(defaultImportDict, outDir + "/functional", root);
23
+ // ITERATE FILES
24
+ await iterate(outDir + "/functional", root);
32
25
  }
33
26
 
34
27
  function emplace(directory: Directory, route: IRoute): void
@@ -70,7 +63,7 @@ export namespace FileGenerator
70
63
  /* ---------------------------------------------------------
71
64
  FILE ITERATOR
72
65
  --------------------------------------------------------- */
73
- async function iterate(defaultImportDict: ImportDictionary, outDir: string, directory: Directory): Promise<void>
66
+ async function iterate(outDir: string, directory: Directory): Promise<void>
74
67
  {
75
68
  // CREATE A NEW DIRECTORY
76
69
  try
@@ -83,7 +76,7 @@ export namespace FileGenerator
83
76
  let content: string = "";
84
77
  for (const it of directory.directories)
85
78
  {
86
- await iterate(defaultImportDict, `${outDir}/${it.first}`, it.second);
79
+ await iterate(`${outDir}/${it.first}`, it.second);
87
80
  content += `export * as ${it.first} from "./${it.first}";\n`;
88
81
  }
89
82
  content += "\n";
@@ -100,10 +93,23 @@ export namespace FileGenerator
100
93
 
101
94
  // FINALIZE THE CONTENT
102
95
  if (directory.routes.length !== 0)
103
- content = defaultImportDict.toScript(outDir) + "\n\n"
104
- + importDict.toScript(outDir) + "\n\n"
96
+ content = ""
97
+ + `import { AesPkcs5, Fetcher, Primitive } from "nestia-fetcher";\n`
98
+ + `import type { IConnection } from "nestia-fetcher";\n`
99
+ +
100
+ (
101
+ importDict.empty()
102
+ ? ""
103
+ : "\n" + importDict.toScript(outDir) + "\n"
104
+ )
105
105
  + content + "\n\n"
106
- + defaultImportDict.listUp();
106
+ + "//---------------------------------------------------------\n"
107
+ + "// TO PREVENT THE UNUSED VARIABLE ERROR\n"
108
+ + "//---------------------------------------------------------\n"
109
+ + "AesPkcs5;\n"
110
+ + "Fetcher;\n"
111
+ + "Primitive;";
112
+
107
113
  content = "/**\n"
108
114
  + " * @packageDocumentation\n"
109
115
  + ` * @module ${directory.module}\n`
@@ -27,7 +27,7 @@ export namespace FunctionGenerator
27
27
  const fetchArguments: string[] =
28
28
  [
29
29
  "connection",
30
- `${route.name}.CONFIG`,
30
+ `${route.name}.ENCRYPTED`,
31
31
  `${route.name}.METHOD`,
32
32
  `${route.name}.path(${parameters.map(p => p.name).join(", ")})`
33
33
  ];
@@ -158,15 +158,15 @@ export namespace FunctionGenerator
158
158
  +
159
159
  (
160
160
  types.length !== 0
161
- ? types.map(tuple => ` export type ${tuple.first} = Primitive<${tuple.second}>;`).join("\n") + "\n\n"
161
+ ? types.map(tuple => ` export type ${tuple.first} = Primitive<${tuple.second}>;`).join("\n") + "\n"
162
162
  : ""
163
163
  )
164
164
  + "\n"
165
165
  + ` export const METHOD = "${route.method}" as const;\n`
166
166
  + ` export const PATH: string = "${route.path}";\n`
167
- + ` export const CONFIG = {\n`
168
- + ` input_encrypted: ${input !== undefined && input.encrypted},\n`
169
- + ` output_encrypted: ${route.encrypted},\n`
167
+ + ` export const ENCRYPTED: Fetcher.IEncrypted = {\n`
168
+ + ` request: ${input !== undefined && input.encrypted},\n`
169
+ + ` response: ${route.encrypted},\n`
170
170
  + ` };\n`
171
171
  + "\n"
172
172
  + ` export function path(${parameters.map(param => `${param.name}: ${param.type}`).join(", ")}): string\n`
@@ -8,6 +8,11 @@ export class ImportDictionary
8
8
  {
9
9
  private readonly dict_: HashMap<string, Pair<boolean, HashSet<string>>> = new HashMap();
10
10
 
11
+ public empty(): boolean
12
+ {
13
+ return this.dict_.empty();
14
+ }
15
+
11
16
  public emplace(file: string, realistic: boolean, instance: string): void
12
17
  {
13
18
  if (file.substr(-5) === ".d.ts")
@@ -36,17 +41,4 @@ export class ImportDictionary
36
41
  }
37
42
  return statements.join("\n");
38
43
  }
39
-
40
- public listUp(): string
41
- {
42
- let content: string = ""
43
- + "//---------------------------------------------------------\n"
44
- + "// TO PREVENT THE UNUSED VARIABLE ERROR\n"
45
- + "//---------------------------------------------------------\n";
46
- for (const it of this.dict_)
47
- if (it.second.first === true)
48
- for (const instance of it.second.second)
49
- content += instance + ";\n";
50
- return content;
51
- }
52
44
  }
package/tsconfig.json CHANGED
@@ -68,15 +68,11 @@
68
68
  /* Experimental Options */
69
69
  "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
70
70
  "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
71
+ "stripInternal": true,
71
72
 
72
73
  /* Advanced Options */
73
74
  "skipLibCheck": true, /* Skip type checking of declaration files. */
74
75
  "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
75
76
  },
76
- "include": [
77
- "src",
78
- "test/default/src",
79
- "test/nestia.config.ts/src",
80
- // "test/tsconfig.json/src"
81
- ]
77
+ "include": ["src"]
82
78
  }
@@ -1,19 +0,0 @@
1
- import * as cp from "child_process";
2
- import { Pair } from "tstl/utility/Pair";
3
-
4
- export namespace Terminal
5
- {
6
- export function execute(...commands: string[]): Promise<Pair<string, string>>
7
- {
8
- return new Promise((resolve, reject) =>
9
- {
10
- cp.exec(commands.join(" && "), (error: Error | null, stdout: string, stderr: string) =>
11
- {
12
- if (error)
13
- reject(error);
14
- else
15
- resolve(new Pair(stdout, stderr));
16
- });
17
- });
18
- }
19
- }