@nestia/sdk 1.1.1 → 1.2.0-dev.20230504-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +12 -1
  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 +52 -56
  14. package/lib/executable/internal/NestiaSdkCommand.js.map +1 -1
  15. package/lib/executable/internal/NestiaSdkConfig.js +5 -1
  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 +105 -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 +2 -3
  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 +12 -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 +123 -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
@@ -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
+ }
@@ -0,0 +1,371 @@
1
+ import { Vector } from "tstl/container/Vector";
2
+ import { Pair } from "tstl/utility/Pair";
3
+ import ts from "typescript";
4
+
5
+ import { Escaper } from "typia/lib/utils/Escaper";
6
+
7
+ import { INestiaConfig } from "../../INestiaConfig";
8
+ import { IRoute } from "../../structures/IRoute";
9
+
10
+ export namespace SdkFunctionProgrammer {
11
+ export const generate =
12
+ (config: INestiaConfig) =>
13
+ (route: IRoute): string => {
14
+ const query: IRoute.IParameter | undefined = route.parameters.find(
15
+ (param) =>
16
+ param.category === "query" && param.field === undefined,
17
+ );
18
+ const input: IRoute.IParameter | undefined = route.parameters.find(
19
+ (param) => param.category === "body",
20
+ );
21
+
22
+ return [head, body, tail]
23
+ .map((closure) => closure(config)(route)({ query, input }))
24
+ .filter((str) => !!str)
25
+ .join("\n");
26
+ };
27
+
28
+ /* ---------------------------------------------------------
29
+ BODY
30
+ --------------------------------------------------------- */
31
+ const body =
32
+ (config: INestiaConfig) =>
33
+ (route: IRoute) =>
34
+ (props: {
35
+ query: IRoute.IParameter | undefined;
36
+ input: IRoute.IParameter | undefined;
37
+ }): string => {
38
+ // FETCH ARGUMENTS WITH REQUST BODY
39
+ const parameters = filter_parameters(route)(props.query);
40
+ const fetchArguments: string[] = [
41
+ "connection",
42
+ `${route.name}.ENCRYPTED`,
43
+ `${route.name}.METHOD`,
44
+ `${route.name}.path(${parameters
45
+ .map((p) => p.name)
46
+ .join(", ")})`,
47
+ ];
48
+ if (props.input !== undefined) {
49
+ fetchArguments.push(props.input.name);
50
+ if (config.json === true)
51
+ fetchArguments.push(`${route.name}.stringify`);
52
+ }
53
+
54
+ const assertions: string =
55
+ config.assert === true && route.parameters.length !== 0
56
+ ? route.parameters
57
+ .map(
58
+ (param) =>
59
+ ` typia.assert<typeof ${param.name}>(${param.name});`,
60
+ )
61
+ .join("\n") + "\n\n"
62
+ : "";
63
+
64
+ // FUNCTION CALL STATEMENT
65
+ const caller: string =
66
+ "Fetcher.fetch\n" +
67
+ " (\n" +
68
+ fetchArguments.map((param) => ` ${param}`).join(",\n") +
69
+ "\n" +
70
+ " )";
71
+ if (route.setHeaders.length === 0)
72
+ return `{\n${assertions} return ${caller};\n}`;
73
+
74
+ // SET HEADERS
75
+ const content: string[] = [
76
+ `{\n`,
77
+ assertions,
78
+ ` const output: ${route.name}.Output = await ${caller};\n`,
79
+ "\n",
80
+ ` // configure header(s)\n`,
81
+ ` connection.headers ??= {};\n`,
82
+ ];
83
+
84
+ for (const header of route.setHeaders) {
85
+ if (header.type === "assigner")
86
+ content.push(
87
+ " ",
88
+ `Object.assign(connection.headers, ${access("output")(
89
+ header.source,
90
+ )});\n`,
91
+ );
92
+ else
93
+ content.push(
94
+ " ",
95
+ `${access("connection.headers")(
96
+ header.target ?? header.source,
97
+ )} = ${access("output")(header.source)};\n`,
98
+ );
99
+ }
100
+ content.push("\n", " return output;\n", "}");
101
+ return content.join("");
102
+ };
103
+
104
+ const filter_parameters =
105
+ (route: IRoute) =>
106
+ (query: IRoute.IParameter | undefined): IRoute.IParameter[] => {
107
+ const parameters: IRoute.IParameter[] = route.parameters.filter(
108
+ (param) =>
109
+ param.category === "param" ||
110
+ (param.category === "query" && param.field !== undefined),
111
+ );
112
+ if (query) parameters.push(query);
113
+ return parameters;
114
+ };
115
+
116
+ const access =
117
+ (x: string) =>
118
+ (y: string): string =>
119
+ y[0] === "[" ? `${x}${y}` : `${x}.${y}`;
120
+
121
+ /* ---------------------------------------------------------
122
+ HEAD & TAIL
123
+ --------------------------------------------------------- */
124
+ const head =
125
+ (config: INestiaConfig) =>
126
+ (route: IRoute) =>
127
+ (props: {
128
+ query: IRoute.IParameter | undefined;
129
+ input: IRoute.IParameter | undefined;
130
+ }): string => {
131
+ //----
132
+ // CONSTRUCT COMMENT
133
+ //----
134
+ // MAIN DESCRIPTION
135
+ const comments: string[] = route.comments
136
+ .map(
137
+ (part) =>
138
+ `${part.kind === "linkText" ? " " : ""}${part.text}`,
139
+ )
140
+ .map((str) => str.split("\r\n").join("\n"))
141
+ .join("")
142
+ .split("\n")
143
+ .filter(
144
+ (str, i, array) => str !== "" || i !== array.length - 1,
145
+ );
146
+ if (comments.length) comments.push("");
147
+
148
+ // FILTER TAGS (VULNERABLE PARAMETERS WOULD BE REMOVED)
149
+ const tagList: ts.JSDocTagInfo[] = route.tags.slice();
150
+ if (tagList.length !== 0) {
151
+ const index: number = tagList.findIndex(
152
+ (t) => t.name === "param",
153
+ );
154
+ if (index !== -1) {
155
+ const capsule: Vector<ts.JSDocTagInfo> =
156
+ Vector.wrap(tagList);
157
+ capsule.insert(capsule.nth(index), {
158
+ name: "param",
159
+ text: [
160
+ {
161
+ kind: "parameterName",
162
+ text: "connection",
163
+ },
164
+ {
165
+ kind: "space",
166
+ text: " ",
167
+ },
168
+ {
169
+ kind: "text",
170
+ text: "connection Information of the remote HTTP(s) server with headers (+encryption password)",
171
+ },
172
+ ],
173
+ });
174
+ }
175
+ comments.push(
176
+ ...tagList.map((tag) =>
177
+ tag.text
178
+ ? `@${tag.name} ${tag.text
179
+ .map((elem) => elem.text)
180
+ .join("")}`
181
+ : `@${tag.name}`,
182
+ ),
183
+ "",
184
+ );
185
+ }
186
+
187
+ // COMPLETE THE COMMENT
188
+ comments.push(
189
+ `@controller ${route.symbol}`,
190
+ `@path ${route.method} ${route.path}`,
191
+ `@nestia Generated by Nestia - https://github.com/samchon/nestia`,
192
+ );
193
+
194
+ //----
195
+ // FINALIZATION
196
+ //----
197
+ // REFORM PARAMETERS TEXT
198
+ const parameters: string[] = [
199
+ "connection: IConnection",
200
+ ...route.parameters.map((param) => {
201
+ const type: string =
202
+ config.primitive !== false &&
203
+ (param === props.query || param === props.input)
204
+ ? `Primitive<${route.name}.${
205
+ param === props.query ? "Query" : "Input"
206
+ }>`
207
+ : param.type.name;
208
+ return `${param.name}${param.optional ? "?" : ""}: ${type}`;
209
+ }),
210
+ ];
211
+
212
+ // OUTPUT TYPE
213
+ const output: string =
214
+ route.output.name === "void" ? "void" : `${route.name}.Output`;
215
+
216
+ // RETURNS WITH CONSTRUCTION
217
+ return (
218
+ "" +
219
+ "/**\n" +
220
+ comments.map((str) => ` * ${str}`).join("\n") +
221
+ "\n" +
222
+ " */\n" +
223
+ `export${route.setHeaders.length ? " async" : ""} function ${
224
+ route.name
225
+ }\n` +
226
+ ` (\n` +
227
+ `${parameters.map((str) => ` ${str}`).join(",\n")}\n` +
228
+ ` ): Promise<${output}>`
229
+ );
230
+ };
231
+
232
+ const tail =
233
+ (config: INestiaConfig) =>
234
+ (route: IRoute) =>
235
+ (props: {
236
+ query: IRoute.IParameter | undefined;
237
+ input: IRoute.IParameter | undefined;
238
+ }): string | null => {
239
+ // LIST UP TYPES
240
+ const types: Pair<string, string>[] = [];
241
+ if (props.query !== undefined)
242
+ types.push(new Pair("Query", props.query.type.name));
243
+ if (props.input !== undefined)
244
+ types.push(new Pair("Input", props.input.type.name));
245
+ if (route.output.name !== "void")
246
+ types.push(new Pair("Output", route.output.name));
247
+
248
+ // PATH WITH PARAMETERS
249
+ const parameters: IRoute.IParameter[] = filter_parameters(route)(
250
+ props.query,
251
+ );
252
+ const path: string = compute_path({
253
+ path: route.path,
254
+ query: props.query,
255
+ parameters,
256
+ });
257
+
258
+ return (
259
+ `export namespace ${route.name}\n` +
260
+ "{\n" +
261
+ (types.length !== 0
262
+ ? types
263
+ .map(
264
+ (tuple) =>
265
+ ` export type ${tuple.first} = ${
266
+ config.primitive !== false
267
+ ? `Primitive<${tuple.second}>`
268
+ : tuple.second
269
+ };`,
270
+ )
271
+ .join("\n") + "\n"
272
+ : "") +
273
+ "\n" +
274
+ ` export const METHOD = "${route.method}" as const;\n` +
275
+ ` export const PATH: string = "${route.path}";\n` +
276
+ ` export const ENCRYPTED: Fetcher.IEncrypted = {\n` +
277
+ ` request: ${
278
+ props.input !== undefined && props.input.encrypted
279
+ },\n` +
280
+ ` response: ${route.encrypted},\n` +
281
+ (route.status !== undefined
282
+ ? ` status: ${route.status},\n`
283
+ : "") +
284
+ ` };\n` +
285
+ "\n" +
286
+ ` export function path(${parameters
287
+ .map((param) => `${param.name}: ${param.type.name}`)
288
+ .join(", ")}): string\n` +
289
+ ` {\n` +
290
+ `${path};\n` +
291
+ ` }\n` +
292
+ (config.json === true &&
293
+ route.parameters.find((param) => param.category === "body") !==
294
+ undefined
295
+ ? ` export const stringify = (input: Input) => typia.assertStringify(input);\n`
296
+ : "") +
297
+ "}"
298
+ );
299
+ };
300
+
301
+ const compute_path = (props: {
302
+ query: IRoute.IParameter | undefined;
303
+ parameters: IRoute.IParameter[];
304
+ path: string;
305
+ }): string => {
306
+ for (const param of props.parameters)
307
+ if (param.category === "param")
308
+ props.path = props.path.replace(
309
+ `:${param.field}`,
310
+ `\${encodeURIComponent(${param.name} ?? "null")}`,
311
+ );
312
+
313
+ // NO QUERY PARAMETER
314
+ const queryParams: IRoute.IParameter[] = props.parameters.filter(
315
+ (param) => param.category === "query" && param.field !== undefined,
316
+ );
317
+ if (props.query === undefined && queryParams.length === 0)
318
+ return `${" ".repeat(8)}return \`${props.path}\``;
319
+
320
+ const computeName = (str: string): string =>
321
+ props.parameters.find((p) => p.name === str) !== undefined
322
+ ? computeName("_" + str)
323
+ : str;
324
+ const variables: string = computeName("variables");
325
+ const search: string = computeName("search");
326
+ const encoded: string = computeName("encoded");
327
+
328
+ const wrapper = (expr: string) =>
329
+ [
330
+ `const ${variables}: Record<any, any> = ${expr};`,
331
+ `const ${search}: URLSearchParams = new URLSearchParams();`,
332
+ `for (const [key, value] of Object.entries(${variables}))`,
333
+ ` if (value === undefined) continue;`,
334
+ ` else if (Array.isArray(value))`,
335
+ ` value.forEach((elem) => ${search}.append(key, String(elem)));`,
336
+ ` else`,
337
+ ` ${search}.set(key, String(value));`,
338
+ `const ${encoded}: string = ${search}.toString();`,
339
+ `return \`${props.path}\${${encoded}.length ? \`?\${${encoded}}\` : ""}\`;`,
340
+ ]
341
+ .map((str) => `${" ".repeat(8)}${str}`)
342
+ .join("\n");
343
+
344
+ if (props.query !== undefined && queryParams.length === 0)
345
+ return wrapper(`${props.query.name} as any`);
346
+ else if (props.query === undefined)
347
+ return wrapper(`
348
+ {
349
+ ${rest_query_parameters(queryParams)}
350
+ } as any`);
351
+
352
+ return wrapper(`
353
+ {
354
+ ...${props.query.name},
355
+ ${rest_query_parameters(queryParams)},
356
+ } as any`);
357
+ };
358
+
359
+ const rest_query_parameters = (parameters: IRoute.IParameter[]): string =>
360
+ parameters
361
+ .map((param) =>
362
+ param.name === param.field
363
+ ? param.name
364
+ : `${
365
+ Escaper.variable(param.field!)
366
+ ? param.field
367
+ : JSON.stringify(param.field)
368
+ }: ${param.name}`,
369
+ )
370
+ .join(`,\n${" ".repeat(12)}`);
371
+ }
@@ -0,0 +1,21 @@
1
+ import { HashMap } from "tstl";
2
+
3
+ import { IRoute } from "../../structures/IRoute";
4
+
5
+ export class SdkRouteDirectory {
6
+ public readonly module: string;
7
+ public readonly directories: HashMap<string, SdkRouteDirectory>;
8
+ public readonly routes: IRoute[];
9
+
10
+ public constructor(
11
+ readonly parent: SdkRouteDirectory | null,
12
+ readonly name: string,
13
+ ) {
14
+ this.directories = new HashMap();
15
+ this.routes = [];
16
+ this.module =
17
+ this.parent !== null
18
+ ? `${this.parent.module}.${name}`
19
+ : `api.${name}`;
20
+ }
21
+ }
@@ -23,5 +23,9 @@ export namespace IController {
23
23
  field: string | undefined;
24
24
  category: ParamCategory;
25
25
  encrypted: boolean;
26
+ meta?: {
27
+ type: string;
28
+ nullable: boolean;
29
+ };
26
30
  }
