@nestia/sdk 1.1.0 → 1.2.0-dev.20230504

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/assets/bundle/api/index.ts +4 -0
  2. package/assets/bundle/api/module.ts +5 -0
  3. package/assets/bundle/e2e/index.ts +41 -0
  4. package/lib/INestiaConfig.d.ts +6 -0
  5. package/lib/NestiaSdkApplication.d.ts +1 -0
  6. package/lib/NestiaSdkApplication.js +35 -14
  7. package/lib/NestiaSdkApplication.js.map +1 -1
  8. package/lib/analyses/ControllerAnalyzer.js +1 -0
  9. package/lib/analyses/ControllerAnalyzer.js.map +1 -1
  10. package/lib/analyses/ReflectAnalyzer.js +14 -2
  11. package/lib/analyses/ReflectAnalyzer.js.map +1 -1
  12. package/lib/executable/internal/NestiaSdkCommand.d.ts +3 -2
  13. package/lib/executable/internal/NestiaSdkCommand.js +53 -57
  14. package/lib/executable/internal/NestiaSdkCommand.js.map +1 -1
  15. package/lib/executable/internal/NestiaSdkConfig.js +23 -50
  16. package/lib/executable/internal/NestiaSdkConfig.js.map +1 -1
  17. package/lib/executable/sdk.js +5 -8
  18. package/lib/executable/sdk.js.map +1 -1
  19. package/lib/generates/E2eGenerator.d.ts +5 -0
  20. package/lib/generates/E2eGenerator.js +52 -0
  21. package/lib/generates/E2eGenerator.js.map +1 -0
  22. package/lib/generates/SdkGenerator.d.ts +1 -2
  23. package/lib/generates/SdkGenerator.js +20 -22
  24. package/lib/generates/SdkGenerator.js.map +1 -1
  25. package/lib/generates/SwaggerGenerator.d.ts +1 -1
  26. package/lib/generates/SwaggerGenerator.js +40 -43
  27. package/lib/generates/SwaggerGenerator.js.map +1 -1
  28. package/lib/generates/internal/E2eFileProgrammer.d.ts +8 -0
  29. package/lib/generates/internal/E2eFileProgrammer.js +101 -0
  30. package/lib/generates/internal/E2eFileProgrammer.js.map +1 -0
  31. package/lib/generates/internal/SdkFileProgrammer.d.ts +5 -0
  32. package/lib/generates/internal/SdkFileProgrammer.js +121 -0
  33. package/lib/generates/internal/SdkFileProgrammer.js.map +1 -0
  34. package/lib/generates/internal/SdkFunctionProgrammer.d.ts +5 -0
  35. package/lib/generates/{FunctionGenerator.js → internal/SdkFunctionProgrammer.js} +58 -57
  36. package/lib/generates/internal/SdkFunctionProgrammer.js.map +1 -0
  37. package/lib/generates/internal/SdkRouteDirectory.d.ts +10 -0
  38. package/lib/generates/internal/SdkRouteDirectory.js +18 -0
  39. package/lib/generates/internal/SdkRouteDirectory.js.map +1 -0
  40. package/lib/structures/IController.d.ts +4 -0
  41. package/lib/structures/IRoute.d.ts +4 -0
  42. package/lib/utils/NestiaConfigUtil.d.ts +4 -0
  43. package/lib/utils/NestiaConfigUtil.js +24 -0
  44. package/lib/utils/NestiaConfigUtil.js.map +1 -0
  45. package/lib/utils/SourceFinder.d.ts +9 -0
  46. package/lib/utils/SourceFinder.js +60 -0
  47. package/lib/utils/SourceFinder.js.map +1 -0
  48. package/package.json +6 -10
  49. package/src/INestiaConfig.ts +7 -0
  50. package/src/NestiaSdkApplication.ts +54 -17
  51. package/src/analyses/ControllerAnalyzer.ts +1 -0
  52. package/src/analyses/ReflectAnalyzer.ts +14 -2
  53. package/src/executable/internal/NestiaSdkCommand.ts +87 -105
  54. package/src/executable/sdk.ts +4 -8
  55. package/src/generates/E2eGenerator.ts +65 -0
  56. package/src/generates/SdkGenerator.ts +29 -30
  57. package/src/generates/SwaggerGenerator.ts +66 -64
  58. package/src/generates/internal/E2eFileProgrammer.ts +119 -0
  59. package/src/generates/internal/SdkFileProgrammer.ts +144 -0
  60. package/src/generates/internal/SdkFunctionProgrammer.ts +371 -0
  61. package/src/generates/internal/SdkRouteDirectory.ts +21 -0
  62. package/src/structures/IController.ts +4 -0
  63. package/src/structures/IRoute.ts +4 -0
  64. package/src/utils/NestiaConfigUtil.ts +21 -0
  65. package/src/utils/SourceFinder.ts +60 -0
  66. package/lib/analyses/SourceFinder.d.ts +0 -4
  67. package/lib/analyses/SourceFinder.js +0 -71
  68. package/lib/analyses/SourceFinder.js.map +0 -1
  69. package/lib/generates/FileGenerator.d.ts +0 -5
  70. package/lib/generates/FileGenerator.js +0 -138
  71. package/lib/generates/FileGenerator.js.map +0 -1
  72. package/lib/generates/FunctionGenerator.d.ts +0 -5
  73. package/lib/generates/FunctionGenerator.js.map +0 -1
  74. package/src/analyses/SourceFinder.ts +0 -59
  75. package/src/generates/FileGenerator.ts +0 -156
  76. package/src/generates/FunctionGenerator.ts +0 -348
  77. /package/assets/bundle/{HttpError.ts → api/HttpError.ts} +0 -0
  78. /package/assets/bundle/{IConnection.ts → api/IConnection.ts} +0 -0
  79. /package/assets/bundle/{Primitive.ts → api/Primitive.ts} +0 -0
