@nestia/core 14.0.0 → 14.0.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 (192) hide show
  1. package/MIGRATION.md +20 -0
  2. package/README.md +1 -1
  3. package/lib/adaptors/McpAdaptor.d.ts +29 -3
  4. package/lib/adaptors/McpAdaptor.js +150 -24
  5. package/lib/adaptors/McpAdaptor.js.map +1 -1
  6. package/lib/adaptors/WebSocketAdaptor.d.ts +37 -0
  7. package/lib/adaptors/WebSocketAdaptor.js +184 -48
  8. package/lib/adaptors/WebSocketAdaptor.js.map +1 -1
  9. package/lib/adaptors/internal/RoutePathMatcher.d.ts +1 -0
  10. package/lib/adaptors/internal/RoutePathMatcher.js +88 -0
  11. package/lib/adaptors/internal/RoutePathMatcher.js.map +1 -0
  12. package/lib/adaptors/internal/external_context.d.ts +1 -0
  13. package/lib/adaptors/internal/external_context.js +56 -0
  14. package/lib/adaptors/internal/external_context.js.map +1 -0
  15. package/lib/decorators/DynamicModule.d.ts +12 -0
  16. package/lib/decorators/DynamicModule.js +12 -0
  17. package/lib/decorators/DynamicModule.js.map +1 -1
  18. package/lib/decorators/EncryptedBody.d.ts +14 -6
  19. package/lib/decorators/EncryptedBody.js +39 -16
  20. package/lib/decorators/EncryptedBody.js.map +1 -1
  21. package/lib/decorators/EncryptedController.d.ts +6 -1
  22. package/lib/decorators/EncryptedController.js +7 -2
  23. package/lib/decorators/EncryptedController.js.map +1 -1
  24. package/lib/decorators/EncryptedModule.d.ts +25 -6
  25. package/lib/decorators/EncryptedModule.js +87 -18
  26. package/lib/decorators/EncryptedModule.js.map +1 -1
  27. package/lib/decorators/EncryptedRoute.d.ts +20 -11
  28. package/lib/decorators/EncryptedRoute.js +27 -13
  29. package/lib/decorators/EncryptedRoute.js.map +1 -1
  30. package/lib/decorators/HumanRoute.d.ts +5 -0
  31. package/lib/decorators/HumanRoute.js +5 -0
  32. package/lib/decorators/HumanRoute.js.map +1 -1
  33. package/lib/decorators/McpRoute.d.ts +31 -19
  34. package/lib/decorators/McpRoute.js +12 -1
  35. package/lib/decorators/McpRoute.js.map +1 -1
  36. package/lib/decorators/PlainBody.d.ts +7 -1
  37. package/lib/decorators/PlainBody.js +16 -8
  38. package/lib/decorators/PlainBody.js.map +1 -1
  39. package/lib/decorators/SwaggerCustomizer.d.ts +15 -0
  40. package/lib/decorators/SwaggerCustomizer.js +5 -0
  41. package/lib/decorators/SwaggerCustomizer.js.map +1 -1
  42. package/lib/decorators/SwaggerExample.d.ts +51 -22
  43. package/lib/decorators/SwaggerExample.js +35 -22
  44. package/lib/decorators/SwaggerExample.js.map +1 -1
  45. package/lib/decorators/TypedBody.d.ts +10 -3
  46. package/lib/decorators/TypedBody.js +20 -10
  47. package/lib/decorators/TypedBody.js.map +1 -1
  48. package/lib/decorators/TypedException.d.ts +10 -0
  49. package/lib/decorators/TypedFormData.d.ts +81 -9
  50. package/lib/decorators/TypedFormData.js +121 -31
  51. package/lib/decorators/TypedFormData.js.map +1 -1
  52. package/lib/decorators/TypedHeaders.d.ts +5 -0
  53. package/lib/decorators/TypedHeaders.js +5 -0
  54. package/lib/decorators/TypedHeaders.js.map +1 -1
  55. package/lib/decorators/TypedParam.d.ts +12 -3
  56. package/lib/decorators/TypedParam.js +12 -3
  57. package/lib/decorators/TypedParam.js.map +1 -1
  58. package/lib/decorators/TypedQuery.d.ts +17 -1
  59. package/lib/decorators/TypedQuery.js +25 -4
  60. package/lib/decorators/TypedQuery.js.map +1 -1
  61. package/lib/decorators/TypedRoute.d.ts +25 -10
  62. package/lib/decorators/TypedRoute.js +19 -10
  63. package/lib/decorators/TypedRoute.js.map +1 -1
  64. package/lib/decorators/WebSocketRoute.d.ts +18 -1
  65. package/lib/decorators/WebSocketRoute.js +18 -1
  66. package/lib/decorators/WebSocketRoute.js.map +1 -1
  67. package/lib/decorators/doNotThrowTransformError.d.ts +14 -0
  68. package/lib/decorators/doNotThrowTransformError.js +14 -0
  69. package/lib/decorators/doNotThrowTransformError.js.map +1 -1
  70. package/lib/decorators/internal/EncryptedConstant.js +13 -2
  71. package/lib/decorators/internal/EncryptedConstant.js.map +1 -1
  72. package/lib/decorators/internal/IRequestBodyValidation.d.ts +66 -0
  73. package/lib/decorators/internal/IRequestBodyValidation.js +3 -0
  74. package/lib/decorators/internal/IRequestBodyValidation.js.map +1 -0
  75. package/lib/decorators/internal/IWebSocketRouteReflect.d.ts +93 -1
  76. package/lib/decorators/internal/get_encryption_password.d.ts +1 -0
  77. package/lib/decorators/internal/get_encryption_password.js +14 -0
  78. package/lib/decorators/internal/get_encryption_password.js.map +1 -0
  79. package/lib/decorators/internal/get_path_and_querify.js +23 -9
  80. package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
  81. package/lib/decorators/internal/get_path_and_stringify.js +3 -7
  82. package/lib/decorators/internal/get_path_and_stringify.js.map +1 -1
  83. package/lib/decorators/internal/get_text_body.js +9 -3
  84. package/lib/decorators/internal/get_text_body.js.map +1 -1
  85. package/lib/decorators/internal/group_entries.d.ts +1 -0
  86. package/lib/decorators/internal/group_entries.js +19 -0
  87. package/lib/decorators/internal/group_entries.js.map +1 -0
  88. package/lib/decorators/internal/load_controller.d.ts +14 -0
  89. package/lib/decorators/internal/load_controller.js +14 -0
  90. package/lib/decorators/internal/load_controller.js.map +1 -1
  91. package/lib/decorators/internal/split_route_arguments.d.ts +1 -0
  92. package/lib/decorators/internal/split_route_arguments.js +22 -0
  93. package/lib/decorators/internal/split_route_arguments.js.map +1 -0
  94. package/lib/decorators/internal/validate_request_body.js +68 -25
  95. package/lib/decorators/internal/validate_request_body.js.map +1 -1
  96. package/lib/decorators/internal/validate_request_form_data.js +3 -1
  97. package/lib/decorators/internal/validate_request_form_data.js.map +1 -1
  98. package/lib/decorators/internal/validate_request_headers.js +2 -1
  99. package/lib/decorators/internal/validate_request_headers.js.map +1 -1
  100. package/lib/decorators/internal/validate_request_query.js +3 -1
  101. package/lib/decorators/internal/validate_request_query.js.map +1 -1
  102. package/lib/options/IRequestBodyValidator.d.ts +71 -0
  103. package/lib/options/IRequestFormDataProps.d.ts +80 -0
  104. package/lib/options/IRequestHeadersValidator.d.ts +67 -0
  105. package/lib/options/IRequestQueryValidator.d.ts +68 -0
  106. package/lib/options/IResponseBodyQuerifier.d.ts +125 -1
  107. package/lib/options/IResponseBodyStringifier.d.ts +107 -0
  108. package/lib/typings/Creator.d.ts +10 -0
  109. package/lib/utils/ArrayUtil.d.ts +21 -0
  110. package/lib/utils/ArrayUtil.js +22 -1
  111. package/lib/utils/ArrayUtil.js.map +1 -1
  112. package/lib/utils/ExceptionManager.d.ts +43 -4
  113. package/lib/utils/ExceptionManager.js +44 -6
  114. package/lib/utils/ExceptionManager.js.map +1 -1
  115. package/lib/utils/SourceFinder.d.ts +22 -0
  116. package/lib/utils/SourceFinder.js +60 -10
  117. package/lib/utils/SourceFinder.js.map +1 -1
  118. package/lib/utils/VersioningStrategy.d.ts +45 -2
  119. package/lib/utils/VersioningStrategy.js +40 -7
  120. package/lib/utils/VersioningStrategy.js.map +1 -1
  121. package/native/go.mod +1 -1
  122. package/native/go.sum +2 -2
  123. package/native/plugin/plan.go +68 -2
  124. package/native/transform/ast.go +23 -3
  125. package/native/transform/build.go +51 -57
  126. package/native/transform/buildoutput/output.go +147 -0
  127. package/native/transform/contributor.go +9 -0
  128. package/native/transform/core_querify.go +63 -9
  129. package/native/transform/core_transform.go +180 -98
  130. package/native/transform/core_websocket.go +111 -14
  131. package/native/transform/node_transform.go +17 -8
  132. package/native/transform/package.go +55 -0
  133. package/native/transform/printer.go +1 -3
  134. package/native/transform/run.go +18 -0
  135. package/native/transform/schemaprojection/project.go +213 -0
  136. package/native/transform/transform.go +29 -18
  137. package/native/transform/typia_fast.go +6 -0
  138. package/native/transform.cjs +49 -0
  139. package/package.json +13 -15
  140. package/src/adaptors/McpAdaptor.ts +215 -33
  141. package/src/adaptors/WebSocketAdaptor.ts +202 -52
  142. package/src/adaptors/internal/RoutePathMatcher.ts +91 -0
  143. package/src/adaptors/internal/external_context.ts +81 -0
  144. package/src/decorators/DynamicModule.ts +12 -0
  145. package/src/decorators/EncryptedBody.ts +39 -15
  146. package/src/decorators/EncryptedController.ts +12 -3
  147. package/src/decorators/EncryptedModule.ts +89 -18
  148. package/src/decorators/EncryptedRoute.ts +27 -16
  149. package/src/decorators/HumanRoute.ts +5 -0
  150. package/src/decorators/McpRoute.ts +31 -19
  151. package/src/decorators/PlainBody.ts +20 -10
  152. package/src/decorators/SwaggerCustomizer.ts +15 -0
  153. package/src/decorators/SwaggerExample.ts +51 -22
  154. package/src/decorators/TypedBody.ts +18 -9
  155. package/src/decorators/TypedException.ts +10 -0
  156. package/src/decorators/TypedFormData.ts +207 -45
  157. package/src/decorators/TypedHeaders.ts +5 -0
  158. package/src/decorators/TypedParam.ts +12 -3
  159. package/src/decorators/TypedQuery.ts +35 -7
  160. package/src/decorators/TypedRoute.ts +25 -10
  161. package/src/decorators/WebSocketRoute.ts +18 -1
  162. package/src/decorators/doNotThrowTransformError.ts +14 -0
  163. package/src/decorators/internal/EncryptedConstant.ts +14 -1
  164. package/src/decorators/internal/IMcpRouteReflect.ts +7 -1
  165. package/src/decorators/internal/IRequestBodyValidation.ts +65 -0
  166. package/src/decorators/internal/IWebSocketRouteReflect.ts +95 -1
  167. package/src/decorators/internal/get_encryption_password.ts +19 -0
  168. package/src/decorators/internal/get_path_and_querify.ts +31 -10
  169. package/src/decorators/internal/get_path_and_stringify.ts +3 -9
  170. package/src/decorators/internal/get_text_body.ts +8 -4
  171. package/src/decorators/internal/group_entries.ts +17 -0
  172. package/src/decorators/internal/load_controller.ts +14 -0
  173. package/src/decorators/internal/split_route_arguments.ts +20 -0
  174. package/src/decorators/internal/validate_request_body.ts +87 -34
  175. package/src/decorators/internal/validate_request_form_data.ts +3 -1
  176. package/src/decorators/internal/validate_request_headers.ts +2 -2
  177. package/src/decorators/internal/validate_request_query.ts +3 -2
  178. package/src/options/IRequestBodyValidator.ts +71 -0
  179. package/src/options/IRequestFormDataProps.ts +80 -0
  180. package/src/options/IRequestHeadersValidator.ts +67 -0
  181. package/src/options/IRequestQueryValidator.ts +68 -0
  182. package/src/options/IResponseBodyQuerifier.ts +126 -1
  183. package/src/options/IResponseBodyStringifier.ts +107 -0
  184. package/src/typings/Creator.ts +10 -0
  185. package/src/utils/ArrayUtil.ts +22 -3
  186. package/src/utils/ExceptionManager.ts +50 -6
  187. package/src/utils/SourceFinder.ts +62 -10
  188. package/src/utils/VersioningStrategy.ts +55 -11
  189. package/lib/transform.d.ts +0 -10
  190. package/lib/transform.js +0 -25
  191. package/lib/transform.js.map +0 -1
  192. package/src/transform.ts +0 -26
