@nestia/sdk 13.0.4 → 14.0.1

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 (186) hide show
  1. package/README.md +1 -1
  2. package/assets/bundle/distribute/package.json +2 -2
  3. package/assets/bundle/distribute/tsconfig.json +15 -105
  4. package/lib/INestiaConfig.d.ts +19 -10
  5. package/lib/NestiaSdkApplication.js +58 -27
  6. package/lib/NestiaSdkApplication.js.map +1 -1
  7. package/lib/NestiaSwaggerComposer.js +11 -10
  8. package/lib/NestiaSwaggerComposer.js.map +1 -1
  9. package/lib/analyses/ConfigAnalyzer.js +4 -4
  10. package/lib/analyses/ConfigAnalyzer.js.map +1 -1
  11. package/lib/analyses/ParameterNameAnalyzer.d.ts +20 -0
  12. package/lib/analyses/ParameterNameAnalyzer.js +48 -0
  13. package/lib/analyses/ParameterNameAnalyzer.js.map +1 -0
  14. package/lib/analyses/PathAnalyzer.d.ts +30 -0
  15. package/lib/analyses/PathAnalyzer.js +108 -16
  16. package/lib/analyses/PathAnalyzer.js.map +1 -1
  17. package/lib/analyses/ReflectControllerAnalyzer.js +2 -1
  18. package/lib/analyses/ReflectControllerAnalyzer.js.map +1 -1
  19. package/lib/analyses/ReflectHttpOperationAnalyzer.js +7 -7
  20. package/lib/analyses/ReflectHttpOperationAnalyzer.js.map +1 -1
  21. package/lib/analyses/ReflectHttpOperationParameterAnalyzer.js +122 -23
  22. package/lib/analyses/ReflectHttpOperationParameterAnalyzer.js.map +1 -1
  23. package/lib/analyses/ReflectHttpOperationResponseAnalyzer.js +5 -2
  24. package/lib/analyses/ReflectHttpOperationResponseAnalyzer.js.map +1 -1
  25. package/lib/analyses/ReflectMcpOperationAnalyzer.js +5 -2
  26. package/lib/analyses/ReflectMcpOperationAnalyzer.js.map +1 -1
  27. package/lib/analyses/ReflectWebSocketOperationAnalyzer.js +6 -3
  28. package/lib/analyses/ReflectWebSocketOperationAnalyzer.js.map +1 -1
  29. package/lib/analyses/SecurityAnalyzer.d.ts +10 -0
  30. package/lib/analyses/SecurityAnalyzer.js +24 -19
  31. package/lib/analyses/SecurityAnalyzer.js.map +1 -1
  32. package/lib/analyses/SwaggerExampleAnalyzer.d.ts +15 -0
  33. package/lib/analyses/SwaggerExampleAnalyzer.js +20 -0
  34. package/lib/analyses/SwaggerExampleAnalyzer.js.map +1 -0
  35. package/lib/analyses/TypedHttpRouteAnalyzer.js +30 -21
  36. package/lib/analyses/TypedHttpRouteAnalyzer.js.map +1 -1
  37. package/lib/analyses/TypedWebSocketRouteAnalyzer.js +1 -1
  38. package/lib/analyses/TypedWebSocketRouteAnalyzer.js.map +1 -1
  39. package/lib/executable/internal/NestiaSdkWatcher.js +3 -3
  40. package/lib/executable/internal/NestiaSdkWatcher.js.map +1 -1
  41. package/lib/executable/sdk.js +16 -1
  42. package/lib/executable/sdk.js.map +1 -1
  43. package/lib/factories/ExpressionFactory.d.ts +3 -0
  44. package/lib/factories/ExpressionFactory.js +6 -2
  45. package/lib/factories/ExpressionFactory.js.map +1 -1
  46. package/lib/factories/LiteralFactory.js +3 -1
  47. package/lib/factories/LiteralFactory.js.map +1 -1
  48. package/lib/generates/SdkGenerator.js +3 -3
  49. package/lib/generates/SdkGenerator.js.map +1 -1
  50. package/lib/generates/SwaggerGenerator.js +135 -27
  51. package/lib/generates/SwaggerGenerator.js.map +1 -1
  52. package/lib/generates/internal/FilePrinter.d.ts +14 -0
  53. package/lib/generates/internal/FilePrinter.js +31 -0
  54. package/lib/generates/internal/FilePrinter.js.map +1 -1
  55. package/lib/generates/internal/ImportDictionary.d.ts +2 -0
  56. package/lib/generates/internal/ImportDictionary.js +13 -0
  57. package/lib/generates/internal/ImportDictionary.js.map +1 -1
  58. package/lib/generates/internal/SdkAliasCollection.d.ts +2 -1
  59. package/lib/generates/internal/SdkAliasCollection.js +8 -4
  60. package/lib/generates/internal/SdkAliasCollection.js.map +1 -1
  61. package/lib/generates/internal/SdkDistributionComposer.js +72 -14
  62. package/lib/generates/internal/SdkDistributionComposer.js.map +1 -1
  63. package/lib/generates/internal/SdkFileProgrammer.js +83 -12
  64. package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
  65. package/lib/generates/internal/SdkHttpFunctionProgrammer.js +21 -23
  66. package/lib/generates/internal/SdkHttpFunctionProgrammer.js.map +1 -1
  67. package/lib/generates/internal/SdkHttpNamespaceProgrammer.js +27 -34
  68. package/lib/generates/internal/SdkHttpNamespaceProgrammer.js.map +1 -1
  69. package/lib/generates/internal/SdkHttpParameterProgrammer.d.ts +40 -1
  70. package/lib/generates/internal/SdkHttpParameterProgrammer.js +105 -6
  71. package/lib/generates/internal/SdkHttpParameterProgrammer.js.map +1 -1
  72. package/lib/generates/internal/SdkHttpRouteProgrammer.js +16 -32
  73. package/lib/generates/internal/SdkHttpRouteProgrammer.js.map +1 -1
  74. package/lib/generates/internal/SdkHttpSimulationProgrammer.js +18 -14
  75. package/lib/generates/internal/SdkHttpSimulationProgrammer.js.map +1 -1
  76. package/lib/generates/internal/SdkMcpRouteProgrammer.js +39 -11
  77. package/lib/generates/internal/SdkMcpRouteProgrammer.js.map +1 -1
  78. package/lib/generates/internal/SdkPathTemplate.d.ts +21 -0
  79. package/lib/generates/internal/SdkPathTemplate.js +50 -0
  80. package/lib/generates/internal/SdkPathTemplate.js.map +1 -0
  81. package/lib/generates/internal/SdkTypeProgrammer.js +15 -15
  82. package/lib/generates/internal/SdkTypeProgrammer.js.map +1 -1
  83. package/lib/generates/internal/SdkTypeTagProgrammer.d.ts +20 -1
  84. package/lib/generates/internal/SdkTypeTagProgrammer.js +386 -67
  85. package/lib/generates/internal/SdkTypeTagProgrammer.js.map +1 -1
  86. package/lib/generates/internal/SdkWebSocketNamespaceProgrammer.js +27 -36
  87. package/lib/generates/internal/SdkWebSocketNamespaceProgrammer.js.map +1 -1
  88. package/lib/generates/internal/SdkWebSocketParameterProgrammer.d.ts +35 -1
  89. package/lib/generates/internal/SdkWebSocketParameterProgrammer.js +72 -5
  90. package/lib/generates/internal/SdkWebSocketParameterProgrammer.js.map +1 -1
  91. package/lib/generates/internal/SdkWebSocketRouteProgrammer.js +57 -33
  92. package/lib/generates/internal/SdkWebSocketRouteProgrammer.js.map +1 -1
  93. package/lib/generates/internal/SwaggerOperationComposer.js +19 -8
  94. package/lib/generates/internal/SwaggerOperationComposer.js.map +1 -1
  95. package/lib/generates/internal/SwaggerOperationParameterComposer.d.ts +1 -1
  96. package/lib/generates/internal/SwaggerOperationParameterComposer.js +201 -19
  97. package/lib/generates/internal/SwaggerOperationParameterComposer.js.map +1 -1
  98. package/lib/generates/internal/SwaggerOperationResponseComposer.d.ts +2 -0
  99. package/lib/generates/internal/SwaggerOperationResponseComposer.js +39 -7
  100. package/lib/generates/internal/SwaggerOperationResponseComposer.js.map +1 -1
  101. package/lib/internal/legacy.d.ts +49 -18
  102. package/lib/internal/legacy.js +84 -25
  103. package/lib/internal/legacy.js.map +1 -1
  104. package/lib/structures/IOperationMetadata.d.ts +22 -0
  105. package/lib/structures/IReflectHttpOperationException.d.ts +3 -2
  106. package/lib/structures/IReflectHttpOperationParameter.d.ts +8 -3
  107. package/lib/structures/IReflectHttpOperationSuccess.d.ts +4 -3
  108. package/lib/structures/ITypedHttpRoute.d.ts +8 -0
  109. package/lib/structures/ITypedHttpRouteException.d.ts +3 -1
  110. package/lib/structures/ITypedHttpRouteParameter.d.ts +3 -1
  111. package/lib/structures/ITypedHttpRouteSuccess.d.ts +3 -1
  112. package/lib/structures/ITypedWebSocketRoute.d.ts +8 -0
  113. package/lib/utils/SourceFinder.d.ts +10 -0
  114. package/lib/utils/SourceFinder.js +27 -4
  115. package/lib/utils/SourceFinder.js.map +1 -1
  116. package/lib/utils/VersioningStrategy.d.ts +13 -1
  117. package/lib/utils/VersioningStrategy.js +24 -12
  118. package/lib/utils/VersioningStrategy.js.map +1 -1
  119. package/native/go.mod +1 -1
  120. package/native/go.sum +2 -2
  121. package/native/sdk/sdk_http_rules.go +210 -0
  122. package/native/sdk/sdk_metadata_json.go +73 -3
  123. package/native/sdk/sdk_transform.go +801 -49
  124. package/package.json +8 -8
  125. package/src/INestiaConfig.ts +19 -10
  126. package/src/NestiaSdkApplication.ts +63 -34
  127. package/src/NestiaSwaggerComposer.ts +11 -8
  128. package/src/analyses/ConfigAnalyzer.ts +3 -3
  129. package/src/analyses/ParameterNameAnalyzer.ts +51 -0
  130. package/src/analyses/PathAnalyzer.ts +106 -14
  131. package/src/analyses/ReflectControllerAnalyzer.ts +2 -1
  132. package/src/analyses/ReflectHttpOperationAnalyzer.ts +4 -4
  133. package/src/analyses/ReflectHttpOperationParameterAnalyzer.ts +122 -39
  134. package/src/analyses/ReflectHttpOperationResponseAnalyzer.ts +6 -7
  135. package/src/analyses/ReflectMcpOperationAnalyzer.ts +6 -2
  136. package/src/analyses/ReflectWebSocketOperationAnalyzer.ts +7 -3
  137. package/src/analyses/SecurityAnalyzer.ts +30 -20
  138. package/src/analyses/SwaggerExampleAnalyzer.ts +23 -0
  139. package/src/analyses/TypedHttpRouteAnalyzer.ts +29 -20
  140. package/src/analyses/TypedWebSocketRouteAnalyzer.ts +1 -0
  141. package/src/executable/internal/NestiaSdkWatcher.ts +2 -2
  142. package/src/executable/sdk.ts +24 -1
  143. package/src/factories/ExpressionFactory.ts +8 -2
  144. package/src/factories/LiteralFactory.ts +7 -2
  145. package/src/generates/SdkGenerator.ts +3 -3
  146. package/src/generates/SwaggerGenerator.ts +173 -32
  147. package/src/generates/internal/FilePrinter.ts +28 -0
  148. package/src/generates/internal/ImportDictionary.ts +11 -0
  149. package/src/generates/internal/SdkAliasCollection.ts +11 -4
  150. package/src/generates/internal/SdkDistributionComposer.ts +89 -15
  151. package/src/generates/internal/SdkFileProgrammer.ts +108 -16
  152. package/src/generates/internal/SdkHttpFunctionProgrammer.ts +28 -30
  153. package/src/generates/internal/SdkHttpNamespaceProgrammer.ts +34 -64
  154. package/src/generates/internal/SdkHttpParameterProgrammer.ts +193 -13
  155. package/src/generates/internal/SdkHttpRouteProgrammer.ts +17 -31
  156. package/src/generates/internal/SdkHttpSimulationProgrammer.ts +51 -14
  157. package/src/generates/internal/SdkMcpRouteProgrammer.ts +112 -15
  158. package/src/generates/internal/SdkPathTemplate.ts +73 -0
  159. package/src/generates/internal/SdkTypeProgrammer.ts +19 -22
  160. package/src/generates/internal/SdkTypeTagProgrammer.ts +506 -78
  161. package/src/generates/internal/SdkWebSocketNamespaceProgrammer.ts +43 -68
  162. package/src/generates/internal/SdkWebSocketParameterProgrammer.ts +126 -5
  163. package/src/generates/internal/SdkWebSocketRouteProgrammer.ts +71 -48
  164. package/src/generates/internal/SwaggerOperationComposer.ts +20 -6
  165. package/src/generates/internal/SwaggerOperationParameterComposer.ts +279 -30
  166. package/src/generates/internal/SwaggerOperationResponseComposer.ts +55 -11
  167. package/src/internal/legacy.ts +114 -26
  168. package/src/structures/IOperationMetadata.ts +28 -0
  169. package/src/structures/IReflectHttpOperationException.ts +7 -2
  170. package/src/structures/IReflectHttpOperationParameter.ts +9 -2
  171. package/src/structures/IReflectHttpOperationSuccess.ts +8 -3
  172. package/src/structures/ITypedHttpRoute.ts +8 -0
  173. package/src/structures/ITypedHttpRouteException.ts +4 -1
  174. package/src/structures/ITypedHttpRouteParameter.ts +3 -1
  175. package/src/structures/ITypedHttpRouteSuccess.ts +4 -1
  176. package/src/structures/ITypedWebSocketRoute.ts +8 -0
  177. package/src/utils/SourceFinder.ts +25 -4
  178. package/src/utils/VersioningStrategy.ts +28 -13
  179. package/lib/validators/HttpHeadersValidator.d.ts +0 -11
  180. package/lib/validators/HttpHeadersValidator.js +0 -15
  181. package/lib/validators/HttpHeadersValidator.js.map +0 -1
  182. package/lib/validators/HttpQueryValidator.d.ts +0 -10
  183. package/lib/validators/HttpQueryValidator.js +0 -14
  184. package/lib/validators/HttpQueryValidator.js.map +0 -1
  185. package/src/validators/HttpHeadersValidator.ts +0 -12
  186. package/src/validators/HttpQueryValidator.ts +0 -11