27
31
  }
@@ -31,5 +31,9 @@ export namespace IRoute {
31
31
  encrypted: boolean;
32
32
  type: ITypeTuple;
33
33
  optional: boolean;
34
+ meta?: {
35
+ type: string;
36
+ nullable: boolean;
37
+ };
34
38
  }
35
39
  }
@@ -0,0 +1,21 @@
1
+ import { INestiaConfig } from "../INestiaConfig";
2
+
3
+ export namespace NestiaConfigUtil {
4
+ export const input = (
5
+ config: INestiaConfig["input"],
6
+ ): INestiaConfig.IInput =>
7
+ Array.isArray(config)
8
+ ? {
9
+ include: config,
10
+ exclude: [],
11
+ }
12
+ : typeof config === "object"
13
+ ? {
14
+ include: config.include,
15
+ exclude: config.exclude ?? [],
16
+ }
17
+ : {
18
+ include: [config],
19
+ exclude: [],
20
+ };
21
+ }
@@ -0,0 +1,60 @@
1
+ import fs from "fs";
2
+ import glob from "glob";
3
+ import path from "path";
4
+
5
+ export namespace SourceFinder {
6
+ export const find = async (props: IProps): Promise<string[]> => {
7
+ const dict: Set<string> = new Set();
8
+
9
+ await emplace(props.filter)(props.include)((str) => dict.add(str));
10
+ if (props.exclude?.length)
11
+ await emplace(props.filter)(props.exclude)((str) =>
12
+ dict.delete(str),
13
+ );
14
+
15
+ return [...dict];
16
+ };
17
+
18
+ const emplace =
19
+ (filter: (file: string) => boolean) =>
20
+ (input: string[]) =>
21
+ async (closure: (location: string) => void): Promise<void> => {
22
+ for (const pattern of input) {
23
+ for (const file of await _Glob(path.resolve(pattern))) {
24
+ const stats: fs.Stats = await fs.promises.stat(file);
25
+ if (stats.isDirectory() === true)
26
+ await iterate(filter)(closure)(file);
27
+ else if (stats.isFile() && filter(file)) closure(file);
28
+ }
29
+ }
30
+ };
31
+
32
+ const iterate =
33
+ (filter: (location: string) => boolean) =>
34
+ (closure: (location: string) => void) =>
35
+ async (location: string): Promise<void> => {
36
+ const directory: string[] = await fs.promises.readdir(location);
37
+ for (const file of directory) {
38
+ const next: string = path.resolve(`${location}/${file}`);
39
+ const stats: fs.Stats = await fs.promises.stat(next);
40
+
41
+ if (stats.isDirectory() === true)
42
+ await iterate(filter)(closure)(next);
43
+ else if (stats.isFile() && filter(next)) closure(next);
44
+ }
45
+ };
46
+
47
+ const _Glob = (pattern: string): Promise<string[]> =>
48
+ new Promise((resolve, reject) => {
49
+ glob(pattern, (err, matches) => {
50
+ if (err) reject(err);
51
+ else resolve(matches.map((str) => path.resolve(str)));
52
+ });
53
+ });
54
+ }
55
+
56
+ interface IProps {
57
+ exclude?: string[];
58
+ include: string[];
59
+ filter: (location: string) => boolean;
60
+ }
@@ -1,4 +0,0 @@
1
- import { INestiaConfig } from "../INestiaConfig";
2
- export declare namespace SourceFinder {
3
- function find(input: INestiaConfig.IInput): Promise<string[]>;
4
- }