@@ -1,44 +1,42 @@
1
1
  import fs from "fs";
2
2
  import NodePath from "path";
3
- import ts from "typescript";
4
3
 
5
4
  import { INestiaConfig } from "../INestiaConfig";
6
5
  import { IRoute } from "../structures/IRoute";
7
- import { FileGenerator } from "./FileGenerator";
6
+ import { SdkFileProgrammer } from "./internal/SdkFileProgrammer";
8
7
 
9
8
  export namespace SdkGenerator {
10
- export async function generate(
11
- _checker: ts.TypeChecker,
12
- config: INestiaConfig,
13
- routeList: IRoute[],
14
- ): Promise<void> {
15
- // PREPARE NEW DIRECTORIES
16
- try {
17
- await fs.promises.mkdir(config.output!);
18
- } catch {}
9
+ export const generate =
10
+ (config: INestiaConfig) =>
11
+ async (routes: IRoute[]): Promise<void> => {
12
+ // PREPARE NEW DIRECTORIES
13
+ try {
14
+ await fs.promises.mkdir(config.output!);
15
+ } catch {}
19
16
 
20
- // BUNDLING
21
- const bundle: string[] = await fs.promises.readdir(BUNDLE_PATH);
22
- for (const file of bundle) {
23
- const current: string = `${BUNDLE_PATH}/${file}`;
24
- const stats: fs.Stats = await fs.promises.stat(current);
17
+ // BUNDLING
18
+ const bundle: string[] = await fs.promises.readdir(BUNDLE_PATH);
19
+ for (const file of bundle) {
20
+ const current: string = `${BUNDLE_PATH}/${file}`;
21
+ const stats: fs.Stats = await fs.promises.stat(current);
25
22
 
26
- if (stats.isFile() === true) {
27
- const content: string = await fs.promises.readFile(
28
- current,
29
- "utf8",
30
- );
31
- await fs.promises.writeFile(
32
- `${config.output}/${file}`,
33
- content,
34
- "utf8",
35
- );
23
+ if (stats.isFile() === true) {
24
+ const content: string = await fs.promises.readFile(
25
+ current,
26
+ "utf8",
27
+ );
28
+ if (fs.existsSync(`${config.output}/${file}`) === false)
29
+ await fs.promises.writeFile(
30
+ `${config.output}/${file}`,
31
+ content,
32
+ "utf8",
33
+ );
34
+ }
36
35
  }
37
- }
38
36
 
39
- // FUNCTIONAL
40
- await FileGenerator.generate(config, routeList);
41
- }
37
+ // FUNCTIONAL
38
+ await SdkFileProgrammer.generate(config)(routes);
39
+ };
42
40
 
43
41
  export const BUNDLE_PATH = NodePath.join(
44
42
  __dirname,
@@ -46,5 +44,6 @@ export namespace SdkGenerator {
46
44
  "..",
47
45
  "assets",
48
46
  "bundle",
47
+ "api",
49
48
  );
50
49
  }
@@ -17,72 +17,74 @@ import { ISwaggerDocument } from "../structures/ISwaggerDocument";
17
17
  import { MapUtil } from "../utils/MapUtil";
18
18
 
19
19
  export namespace SwaggerGenerator {
20
- export async function generate(
21
- checker: ts.TypeChecker,
22
- config: INestiaConfig.ISwaggerConfig,
23
- routeList: IRoute[],
24
- ): Promise<void> {
25
- // PREPARE ASSETS
26
- const parsed: NodePath.ParsedPath = NodePath.parse(config.output);
27
- const location: string = !!parsed.ext
28
- ? NodePath.resolve(config.output)
29
- : NodePath.join(NodePath.resolve(config.output), "swagger.json");
30
-
31
- const collection: MetadataCollection = new MetadataCollection({
32
- replace: MetadataCollection.replace,
33
- });
34
-
35
- // CONSTRUCT SWAGGER DOCUMENTS
36
- const tupleList: Array<ISchemaTuple> = [];
37
- const swagger: ISwaggerDocument = await initialize(location);
38
- const pathDict: Map<string, ISwaggerDocument.IPath> = new Map();
39
-
40
- for (const route of routeList) {
41
- if (route.tags.find((tag) => tag.name === "internal")) continue;
42
-
43
- const path: ISwaggerDocument.IPath = MapUtil.take(
44
- pathDict,
45
- get_path(route.path, route.parameters),
46
- () => ({}),
47
- );
48
- path[route.method.toLowerCase()] = generate_route(
49
- checker,
50
- collection,
51
- tupleList,
52
- route,
20
+ export const generate =
21
+ (checker: ts.TypeChecker) =>
22
+ (config: INestiaConfig.ISwaggerConfig) =>
23
+ async (routeList: IRoute[]): Promise<void> => {
24
+ // PREPARE ASSETS
25
+ const parsed: NodePath.ParsedPath = NodePath.parse(config.output);
26
+ const location: string = !!parsed.ext
27
+ ? NodePath.resolve(config.output)
28
+ : NodePath.join(
29
+ NodePath.resolve(config.output),
30
+ "swagger.json",
31
+ );
32
+
33
+ const collection: MetadataCollection = new MetadataCollection({
34
+ replace: MetadataCollection.replace,
35
+ });
36
+
37
+ // CONSTRUCT SWAGGER DOCUMENTS
38
+ const tupleList: Array<ISchemaTuple> = [];
39
+ const swagger: ISwaggerDocument = await initialize(location);
40
+ const pathDict: Map<string, ISwaggerDocument.IPath> = new Map();
41
+
42
+ for (const route of routeList) {
43
+ if (route.tags.find((tag) => tag.name === "internal")) continue;
44
+
45
+ const path: ISwaggerDocument.IPath = MapUtil.take(
46
+ pathDict,
47
+ get_path(route.path, route.parameters),
48
+ () => ({}),
49
+ );
50
+ path[route.method.toLowerCase()] = generate_route(
51
+ checker,
52
+ collection,
53
+ tupleList,
54
+ route,
55
+ );
56
+ }
57
+ swagger.paths = {};
58
+ for (const [path, routes] of pathDict) {
59
+ swagger.paths[path] = routes;
60
+ }
61
+
62
+ // FILL JSON-SCHEMAS
63
+ const application: IJsonApplication = ApplicationProgrammer.write({
64
+ purpose: "swagger",
65
+ })(tupleList.map(({ metadata }) => metadata));
66
+ swagger.components = {
67
+ ...(swagger.components ?? {}),
68
+ schemas: application.components.schemas,
69
+ };
70
+ tupleList.forEach(({ schema }, index) => {
71
+ Object.assign(schema, application.schemas[index]!);
72
+ });
73
+
74
+ // CONFIGURE SECURITY
75
+ if (config.security) fill_security(config.security, swagger);
76
+
77
+ // ERASE IJsonComponents.IObject.$id
78
+ for (const obj of Object.values(swagger.components.schemas))
79
+ if (obj.$id) delete obj.$id;
80
+
81
+ // DO GENERATE
82
+ await fs.promises.writeFile(
83
+ location,
84
+ JSON.stringify(swagger, null, 2),
85
+ "utf8",
53
86
  );
54
- }
55
- swagger.paths = {};
56
- for (const [path, routes] of pathDict) {
57
- swagger.paths[path] = routes;
58
- }
59
-
60
- // FILL JSON-SCHEMAS
61
- const application: IJsonApplication = ApplicationProgrammer.write({
62
- purpose: "swagger",
63
- })(tupleList.map(({ metadata }) => metadata));
64
- swagger.components = {
65
- ...(swagger.components ?? {}),
66
- schemas: application.components.schemas,
67
87
  };
68
- tupleList.forEach(({ schema }, index) => {
69
- Object.assign(schema, application.schemas[index]!);
70
- });
71
-
72
- // CONFIGURE SECURITY
73
- if (config.security) fill_security(config.security, swagger);
74
-
75
- // ERASE IJsonComponents.IObject.$id
76
- for (const obj of Object.values(swagger.components.schemas))
77
- if (obj.$id) delete obj.$id;
78
-
79
- // DO GENERATE
80
- await fs.promises.writeFile(
81
- location,
82
- JSON.stringify(swagger, null, 2),
83
- "utf8",
84
- );
85
- }
86
88
 
87
89
  /* ---------------------------------------------------------
88
90
  INITIALIZERS
@@ -0,0 +1,119 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ import { INestiaConfig } from "../../INestiaConfig";
5
+ import { IRoute } from "../../structures/IRoute";
6
+ import { ImportDictionary } from "../../utils/ImportDictionary";
7
+
8
+ export namespace E2eFileProgrammer {
9
+ export const generate =
10
+ (config: INestiaConfig) =>
11
+ (props: { api: string; current: string }) =>
12
+ async (route: IRoute): Promise<void> => {
13
+ const importDict: ImportDictionary = new ImportDictionary();
14
+ for (const tuple of route.imports)
15
+ for (const instance of tuple[1])
16
+ importDict.emplace(tuple[0], false, instance);
17
+
18
+ const uuid: boolean = route.parameters.some(
19
+ (p) => p.category === "param" && p.meta?.type === "uuid",
20
+ );
21
+ const content: string = [
22
+ config.primitive === false
23
+ ? `import typia from "typia";`
24
+ : `import typia, { Primitive } from "typia";`,
25
+ "",
26
+ `import api from "./${path
27
+ .relative(props.current, props.api)
28
+ .split("\\")
29
+ .join("/")}";`,
30
+ ...(importDict.empty()
31
+ ? []
32
+ : [importDict.toScript(props.current)]),
33
+ "",
34
+ arrow(config)(route),
35
+ ...(uuid ? ["", UUID] : []),
36
+ ].join("\n");
37
+
38
+ await fs.promises.writeFile(
39
+ `${props.current}/${name(route)}.ts`,
40
+ content,
41
+ "utf8",
42
+ );
43
+ };
44
+
45
+ const arrow =
46
+ (config: INestiaConfig) =>
47
+ (route: IRoute): string => {
48
+ const tab: number = route.output.name === "void" ? 2 : 3;
49
+ const output = [
50
+ `await ${accessor(route)}(`,
51
+ `${" ".repeat(tab * 4)}connection,`,
52
+ ...route.parameters.map(parameter(config)(tab)),
53
+ `${" ".repeat((tab - 1) * 4)});`,
54
+ ].join("\n");
55
+ return [
56
+ `export const ${name(route)} = async (`,
57
+ ` connection: api.IConnection`,
58
+ `): Promise<void> => {`,
59
+ ...(route.output.name === "void"
60
+ ? [` ${output}`]
61
+ : [
62
+ ` const output: ${primitive(config)(
63
+ route.output.name,
64
+ )} = `,
65
+ ` ${output}`,
66
+ ` typia.assert(output);`,
67
+ ]),
68
+ `};`,
69
+ ].join("\n");
70
+ };
71
+
72
+ const parameter =
73
+ (config: INestiaConfig) =>
74
+ (tab: number) =>
75
+ (param: IRoute.IParameter): string => {
76
+ const middle: string =
77
+ param.category === "param" && param.meta?.type === "uuid"
78
+ ? param.meta.nullable
79
+ ? `Math.random() < .2 ? null : uuid()`
80
+ : `uuid()`
81
+ : `typia.random<${primitive(config)(param.type.name)}>()`;
82
+ return `${" ".repeat(4 * tab)}${middle},`;
83
+ };
84
+
85
+ const name = (route: IRoute): string =>
86
+ [
87
+ "test_api",
88
+ ...route.path
89
+ .split("/")
90
+ .filter((str) => str.length && str[0] !== ":")
91
+ .map(normalize),
92
+ route.name,
93
+ ].join("_");
94
+
95
+ const accessor = (route: IRoute): string =>
96
+ [
97
+ "api.functional",
98
+ ...route.path
99
+ .split("/")
100
+ .filter((str) => str.length && str[0] !== ":")
101
+ .map(normalize),
102
+ route.name,
103
+ ].join(".");
104
+
105
+ const normalize = (str: string) =>
106
+ str.split("-").join("_").split(".").join("_");
107
+
108
+ const primitive =
109
+ (config: INestiaConfig) =>
110
+ (name: string): string =>
111
+ config.primitive !== false ? `Primitive<${name}>` : name;
112
+ }
113
+
114
+ const UUID = `const uuid = (): string =>
115
+ "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
116
+ const r = (Math.random() * 16) | 0;
117
+ const v = c === "x" ? r : (r & 0x3) | 0x8;
118
+ return v.toString(16);
119
+ });`;
@@ -0,0 +1,144 @@
1
+ import fs from "fs";
2
+
3
+ import { INestiaConfig } from "../../INestiaConfig";
4
+ import { IRoute } from "../../structures/IRoute";
5
+ import { ImportDictionary } from "../../utils/ImportDictionary";
6
+ import { SdkFunctionProgrammer } from "./SdkFunctionProgrammer";
7
+ import { SdkRouteDirectory } from "./SdkRouteDirectory";
8
+
9
+ export namespace SdkFileProgrammer {
10
+ /* ---------------------------------------------------------
11
+ CONSTRUCTOR
12
+ --------------------------------------------------------- */
13
+ export const generate =
14
+ (config: INestiaConfig) =>
15
+ async (routeList: IRoute[]): Promise<void> => {
16
+ // CONSTRUCT FOLDER TREE
17
+ const root: SdkRouteDirectory = new SdkRouteDirectory(
18
+ null,
19
+ "functional",
20
+ );
21
+ for (const route of routeList) emplace(root)(route);
22
+
23
+ // RELOCATE FOR ONLY ONE CONTROLLER METHOD IN AN URL CASE
24
+ relocate(root);
25
+
26
+ // ITERATE FILES
27
+ await iterate(config)(root)(config.output + "/functional");
28
+ };
29
+
30
+ const emplace =
31
+ (directory: SdkRouteDirectory) =>
32
+ (route: IRoute): void => {
33
+ // SEPARATE IDENTIFIERS
34
+ const identifiers: string[] = route.path
35
+ .split("/")
36
+ .filter((str) => str.length && str[0] !== ":")
37
+ .map((str) => str.split("-").join("_").split(".").join("_"));
38
+
39
+ // OPEN DIRECTORIES
40
+ for (const key of identifiers) {
41
+ directory = directory.directories.take(
42
+ key,
43
+ () => new SdkRouteDirectory(directory, key),
44
+ );
45
+ }
46
+
47
+ // ADD ROUTE
48
+ directory.routes.push(route);
49
+ };
50
+
51
+ const relocate = (directory: SdkRouteDirectory): void => {
52
+ if (
53
+ directory.parent !== null &&
54
+ directory.directories.empty() &&
55
+ directory.routes.length === 1 &&
56
+ directory.name === directory.routes[0].name
57
+ ) {
58
+ directory.parent.routes.push(directory.routes[0]);
59
+ directory.parent.directories.erase(directory.name);
60
+ } else if (directory.directories.empty() === false)
61
+ for (const it of directory.directories) relocate(it.second);
62
+ };
63
+
64
+ /* ---------------------------------------------------------
65
+ FILE ITERATOR
66
+ --------------------------------------------------------- */
67
+ const iterate =
68
+ (config: INestiaConfig) =>
69
+ (directory: SdkRouteDirectory) =>
70
+ async (outDir: string): Promise<void> => {
71
+ // CREATE A NEW DIRECTORY
72
+ try {
73
+ await fs.promises.mkdir(outDir);
74
+ } catch {}
75
+
76
+ // ITERATE CHILDREN
77
+ const content: string[] = [];
78
+ for (const it of directory.directories) {
79
+ await iterate(config)(it.second)(`${outDir}/${it.first}`);
80
+ content.push(`export * as ${it.first} from "./${it.first}";`);
81
+ }
82
+ if (content.length && directory.routes.length) content.push("");
83
+
84
+ // ITERATE ROUTES
85
+ const importDict: ImportDictionary = new ImportDictionary();
86
+ directory.routes.forEach((route, i) => {
87
+ for (const tuple of route.imports)
88
+ for (const instance of tuple[1])
89
+ importDict.emplace(tuple[0], false, instance);
90
+
91
+ content.push(SdkFunctionProgrammer.generate(config)(route));
92
+ if (i !== directory.routes.length - 1) content.push("");
93
+ });
94
+
95
+ // FINALIZE THE CONTENT
96
+ if (directory.routes.length !== 0) {
97
+ const primitived: boolean =
98
+ config.primitive !== false &&
99
+ directory.routes.some(
100
+ (route) =>
101
+ route.output.name !== "void" ||
102
+ route.parameters.some(
103
+ (param) => param.category !== "param",
104
+ ),
105
+ );
106
+ const asserted: boolean =
107
+ config.assert === true &&
108
+ directory.routes.some(
109
+ (route) => route.parameters.length !== 0,
110
+ );
111
+ const json: boolean =
112
+ config.json === true &&
113
+ directory.routes.some(
114
+ (route) =>
115
+ route.method === "POST" ||
116
+ route.method === "PUT" ||
117
+ route.method === "PATCH",
118
+ );
119
+
120
+ const fetcher: string[] = ["Fetcher"];
121
+ if (primitived) fetcher.push("Primitive");
122
+
123
+ const head: string[] = [
124
+ `import { ${fetcher.join(", ")} } from "@nestia/fetcher";`,
125
+ `import type { IConnection } from "@nestia/fetcher";`,
126
+ ];
127
+ if (asserted || json) head.push(`import typia from "typia";`);
128
+ if (!importDict.empty())
129
+ head.push("", importDict.toScript(outDir));
130
+
131
+ content.push(...head, "", ...content.splice(0, content.length));
132
+ }
133
+
134
+ const script: string =
135
+ "/**\n" +
136
+ " * @packageDocumentation\n" +
137
+ ` * @module ${directory.module}\n` +
138
+ " * @nestia Generated by Nestia - https://github.com/samchon/nestia \n" +
139
+ " */\n" +
140
+ "//================================================================\n" +
141
+ content.join("\n");
142
+ await fs.promises.writeFile(`${outDir}/index.ts`, script, "utf8");
143
+ };
144
+ }