@@ -2,20 +2,14 @@ import { SwaggerExample } from "@nestia/core";
2
2
  import { ROUTE_ARGS_METADATA } from "@nestjs/common/constants";
3
3
  import { RouteParamtypes } from "@nestjs/common/enums/route-paramtypes.enum";
4
4
 
5
- import {
6
- HttpFormDataProgrammer,
7
- HttpHeadersProgrammer,
8
- HttpParameterProgrammer,
9
- HttpQueryProgrammer,
10
- JsonMetadataFactory,
11
- } from "../internal/legacy";
5
+ import { JsonMetadataFactory } from "../internal/legacy";
12
6
  import { IOperationMetadata } from "../structures/IOperationMetadata";
13
7
  import { IReflectController } from "../structures/IReflectController";
14
8
  import { IReflectHttpOperationParameter } from "../structures/IReflectHttpOperationParameter";
15
9
  import { IReflectOperationError } from "../structures/IReflectOperationError";
16
- import { HttpHeadersValidator } from "../validators/HttpHeadersValidator";
17
- import { HttpQueryValidator } from "../validators/HttpQueryValidator";
18
10
  import { TextPlainValidator } from "../validators/TextPlainValidator";
11
+ import { ParameterNameAnalyzer } from "./ParameterNameAnalyzer";
12
+ import { SwaggerExampleAnalyzer } from "./SwaggerExampleAnalyzer";
19
13
 
