@nestia/sdk 12.0.0-dev.20260601.1 → 12.0.0-dev.20260612.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 (209) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +93 -93
  3. package/assets/bundle/api/HttpError.ts +1 -1
  4. package/assets/bundle/api/IConnection.ts +1 -1
  5. package/assets/bundle/api/Primitive.ts +1 -1
  6. package/assets/bundle/api/Resolved.ts +1 -1
  7. package/assets/bundle/api/index.ts +4 -4
  8. package/assets/bundle/api/module.ts +6 -6
  9. package/assets/bundle/distribute/README.md +37 -37
  10. package/assets/bundle/distribute/package.json +28 -28
  11. package/assets/bundle/distribute/tsconfig.json +109 -109
  12. package/assets/bundle/e2e/index.ts +42 -42
  13. package/assets/config/nestia.config.ts +97 -97
  14. package/lib/NestiaSdkApplication.js +29 -7
  15. package/lib/NestiaSdkApplication.js.map +1 -1
  16. package/lib/NestiaSwaggerComposer.js +21 -13
  17. package/lib/NestiaSwaggerComposer.js.map +1 -1
  18. package/lib/analyses/AccessorAnalyzer.d.ts +4 -1
  19. package/lib/analyses/AccessorAnalyzer.js.map +1 -1
  20. package/lib/analyses/ConfigAnalyzer.js +1 -1
  21. package/lib/analyses/PathAnalyzer.d.ts +18 -3
  22. package/lib/analyses/PathAnalyzer.js +32 -0
  23. package/lib/analyses/PathAnalyzer.js.map +1 -1
  24. package/lib/analyses/ReflectControllerAnalyzer.js +3 -2
  25. package/lib/analyses/ReflectControllerAnalyzer.js.map +1 -1
  26. package/lib/analyses/ReflectHttpOperationAnalyzer.d.ts +1 -1
  27. package/lib/analyses/ReflectHttpOperationAnalyzer.js +1 -1
  28. package/lib/analyses/ReflectHttpOperationAnalyzer.js.map +1 -1
  29. package/lib/analyses/ReflectHttpOperationResponseAnalyzer.d.ts +1 -1
  30. package/lib/analyses/ReflectHttpOperationResponseAnalyzer.js +53 -20
  31. package/lib/analyses/ReflectHttpOperationResponseAnalyzer.js.map +1 -1
  32. package/lib/analyses/ReflectMcpOperationAnalyzer.d.ts +14 -0
  33. package/lib/analyses/ReflectMcpOperationAnalyzer.js +79 -0
  34. package/lib/analyses/ReflectMcpOperationAnalyzer.js.map +1 -0
  35. package/lib/analyses/TypedMcpRouteAnalyzer.d.ts +9 -0
  36. package/lib/analyses/TypedMcpRouteAnalyzer.js +31 -0
  37. package/lib/analyses/TypedMcpRouteAnalyzer.js.map +1 -0
  38. package/lib/executable/internal/NestiaConfigLoader.js +5 -1
  39. package/lib/executable/internal/NestiaConfigLoader.js.map +1 -1
  40. package/lib/executable/internal/NestiaSdkCommand.js +30 -14
  41. package/lib/executable/internal/NestiaSdkCommand.js.map +1 -1
  42. package/lib/executable/internal/NestiaSdkWatcher.d.ts +10 -0
  43. package/lib/executable/internal/NestiaSdkWatcher.js +322 -0
  44. package/lib/executable/internal/NestiaSdkWatcher.js.map +1 -0
  45. package/lib/executable/sdk.js +12 -12
  46. package/lib/executable/sdk.js.map +1 -1
  47. package/lib/generates/CloneGenerator.js +4 -2
  48. package/lib/generates/CloneGenerator.js.map +1 -1
  49. package/lib/generates/SdkGenerator.js +50 -1
  50. package/lib/generates/SdkGenerator.js.map +1 -1
  51. package/lib/generates/SwaggerGenerator.js +18 -2
  52. package/lib/generates/SwaggerGenerator.js.map +1 -1
  53. package/lib/generates/internal/E2eFileProgrammer.js +3 -1
  54. package/lib/generates/internal/E2eFileProgrammer.js.map +1 -1
  55. package/lib/generates/internal/ImportDictionary.d.ts +1 -0
  56. package/lib/generates/internal/ImportDictionary.js +9 -4
  57. package/lib/generates/internal/ImportDictionary.js.map +1 -1
  58. package/lib/generates/internal/SdkAliasCollection.d.ts +2 -0
  59. package/lib/generates/internal/SdkAliasCollection.js +11 -2
  60. package/lib/generates/internal/SdkAliasCollection.js.map +1 -1
  61. package/lib/generates/internal/SdkDistributionComposer.d.ts +1 -0
  62. package/lib/generates/internal/SdkDistributionComposer.js +3 -0
  63. package/lib/generates/internal/SdkDistributionComposer.js.map +1 -1
  64. package/lib/generates/internal/SdkFileProgrammer.js +4 -1
  65. package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
  66. package/lib/generates/internal/SdkHttpCloneReferencer.d.ts +1 -1
  67. package/lib/generates/internal/SdkHttpCloneReferencer.js +42 -9
  68. package/lib/generates/internal/SdkHttpCloneReferencer.js.map +1 -1
  69. package/lib/generates/internal/SdkHttpFunctionProgrammer.js +3 -4
  70. package/lib/generates/internal/SdkHttpFunctionProgrammer.js.map +1 -1
  71. package/lib/generates/internal/SdkHttpNamespaceProgrammer.js +2 -1
  72. package/lib/generates/internal/SdkHttpNamespaceProgrammer.js.map +1 -1
  73. package/lib/generates/internal/SdkHttpSimulationProgrammer.js +6 -3
  74. package/lib/generates/internal/SdkHttpSimulationProgrammer.js.map +1 -1
  75. package/lib/generates/internal/SdkMcpRouteProgrammer.d.ts +15 -0
  76. package/lib/generates/internal/SdkMcpRouteProgrammer.js +148 -0
  77. package/lib/generates/internal/SdkMcpRouteProgrammer.js.map +1 -0
  78. package/lib/generates/internal/SdkRouteDirectory.d.ts +2 -1
  79. package/lib/generates/internal/SdkRouteDirectory.js.map +1 -1
  80. package/lib/generates/internal/SdkWebSocketCloneProgrammer.d.ts +6 -0
  81. package/lib/generates/internal/SdkWebSocketCloneProgrammer.js +283 -0
  82. package/lib/generates/internal/SdkWebSocketCloneProgrammer.js.map +1 -0
  83. package/lib/generates/internal/SdkWebSocketRouteProgrammer.js +11 -9
  84. package/lib/generates/internal/SdkWebSocketRouteProgrammer.js.map +1 -1
  85. package/lib/generates/internal/SwaggerOperationParameterComposer.js +10 -2
  86. package/lib/generates/internal/SwaggerOperationParameterComposer.js.map +1 -1
  87. package/lib/generates/internal/SwaggerOperationResponseComposer.d.ts +1 -1
  88. package/lib/generates/internal/SwaggerOperationResponseComposer.js +6 -1
  89. package/lib/generates/internal/SwaggerOperationResponseComposer.js.map +1 -1
  90. package/lib/generates/internal/SwaggerReadonlyArrayEmender.d.ts +9 -0
  91. package/lib/generates/internal/SwaggerReadonlyArrayEmender.js +174 -0
  92. package/lib/generates/internal/SwaggerReadonlyArrayEmender.js.map +1 -0
  93. package/lib/structures/INestiaSdkInput.d.ts +9 -2
  94. package/lib/structures/IReflectController.d.ts +2 -1
  95. package/lib/structures/IReflectHttpOperationSuccess.d.ts +4 -2
  96. package/lib/structures/IReflectMcpOperation.d.ts +35 -0
  97. package/lib/structures/IReflectMcpOperation.js +3 -0
  98. package/lib/structures/IReflectMcpOperation.js.map +1 -0
  99. package/lib/structures/IReflectMcpOperationParameter.d.ts +19 -0
  100. package/lib/structures/IReflectMcpOperationParameter.js +3 -0
  101. package/lib/structures/IReflectMcpOperationParameter.js.map +1 -0
  102. package/lib/structures/ITypedApplication.d.ts +2 -1
  103. package/lib/structures/ITypedHttpRouteSuccess.d.ts +3 -1
  104. package/lib/structures/ITypedMcpRoute.d.ts +31 -0
  105. package/lib/structures/ITypedMcpRoute.js +3 -0
  106. package/lib/structures/ITypedMcpRoute.js.map +1 -0
  107. package/lib/utils/HttpResponseContentTypeUtil.d.ts +5 -0
  108. package/lib/utils/HttpResponseContentTypeUtil.js +22 -0
  109. package/lib/utils/HttpResponseContentTypeUtil.js.map +1 -0
  110. package/native/go.mod +52 -52
  111. package/native/go.sum +84 -54
  112. package/native/sdk/register.go +322 -165
  113. package/native/sdk/sdk.go +17 -17
  114. package/native/sdk/sdk_metadata_json.go +327 -327
  115. package/native/sdk/sdk_transform.go +1879 -1549
  116. package/package.json +11 -9
  117. package/src/INestiaConfig.ts +267 -267
  118. package/src/NestiaSdkApplication.ts +39 -8
  119. package/src/NestiaSwaggerComposer.ts +153 -142
  120. package/src/analyses/AccessorAnalyzer.ts +64 -67
  121. package/src/analyses/ConfigAnalyzer.ts +330 -330
  122. package/src/analyses/ImportAnalyzer.ts +92 -92
  123. package/src/analyses/PathAnalyzer.ts +130 -69
  124. package/src/analyses/ReflectControllerAnalyzer.ts +112 -105
  125. package/src/analyses/ReflectHttpOperationAnalyzer.ts +183 -183
  126. package/src/analyses/ReflectHttpOperationExceptionAnalyzer.ts +90 -90
  127. package/src/analyses/ReflectHttpOperationParameterAnalyzer.ts +350 -350
  128. package/src/analyses/ReflectHttpOperationResponseAnalyzer.ts +163 -130
  129. package/src/analyses/ReflectMcpOperationAnalyzer.ts +124 -0
  130. package/src/analyses/ReflectMetadataAnalyzer.ts +44 -44
  131. package/src/analyses/SecurityAnalyzer.ts +25 -25
  132. package/src/analyses/TypedMcpRouteAnalyzer.ts +34 -0
  133. package/src/decorators/OperationMetadata.ts +29 -29
  134. package/src/executable/internal/CommandParser.ts +15 -15
  135. package/src/executable/internal/NestiaConfigLoader.ts +451 -446
  136. package/src/executable/internal/NestiaSdkCommand.ts +124 -106
  137. package/src/executable/internal/NestiaSdkWatcher.ts +342 -0
  138. package/src/executable/sdk.ts +90 -88
  139. package/src/generates/CloneGenerator.ts +73 -66
  140. package/src/generates/E2eGenerator.ts +32 -32
  141. package/src/generates/SdkGenerator.ts +176 -118
  142. package/src/generates/SwaggerGenerator.ts +342 -310
  143. package/src/generates/internal/E2eFileProgrammer.ts +240 -233
  144. package/src/generates/internal/FilePrinter.ts +65 -65
  145. package/src/generates/internal/ImportDictionary.ts +209 -204
  146. package/src/generates/internal/SdkAliasCollection.ts +274 -261
  147. package/src/generates/internal/SdkDistributionComposer.ts +123 -116
  148. package/src/generates/internal/SdkFileProgrammer.ts +116 -112
  149. package/src/generates/internal/SdkHttpCloneProgrammer.ts +126 -126
  150. package/src/generates/internal/SdkHttpCloneReferencer.ts +131 -77
  151. package/src/generates/internal/SdkHttpFunctionProgrammer.ts +301 -301
  152. package/src/generates/internal/SdkHttpNamespaceProgrammer.ts +520 -510
  153. package/src/generates/internal/SdkHttpParameterProgrammer.ts +165 -165
  154. package/src/generates/internal/SdkHttpRouteProgrammer.ts +109 -109
  155. package/src/generates/internal/SdkHttpSimulationProgrammer.ts +331 -314
  156. package/src/generates/internal/SdkImportWizard.ts +62 -62
  157. package/src/generates/internal/SdkMcpRouteProgrammer.ts +452 -0
  158. package/src/generates/internal/SdkRouteDirectory.ts +21 -18
  159. package/src/generates/internal/SdkTypeTagProgrammer.ts +114 -114
  160. package/src/generates/internal/SdkWebSocketCloneProgrammer.ts +319 -0
  161. package/src/generates/internal/SdkWebSocketNamespaceProgrammer.ts +389 -389
  162. package/src/generates/internal/SdkWebSocketParameterProgrammer.ts +89 -89
  163. package/src/generates/internal/SdkWebSocketRouteProgrammer.ts +331 -323
  164. package/src/generates/internal/SwaggerDescriptionComposer.ts +64 -64
  165. package/src/generates/internal/SwaggerOperationComposer.ts +119 -119
  166. package/src/generates/internal/SwaggerOperationParameterComposer.ts +175 -162
  167. package/src/generates/internal/SwaggerOperationResponseComposer.ts +115 -110
  168. package/src/generates/internal/SwaggerReadonlyArrayEmender.ts +262 -0
  169. package/src/index.ts +4 -4
  170. package/src/internal/legacy.ts +492 -492
  171. package/src/module.ts +4 -4
  172. package/src/structures/INestiaProject.ts +10 -10
  173. package/src/structures/INestiaSdkInput.ts +27 -20
  174. package/src/structures/IOperationMetadata.ts +41 -41
  175. package/src/structures/IReflectController.ts +18 -15
  176. package/src/structures/IReflectHttpOperation.ts +26 -26
  177. package/src/structures/IReflectHttpOperationException.ts +18 -18
  178. package/src/structures/IReflectHttpOperationParameter.ts +79 -79
  179. package/src/structures/IReflectHttpOperationSuccess.ts +18 -21
  180. package/src/structures/IReflectImport.ts +6 -6
  181. package/src/structures/IReflectMcpOperation.ts +38 -0
  182. package/src/structures/IReflectMcpOperationParameter.ts +27 -0
  183. package/src/structures/IReflectOperationError.ts +26 -26
  184. package/src/structures/IReflectType.ts +4 -4
  185. package/src/structures/IReflectWebSocketOperation.ts +17 -17
  186. package/src/structures/ITypedApplication.ts +12 -11
  187. package/src/structures/ITypedHttpRoute.ts +41 -41
  188. package/src/structures/ITypedHttpRouteException.ts +15 -15
  189. package/src/structures/ITypedHttpRouteParameter.ts +41 -41
  190. package/src/structures/ITypedHttpRouteSuccess.ts +18 -22
  191. package/src/structures/ITypedMcpRoute.ts +33 -0
  192. package/src/structures/ITypedWebSocketRoute.ts +24 -24
  193. package/src/structures/ITypedWebSocketRouteParameter.ts +3 -3
  194. package/src/transform.ts +59 -59
  195. package/src/typings/get-function-location.d.ts +7 -7
  196. package/src/utils/ArrayUtil.ts +26 -26
  197. package/src/utils/EmittedJavaScriptPatcher.ts +88 -88
  198. package/src/utils/FileRetriever.ts +22 -22
  199. package/src/utils/HttpResponseContentTypeUtil.ts +30 -0
  200. package/src/utils/MapUtil.ts +14 -14
  201. package/src/utils/PathUtil.ts +10 -10
  202. package/src/utils/SourceFinder.ts +63 -63
  203. package/src/utils/StringUtil.ts +17 -17
  204. package/src/utils/TsConfigReader.ts +108 -108
  205. package/src/utils/TtscExecutor.ts +68 -68
  206. package/src/utils/VersioningStrategy.ts +28 -28
  207. package/src/validators/HttpHeadersValidator.ts +11 -11
  208. package/src/validators/HttpQueryValidator.ts +11 -11
  209. package/src/validators/TextPlainValidator.ts +17 -17