@@ -3,7 +3,6 @@ package transform
3
3
  import (
4
4
  "fmt"
5
5
  "os"
6
- "path/filepath"
7
6
  "regexp"
8
7
  "runtime/debug"
9
8
  "strings"
@@ -94,10 +93,64 @@ type nestiaCoreFileContext struct {
94
93
  coreImports map[string]string
95
94
  }
96
95
 
96
+ // The values the validate and stringify options accept.
97
+ var (
98
+ nestiaCoreValidateModes = []string{
99
+ "assert", "is", "validate", "equals",
100
+ "assertEquals", "validateEquals",
101
+ "assertClone", "validateClone",
102
+ "assertPrune", "validatePrune",
103
+ }
104
+ nestiaCoreStringifyModes = []string{"stringify", "assert", "is", "validate", "validate.log"}
105
+ )
106
+
107
+ // nestiaCoreOptionErrors reports each validate or stringify option value that
108
+ // is no mode: a generator would otherwise take its default branch, and a typo
109
+ // such as "assertEqual" silently weakens validation.
110
+ func nestiaCoreOptionErrors(plan plugin.Plan) []string {
111
+ errors := []string{}
112
+ check := func(name string, value any, modes []string, nullable bool) {
113
+ if value == nil && nullable {
114
+ return
115
+ }
116
+ if text, ok := value.(string); ok {
117
+ for _, mode := range modes {
118
+ if mode == text {
119
+ return
120
+ }
121
+ }
122
+ }
123
+ accepted := "\"" + strings.Join(modes, "\", \"") + "\""
124
+ if nullable {
125
+ accepted += ", or null"
126
+ }
127
+ errors = append(errors, fmt.Sprintf("invalid %q option %s: it must be one of %s.", name, nestiaCoreOptionText(value), accepted))
128
+ }
129
+ for _, entry := range plan.Entries {
130
+ if entry.Kind() != "core" {
131
+ continue
132
+ }
133
+ if value, ok := entry.Config["validate"]; ok {
134
+ check("validate", value, nestiaCoreValidateModes, false)
135
+ }
136
+ if value, ok := entry.Config["stringify"]; ok {
137
+ check("stringify", value, nestiaCoreStringifyModes, true)
138
+ }
139
+ }
140
+ return errors
141
+ }
142
+
143
+ func nestiaCoreOptionText(value any) string {
144
+ if text, ok := value.(string); ok {
145
+ return fmt.Sprintf("%q", text)
146
+ }
147
+ return fmt.Sprintf("%v", value)
148
+ }
149
+
97
150
  func readNestiaCoreOptions(plan plugin.Plan) nestiaCoreOptions {
98
151
  options := nestiaCoreOptions{}
99
152
  for _, entry := range plan.Entries {
100
- if entry.Name != "@nestia/core" && !strings.Contains(entry.Transform, "@nestia/core") {
153
+ if entry.Kind() != "core" {
101
154
  continue
102
155
  }
103
156
  if value, ok := entry.Config["validate"].(string); ok {
@@ -168,12 +221,37 @@ func nestiaCoreDecoratorCall(prog *driver.Program, decorator *shimast.Node) (*sh
168
221
  }
169
222
  context := newNestiaCoreFileContext(shimast.GetSourceFileOfNode(decorator))
170
223
  canonical := nestiaCoreCanonicalSegments(context, segments)
171
- if nestiaCoreDecoratorReference(prog, context, decorator, segments, canonical) == false {
224
+ if IsNestiaCoreCall(prog, call.AsNode()) == false {
172
225
  return nil, nil, false
173
226
  }
174
227
  return call, canonical, true
175
228
  }
176
229
 
230
+ // NestiaCoreCanonicalDecoratorSegments names a decorator call's callee by the
231
+ // @nestia/core export its import binds, so `import { TypedException as TE }`
232
+ // reads `TE<T>()` as `TypedException`. It returns nil for a decorator that is
233
+ // no call.
234
+ //
235
+ // This mapping names the imported syntax. IsNestiaCoreCall separately verifies
236
+ // that the resolved declaration belongs to core, including re-export facades.
237
+ //
238
+ // @evidence contracts/common.md#principled-implementation The decorator's callee is split into identifier segments, and the leading identifier is replaced by the `@nestia/core` export that its import binds, so an aliased import reads as the canonical name; a decorator that is not a call reads as nil.
239
+ // @evidence contracts/common.md#clear-and-simple-design A thin exported wrapper that builds the file's import context and delegates the mapping to a private function.
240
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The names come from the import declarations of the file, and no alias or file name is special-cased.
241
+ // @evidence contracts/common.md#meaningful-documentation The comment gives the aliasing example and the nil result.
242
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation The import mapping reads TypeScript AST module specifiers and names rather than native paths.
243
+ // @evidence contracts/performance.md#efficient-algorithms The helper scans file statements and named imports to build its mapping on each call, then walks the callee segments. Repeated decorator calls in one file rebuild that mapping.
244
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work This operation does not coordinate equivalent requests; its result is derived from the supplied value or the current command and compiler program.
245
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The file context map and returned segment slice are call-local; no program is retained globally by this helper.
246
+ func NestiaCoreCanonicalDecoratorSegments(decorator *shimast.Node) []string {
247
+ _, segments, ok := nestiaCoreRawDecoratorCall(decorator)
248
+ if !ok {
249
+ return nil
250
+ }
251
+ context := newNestiaCoreFileContext(shimast.GetSourceFileOfNode(decorator))
252
+ return nestiaCoreCanonicalSegments(context, segments)
253
+ }
254
+
177
255
  func newNestiaCoreFileContext(file *shimast.SourceFile) nestiaCoreFileContext {
178
256
  context := nestiaCoreFileContext{
179
257
  file: file,
@@ -241,32 +319,24 @@ func nestiaCoreCanonicalSegments(context nestiaCoreFileContext, segments []strin
241
319
  return canonical
242
320
  }
243
321
 
244
- func nestiaCoreDecoratorReference(
245
- prog *driver.Program,
246
- context nestiaCoreFileContext,
247
- decorator *shimast.Node,
248
- segments []string,
249
- canonical []string,
250
- ) bool {
251
- if len(segments) == 0 {
252
- return false
253
- }
254
- if _, ok := context.coreImports[segments[0]]; ok {
255
- return true
256
- }
257
- if nestiaCorePotentialDecoratorSegments(canonical) == false {
322
+ // IsNestiaCoreCall reports whether the call resolves to a declaration in `@nestia/core`.
323
+ //
324
+ // The resolved signature's declaration belongs to core only when its nearest
325
+ // package manifest names @nestia/core, including relocated or re-exported core
326
+ // declarations and excluding foreign nested packages.
327
+ //
328
+ // @evidence contracts/common.md#principled-implementation The resolved signature identifies the actual declaration source, and SourceFilePackageName establishes its nearest owner; unresolved calls or non-core ownership return false without relying on lexical aliases or directory spelling.
329
+ // @evidence contracts/common.md#clear-and-simple-design Signature/source nil guards precede one shared package-ownership operation and an exact name comparison.
330
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Foreign workspace lookalikes and nested packages cannot inherit core identity from their path, while a relocated or transparently re-exported core declaration keeps its actual manifest owner.
331
+ // @evidence contracts/common.md#meaningful-documentation The comment defines resolved declaration ownership and describes relocation, re-export and nested-package behavior.
332
+ // @evidence contracts/portability.md#os-neutral-implementation Declaration package ownership delegates to SourceFilePackageName, which walks native ancestors through filepath and the loaded program filesystem rather than source-path substrings.
333
+ // @evidence contracts/performance.md#efficient-algorithms Resolved signature lookup is followed by one nearest-manifest ancestor walk, proportional to directory depth and manifest bytes.
334
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work This operation does not coordinate equivalent requests; its result is derived from the supplied value or the current command and compiler program.
335
+ // @evidence contracts/performance.md#bound-retention-and-release-resources No separate cache is retained by this predicate; the compiler program owns resolved signatures and filesystem observations.
336
+ func IsNestiaCoreCall(prog *driver.Program, node *shimast.Node) bool {
337
+ if prog == nil || prog.Checker == nil || node == nil {
258
338
  return false
259
339
  }
260
- return IsNestiaCoreCall(prog, decorator.AsDecorator().Expression)
261
- }
262
-
263
- func nestiaCorePotentialDecoratorSegments(segments []string) bool {
264
- return nestiaCoreParameterKind(segments) != "" ||
265
- nestiaCoreMethodKind(segments) != "" ||
266
- (len(segments) != 0 && segments[len(segments)-1] == "WebSocketRoute")
267
- }
268
-
269
- func IsNestiaCoreCall(prog *driver.Program, node *shimast.Node) bool {
270
340
  signature := prog.Checker.GetResolvedSignature(node)
271
341
  if signature == nil || signature.Declaration() == nil {
272
342
  return false
@@ -275,11 +345,7 @@ func IsNestiaCoreCall(prog *driver.Program, node *shimast.Node) bool {
275
345
  if source == nil {
276
346
  return false
277
347
  }
278
- location := filepath.ToSlash(source.FileName())
279
- return strings.Contains(location, "@nestia/core/lib/") ||
280
- strings.Contains(location, "packages/core/lib/") ||
281
- strings.Contains(location, "@nestia/core/src/decorators/") ||
282
- strings.Contains(location, "packages/core/src/decorators/")
348
+ return SourceFilePackageName(prog, source) == "@nestia/core"
283
349
  }
284
350
  func nestiaCoreParameterKind(segments []string) string {
285
351
  suffixes := map[string]string{
@@ -394,6 +460,12 @@ func nestiaCoreParameterArgumentNodes(
394
460
  return output, true, nil
395
461
  }
396
462
 
463
+ // nestiaCoreGenerateTypedBody preserves the selected helper's validation,
464
+ // equality, clone or prune operation within the public three-tag validator ABI.
465
+ // The shared runtime resolver retains assert returns and validation.data for
466
+ // decorated arguments, so clone helpers supply copies while is retains the
467
+ // input. Prune helpers mutate and return that same input. Explicit success tags
468
+ // distinguish valid Error, null or undefined data from validation failures.
397
469
  func nestiaCoreGenerateTypedBody(
398
470
  prog *driver.Program,
399
471
  importer *nativecontext.ImportProgrammer, ec *shimprinter.EmitContext,
@@ -455,13 +527,12 @@ func nestiaCoreGenerateTypedBody(
455
527
  }
456
528
  }
457
529
 
458
- // nestiaCoreGenerateTypedHeaders intentionally collapses the 10-mode validate
459
- // option down to {assert, is, validate}. Header values are strings keyed by
460
- // name; deep-clone and prune semantics that @TypedBody honors (assertClone,
461
- // assertPrune, validateClone, validatePrune, etc.) have no meaningful effect
462
- // on a flat string→string map. Pass-through to the base programmer is the
463
- // intended behavior, not a fallthrough — matches v6 parity. See also
464
- // nestiaCoreGenerateTypedQuery and nestiaCoreGenerateTypedFormDataBody.
530
+ // nestiaCoreGenerateTypedHeaders maps all ten validate options onto the HTTP
531
+ // header decoder's three base validator families: assert, is and validate.
532
+ // Equality, clone and prune options select their corresponding base family;
533
+ // this operation generates decoded header values rather than a plain body helper.
534
+ // Body helper return semantics and the decorator's returned body are distinct:
535
+ // see nestiaCoreGenerateTypedBody for clone results and in-place pruning.
465
536
  func nestiaCoreGenerateTypedHeaders(prog *driver.Program, importer *nativecontext.ImportProgrammer, ec *shimprinter.EmitContext, options nestiaCoreOptions, modulo *shimast.Node, typ *shimchecker.Type) *shimast.Node {
466
537
  context := nestiaCoreTypiaContext(prog, importer, ec, false, false, false)
467
538
  name := nestiaCoreTypeName(prog, typ)
@@ -538,7 +609,13 @@ func nestiaCoreFormDataFiles(prog *driver.Program, typ *shimchecker.Type) []nest
538
609
  Type: typ,
539
610
  })
540
611
  if result.Success == false {
541
- panic(fmt.Errorf("failed to analyze form-data metadata: %d error(s)", len(result.Errors)))
612
+ panic(nativecontext.TransformerError_from(struct {
613
+ Code string
614
+ Errors []nativecontext.TransformerError_MetadataFactory_IError
615
+ }{
616
+ Code: "nestia.core.TypedFormDataBody",
617
+ Errors: nestiaCoreMetadataErrors(result.Errors),
618
+ }))
542
619
  }
543
620
  files := []nestiaCoreFormDataFile{}
544
621
  if result.Data == nil || len(result.Data.Objects) == 0 || result.Data.Objects[0] == nil || result.Data.Objects[0].Type == nil {
@@ -678,6 +755,8 @@ func nestiaCoreGenerateTypedQueryRoute(prog *driver.Program, importer *nativecon
678
755
  return nestiaCoreValidatorObject("type", "validate", nestiaCoreHttpValidateQuerifyProgrammer(prog, importer, ec, modulo, typ), ec)
679
756
  case "stringify":
680
757
  return nestiaCoreValidatorObject("type", "stringify", nestiaCoreHttpQuerifyProgrammer(prog, ec, typ), ec)
758
+ case "validate.log":
759
+ return nestiaCoreValidatorObjectWithKey("type", "validate.log", "validate", nestiaCoreHttpValidateQuerifyProgrammer(prog, importer, ec, modulo, typ), ec)
681
760
  default:
682
761
  return nestiaCoreValidatorObject("type", "assert", nestiaCoreHttpAssertQuerifyProgrammer(prog, importer, ec, modulo, typ), ec)
683
762
  }
@@ -1010,6 +1089,18 @@ func safeNestiaCoreGenerateNode(generator func() (*shimast.Node, error)) (node *
1010
1089
 
1011
1090
  var nestiaCoreSingleParameterArrowPattern = regexp.MustCompile(`(^|[\s(=,:?])([A-Za-z_$][A-Za-z0-9_$]*) =>`)
1012
1091
 
1092
+ // NestiaCoreMethodReturnType returns the type of a route method's response body.
1093
+ //
1094
+ // The declared return type is used when it is `Promise<T>` or an rxjs `Observable<T>`, and `T` is returned, so an asynchronous method is typed by what it resolves to. A missing signature returns nil.
1095
+ //
1096
+ // @evidence contracts/common.md#principled-implementation An explicit annotation is unwrapped only when its resolved symbol declares the TypeScript library Promise or an rxjs-owned Observable. Otherwise the checker's resolved return type uses the same provenance rule, preserving user wrappers and allowing aliases to the library type. Global Promise augmentations retain the library declaration in their merged symbol.
1097
+ // @evidence contracts/common.md#clear-and-simple-design One function with two unwrapping paths, sharing the private wrapper predicates.
1098
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The wrappers are the two types NestJS handlers return; no method or controller name is special-cased.
1099
+ // @evidence contracts/common.md#meaningful-documentation The comment states the unwrapped wrappers and the nil result.
1100
+ // @evidence contracts/portability.md#os-neutral-implementation Promise provenance comes from compiler library identity; Observable package ownership uses SourceFilePackageName and the program filesystem with native filepath ancestors.
1101
+ // @evidence contracts/performance.md#efficient-algorithms The function inspects one annotation/signature and its wrapper declarations; Observable ownership may perform one native ancestor walk per candidate declaration.
1102
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work This operation does not coordinate equivalent requests; its result is derived from the supplied value or the current command and compiler program.
1103
+ // @evidence contracts/performance.md#bound-retention-and-release-resources Returned types are owned by the caller program; this helper retains no type or program cache.
1013
1104
  func NestiaCoreMethodReturnType(prog *driver.Program, node *shimast.Node) *shimchecker.Type {
1014
1105
  if typ := nestiaCoreExplicitAsyncReturnType(prog, node); typ != nil {
1015
1106
  return typ
@@ -1024,7 +1115,7 @@ func NestiaCoreMethodReturnType(prog *driver.Program, node *shimast.Node) *shimc
1024
1115
  }
1025
1116
  symbol := typ.Symbol()
1026
1117
  if symbol != nil &&
1027
- nestiaCoreIsAsyncReturnWrapperSymbol(symbol.Name, symbol.Declarations) {
1118
+ nestiaCoreIsAsyncReturnWrapperSymbol(prog, symbol.Name, symbol.Declarations) {
1028
1119
  args := prog.Checker.GetTypeArguments(typ)
1029
1120
  if len(args) == 1 {
1030
1121
  return args[0]
@@ -1045,92 +1136,72 @@ func nestiaCoreExplicitAsyncReturnType(prog *driver.Program, node *shimast.Node)
1045
1136
  if ref == nil || ref.TypeArguments == nil || len(ref.TypeArguments.Nodes) != 1 {
1046
1137
  return nil
1047
1138
  }
1048
- if nestiaCoreIsAsyncReturnWrapperReference(prog, ref.TypeName) == false {
1139
+ if NestiaCoreIsAsyncReturnWrapperReference(prog, ref.TypeName) == false {
1049
1140
  return nil
1050
1141
  }
1051
1142
  return prog.Checker.GetTypeFromTypeNode(ref.TypeArguments.Nodes[0])
1052
1143
  }
1053
1144
 
1054
- func nestiaCoreIsAsyncReturnWrapperReference(
1145
+ // NestiaCoreIsAsyncReturnWrapperReference reports whether a type reference names
1146
+ // a wrapper a route method's return type is unwrapped from: `Promise`, or the
1147
+ // rxjs `Observable` however it is imported, aliased or re-exported.
1148
+ //
1149
+ // Promise must have a resolved declaration in the program's TypeScript library.
1150
+ // Observable must have a resolved declaration owned by rxjs. A same-spelled
1151
+ // user wrapper remains an ordinary response type.
1152
+ //
1153
+ // @evidence contracts/common.md#principled-implementation The checker resolves the reference and import aliases to their declarations. Promise requires a declaration in the compiler-identified library; Observable requires a nearest manifest naming rxjs. Namespace user types and missing library declarations cannot become asynchronous wrappers merely by spelling.
1154
+ // @evidence contracts/common.md#clear-and-simple-design One function that resolves the symbol and delegates the name and ownership test to the shared symbol predicate, so the syntactic annotation path and the checker path of the return type use one rule; the SDK reads the same function rather than keeping a second copy.
1155
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts No source spelling, fixture identity, filename fragment or folder name substitutes for declaration provenance. The TypeScript program identifies its actual library files and the program filesystem supplies nearest package ownership.
1156
+ // @evidence contracts/common.md#meaningful-documentation The comment identifies both supported wrappers and their declaration provenance requirements, including preservation of user lookalikes.
1157
+ // @evidence contracts/portability.md#os-neutral-implementation Library identity uses the compiler; rxjs ownership uses program filesystem reads and filepath ancestor traversal through SourceFilePackageName.
1158
+ // @evidence contracts/performance.md#efficient-algorithms Symbol/alias resolution is followed by a declaration scan and at most one ancestor walk per Observable declaration.
1159
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work This operation does not coordinate equivalent requests; its result is derived from the supplied value or the current command and compiler program.
1160
+ // @evidence contracts/performance.md#bound-retention-and-release-resources No result cache or handle is owned here; returned truth is independent of any retained storage.
1161
+ func NestiaCoreIsAsyncReturnWrapperReference(
1055
1162
  prog *driver.Program,
1056
1163
  node *shimast.Node,
1057
1164
  ) bool {
1058
- name := nestiaCoreTypeNodeText(node)
1059
- if name == "Promise" {
1060
- return true
1165
+ if node == nil {
1166
+ return false
1061
1167
  }
1062
- if name != "Observable" || prog == nil || prog.Checker == nil {
1168
+ if prog == nil || prog.Checker == nil {
1063
1169
  return false
1064
1170
  }
1065
1171
  symbol := prog.Checker.GetSymbolAtLocation(node)
1066
- return nestiaCoreIsRxjsObservableImport(node) ||
1067
- (symbol != nil && nestiaCoreIsRxjsDeclarations(symbol.Declarations))
1172
+ if symbol != nil && symbol.Flags&shimast.SymbolFlagsAlias != 0 {
1173
+ if aliased := shimchecker.Checker_getAliasedSymbol(prog.Checker, symbol); aliased != nil {
1174
+ symbol = aliased
1175
+ }
1176
+ }
1177
+ return symbol != nil && nestiaCoreIsAsyncReturnWrapperSymbol(prog, symbol.Name, symbol.Declarations)
1068
1178
  }
1069
1179
 
1070
1180
  func nestiaCoreIsAsyncReturnWrapperSymbol(
1181
+ prog *driver.Program,
1071
1182
  name string,
1072
1183
  declarations []*shimast.Node,
1073
1184
  ) bool {
1074
- if name == "Promise" {
1075
- return true
1185
+ if name == "Promise" && prog != nil && prog.TSProgram != nil {
1186
+ for _, decl := range declarations {
1187
+ if source := shimast.GetSourceFileOfNode(decl); source != nil && prog.TSProgram.IsLibFile(source) {
1188
+ return true
1189
+ }
1190
+ }
1076
1191
  }
1077
- return name == "Observable" && nestiaCoreIsRxjsDeclarations(declarations)
1192
+ return name == "Observable" && nestiaCoreIsRxjsDeclarations(prog, declarations)
1078
1193
  }
1079
1194
 
1080
- func nestiaCoreIsRxjsDeclarations(declarations []*shimast.Node) bool {
1195
+ func nestiaCoreIsRxjsDeclarations(prog *driver.Program, declarations []*shimast.Node) bool {
1081
1196
  for _, decl := range declarations {
1082
1197
  sourceFile := shimast.GetSourceFileOfNode(decl)
1083
- if sourceFile == nil {
1084
- continue
1085
- }
1086
- file := filepath.ToSlash(sourceFile.FileName())
1087
- if strings.Contains(file, "/node_modules/rxjs/") {
1198
+ if sourceFile != nil && SourceFilePackageName(prog, sourceFile) == "rxjs" {
1088
1199
  return true
1089
1200
  }
1090
1201
  }
1091
1202
  return false
1092
1203
  }
1093
1204
 
1094
- func nestiaCoreIsRxjsObservableImport(node *shimast.Node) bool {
1095
- source, ok := SourceFileText(shimast.GetSourceFileOfNode(node))
1096
- return ok && nestiaCoreHasNamedImport(source, "rxjs", "Observable", "Observable")
1097
- }
1098
-
1099
- func nestiaCoreHasNamedImport(
1100
- source string,
1101
- module string,
1102
- imported string,
1103
- local string,
1104
- ) bool {
1105
- for _, match := range nestiaCoreImportFromPattern.FindAllStringSubmatch(source, -1) {
1106
- if len(match) < 3 || match[2] != module {
1107
- continue
1108
- }
1109
- open := strings.Index(match[1], "{")
1110
- close := strings.LastIndex(match[1], "}")
1111
- if open < 0 || close <= open {
1112
- continue
1113
- }
1114
- for _, part := range strings.Split(match[1][open+1:close], ",") {
1115
- fields := strings.Fields(strings.TrimPrefix(strings.TrimSpace(part), "type "))
1116
- if len(fields) == 1 && fields[0] == local && imported == local {
1117
- return true
1118
- }
1119
- if len(fields) == 3 &&
1120
- fields[0] == imported &&
1121
- fields[1] == "as" &&
1122
- fields[2] == local {
1123
- return true
1124
- }
1125
- }
1126
- }
1127
- return false
1128
- }
1129
-
1130
- var nestiaCoreImportFromPattern = regexp.MustCompile(
1131
- `(?s)import\s+(?:type\s+)?(.+?)\s+from\s+["']([^"']+)["']`,
1132
- )
1133
-
1134
1205
  func nestiaCoreTypeNodeText(node *shimast.Node) string {
1135
1206
  if node == nil {
1136
1207
  return ""
@@ -1241,6 +1312,17 @@ func matchClosingParen(text string, pos int) (int, bool) {
1241
1312
  }
1242
1313
  return 0, false
1243
1314
  }
1315
+
1316
+ // NestiaCoreExpressionSegments returns the identifier segments of an identifier or a property-access chain, such as `core.TypedRoute.Get` as `core`, `TypedRoute`, `Get`, and nil for any other expression.
1317
+ //
1318
+ // @evidence contracts/common.md#principled-implementation The recursion follows the left side of each property access to its root identifier and appends the property names, and any other node kind makes the whole chain not a name, so a call, an element access, or a computed name is never read as a path.
1319
+ // @evidence contracts/common.md#clear-and-simple-design One recursive function over two node kinds.
1320
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts It reads the syntax only.
1321
+ // @evidence contracts/common.md#meaningful-documentation The comment gives the example and the nil result.
1322
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation Identifier segments describe source syntax rather than native file paths.
1323
+ // @evidence contracts/performance.md#efficient-algorithms The recursion visits each property access once; append amortizes slice growth, with space proportional to chain depth.
1324
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work This operation does not coordinate equivalent requests; its result is derived from the supplied value or the current command and compiler program.
1325
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The returned slice is caller-owned and recursion state ends at return.
1244
1326
  func NestiaCoreExpressionSegments(node *shimast.Node) []string {
1245
1327
  if node == nil {
1246
1328
  return nil
@@ -2,9 +2,9 @@ package transform
2
2
 
3
3
  import (
4
4
  "fmt"
5
- "strings"
6
5
 
7
6
  shimast "github.com/microsoft/typescript-go/shim/ast"
7
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
8
8
  shimscanner "github.com/microsoft/typescript-go/shim/scanner"
9
9
  "github.com/samchon/ttsc/packages/ttsc/driver"
10
10
  )
@@ -42,14 +42,14 @@ func validateNestiaCoreWebSocketRoute(
42
42
  switch category {
43
43
  case "Acceptor":
44
44
  accepted = true
45
- if strings.HasPrefix(nestiaCoreWebSocketParameterTypeName(param), "WebSocketAcceptor") == false {
45
+ if _, target := NestiaCoreWebSocketTypeReference(prog, nestiaCoreWebSocketParameterTypeNode(param)); target != "WebSocketAcceptor" {
46
46
  diagnostics = append(diagnostics, nestiaCoreWebSocketDiagnostic(context.file, param, "WebSocketRoute", fmt.Sprintf(
47
47
  "parameter %q must have WebSocketAcceptor<Header, Provider, Listener> type.",
48
48
  name,
49
49
  )))
50
50
  }
51
51
  case "Driver":
52
- if strings.HasPrefix(nestiaCoreWebSocketParameterTypeName(param), "Driver") == false {
52
+ if _, target := NestiaCoreWebSocketTypeReference(prog, nestiaCoreWebSocketParameterTypeNode(param)); target != "Driver" {
53
53
  diagnostics = append(diagnostics, nestiaCoreWebSocketDiagnostic(context.file, param, "WebSocketRoute", fmt.Sprintf(
54
54
  "parameter %q must have Driver<Listener> type.",
55
55
  name,
@@ -66,6 +66,21 @@ func validateNestiaCoreWebSocketRoute(
66
66
  return diagnostics
67
67
  }
68
68
 
69
+ // NestiaCoreWebSocketParameterCategory names the WebSocketRoute parameter
70
+ // decorator on a parameter, such as "Acceptor" or "Driver", or "" for none.
71
+ //
72
+ // @evidence contracts/common.md#principled-implementation A parameter that has exactly one decorator, a call whose callee ends in `WebSocketRoute.<Category>`, reports that category name, and any other parameter reports the empty string.
73
+ // @evidence contracts/common.md#clear-and-simple-design An exported wrapper over the private classifier so that the SDK contributor and the core transform share one definition.
74
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The rule is structural, and no parameter name is special-cased.
75
+ // @evidence contracts/common.md#meaningful-documentation The comment gives examples of the categories and the empty result.
76
+ // @evidence contracts/portability.md#os-neutral-implementation Resolved decorator ownership delegates native package paths to SourceFilePackageName; returned category names are protocol metadata.
77
+ // @evidence contracts/performance.md#efficient-algorithms Exactly-one-decorator checks are constant; classification also builds a file import context and resolves declaration ownership.
78
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work This operation does not coordinate equivalent requests; its result is derived from the supplied value or the current command and compiler program.
79
+ // @evidence contracts/performance.md#bound-retention-and-release-resources Context maps are local and returned category text refers to compiler data; no cross-program storage is created.
80
+ func NestiaCoreWebSocketParameterCategory(prog *driver.Program, param *shimast.Node) string {
81
+ return nestiaCoreWebSocketParameterCategory(prog, param)
82
+ }
83
+
69
84
  func nestiaCoreWebSocketParameterCategory(prog *driver.Program, param *shimast.Node) string {
70
85
  if param == nil || len(param.Decorators()) != 1 {
71
86
  return ""
@@ -78,19 +93,99 @@ func nestiaCoreWebSocketParameterCategory(prog *driver.Program, param *shimast.N
78
93
  return segments[len(segments)-1]
79
94
  }
80
95
 
81
- func nestiaCoreWebSocketParameterTypeName(param *shimast.Node) string {
82
- if param == nil || param.AsParameterDeclaration() == nil || param.AsParameterDeclaration().Type == nil {
83
- return ""
96
+ func nestiaCoreWebSocketParameterTypeNode(param *shimast.Node) *shimast.Node {
97
+ if param == nil || param.AsParameterDeclaration() == nil {
98
+ return nil
99
+ }
100
+ return param.AsParameterDeclaration().Type
101
+ }
102
+
103
+ // NestiaCoreWebSocketTypeReference follows a parameter's type annotation
104
+ // through renamed imports, import types, and type aliases to the tgrid type
105
+ // reference it spells, such as `WebSocketAcceptor<Header, Provider, Listener>`
106
+ // or `Driver<Listener>`, so the transform and the SDK accept every spelling of
107
+ // the same type and still reject another type of the same name. It returns the
108
+ // type references it passed, each a type reference or an import type such as
109
+ // `import("tgrid").Driver<Listener>`, from the annotation to the tgrid
110
+ // reference, each after the first written in the type alias the one before it
111
+ // names, and the name tgrid declares the type by; or nil and "" when the
112
+ // annotation leads to no tgrid type.
113
+ //
114
+ // @evidence contracts/common.md#principled-implementation The annotation is followed through parentheses, import aliases, and type aliases, with each step recorded, until a declaration of the `tgrid` package is reached, and the walk is capped at 32 steps so a cyclic alias cannot loop; the chain and the name tgrid declares are returned, so every spelling of the same type is accepted and a type of the same name from another package is rejected.
115
+ // @evidence contracts/common.md#clear-and-simple-design One loop with a depth guard, built on private helpers for the reference name and the tgrid declaration test.
116
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The type is identified by its resolved declaration, not by its spelling, which is the point of the function.
117
+ // @evidence contracts/common.md#meaningful-documentation The comment states the spellings followed, what the chain contains, and the nil result.
118
+ // @evidence contracts/portability.md#os-neutral-implementation tgrid provenance delegates nearest-manifest reads to SourceFilePackageName and the program filesystem instead of deriving package identity from directory spelling.
119
+ // @evidence contracts/performance.md#efficient-algorithms The alias walk is capped at 32 steps; each step resolves a symbol and scans declarations, including package ancestor walks for ownership.
120
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work This operation does not coordinate equivalent requests; its result is derived from the supplied value or the current command and compiler program.
121
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The chain holds at most 32 node references and is returned to the caller; no global alias graph is retained.
122
+ func NestiaCoreWebSocketTypeReference(prog *driver.Program, node *shimast.Node) ([]*shimast.Node, string) {
123
+ chain := []*shimast.Node{}
124
+ for depth := 0; node != nil && depth < 32; depth++ {
125
+ if node.Kind == shimast.KindParenthesizedType {
126
+ node = node.AsParenthesizedTypeNode().Type
127
+ continue
128
+ }
129
+ name := nestiaCoreTypeReferenceName(node)
130
+ if name == nil || prog == nil || prog.Checker == nil {
131
+ return nil, ""
132
+ }
133
+ chain = append(chain, node)
134
+ symbol := prog.Checker.GetSymbolAtLocation(name)
135
+ if symbol != nil && symbol.Flags&shimast.SymbolFlagsAlias != 0 {
136
+ if aliased := shimchecker.Checker_getAliasedSymbol(prog.Checker, symbol); aliased != nil {
137
+ symbol = aliased
138
+ }
139
+ }
140
+ if symbol == nil {
141
+ return nil, ""
142
+ }
143
+ if nestiaCoreIsTgridDeclarations(prog, symbol.Declarations) {
144
+ return chain, symbol.Name
145
+ }
146
+ if symbol.Flags&shimast.SymbolFlagsTypeAlias == 0 {
147
+ return nil, ""
148
+ }
149
+ var alias *shimast.Node
150
+ for _, declaration := range symbol.Declarations {
151
+ if declaration != nil && declaration.Kind == shimast.KindTypeAliasDeclaration {
152
+ alias = declaration
153
+ break
154
+ }
155
+ }
156
+ if alias == nil {
157
+ return nil, ""
158
+ }
159
+ node = alias.AsTypeAliasDeclaration().Type
84
160
  }
85
- text := NodeText(param.AsParameterDeclaration().Type)
86
- text = strings.TrimSpace(text)
87
- if index := strings.Index(text, "<"); index >= 0 {
88
- text = text[:index]
161
+ return nil, ""
162
+ }
163
+
164
+ // nestiaCoreTypeReferenceName is the entity a type names: the `A.B` of
165
+ // `A.B<T>`, or the qualifier of an import type such as `import("m").A.B<T>`.
166
+ // It is nil for any other type, including `typeof import("m")`, the type of a
167
+ // module's value.
168
+ func nestiaCoreTypeReferenceName(node *shimast.Node) *shimast.Node {
169
+ switch node.Kind {
170
+ case shimast.KindTypeReference:
171
+ return node.AsTypeReferenceNode().TypeName
172
+ case shimast.KindImportType:
173
+ if node.AsImportTypeNode().IsTypeOf {
174
+ return nil
175
+ }
176
+ return node.AsImportTypeNode().Qualifier
89
177
  }
90
- if index := strings.LastIndex(text, "."); index >= 0 {
91
- text = text[index+1:]
178
+ return nil
179
+ }
180
+
181
+ func nestiaCoreIsTgridDeclarations(prog *driver.Program, declarations []*shimast.Node) bool {
182
+ for _, declaration := range declarations {
183
+ source := shimast.GetSourceFileOfNode(declaration)
184
+ if source != nil && SourceFilePackageName(prog, source) == "tgrid" {
185
+ return true
186
+ }
92
187
  }
93
- return text
188
+ return false
94
189
  }
95
190
 
96
191
  func nestiaCoreWebSocketDiagnostic(file *shimast.SourceFile, node *shimast.Node, kind string, message string) Diagnostic {
@@ -99,7 +194,9 @@ func nestiaCoreWebSocketDiagnostic(file *shimast.SourceFile, node *shimast.Node,
99
194
  if file != nil {
100
195
  filePath = file.FileName()
101
196
  if node != nil {
102
- if pos := node.Pos(); pos >= 0 {
197
+ // the first token's position: Pos() starts at the leading trivia,
198
+ // the previous line's end and any JSDoc
199
+ if pos := shimscanner.GetTokenPosOfNode(node, file, false); pos >= 0 {
103
200
  l, c := shimscanner.GetECMALineAndByteOffsetOfPosition(file, pos)
104
201
  line, column = l+1, c+1
105
202
  }
@@ -30,6 +30,7 @@ func nestiaCoreNodeTransform(
30
30
  }
31
31
  }
32
32
  options := readNestiaCoreOptions(plan)
33
+ optionErrors := nestiaCoreOptionErrors(plan)
33
34
  strictReported := false
34
35
  return func(ec *shimprinter.EmitContext, sf *shimast.SourceFile) *shimast.SourceFile {
35
36
  if sf == nil || sf.IsDeclarationFile {
@@ -39,6 +40,11 @@ func nestiaCoreNodeTransform(
39
40
  strictReported = true
40
41
  addDiagnostic(nestiaCoreGlobalDiagnostic("@nestia/core", "strict mode is required."))
41
42
  }
43
+ // reported once, with the first file transformed
44
+ for _, message := range optionErrors {
45
+ addDiagnostic(nestiaCoreGlobalDiagnostic("@nestia/core", message))
46
+ }
47
+ optionErrors = nil
42
48
  importer := nativecontext.NewImportProgrammer(nativecontext.ImportProgrammer_IOptions{
43
49
  InternalPrefix: "typia_transform_",
44
50
  Runtime: "typia",
@@ -153,7 +159,7 @@ func nestiaCoreCollectParameterReplacements(
153
159
  }
154
160
  canonical := nestiaCoreCanonicalSegments(context, segments)
155
161
  kind := nestiaCoreParameterKind(canonical)
156
- if kind == "" || nestiaCoreDecoratorReference(state.prog, context, decorator, segments, canonical) == false {
162
+ if kind == "" || IsNestiaCoreCall(state.prog, nestiaCoreOriginalNode(state.ec, call.AsNode())) == false {
157
163
  continue
158
164
  }
159
165
  candidates = append(candidates, candidate{call: call, segments: segments, kind: kind})
@@ -204,7 +210,7 @@ func nestiaCoreCollectMethodReplacements(
204
210
  continue
205
211
  }
206
212
  canonical := nestiaCoreCanonicalSegments(context, segments)
207
- if nestiaCoreDecoratorReference(state.prog, context, decorator, segments, canonical) == false {
213
+ if IsNestiaCoreCall(state.prog, nestiaCoreOriginalNode(state.ec, call.AsNode())) == false {
208
214
  continue
209
215
  }
210
216
  // @WebSocketRoute carries no injected validator (it is not a method kind),
@@ -224,7 +230,7 @@ func nestiaCoreCollectMethodReplacements(
224
230
  if nestiaCoreMcpRouteAlreadyTransformed(call) {
225
231
  continue
226
232
  }
227
- } else if nestiaCoreShouldSkipMethodDecorator(state.prog, call) {
233
+ } else if nestiaCoreShouldSkipMethodDecorator(state.prog, nestiaCoreOriginalNode(state.ec, call.AsNode()).AsCallExpression()) {
228
234
  continue
229
235
  }
230
236
  candidates = append(candidates, candidate{call: call, segments: segments, kind: kind})
@@ -258,13 +264,16 @@ func nestiaCoreCollectMethodReplacements(
258
264
  }
259
265
 
260
266
  // nestiaCoreOriginalNode returns the parse-tree node a (possibly synthetic) node
261
- // was rebuilt from, so checker queries that need binder symbols or types resolve
262
- // against the original. core runs after typia in the shared emit pass: when typia
267
+ // was rebuilt from, so checker queries that need binder symbols, signatures or
268
+ // types and source-text decisions resolve against the original. The rewritten
269
+ // call remains the emission target so typia's generated arguments are retained.
270
+ // core runs after typia in the shared emit pass: when typia
263
271
  // lowers a `typia.random<T>()` call that sits in a decorator argument or method
264
272
  // body, it rebuilds the enclosing method (and its parameters) into synthetic
265
- // nodes that carry no binder symbol. GetSignatureFromDeclaration /
266
- // GetTypeAtLocation then nil-panic on them, and GetTextOfNode on a synthetic
267
- // callee has no source span. ec.MostOriginal walks the original-link chain back
273
+ // nodes that carry no binder symbol or checker parent context.
274
+ // GetResolvedSignature / GetSignatureFromDeclaration / GetTypeAtLocation can
275
+ // nil-panic on them, and source lookup on a synthetic call or callee has no
276
+ // original source span. ec.MostOriginal walks the original-link chain back
268
277
  // to the parse node; it is a no-op for nodes core sees before typia touched them.
269
278
  func nestiaCoreOriginalNode(ec *shimprinter.EmitContext, node *shimast.Node) *shimast.Node {
270
279
  if ec == nil || node == nil {