20
14
  export namespace ReflectHttpOperationParameterAnalyzer {
21
15
  export interface IContext {
@@ -89,7 +83,9 @@ export namespace ReflectHttpOperationParameterAnalyzer {
89
83
  preconfigured
90
84
  .filter((x) => x.category === "headers")
91
85
  .map((x) => x.field)
92
- .filter((field) => field !== undefined),
86
+ .filter((field) => field !== undefined)
87
+ // NestJS reads `req.headers[name.toLowerCase()]`
88
+ .map((field) => field.toLowerCase()),
93
89
  ) === false
94
90
  )
95
91
  contradict(`Duplicated field names of headers are not allowed.`);
@@ -105,7 +101,7 @@ export namespace ReflectHttpOperationParameterAnalyzer {
105
101
  //----
106
102
  // COMPOSE PARAMETERS
107
103
  //----
108
- const parameters: IReflectHttpOperationParameter[] = preconfigured
104
+ const declared: IReflectHttpOperationParameter[] = preconfigured
109
105
  .map((p): IReflectHttpOperationParameter | null => {
110
106
  // METADATA INFO
111
107
  const pErrorContents: Array<string | IOperationMetadata.IError> = [];
@@ -116,7 +112,8 @@ export namespace ReflectHttpOperationParameterAnalyzer {
116
112
  file: ctx.controller.file,
117
113
  class: ctx.controller.class.name,
118
114
  function: ctx.functionName,
119
- from: `parameter ${matched ? JSON.stringify(matched.name) : `of ${p.index} th`}`,
115
+ // a destructured parameter has no name to report
116
+ from: `parameter ${matched?.name ? JSON.stringify(matched.name) : `of ${p.index} th`}`,
120
117
  contents: pErrorContents,
121
118
  });
122
119
  return null;
@@ -129,20 +126,31 @@ export namespace ReflectHttpOperationParameterAnalyzer {
129
126
  pErrorContents.push(`Failed to get the type info.`);
130
127
 
131
128
  // CONSIDER KIND
132
- const schema: IOperationMetadata.ISchema | null = (() => {
129
+ const pipe = (() => {
133
130
  if (matched === undefined) return null;
134
- const result =
135
- p.category === "body" &&
131
+ return p.category === "body" &&
136
132
  (p.contentType === "application/json" || p.encrypted === true)
137
- ? matched.primitive
138
- : matched.resolved;
139
- return result.success ? result.data : null;
133
+ ? matched.primitive
134
+ : matched.resolved;
140
135
  })();
136
+ const schema: IOperationMetadata.ISchema | null =
137
+ pipe?.success === true ? pipe.data : null;
138
+ // A type the metadata analysis could not read has no SDK or Swagger
139
+ // form; dropping the parameter would leave a function that cannot
140
+ // send what the route requires.
141
+ if (pipe?.success === false) pErrorContents.push(...pipe.errors);
141
142
  if (p.category === "body" && p.field !== undefined)
142
143
  pErrorContents.push(`@Body() must not have a field name.`);
143
144
  else if (p.category === "param" && p.field === undefined)
144
145
  pErrorContents.push(`@Param() must have a field name.`);
145
146
 
147
+ // The wire rules of the parameter's HTTP input: a type they reject
148
+ // cannot be carried as a query string, headers, a path segment, or a
149
+ // form, so the SDK would send garbage and the document would lie.
150
+ const rule: keyof IOperationMetadata.IHttpRules | null = httpRuleOf(p);
151
+ if (rule !== null && schema !== null)
152
+ pErrorContents.push(...(schema.http?.[rule] ?? []));
153
+
146
154
  if (pErrorContents.length) return report();
147
155
  else if (
148
156
  matched === undefined ||
@@ -150,6 +158,7 @@ export namespace ReflectHttpOperationParameterAnalyzer {
150
158
  schema === null
151
159
  )
152
160
  return null; // unreachable
161
+ const { http: _http, ...shape } = schema;
153
162
 
154
163
  const example: SwaggerExample.IData<any> | undefined = (
155
164
  Reflect.getMetadata(
@@ -167,12 +176,11 @@ export namespace ReflectHttpOperationParameterAnalyzer {
167
176
  field: p.field!,
168
177
  name: matched.name,
169
178
  type: matched.type,
170
- validate: HttpParameterProgrammer.validate,
171
179
  description: matched.description,
172
180
  jsDocTags: matched.jsDocTags,
173
181
  example: example?.example,
174
- examples: example?.examples,
175
- ...schema,
182
+ examples: SwaggerExampleAnalyzer.examples(example),
183
+ ...shape,
176
184
  };
177
185
  else if (p.category === "query")
178
186
  return {
@@ -181,14 +189,11 @@ export namespace ReflectHttpOperationParameterAnalyzer {
181
189
  field: p.field ?? null,
182
190
  name: matched.name,
183
191
  type: matched.type,
184
- validate: p.field
185
- ? HttpQueryValidator.validate
186
- : HttpQueryProgrammer.validate,
187
192
  description: matched.description,
188
193
  jsDocTags: matched.jsDocTags,
189
194
  example: example?.example,
190
- examples: example?.examples,
191
- ...schema,
195
+ examples: SwaggerExampleAnalyzer.examples(example),
196
+ ...shape,
192
197
  };
193
198
  else if (p.category === "headers")
194
199
  return {
@@ -197,14 +202,11 @@ export namespace ReflectHttpOperationParameterAnalyzer {
197
202
  field: p.field ?? null,
198
203
  name: matched.name,
199
204
  type: matched.type,
200
- validate: p.field
201
- ? HttpHeadersValidator.validate
202
- : HttpHeadersProgrammer.validate,
203
205
  description: matched.description,
204
206
  jsDocTags: matched.jsDocTags,
205
207
  example: example?.example,
206
- examples: example?.examples,
207
- ...schema,
208
+ examples: SwaggerExampleAnalyzer.examples(example),
209
+ ...shape,
208
210
  };
209
211
  else if (p.category === "body")
210
212
  return {
@@ -217,16 +219,14 @@ export namespace ReflectHttpOperationParameterAnalyzer {
217
219
  validate:
218
220
  p.contentType === "application/json" || p.encrypted === true
219
221
  ? JsonMetadataFactory.validate
220
- : p.contentType === "application/x-www-form-urlencoded"
221
- ? HttpQueryProgrammer.validate
222
- : p.contentType === "multipart/form-data"
223
- ? HttpFormDataProgrammer.validate
224
- : TextPlainValidator.validate,
222
+ : p.contentType === "text/plain"
223
+ ? TextPlainValidator.validate
224
+ : undefined,
225
225
  description: matched.description,
226
226
  jsDocTags: matched.jsDocTags,
227
227
  example: example?.example,
228
- examples: example?.examples,
229
- ...schema,
228
+ examples: SwaggerExampleAnalyzer.examples(example),
229
+ ...shape,
230
230
  };
231
231
  else {
232
232
  pErrorContents.push(`Unknown kind of the parameter.`);
@@ -234,11 +234,93 @@ export namespace ReflectHttpOperationParameterAnalyzer {
234
234
  }
235
235
  })
236
236
  .filter((x): x is IReflectHttpOperationParameter => x !== null);
237
+ // a destructured parameter declares no name, so it is given one
238
+ const parameters: IReflectHttpOperationParameter[] =
239
+ ParameterNameAnalyzer.name(declared);
237
240
 
241
+ const duplicated: string[] = findDuplicatedKeys(parameters);
242
+ if (duplicated.length)
243
+ errors.push({
244
+ file: ctx.controller.file,
245
+ class: ctx.controller.class.name,
246
+ function: ctx.functionName,
247
+ from: "",
248
+ contents: duplicated,
249
+ });
238
250
  if (errors.length) ctx.errors.push(...errors);
239
251
  return parameters;
240
252
  };
241
253
 
254
+ /**
255
+ * The HTTP input rules a parameter's type must satisfy, or `null` for a JSON
256
+ * or text body, whose policies are checked on the TypeScript side.
257
+ *
258
+ * The rules are typia's, baked per parameter by the SDK transform (see
259
+ * `nestiaSDKHttpRules`), because only this analyzer knows the decorator. A
260
+ * typed decorator was already held to them by the core transform, so the
261
+ * verdict is only news for a vanilla `@Query()`, `@Headers()`, or `@Param()`,
262
+ * which no transform touches.
263
+ */
264
+ const httpRuleOf = (
265
+ p: IReflectHttpOperationParameter.IPreconfigured,
266
+ ): keyof IOperationMetadata.IHttpRules | null => {
267
+ if (p.category === "param") return "param";
268
+ else if (p.category === "query")
269
+ return p.field !== undefined ? "field" : "query";
270
+ else if (p.category === "headers")
271
+ return p.field !== undefined ? "field" : "headers";
272
+ else if (p.contentType === "application/x-www-form-urlencoded")
273
+ return "query";
274
+ else if (p.contentType === "multipart/form-data") return "formData";
275
+ return null;
276
+ };
277
+
278
+ /**
279
+ * Reports a query key or header both a field parameter and the object
280
+ * parameter of its category declare.
281
+ *
282
+ * On the wire they are one key: NestJS hands both parameters the same
283
+ * `req.query.keyword`, and header names match case-insensitively, as NestJS
284
+ * lowercases the name a `@Headers("X-Tenant")` reads. Documenting both lists
285
+ * one `name` + `in` pair twice, which OpenAPI forbids, and the SDK would ask
286
+ * its caller for the same key twice and send one of the two values. Like two
287
+ * field parameters of the same name, the declaration is contradictory.
288
+ */
289
+ const findDuplicatedKeys = (
290
+ parameters: IReflectHttpOperationParameter[],
291
+ ): string[] => {
292
+ const messages: string[] = [];
293
+ for (const [category, normalize, noun] of [
294
+ ["query", (key: string) => key, "Query key"],
295
+ ["headers", (key: string) => key.toLowerCase(), "Header"],
296
+ ] as const) {
297
+ const fields: Map<string, string> = new Map();
298
+ for (const p of parameters)
299
+ if (p.category === category && p.field !== null)
300
+ fields.set(normalize(p.field), p.field);
301
+ for (const p of parameters) {
302
+ if (p.category !== category || p.field !== null) continue;
303
+ for (const key of objectKeys(p)) {
304
+ const field: string | undefined = fields.get(normalize(key));
305
+ if (field !== undefined)
306
+ messages.push(
307
+ `${noun} ${JSON.stringify(field)} is declared both by a field parameter and by the ${category} object.`,
308
+ );
309
+ }
310
+ }
311
+ }
312
+ return messages;
313
+ };
314
+
315
+ /** Literal property keys of an object parameter's object type. */
316
+ const objectKeys = (p: IReflectHttpOperationParameter): string[] => {
317
+ const name: string | undefined = p.metadata.objects[0]?.name;
318
+ const object = p.components.objects.find((o) => o.name === name);
319
+ return (object?.properties ?? [])
320
+ .map((property) => property.key.constants[0]?.values[0]?.value)
321
+ .filter((key): key is string => typeof key === "string");
322
+ };
323
+
242
324
  const analyzePreconfigured = (
243
325
  props: IContext,
244
326
  ): IReflectHttpOperationParameter.IPreconfigured[] => {
@@ -263,9 +345,10 @@ export namespace ReflectHttpOperationParameterAnalyzer {
263
345
  const symbol: string = key.split(":")[0]!;
264
346
  if (symbol.indexOf("__custom") !== -1) return analyzeCustomParameter(param);
265
347
 
348
+ // the whole number: `RAW_BODY` is 12, which the first digit misread as 1
266
349
  const category:
267
350
  | IReflectHttpOperationParameter.IPreconfigured["category"]
268
- | null = getNestParamType(Number(symbol[0]) as RouteParamtypes);
351
+ | null = getNestParamType(Number(symbol) as RouteParamtypes);
269
352
  if (category === null) return null;
270
353
  if (category === "body")
271
354
  return {
@@ -5,17 +5,14 @@ import {
5
5
  INTERCEPTORS_METADATA,
6
6
  } from "@nestjs/common/constants";
7
7
 
8
- import {
9
- HttpQueryProgrammer,
10
- JsonMetadataFactory,
11
- sizeOf,
12
- } from "../internal/legacy";
8
+ import { JsonMetadataFactory, sizeOf } from "../internal/legacy";
13
9
  import { IOperationMetadata } from "../structures/IOperationMetadata";
14
10
  import { IReflectController } from "../structures/IReflectController";
15
11
  import { IReflectHttpOperationSuccess } from "../structures/IReflectHttpOperationSuccess";
16
12
  import { IReflectOperationError } from "../structures/IReflectOperationError";
17
13
  import { HttpResponseContentTypeUtil } from "../utils/HttpResponseContentTypeUtil";
18
14
  import { TextPlainValidator } from "../validators/TextPlainValidator";
15
+ import { SwaggerExampleAnalyzer } from "./SwaggerExampleAnalyzer";
19
16
 
20
17
  export namespace ReflectHttpOperationResponseAnalyzer {
21
18
  export interface IContext {
@@ -102,7 +99,9 @@ export namespace ReflectHttpOperationResponseAnalyzer {
102
99
  : contentType === "application/json" || encrypted === true
103
100
  ? JsonMetadataFactory.validate
104
101
  : contentType === "application/x-www-form-urlencoded"
105
- ? HttpQueryProgrammer.validate
102
+ ? // a typed query response, held to typia's query rules by the
103
+ // core transform
104
+ undefined
106
105
  : contentType === "text/plain"
107
106
  ? TextPlainValidator.validate
108
107
  : (next) =>
@@ -110,7 +109,7 @@ export namespace ReflectHttpOperationResponseAnalyzer {
110
109
  ? ["HEAD method must not have any return value."]
111
110
  : [],
112
111
  example: example?.example,
113
- examples: example?.examples,
112
+ examples: SwaggerExampleAnalyzer.examples(example),
114
113
  };
115
114
  };
116
115
 
@@ -7,6 +7,7 @@ import { IReflectImport } from "../structures/IReflectImport";
7
7
  import { IReflectMcpOperation } from "../structures/IReflectMcpOperation";
8
8
  import { IReflectMcpOperationParameter } from "../structures/IReflectMcpOperationParameter";
9
9
  import { ImportAnalyzer } from "./ImportAnalyzer";
10
+ import { ParameterNameAnalyzer } from "./ParameterNameAnalyzer";
10
11
 
11
12
  export namespace ReflectMcpOperationAnalyzer {
12
13
  export interface IProps {
@@ -60,7 +61,7 @@ export namespace ReflectMcpOperationAnalyzer {
60
61
  );
61
62
 
62
63
  const imports: IReflectImport[] = [];
63
- const parameters: IReflectMcpOperationParameter[] = preconfigured
64
+ const declared: IReflectMcpOperationParameter[] = preconfigured
64
65
  .map((p) => {
65
66
  const matched: IOperationMetadata.IParameter | undefined =
66
67
  ctx.metadata.parameters.find(
@@ -74,7 +75,7 @@ export namespace ReflectMcpOperationAnalyzer {
74
75
  }
75
76
  if (matched.type === null) {
76
77
  errors.push(
77
- `Failed to analyze the parameter type of ${JSON.stringify(matched.name)}.`,
78
+ `Failed to analyze the parameter type of ${matched.name ? JSON.stringify(matched.name) : `the ${p.index} (th) argument`}.`,
78
79
  );
79
80
  return null;
80
81
  }
@@ -90,6 +91,9 @@ export namespace ReflectMcpOperationAnalyzer {
90
91
  };
91
92
  })
92
93
  .filter((p): p is IReflectMcpOperationParameter => !!p);
94
+ // a destructured parameter declares no name, so it is given one
95
+ const parameters: IReflectMcpOperationParameter[] =
96
+ ParameterNameAnalyzer.name(declared);
93
97
 
94
98
  if (ctx.metadata.success?.imports?.length)
95
99
  imports.push(...ctx.metadata.success.imports);
@@ -8,6 +8,7 @@ import { IReflectWebSocketOperation } from "../structures/IReflectWebSocketOpera
8
8
  import { IReflectWebSocketOperationParameter } from "../structures/IReflectWebSocketOperationParameter";
9
9
  import { StringUtil } from "../utils/StringUtil";
10
10
  import { ImportAnalyzer } from "./ImportAnalyzer";
11
+ import { ParameterNameAnalyzer } from "./ParameterNameAnalyzer";
11
12
  import { PathAnalyzer } from "./PathAnalyzer";
12
13
  import { ReflectMetadataAnalyzer } from "./ReflectMetadataAnalyzer";
13
14
 
@@ -51,7 +52,7 @@ export namespace ReflectWebSocketOperationAnalyzer {
51
52
  );
52
53
 
53
54
  const imports: IReflectImport[] = [];
54
- const parameters: IReflectWebSocketOperationParameter[] = preconfigured
55
+ const declared: IReflectWebSocketOperationParameter[] = preconfigured
55
56
  .map((p) => {
56
57
  const reject = (message: string): null => {
57
58
  errors.push(message);
@@ -69,7 +70,7 @@ export namespace ReflectWebSocketOperationAnalyzer {
69
70
  );
70
71
  else if (matched.type === null)
71
72
  return reject(
72
- `Failed to analyze the parameter type of the ${JSON.stringify(matched.name)}.`,
73
+ `Failed to analyze the parameter type of the ${matched.name ? JSON.stringify(matched.name) : `${p.index} th parameter`}.`,
73
74
  );
74
75
  else if (
75
76
  p.category === "param" &&
@@ -133,6 +134,9 @@ export namespace ReflectWebSocketOperationAnalyzer {
133
134
  }
134
135
  })
135
136
  .filter((p): p is IReflectWebSocketOperationParameter => !!p);
137
+ // a destructured parameter declares no name, so it is given one
138
+ const parameters: IReflectWebSocketOperationParameter[] =
139
+ ParameterNameAnalyzer.name(declared);
136
140
 
137
141
  const fields: string[] = preconfigured
138
142
  .filter((p) => p.category === "param")
@@ -142,7 +146,7 @@ export namespace ReflectWebSocketOperationAnalyzer {
142
146
  for (const cLoc of ctx.controller.paths)
143
147
  for (const mLoc of route.paths) {
144
148
  const location: string = PathAnalyzer.join(cLoc, mLoc);
145
- if (location.includes("*")) continue;
149
+ if (PathAnalyzer.wildcard(location)) continue;
146
150
 
147
151
  const binded: string[] | null = PathAnalyzer.parameters(location);
148
152
  if (binded === null)
@@ -1,25 +1,35 @@
1
- import { MapUtil } from "../utils/MapUtil";
2
-
3
1
  export namespace SecurityAnalyzer {
4
- export const merge = (...entire: Record<string, string[]>[]) => {
5
- const dict: Map<string | typeof none, Set<string>> = new Map();
6
- for (const obj of entire) {
7
- const entries = Object.entries(obj);
8
- for (const [key, value] of entries) {
9
- const set = MapUtil.take(dict, key, () => new Set());
10
- for (const val of value) set.add(val);
11
- }
12
- if (entries.length === 0) MapUtil.take(dict, none, () => new Set());
13
- }
2
+ /**
3
+ * Joins lists of security requirements into one, as OpenAPI reads them.
4
+ *
5
+ * Each requirement is an alternative, any one of which suffices, and every
6
+ * scheme of one requirement must hold, so each is kept as declared. Merging
7
+ * them by scheme name turned `{ a, b }` (both) into `a` or `b`, and the
8
+ * `read` or `write` scope alternatives of one scheme into both scopes. Only a
9
+ * requirement repeating an earlier one, such as a controller's restated on
10
+ * its method, is dropped; an empty one, anonymous access, is kept.
11
+ */
12
+ export const merge = (
13
+ ...entire: Record<string, string[]>[]
14
+ ): Record<string, string[]>[] => {
14
15
  const output: Record<string, string[]>[] = [];
15
- for (const [key, set] of dict)
16
- key === none
17
- ? output.push({})
18
- : output.push({
19
- [key]: [...set],
20
- });
16
+ const visited: Set<string> = new Set();
17
+ for (const requirement of entire) {
18
+ const normalized: Record<string, string[]> = Object.fromEntries(
19
+ Object.entries(requirement).map(([name, scopes]) => [
20
+ name,
21
+ [...new Set(scopes)],
22
+ ]),
23
+ );
24
+ const key: string = JSON.stringify(
25
+ Object.entries(normalized)
26
+ .map(([name, scopes]) => [name, [...scopes].sort()] as const)
27
+ .sort(([x], [y]) => (x < y ? -1 : x > y ? 1 : 0)),
28
+ );
29
+ if (visited.has(key)) continue;
30
+ visited.add(key);
31
+ output.push(normalized);
32
+ }
21
33
  return output;
22
34
  };
23
-
24
- const none = Symbol("none");
25
35
  }
@@ -0,0 +1,23 @@
1
+ import { SwaggerExample } from "@nestia/core";
2
+ import { OpenApi } from "@typia/interface";
3
+
4
+ export namespace SwaggerExampleAnalyzer {
5
+ /**
6
+ * The named examples `@SwaggerExample` attached, as the Example Objects an
7
+ * OpenAPI `examples` map holds.
8
+ *
9
+ * The decorator stores each named example as its value, and documents used to
10
+ * carry the value itself. OpenAPI 3.x names examples with Example Objects,
11
+ * whose `value` holds the value, so a tool read the raw value as an Example
12
+ * Object and found no example in it (#1649). `@TypedException()` examples are
13
+ * declared as Example Objects already.
14
+ */
15
+ export const examples = (
16
+ data: SwaggerExample.IData<any> | undefined,
17
+ ): Record<string, OpenApi.IExample> | undefined =>
18
+ data?.examples === undefined
19
+ ? undefined
20
+ : Object.fromEntries(
21
+ Object.entries(data.examples).map(([key, value]) => [key, { value }]),
22
+ );
23
+ }
@@ -54,7 +54,7 @@ export namespace TypedHttpRouteAnalyzer {
54
54
  next: {
55
55
  metadata: IMetadataSchema;
56
56
  components: IMetadataComponents;
57
- validate: MetadataFactory.Validator;
57
+ validate?: MetadataFactory.Validator;
58
58
  },
59
59
  from: string,
60
60
  escape: boolean,
@@ -66,16 +66,19 @@ export namespace TypedHttpRouteAnalyzer {
66
66
  next.metadata,
67
67
  components.dictionary,
68
68
  );
69
- const metaErrors: MetadataFactory.IError[] = MetadataFactory.validate({
70
- options: {
71
- escape,
72
- constant: true,
73
- absorb: true,
74
- validate: next.validate, // @todo -> CHECK IN TYPIA
75
- },
76
- functor: next.validate, // @todo -> CHECK IN TYPIA
77
- metadata,
78
- });
69
+ const metaErrors: MetadataFactory.IError[] =
70
+ next.validate === undefined
71
+ ? []
72
+ : MetadataFactory.validate({
73
+ options: {
74
+ escape,
75
+ constant: true,
76
+ absorb: true,
77
+ validate: next.validate,
78
+ },
79
+ functor: next.validate,
80
+ metadata,
81
+ });
79
82
  if (metaErrors.length)
80
83
  errors.push({
81
84
  file: props.controller.file,
@@ -137,18 +140,23 @@ export namespace TypedHttpRouteAnalyzer {
137
140
  t.text[0]!.text &&
138
141
  (t.name === "setHeader" || t.name === "assignHeaders"),
139
142
  )
140
- .map((t) =>
141
- t.name === "setHeader"
143
+ .map((t) => {
144
+ // the words of the first line; the next lines only describe
145
+ const words: string[] = t
146
+ .text![0]!.text.split("\n")[0]!
147
+ .trim()
148
+ .split(/\s+/);
149
+ return t.name === "setHeader"
142
150
  ? {
143
- type: "setter",
144
- source: t.text![0]!.text.split(" ")[0]!.trim(),
145
- target: t.text![0]!.text.split(" ")[1]?.trim(),
151
+ type: "setter" as const,
152
+ source: words[0]!,
153
+ target: words[1],
146
154
  }
147
155
  : {
148
- type: "assigner",
149
- source: t.text![0]!.text,
150
- },
151
- ),
156
+ type: "assigner" as const,
157
+ source: words[0]!,
158
+ };
159
+ }),
152
160
  };
153
161
  if (errors.length) {
154
162
  props.errors.push(...errors);
@@ -159,6 +167,7 @@ export namespace TypedHttpRouteAnalyzer {
159
167
  ({
160
168
  ...props.operation,
161
169
  controller: props.controller,
170
+ key: props.operation.name,
162
171
  path,
163
172
  accessor: [...PathUtil.accessors(path), props.operation.name],
164
173
  exceptions,
@@ -12,6 +12,7 @@ export namespace TypedWebSocketRouteAnalyzer {
12
12
  props.paths.map((path) => ({
13
13
  ...props.operation,
14
14
  controller: props.controller,
15
+ key: props.operation.name,
15
16
  path,
16
17
  accessor: [...PathUtil.accessors(path), props.operation.name],
17
18
  header:
@@ -1,8 +1,8 @@
1
1
  import fs from "fs";
2
- import { glob } from "glob";
3
2
  import path from "path";
4
3
 
5
4
  import { INestiaConfig } from "../../INestiaConfig";
5
+ import { SourceFinder } from "../../utils/SourceFinder";
6
6
 
7
7
  export namespace NestiaSdkWatcher {
8
8
  export interface IProps {
@@ -202,7 +202,7 @@ const patternTargets = async (pattern: string): Promise<string[]> => {
202
202
  if (hasGlobMagic(pattern)) {
203
203
  const root: string = staticRoot(pattern);
204
204
  if (fs.existsSync(root)) output.add(root);
205
- for (const match of await glob(pattern)) output.add(path.resolve(match));
205
+ for (const match of await SourceFinder.expand(pattern)) output.add(match);
206
206
  }
207
207
  return [...output];
208
208
  };
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import cp from "child_process";
3
3
  import fs from "fs";
4
+ import path from "path";
4
5
  import process from "process";
5
6
 
6
7
  import { CommandParser } from "./internal/CommandParser";
@@ -31,13 +32,35 @@ function dependencies(argv: string[]): void {
31
32
  const module = options.manager ?? options.module ?? "npm";
32
33
  const prefix: string = module === "yarn" ? "yarn add" : `${module} install`;
33
34
 
34
- for (const lib of ["@nestia/e2e", "@nestia/fetcher", "typia"]) {
35
+ // typia is the release the Nestia transform links, saved exactly: its latest
36
+ // may be another, which @nestia/core refuses
37
+ for (const lib of [
38
+ "@nestia/e2e",
39
+ "@nestia/fetcher",
40
+ `typia@${linkedTypiaVersion()} -E`,
41
+ ]) {
35
42
  const command: string = `${prefix} ${lib}`;
36
43
  console.log(`\n$ ${command}`);
37
44
  cp.execSync(command, { stdio: "inherit" });
38
45
  }
39
46
  }
40
47
 
48
+ /**
49
+ * The typia release the Nestia transform links: the one `@nestia/core`
50
+ * resolves, which its exact dependency holds at that release.
51
+ */
52
+ function linkedTypiaVersion(): string {
53
+ const core: string = path.dirname(
54
+ require.resolve("@nestia/core/package.json"),
55
+ );
56
+ return JSON.parse(
57
+ fs.readFileSync(
58
+ require.resolve("typia/package.json", { paths: [core] }),
59
+ "utf8",
60
+ ),
61
+ ).version;
62
+ }
63
+
41
64
  async function initialize(): Promise<void> {
42
65
  if (fs.existsSync("nestia.config.ts") === true)
43
66
  halt(
@@ -9,6 +9,9 @@ import {
9
9
  * Numeric-literal helper that handles negative values via a leading
10
10
  * `MinusToken` prefix unary, matching how the TypeScript factory itself emits
11
11
  * negative numeric literals.
12
+ *
13
+ * Infinity is written `1e999`, the literal that evaluates to it and the only
14
+ * spelling that is also a literal type; `Infinity` names a value, not a type.
12
15
  */
13
16
  export namespace ExpressionFactory {
14
17
  export const number = (
@@ -17,7 +20,10 @@ export namespace ExpressionFactory {
17
20
  value < 0
18
21
  ? factory.createPrefixUnaryExpression(
19
22
  SyntaxKind.MinusToken,
20
- factory.createNumericLiteral(Math.abs(value)),
23
+ literal(-value),
21
24
  )
22
- : factory.createNumericLiteral(value);
25
+ : literal(value);
26
+
27
+ const literal = (value: number): NumericLiteral =>
28
+ factory.createNumericLiteral(value === Infinity ? "1e999" : value);
23
29
  }