@@ -1,106 +1,124 @@
1
- import { INestiaConfig } from "../../INestiaConfig";
2
- import { NestiaSdkApplication } from "../../NestiaSdkApplication";
3
- import { NestiaConfigLoader } from "./NestiaConfigLoader";
4
-
5
- export namespace NestiaSdkCommand {
6
- export const sdk = () =>
7
- main({
8
- title: "SDK library",
9
- generate: (app) => app.sdk(),
10
- validate: (config) => !!config.output,
11
- solution: "configure INestiaConfig.output property.",
12
- });
13
-
14
- export const swagger = () =>
15
- main({
16
- title: "Swagger Document",
17
- generate: (app) => app.swagger(),
18
- validate: (config) => !!config.swagger?.output,
19
- solution: "configure INestiaConfig.swagger property.",
20
- });
21
-
22
- export const e2e = () =>
23
- main({
24
- title: "E2E Functions",
25
- generate: (app) => app.e2e(),
26
- validate: (config) => !!config.e2e,
27
- solution: [
28
- "configure two properties:",
29
- "",
30
- " - INestiaConfig.output",
31
- " - INestiaConfig.e2e",
32
- ].join("\n"),
33
- });
34
-
35
- export const all = () =>
36
- main({
37
- title: "everything",
38
- generate: (app) => app.all(),
39
- validate: () => true,
40
- solution: [
41
- "configure at least one property of below:",
42
- "",
43
- " - INestiaConfig.output",
44
- " - INestiaConfig.swagger.output",
45
- ].join("\n"),
46
- });
47
-
48
- const main = async (props: {
49
- title: string;
50
- solution: string;
51
- generate: (app: NestiaSdkApplication) => Promise<void>;
52
- validate: (config: INestiaConfig) => boolean;
53
- }) => {
54
- // LOAD CONFIG INFO
55
- const project: string =
56
- getFileArgument({
57
- type: "project",
58
- extension: "json",
59
- }) ?? "tsconfig.json";
60
- process.env.NESTIA_PROJECT = project;
61
- const command: NestiaConfigLoader.ICompilerOptions =
62
- await NestiaConfigLoader.compilerOptions(project);
63
-
64
- const configurations: INestiaConfig[] =
65
- await NestiaConfigLoader.configurations(
66
- getFileArgument({
67
- type: "config",
68
- extension: "ts",
69
- }) ?? "nestia.config.ts",
70
- command.raw.compilerOptions ?? {},
71
- );
72
-
73
- // GENERATE
74
- if (
75
- configurations.length > 1 &&
76
- configurations.some(props.validate) === false
77
- )
78
- throw new Error(
79
- `Every configurations are invalid to generate ${props.title}, ${props.solution}`,
80
- );
81
- for (const config of configurations) {
82
- if (configurations.length > 1 && props.validate(config) === false)
83
- continue;
84
- const app: NestiaSdkApplication = new NestiaSdkApplication(config);
85
- await props.generate(app);
86
- }
87
- };
88
-
89
- const getFileArgument = (props: {
90
- type: string;
91
- extension: string;
92
- }): string | null => {
93
- const argv: string[] = process.argv.slice(3);
94
- if (argv.length === 0) return null;
95
-
96
- const index: number = argv.findIndex((str) => str === `--${props.type}`);
97
- if (index === -1) return null;
98
- else if (argv.length === 1)
99
- throw new Error(`${props.type} file must be provided`);
100
-
101
- const file: string = argv[index + 1]!;
102
- if (file.endsWith(props.extension) === false)
103
- throw new Error(`${props.type} file must be ${props.extension} file`);
104
- return file;
105
- };
106
- }
1
+ import { INestiaConfig } from "../../INestiaConfig";
2
+ import { NestiaSdkApplication } from "../../NestiaSdkApplication";
3
+ import { NestiaConfigLoader } from "./NestiaConfigLoader";
4
+ import { NestiaSdkWatcher } from "./NestiaSdkWatcher";
5
+
6
+ export namespace NestiaSdkCommand {
7
+ export const sdk = () =>
8
+ main({
9
+ title: "SDK library",
10
+ generate: (app) => app.sdk(),
11
+ validate: (config) => !!config.output,
12
+ solution: "configure INestiaConfig.output property.",
13
+ });
14
+
15
+ export const swagger = () =>
16
+ main({
17
+ title: "Swagger Document",
18
+ generate: (app) => app.swagger(),
19
+ validate: (config) => !!config.swagger?.output,
20
+ solution: "configure INestiaConfig.swagger property.",
21
+ watch: hasFlagArgument("watch"),
22
+ });
23
+
24
+ export const e2e = () =>
25
+ main({
26
+ title: "E2E Functions",
27
+ generate: (app) => app.e2e(),
28
+ validate: (config) => !!config.e2e,
29
+ solution: [
30
+ "configure two properties:",
31
+ "",
32
+ " - INestiaConfig.output",
33
+ " - INestiaConfig.e2e",
34
+ ].join("\n"),
35
+ });
36
+
37
+ export const all = () =>
38
+ main({
39
+ title: "everything",
40
+ generate: (app) => app.all(),
41
+ validate: () => true,
42
+ solution: [
43
+ "configure at least one property of below:",
44
+ "",
45
+ " - INestiaConfig.output",
46
+ " - INestiaConfig.swagger.output",
47
+ ].join("\n"),
48
+ });
49
+
50
+ const main = async (props: {
51
+ title: string;
52
+ solution: string;
53
+ generate: (app: NestiaSdkApplication) => Promise<void>;
54
+ validate: (config: INestiaConfig) => boolean;
55
+ watch?: boolean;
56
+ }) => {
57
+ // LOAD CONFIG INFO
58
+ const project: string =
59
+ getFileArgument({
60
+ type: "project",
61
+ extension: "json",
62
+ }) ?? "tsconfig.json";
63
+ process.env.NESTIA_PROJECT = project;
64
+ const configFile: string =
65
+ getFileArgument({
66
+ type: "config",
67
+ extension: "ts",
68
+ }) ?? "nestia.config.ts";
69
+ const configurations = async (): Promise<INestiaConfig[]> => {
70
+ const command: NestiaConfigLoader.ICompilerOptions =
71
+ await NestiaConfigLoader.compilerOptions(project);
72
+ return NestiaConfigLoader.configurations(
73
+ configFile,
74
+ command.raw.compilerOptions ?? {},
75
+ );
76
+ };
77
+ const generate = async (configurations: INestiaConfig[]): Promise<void> => {
78
+ if (
79
+ configurations.length > 1 &&
80
+ configurations.some(props.validate) === false
81
+ )
82
+ throw new Error(
83
+ `Every configurations are invalid to generate ${props.title}, ${props.solution}`,
84
+ );
85
+ for (const config of configurations) {
86
+ if (configurations.length > 1 && props.validate(config) === false)
87
+ continue;
88
+ const app: NestiaSdkApplication = new NestiaSdkApplication(config);
89
+ await props.generate(app);
90
+ }
91
+ };
92
+
93
+ if (props.watch === true)
94
+ return NestiaSdkWatcher.watch({
95
+ configFile,
96
+ configurations,
97
+ generate,
98
+ projectFile: project,
99
+ });
100
+
101
+ await generate(await configurations());
102
+ };
103
+
104
+ const getFileArgument = (props: {
105
+ type: string;
106
+ extension: string;
107
+ }): string | null => {
108
+ const argv: string[] = process.argv.slice(3);
109
+ if (argv.length === 0) return null;
110
+
111
+ const index: number = argv.findIndex((str) => str === `--${props.type}`);
112
+ if (index === -1) return null;
113
+ else if (argv.length === 1)
114
+ throw new Error(`${props.type} file must be provided`);
115
+
116
+ const file: string = argv[index + 1]!;
117
+ if (file.endsWith(props.extension) === false)
118
+ throw new Error(`${props.type} file must be ${props.extension} file`);
119
+ return file;
120
+ };
121
+
122
+ const hasFlagArgument = (type: string): boolean =>
123
+ process.argv.slice(3).some((str) => str === `--${type}`);
124
+ }
@@ -0,0 +1,342 @@
1
+ import fs from "fs";
2
+ import { glob } from "glob";
3
+ import path from "path";
4
+
5
+ import { INestiaConfig } from "../../INestiaConfig";
6
+
7
+ export namespace NestiaSdkWatcher {
8
+ export interface IProps {
9
+ configFile: string;
10
+ configurations: () => Promise<INestiaConfig[]>;
11
+ generate: (configurations: INestiaConfig[]) => Promise<void>;
12
+ projectFile: string;
13
+ }
14
+
15
+ export const watch = async (props: IProps): Promise<void> => {
16
+ const session = new WatchSession(props);
17
+ session.registerSignals();
18
+ await session.regenerate("initial generation");
19
+ await new Promise<void>(() => {});
20
+ };
21
+ }
22
+
23
+ class WatchSession {
24
+ private readonly watchers: Map<string, fs.FSWatcher> = new Map();
25
+ private debounce: NodeJS.Timeout | null = null;
26
+ private ignored: string[] = [];
27
+ private pending: string | null = null;
28
+ private running: boolean = false;
29
+ private stopped: boolean = false;
30
+
31
+ public constructor(private readonly props: NestiaSdkWatcher.IProps) {}
32
+
33
+ public registerSignals(): void {
34
+ const stop = (): void => {
35
+ this.dispose();
36
+ process.exit(0);
37
+ };
38
+ process.once("SIGINT", stop);
39
+ process.once("SIGTERM", stop);
40
+ }
41
+
42
+ public async regenerate(reason: string): Promise<void> {
43
+ if (this.stopped) return;
44
+ if (this.running) {
45
+ this.pending = reason;
46
+ return;
47
+ }
48
+
49
+ this.running = true;
50
+ let nextReason: string | null = reason;
51
+ do {
52
+ const current: string = nextReason;
53
+ nextReason = null;
54
+ this.pending = null;
55
+ let configurations: INestiaConfig[] = [];
56
+ try {
57
+ if (current === "initial generation")
58
+ console.log("Starting nestia swagger watch mode");
59
+ else console.log(`Change detected (${current}); regenerating swagger`);
60
+
61
+ configurations = await this.props.configurations();
62
+ await this.props.generate(configurations);
63
+ await this.refresh(configurations);
64
+ console.log(
65
+ `Watching ${this.watchers.size} directories. Press Ctrl+C to stop.`,
66
+ );
67
+ } catch (error) {
68
+ await this.refresh(configurations);
69
+ console.error("Nestia swagger watch generation failed:");
70
+ printError(error);
71
+ }
72
+ nextReason = this.pending;
73
+ } while (nextReason !== null && !this.stopped);
74
+ this.running = false;
75
+ }
76
+
77
+ private schedule(reason: string): void {
78
+ if (this.stopped) return;
79
+ if (this.debounce !== null) clearTimeout(this.debounce);
80
+ this.debounce = setTimeout(() => {
81
+ this.debounce = null;
82
+ void this.regenerate(reason);
83
+ }, 250);
84
+ }
85
+
86
+ private async refresh(configurations: INestiaConfig[]): Promise<void> {
87
+ const next = await collectWatchPlan({
88
+ configurations,
89
+ configFile: this.props.configFile,
90
+ projectFile: this.props.projectFile,
91
+ });
92
+ this.ignored = next.ignored;
93
+
94
+ const directories: Set<string> = new Set();
95
+ for (const target of next.targets)
96
+ await collectDirectories({
97
+ directories,
98
+ ignored: next.ignored,
99
+ target,
100
+ });
101
+ this.sync(directories);
102
+ }
103
+
104
+ private sync(next: Set<string>): void {
105
+ for (const [directory, watcher] of this.watchers)
106
+ if (next.has(directory) === false) {
107
+ watcher.close();
108
+ this.watchers.delete(directory);
109
+ }
110
+
111
+ for (const directory of next) {
112
+ if (this.watchers.has(directory)) continue;
113
+ try {
114
+ const watcher = fs.watch(directory, (event, filename) => {
115
+ const target =
116
+ filename === null || filename === undefined
117
+ ? directory
118
+ : path.resolve(directory, String(filename));
119
+ if (shouldIgnore(target, this.ignored)) return;
120
+ if (shouldReact({ event, target }) === false) return;
121
+ this.schedule(relative(target));
122
+ });
123
+ watcher.on("error", () => {
124
+ watcher.close();
125
+ this.watchers.delete(directory);
126
+ this.schedule(relative(directory));
127
+ });
128
+ this.watchers.set(directory, watcher);
129
+ } catch {
130
+ continue;
131
+ }
132
+ }
133
+ }
134
+
135
+ private dispose(): void {
136
+ this.stopped = true;
137
+ if (this.debounce !== null) clearTimeout(this.debounce);
138
+ for (const watcher of this.watchers.values()) watcher.close();
139
+ this.watchers.clear();
140
+ }
141
+ }
142
+
143
+ interface IWatchPlan {
144
+ ignored: string[];
145
+ targets: string[];
146
+ }
147
+
148
+ const collectWatchPlan = async (props: {
149
+ configurations: INestiaConfig[];
150
+ configFile: string;
151
+ projectFile: string;
152
+ }): Promise<IWatchPlan> => {
153
+ const targets: Set<string> = new Set([
154
+ path.resolve(props.configFile),
155
+ path.resolve(props.projectFile),
156
+ ]);
157
+ const ignored: Set<string> = new Set([
158
+ path.resolve("node_modules"),
159
+ path.resolve(".git"),
160
+ ]);
161
+
162
+ for (const config of props.configurations) {
163
+ for (const target of await inputTargets(config.input)) targets.add(target);
164
+ for (const target of outputTargets(config)) ignored.add(target);
165
+ }
166
+ return {
167
+ ignored: [...ignored],
168
+ targets: [...targets],
169
+ };
170
+ };
171
+
172
+ const inputTargets = async (
173
+ input: INestiaConfig["input"],
174
+ ): Promise<string[]> => {
175
+ if (typeof input === "function") {
176
+ const fallback: string = path.resolve("src");
177
+ return [fs.existsSync(fallback) ? fallback : process.cwd()];
178
+ }
179
+
180
+ const patterns: string[] = Array.isArray(input)
181
+ ? input
182
+ : typeof input === "object"
183
+ ? input.include
184
+ : [input];
185
+ const output: Set<string> = new Set();
186
+ for (const pattern of patterns) {
187
+ for (const target of await patternTargets(pattern)) output.add(target);
188
+ }
189
+ return [...output];
190
+ };
191
+
192
+ const patternTargets = async (pattern: string): Promise<string[]> => {
193
+ const output: Set<string> = new Set();
194
+ const direct: string = path.resolve(pattern);
195
+ if (fs.existsSync(direct)) output.add(direct);
196
+
197
+ if (hasGlobMagic(pattern)) {
198
+ const root: string = staticRoot(pattern);
199
+ if (fs.existsSync(root)) output.add(root);
200
+ for (const match of await glob(pattern)) output.add(path.resolve(match));
201
+ }
202
+ return [...output];
203
+ };
204
+
205
+ const outputTargets = (config: INestiaConfig): string[] => {
206
+ const output: string[] = [];
207
+ for (const value of [config.output, config.e2e, config.distribute])
208
+ if (typeof value === "string") output.push(path.resolve(value));
209
+ if (config.swagger?.output !== undefined) {
210
+ const parsed: path.ParsedPath = path.parse(config.swagger.output);
211
+ output.push(
212
+ parsed.ext
213
+ ? path.resolve(config.swagger.output)
214
+ : path.resolve(config.swagger.output, "swagger.json"),
215
+ );
216
+ }
217
+ return output;
218
+ };
219
+
220
+ const collectDirectories = async (props: {
221
+ directories: Set<string>;
222
+ ignored: string[];
223
+ target: string;
224
+ }): Promise<void> => {
225
+ const stats: fs.Stats | null = await safeStat(props.target);
226
+ if (stats === null) return;
227
+ const root: string = stats.isDirectory()
228
+ ? props.target
229
+ : path.dirname(props.target);
230
+ await iterateDirectories({
231
+ directories: props.directories,
232
+ ignored: props.ignored,
233
+ root,
234
+ });
235
+ };
236
+
237
+ const iterateDirectories = async (props: {
238
+ directories: Set<string>;
239
+ ignored: string[];
240
+ root: string;
241
+ }): Promise<void> => {
242
+ const location: string = path.resolve(props.root);
243
+ if (shouldIgnore(location, props.ignored)) return;
244
+ props.directories.add(location);
245
+
246
+ let entries: fs.Dirent[];
247
+ try {
248
+ entries = await fs.promises.readdir(location, { withFileTypes: true });
249
+ } catch {
250
+ return;
251
+ }
252
+
253
+ for (const entry of entries) {
254
+ if (entry.isDirectory() === false) continue;
255
+ if (SKIPPED_DIRECTORIES.has(entry.name)) continue;
256
+ await iterateDirectories({
257
+ directories: props.directories,
258
+ ignored: props.ignored,
259
+ root: path.join(location, entry.name),
260
+ });
261
+ }
262
+ };
263
+
264
+ const safeStat = async (location: string): Promise<fs.Stats | null> => {
265
+ try {
266
+ return await fs.promises.stat(location);
267
+ } catch {
268
+ return null;
269
+ }
270
+ };
271
+
272
+ const shouldReact = (props: { event: string; target: string }): boolean => {
273
+ const ext: string = path.extname(props.target).toLowerCase();
274
+ if (SOURCE_EXTENSIONS.has(ext)) return true;
275
+ return props.event === "rename" && ext.length === 0;
276
+ };
277
+
278
+ const shouldIgnore = (location: string, ignored: string[]): boolean =>
279
+ ignored.some((elem) => isSameOrInside(location, elem));
280
+
281
+ const isSameOrInside = (child: string, parent: string): boolean => {
282
+ const left: string = comparable(child);
283
+ const right: string = comparable(parent);
284
+ return left === right || left.startsWith(`${right}${path.sep}`);
285
+ };
286
+
287
+ const comparable = (location: string): string => {
288
+ const resolved: string = path.resolve(location);
289
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
290
+ };
291
+
292
+ const staticRoot = (pattern: string): string => {
293
+ const absolute: string = path.resolve(pattern);
294
+ const parsed: path.ParsedPath = path.parse(absolute);
295
+ const segments: string[] = absolute
296
+ .slice(parsed.root.length)
297
+ .split(/[\\/]+/)
298
+ .filter((segment) => segment.length !== 0);
299
+ const stable: string[] = [];
300
+ for (const segment of segments) {
301
+ if (hasGlobMagic(segment)) break;
302
+ stable.push(segment);
303
+ }
304
+ return stable.length === 0 ? parsed.root : path.join(parsed.root, ...stable);
305
+ };
306
+
307
+ const hasGlobMagic = (pattern: string): boolean => /[*?[\]{}]/.test(pattern);
308
+
309
+ const relative = (location: string): string => {
310
+ const output: string = path.relative(process.cwd(), location);
311
+ return output.length === 0 ? "." : output.split(path.sep).join("/");
312
+ };
313
+
314
+ const printError = (error: unknown): void => {
315
+ let current: unknown = error;
316
+ let depth: number = 0;
317
+ while (current !== undefined && current !== null && depth < 10) {
318
+ const message: string =
319
+ current instanceof Error ? current.message : String(current);
320
+ console.error(
321
+ `${" ".repeat(depth)}${depth === 0 ? "" : "caused by: "}${message}`,
322
+ );
323
+ if (current instanceof Error && current.cause !== undefined) {
324
+ current = current.cause;
325
+ ++depth;
326
+ } else break;
327
+ }
328
+ };
329
+
330
+ const SOURCE_EXTENSIONS: Set<string> = new Set([
331
+ ".cts",
332
+ ".json",
333
+ ".mts",
334
+ ".ts",
335
+ ".tsx",
336
+ ]);
337
+
338
+ const SKIPPED_DIRECTORIES: Set<string> = new Set([
339
+ ".git",
340
+ ".nestia",
341
+ "node_modules",
342
+ ]);