@nestia/core 14.0.1 → 14.0.3

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 (156) hide show
  1. package/lib/adaptors/McpAdaptor.d.ts +24 -3
  2. package/lib/adaptors/McpAdaptor.js +72 -34
  3. package/lib/adaptors/McpAdaptor.js.map +1 -1
  4. package/lib/adaptors/WebSocketAdaptor.d.ts +37 -0
  5. package/lib/adaptors/WebSocketAdaptor.js +60 -16
  6. package/lib/adaptors/WebSocketAdaptor.js.map +1 -1
  7. package/lib/adaptors/internal/external_context.js +18 -5
  8. package/lib/adaptors/internal/external_context.js.map +1 -1
  9. package/lib/decorators/DynamicModule.d.ts +13 -1
  10. package/lib/decorators/DynamicModule.js +12 -0
  11. package/lib/decorators/DynamicModule.js.map +1 -1
  12. package/lib/decorators/EncryptedBody.d.ts +14 -6
  13. package/lib/decorators/EncryptedBody.js +37 -14
  14. package/lib/decorators/EncryptedBody.js.map +1 -1
  15. package/lib/decorators/EncryptedController.d.ts +6 -1
  16. package/lib/decorators/EncryptedController.js +6 -1
  17. package/lib/decorators/EncryptedController.js.map +1 -1
  18. package/lib/decorators/EncryptedModule.d.ts +18 -3
  19. package/lib/decorators/EncryptedModule.js +18 -3
  20. package/lib/decorators/EncryptedModule.js.map +1 -1
  21. package/lib/decorators/EncryptedRoute.d.ts +20 -11
  22. package/lib/decorators/EncryptedRoute.js +20 -11
  23. package/lib/decorators/EncryptedRoute.js.map +1 -1
  24. package/lib/decorators/HumanRoute.d.ts +5 -0
  25. package/lib/decorators/HumanRoute.js +5 -0
  26. package/lib/decorators/HumanRoute.js.map +1 -1
  27. package/lib/decorators/McpRoute.d.ts +31 -19
  28. package/lib/decorators/McpRoute.js +12 -1
  29. package/lib/decorators/McpRoute.js.map +1 -1
  30. package/lib/decorators/PlainBody.d.ts +7 -1
  31. package/lib/decorators/PlainBody.js +16 -8
  32. package/lib/decorators/PlainBody.js.map +1 -1
  33. package/lib/decorators/SwaggerCustomizer.d.ts +15 -0
  34. package/lib/decorators/SwaggerCustomizer.js +5 -0
  35. package/lib/decorators/SwaggerCustomizer.js.map +1 -1
  36. package/lib/decorators/SwaggerExample.d.ts +51 -22
  37. package/lib/decorators/SwaggerExample.js +35 -22
  38. package/lib/decorators/SwaggerExample.js.map +1 -1
  39. package/lib/decorators/TypedBody.d.ts +9 -2
  40. package/lib/decorators/TypedBody.js +19 -9
  41. package/lib/decorators/TypedBody.js.map +1 -1
  42. package/lib/decorators/TypedException.d.ts +10 -0
  43. package/lib/decorators/TypedFormData.d.ts +59 -1
  44. package/lib/decorators/TypedFormData.js +5 -0
  45. package/lib/decorators/TypedFormData.js.map +1 -1
  46. package/lib/decorators/TypedHeaders.d.ts +5 -0
  47. package/lib/decorators/TypedHeaders.js +5 -0
  48. package/lib/decorators/TypedHeaders.js.map +1 -1
  49. package/lib/decorators/TypedParam.d.ts +9 -0
  50. package/lib/decorators/TypedParam.js +9 -0
  51. package/lib/decorators/TypedParam.js.map +1 -1
  52. package/lib/decorators/TypedQuery.d.ts +16 -0
  53. package/lib/decorators/TypedQuery.js +16 -0
  54. package/lib/decorators/TypedQuery.js.map +1 -1
  55. package/lib/decorators/TypedRoute.d.ts +25 -10
  56. package/lib/decorators/TypedRoute.js +19 -10
  57. package/lib/decorators/TypedRoute.js.map +1 -1
  58. package/lib/decorators/WebSocketRoute.d.ts +18 -1
  59. package/lib/decorators/WebSocketRoute.js +18 -1
  60. package/lib/decorators/WebSocketRoute.js.map +1 -1
  61. package/lib/decorators/doNotThrowTransformError.d.ts +14 -0
  62. package/lib/decorators/doNotThrowTransformError.js +14 -0
  63. package/lib/decorators/doNotThrowTransformError.js.map +1 -1
  64. package/lib/decorators/internal/IRequestBodyValidation.d.ts +66 -0
  65. package/lib/decorators/internal/IRequestBodyValidation.js +3 -0
  66. package/lib/decorators/internal/IRequestBodyValidation.js.map +1 -0
  67. package/lib/decorators/internal/IWebSocketRouteReflect.d.ts +93 -1
  68. package/lib/decorators/internal/get_path_and_querify.js +3 -7
  69. package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
  70. package/lib/decorators/internal/get_path_and_stringify.js +3 -7
  71. package/lib/decorators/internal/get_path_and_stringify.js.map +1 -1
  72. package/lib/decorators/internal/load_controller.d.ts +14 -0
  73. package/lib/decorators/internal/load_controller.js +14 -0
  74. package/lib/decorators/internal/load_controller.js.map +1 -1
  75. package/lib/decorators/internal/split_route_arguments.d.ts +1 -0
  76. package/lib/decorators/internal/split_route_arguments.js +22 -0
  77. package/lib/decorators/internal/split_route_arguments.js.map +1 -0
  78. package/lib/decorators/internal/validate_request_body.js +68 -25
  79. package/lib/decorators/internal/validate_request_body.js.map +1 -1
  80. package/lib/options/IRequestBodyValidator.d.ts +71 -0
  81. package/lib/options/IRequestFormDataProps.d.ts +80 -0
  82. package/lib/options/IRequestHeadersValidator.d.ts +67 -0
  83. package/lib/options/IRequestQueryValidator.d.ts +68 -0
  84. package/lib/options/IResponseBodyQuerifier.d.ts +120 -0
  85. package/lib/options/IResponseBodyStringifier.d.ts +107 -0
  86. package/lib/typings/Creator.d.ts +10 -0
  87. package/lib/utils/ArrayUtil.d.ts +21 -0
  88. package/lib/utils/ArrayUtil.js +22 -1
  89. package/lib/utils/ArrayUtil.js.map +1 -1
  90. package/lib/utils/ExceptionManager.d.ts +40 -0
  91. package/lib/utils/ExceptionManager.js +34 -0
  92. package/lib/utils/ExceptionManager.js.map +1 -1
  93. package/lib/utils/SourceFinder.d.ts +22 -0
  94. package/lib/utils/SourceFinder.js +34 -7
  95. package/lib/utils/SourceFinder.js.map +1 -1
  96. package/lib/utils/VersioningStrategy.d.ts +37 -2
  97. package/lib/utils/VersioningStrategy.js +30 -9
  98. package/lib/utils/VersioningStrategy.js.map +1 -1
  99. package/native/go.mod +1 -1
  100. package/native/go.sum +2 -2
  101. package/native/plugin/plan.go +68 -2
  102. package/native/transform/ast.go +19 -1
  103. package/native/transform/build.go +51 -57
  104. package/native/transform/buildoutput/output.go +147 -0
  105. package/native/transform/contributor.go +9 -0
  106. package/native/transform/core_querify.go +37 -3
  107. package/native/transform/core_transform.go +105 -98
  108. package/native/transform/core_websocket.go +21 -5
  109. package/native/transform/node_transform.go +11 -8
  110. package/native/transform/package.go +55 -0
  111. package/native/transform/printer.go +1 -3
  112. package/native/transform/run.go +18 -0
  113. package/native/transform/schemaprojection/project.go +213 -0
  114. package/native/transform/transform.go +21 -0
  115. package/package.json +9 -7
  116. package/src/adaptors/McpAdaptor.ts +86 -26
  117. package/src/adaptors/WebSocketAdaptor.ts +70 -15
  118. package/src/adaptors/internal/external_context.ts +43 -6
  119. package/src/decorators/DynamicModule.ts +13 -1
  120. package/src/decorators/EncryptedBody.ts +37 -13
  121. package/src/decorators/EncryptedController.ts +6 -1
  122. package/src/decorators/EncryptedModule.ts +18 -3
  123. package/src/decorators/EncryptedRoute.ts +21 -12
  124. package/src/decorators/HumanRoute.ts +5 -0
  125. package/src/decorators/McpRoute.ts +31 -19
  126. package/src/decorators/PlainBody.ts +20 -10
  127. package/src/decorators/SwaggerCustomizer.ts +15 -0
  128. package/src/decorators/SwaggerExample.ts +51 -22
  129. package/src/decorators/TypedBody.ts +17 -8
  130. package/src/decorators/TypedException.ts +10 -0
  131. package/src/decorators/TypedFormData.ts +60 -2
  132. package/src/decorators/TypedHeaders.ts +5 -0
  133. package/src/decorators/TypedParam.ts +9 -0
  134. package/src/decorators/TypedQuery.ts +17 -1
  135. package/src/decorators/TypedRoute.ts +26 -11
  136. package/src/decorators/WebSocketRoute.ts +18 -1
  137. package/src/decorators/doNotThrowTransformError.ts +14 -0
  138. package/src/decorators/internal/IMcpRouteReflect.ts +7 -1
  139. package/src/decorators/internal/IRequestBodyValidation.ts +65 -0
  140. package/src/decorators/internal/IWebSocketRouteReflect.ts +95 -1
  141. package/src/decorators/internal/get_path_and_querify.ts +3 -9
  142. package/src/decorators/internal/get_path_and_stringify.ts +3 -9
  143. package/src/decorators/internal/load_controller.ts +14 -0
  144. package/src/decorators/internal/split_route_arguments.ts +20 -0
  145. package/src/decorators/internal/validate_request_body.ts +87 -34
  146. package/src/options/IRequestBodyValidator.ts +71 -0
  147. package/src/options/IRequestFormDataProps.ts +80 -0
  148. package/src/options/IRequestHeadersValidator.ts +67 -0
  149. package/src/options/IRequestQueryValidator.ts +68 -0
  150. package/src/options/IResponseBodyQuerifier.ts +120 -0
  151. package/src/options/IResponseBodyStringifier.ts +107 -0
  152. package/src/typings/Creator.ts +10 -0
  153. package/src/utils/ArrayUtil.ts +22 -3
  154. package/src/utils/ExceptionManager.ts +40 -0
  155. package/src/utils/SourceFinder.ts +38 -7
  156. package/src/utils/VersioningStrategy.ts +47 -14
@@ -0,0 +1,147 @@
1
+ // Package buildoutput owns a build's buffered artifact publication and reporting.
2
+ package buildoutput
3
+
4
+ import (
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "os"
9
+ "path/filepath"
10
+ "sync"
11
+ )
12
+
13
+ // Options fixes the command's reporting and publication policy for one build.
14
+ //
15
+ // @evidence contracts/common.md#principled-implementation Emit reflects the original compiler configuration, while Quiet and Manifest retain the requested output protocol; the three plugin flags and project paths describe the actual build summary.
16
+ // @evidence contracts/common.md#clear-and-simple-design One value separates immutable command policy from buffered artifacts and the filesystem writer.
17
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts These values come from resolved command inputs and compiler options, without fixture or caller-name decisions.
18
+ // @evidence contracts/common.md#meaningful-documentation The declaration identifies the lifetime and meaning of publication policy.
19
+ // @evidence contracts/performance.md#efficient-algorithms This fixed policy record performs no traversal or computation; session construction copies its scalar fields and string references once.
20
+ // @evidence contracts/performance.md#reuse-equivalent-work One immutable resolved policy is shared by callbacks of the same build; a new build receives its own policy instead of a cached compiler configuration.
21
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The record holds flags and path strings only, with no handles or independently growing collection; its lifetime belongs to one output session.
22
+ // @evidence contracts/portability.md#os-neutral-implementation Paths remain caller-provided opaque strings until the manifest filesystem owner interprets them through native filepath operations; the policy introduces no separator or platform-name assumptions.
23
+ type Options struct {
24
+ // TSConfig is the requested project path shown in the initial summary.
25
+ TSConfig string
26
+
27
+ // CWD is the resolved working directory shown in the initial summary.
28
+ CWD string
29
+
30
+ // Manifest is the explicitly requested output-list path; empty disables it.
31
+ Manifest string
32
+
33
+ // Core, SDK and Typia are the resolved composing-plugin flags.
34
+ Core, SDK, Typia bool
35
+
36
+ // Emit is the original compiler policy, before any private analysis traversal.
37
+ Emit bool
38
+
39
+ // Quiet suppresses both initial and successful-publication summaries.
40
+ Quiet bool
41
+ }
42
+
43
+ // Publisher buffers compiler artifacts until the caller reports successful completion.
44
+ // Its write callback is the build's artifact filesystem boundary; manifest output
45
+ // uses native filesystem operations. Finish follows completed producer callbacks
46
+ // and terminates the session, releasing all buffered strings even on failure.
47
+ //
48
+ // @evidence contracts/common.md#principled-implementation Compiler callbacks append under a mutex only for an emitting configuration. The completion verdict gates all artifact and requested-manifest writes; artifacts precede the manifest, and any filesystem failure stops publication and the success summary.
49
+ // @evidence contracts/common.md#clear-and-simple-design One command-scoped session owns the immutable policy, concurrent buffer and output dependencies; no compiler, transform or project loader is retained.
50
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The caller supplies the existing production artifact writer, not a replaced foreign method. Completion comes from actual compiler and transform diagnostics; this layer does not invent their verdict.
51
+ // @evidence contracts/common.md#meaningful-documentation The declaration states writer ownership, producer completion, session termination and failure release behavior.
52
+ // @evidence contracts/performance.md#efficient-algorithms Buffering and publication each visit every emitted artifact once; manifest encoding costs the sum of path lengths.
53
+ // @evidence contracts/performance.md#reuse-equivalent-work Immutable build policy is shared by all concurrent compiler callbacks of this session only, and a manifest uses the same ordered artifact paths.
54
+ // @evidence contracts/performance.md#bound-retention-and-release-resources Only one build's output strings are retained until Finish, which detaches the entire buffer before any filesystem call. No package state or file handle survives completion.
55
+ // @evidence contracts/portability.md#os-neutral-implementation Artifact paths are delegated unchanged to the production writer, and the manifest uses native filesystem operations in Finish. Session state and mutex semantics do not depend on a host operating system.
56
+ type Publisher struct {
57
+ options Options
58
+ stdout io.Writer
59
+ writeFile func(string, string) error
60
+ mutex sync.Mutex
61
+ pending []artifact
62
+ }
63
+
64
+ type artifact struct{ fileName, text string }
65
+
66
+ // New begins one output session and writes its initial summary when requested.
67
+ //
68
+ // @evidence contracts/common.md#principled-implementation The initial summary reports the original Emit policy before transforms run, so a verbose analysis-only build reports emit=false even when its private transformer traversal emits into a discarded buffer.
69
+ // @evidence contracts/common.md#clear-and-simple-design Construction stores policy and actual output boundaries, then writes one conditional summary.
70
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The actual command supplies paths, plugin flags and writers; no expected diagnostic or artifact is fabricated.
71
+ // @evidence contracts/common.md#meaningful-documentation The comment distinguishes the initial summary from successful publication reporting.
72
+ // @evidence contracts/performance.md#efficient-algorithms Construction allocates one fixed session and formats at most one summary, costing the reported path lengths rather than loading or scanning a compiler project.
73
+ // @evidence contracts/performance.md#reuse-equivalent-work The same options and writer references serve all callbacks in this one build, with no cross-build cache or compiler identity reuse.
74
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The command owns the returned session; construction creates no file or process handles, and the command's terminal Finish releases buffered outputs.
75
+ // @evidence contracts/portability.md#os-neutral-implementation The initial report prints caller-provided paths as text and does not join, normalize or infer an operating system from them.
76
+ func New(options Options, stdout io.Writer, writeFile func(string, string) error) *Publisher {
77
+ p := &Publisher{options: options, stdout: stdout, writeFile: writeFile}
78
+ if !options.Quiet {
79
+ fmt.Fprintf(stdout, "// ttsc-nestia build: tsconfig=%s cwd=%s core=%v sdk=%v typia=%v emit=%v\n", options.TSConfig, options.CWD, options.Core, options.SDK, options.Typia, options.Emit)
80
+ }
81
+ return p
82
+ }
83
+
84
+ // Write buffers a completed artifact without publishing bytes; analysis-only output is discarded.
85
+ //
86
+ // @evidence contracts/common.md#principled-implementation Original non-emitting policy discards callback output. An emitting session serializes each filename/text pair under its mutex so parallel declaration callbacks cannot lose or misassociate artifacts.
87
+ // @evidence contracts/common.md#clear-and-simple-design One policy check and one protected append implement the compiler callback boundary.
88
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts Every buffered filename and byte string comes from the actual compiler callback; this operation invokes no filesystem or compiler surrogate.
89
+ // @evidence contracts/common.md#meaningful-documentation The comment states deferred publication and the analysis-only discard rule.
90
+ // @evidence contracts/performance.md#efficient-algorithms Each callback performs one policy check and amortized constant-time append under a mutex; it retains the compiler strings without reparsing or copying their contents.
91
+ // @evidence contracts/performance.md#reuse-equivalent-work Callbacks share only the immutable session policy and its protected buffer. No artifact is inferred from or substituted for another callback's bytes.
92
+ // @evidence contracts/performance.md#bound-retention-and-release-resources Each filename/text pair remains scoped to this build until Finish detaches the complete buffer, including rejected and analysis-only completion paths.
93
+ // @evidence contracts/portability.md#os-neutral-implementation The callback preserves compiler-provided filename text and performs no path interpretation, filesystem call or platform-dependent concurrency branch.
94
+ func (p *Publisher) Write(fileName, text string) {
95
+ if !p.options.Emit {
96
+ return
97
+ }
98
+ p.mutex.Lock()
99
+ defer p.mutex.Unlock()
100
+ p.pending = append(p.pending, artifact{fileName: fileName, text: text})
101
+ }
102
+
103
+ // Finish commits successful emitting output in artifact/manifest/summary order.
104
+ // A rejected or analysis-only build writes no artifacts or manifest. The buffer
105
+ // is released on every path; a filesystem failure can leave preceding artifacts
106
+ // and is returned to the command for its nonzero diagnostic protocol.
107
+ //
108
+ // @evidence contracts/common.md#principled-implementation The actual completion verdict and Emit policy jointly gate publication. Each callback artifact is written unchanged; the explicitly requested manifest contains exactly those paths, and the success summary appears only after all writes succeed. Earlier writes are not claimed to be transactional on an IO failure.
109
+ // @evidence contracts/common.md#clear-and-simple-design One terminal operation detaches the session buffer, applies two guards and executes ordered artifact, manifest and summary stages with immediate error returns.
110
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The operation does not invoke a compiler or infer success from output presence. Its callback is the caller-owned filesystem boundary, and manifest creation uses standard native IO.
111
+ // @evidence contracts/common.md#meaningful-documentation The comment states artifact ordering, no-output states, buffer release and the partial-write limit on IO failure.
112
+ // @evidence contracts/portability.md#os-neutral-implementation The requested manifest uses filepath.Dir and native os.MkdirAll/os.WriteFile. Artifact paths are passed unchanged to the production writer; no platform-name predicate or path separator assumption is introduced.
113
+ // @evidence contracts/performance.md#efficient-algorithms Completion traverses each retained artifact once and encodes the ordered path list once, costing artifact count plus emitted bytes and manifest path lengths; it starts no compiler or product host.
114
+ // @evidence contracts/performance.md#reuse-equivalent-work The manifest derives from the exact successfully written ordered artifacts of this session, and no output list or success verdict is cached across builds.
115
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The session buffer is detached before either guard or any filesystem write; local output references are released at return even on the first IO error, and native write operations close their own handles.
116
+ func (p *Publisher) Finish(succeeded bool) error {
117
+ p.mutex.Lock()
118
+ pending := p.pending
119
+ p.pending = nil
120
+ p.mutex.Unlock()
121
+ if !succeeded || !p.options.Emit {
122
+ return nil
123
+ }
124
+ emitted := make([]string, 0, len(pending))
125
+ for _, output := range pending {
126
+ if err := p.writeFile(output.fileName, output.text); err != nil {
127
+ return fmt.Errorf("emit write failed: %w", err)
128
+ }
129
+ emitted = append(emitted, output.fileName)
130
+ }
131
+ if p.options.Manifest != "" {
132
+ data, err := json.Marshal(emitted)
133
+ if err != nil {
134
+ return fmt.Errorf("manifest marshal failed: %w", err)
135
+ }
136
+ if err := os.MkdirAll(filepath.Dir(p.options.Manifest), 0o755); err != nil {
137
+ return fmt.Errorf("manifest mkdir failed: %w", err)
138
+ }
139
+ if err := os.WriteFile(p.options.Manifest, data, 0o644); err != nil {
140
+ return fmt.Errorf("manifest write failed: %w", err)
141
+ }
142
+ }
143
+ if !p.options.Quiet {
144
+ fmt.Fprintf(p.stdout, "// ttsc-nestia build: emitted=%d files\n", len(emitted))
145
+ }
146
+ return nil
147
+ }
@@ -14,6 +14,15 @@ var emitTransformCollectors []emitTransformCollector
14
14
  // inside the shared EmitContext alongside the typia and core node transforms, so
15
15
  // a linked contributor (e.g. @nestia/sdk) participates in the same emit pass
16
16
  // rather than patching its output afterwards.
17
+ //
18
+ // @evidence contracts/common.md#principled-implementation A statically linked contributor registers a collector once at package initialization, and the host runs all collectors in registration order inside the shared emit pass, so contributors take part in the same emit rather than rewriting its output afterwards; a nil collector is ignored.
19
+ // @evidence contracts/common.md#clear-and-simple-design One append to a package-level list, read by one private function.
20
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts It is the documented registration point for linked contributors, not a hook into foreign code; the list is written only during initialization, which is single-threaded.
21
+ // @evidence contracts/common.md#meaningful-documentation The comment states who calls it, when the collectors run, and why.
22
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation A collector is an in-process function registered by linked Go packages, with no native path or subprocess representation.
23
+ // @evidence contracts/performance.md#efficient-algorithms A non-nil registration appends once with amortized constant work; executing all registered collectors later is linear in their count plus their own work.
24
+ // @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.
25
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The process owns the collector slice for its lifetime. Registration is intended for package initialization; repeated later registrations would grow it without a bound.
17
26
  func RegisterEmitTransformCollector(collector emitTransformCollector) {
18
27
  if collector != nil {
19
28
  emitTransformCollectors = append(emitTransformCollectors, collector)
@@ -66,9 +66,7 @@ func nestiaCoreHttpQuerifyProgrammer(prog *driver.Program, ec *shimprinter.EmitC
66
66
  if ok == false || property == nil || property.Value == nil {
67
67
  continue
68
68
  }
69
- statements = append(statements, nestiaCoreFactory.NewExpressionStatement(
70
- nestiaCoreHttpQuerifyDecode(key, property.Value, ec),
71
- ))
69
+ statements = append(statements, nestiaCoreHttpQuerifyDecode(key, property.Value, ec))
72
70
  }
73
71
  }
74
72
  statements = append(statements, nestiaCoreFactory.NewReturnStatement(nestiaCoreFactory.NewIdentifier("output")))
@@ -194,7 +192,43 @@ func nestiaCoreQueryWrapperArrow(statements []*shimast.Node) *shimast.Node {
194
192
  )
195
193
  }
196
194
 
195
+ // nestiaCoreHttpQuerifyDecode writes one property into the URLSearchParams.
196
+ //
197
+ // An omitted optional property and a null array both write nothing, which the
198
+ // request decoder reads back as undefined and null; a nullable scalar still
199
+ // writes its "null" marker.
197
200
  func nestiaCoreHttpQuerifyDecode(key string, value *schemametadata.MetadataSchema, ec *shimprinter.EmitContext) *shimast.Node {
201
+ statement := nestiaCoreFactory.NewExpressionStatement(nestiaCoreHttpQuerifyWrite(key, value, ec))
202
+ absent := []shimast.Kind{}
203
+ if value.IsRequired() == false {
204
+ absent = append(absent, shimast.KindUndefinedKeyword)
205
+ }
206
+ if len(value.Arrays) != 0 && value.Nullable {
207
+ absent = append(absent, shimast.KindNullKeyword)
208
+ }
209
+ for i := len(absent) - 1; i >= 0; i-- {
210
+ var marker *shimast.Node
211
+ if absent[i] == shimast.KindUndefinedKeyword {
212
+ marker = nestiaCoreFactory.NewIdentifier("undefined")
213
+ } else {
214
+ marker = nestiaCoreFactory.NewKeywordExpression(absent[i])
215
+ }
216
+ statement = nestiaCoreFactory.NewIfStatement(
217
+ nestiaCoreFactory.NewBinaryExpression(
218
+ nil,
219
+ nativefactories.IdentifierFactory.Access(ec, nestiaCoreFactory.NewIdentifier("input"), key),
220
+ nil,
221
+ nestiaCoreFactory.NewToken(shimast.KindExclamationEqualsEqualsToken),
222
+ marker,
223
+ ),
224
+ statement,
225
+ nil,
226
+ )
227
+ }
228
+ return statement
229
+ }
230
+
231
+ func nestiaCoreHttpQuerifyWrite(key string, value *schemametadata.MetadataSchema, ec *shimprinter.EmitContext) *shimast.Node {
198
232
  if len(value.Arrays) != 0 {
199
233
  return nestiaCoreFactory.NewCallExpression(
200
234
  nativefactories.IdentifierFactory.Access(
@@ -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"
@@ -128,7 +127,7 @@ func nestiaCoreOptionErrors(plan plugin.Plan) []string {
128
127
  errors = append(errors, fmt.Sprintf("invalid %q option %s: it must be one of %s.", name, nestiaCoreOptionText(value), accepted))
129
128
  }
130
129
  for _, entry := range plan.Entries {
131
- if entry.Name != "@nestia/core" && !strings.Contains(entry.Transform, "@nestia/core") {
130
+ if entry.Kind() != "core" {
132
131
  continue
133
132
  }
134
133
  if value, ok := entry.Config["validate"]; ok {
@@ -151,7 +150,7 @@ func nestiaCoreOptionText(value any) string {
151
150
  func readNestiaCoreOptions(plan plugin.Plan) nestiaCoreOptions {
152
151
  options := nestiaCoreOptions{}
153
152
  for _, entry := range plan.Entries {
154
- if entry.Name != "@nestia/core" && !strings.Contains(entry.Transform, "@nestia/core") {
153
+ if entry.Kind() != "core" {
155
154
  continue
156
155
  }
157
156
  if value, ok := entry.Config["validate"].(string); ok {
@@ -222,7 +221,7 @@ func nestiaCoreDecoratorCall(prog *driver.Program, decorator *shimast.Node) (*sh
222
221
  }
223
222
  context := newNestiaCoreFileContext(shimast.GetSourceFileOfNode(decorator))
224
223
  canonical := nestiaCoreCanonicalSegments(context, segments)
225
- if nestiaCoreDecoratorReference(prog, context, decorator, segments, canonical) == false {
224
+ if IsNestiaCoreCall(prog, call.AsNode()) == false {
226
225
  return nil, nil, false
227
226
  }
228
227
  return call, canonical, true
@@ -232,6 +231,18 @@ func nestiaCoreDecoratorCall(prog *driver.Program, decorator *shimast.Node) (*sh
232
231
  // @nestia/core export its import binds, so `import { TypedException as TE }`
233
232
  // reads `TE<T>()` as `TypedException`. It returns nil for a decorator that is
234
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.
235
246
  func NestiaCoreCanonicalDecoratorSegments(decorator *shimast.Node) []string {
236
247
  _, segments, ok := nestiaCoreRawDecoratorCall(decorator)
237
248
  if !ok {
@@ -308,32 +319,24 @@ func nestiaCoreCanonicalSegments(context nestiaCoreFileContext, segments []strin
308
319
  return canonical
309
320
  }
310
321
 
311
- func nestiaCoreDecoratorReference(
312
- prog *driver.Program,
313
- context nestiaCoreFileContext,
314
- decorator *shimast.Node,
315
- segments []string,
316
- canonical []string,
317
- ) bool {
318
- if len(segments) == 0 {
319
- return false
320
- }
321
- if _, ok := context.coreImports[segments[0]]; ok {
322
- return true
323
- }
324
- 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 {
325
338
  return false
326
339
  }
327
- return IsNestiaCoreCall(prog, decorator.AsDecorator().Expression)
328
- }
329
-
330
- func nestiaCorePotentialDecoratorSegments(segments []string) bool {
331
- return nestiaCoreParameterKind(segments) != "" ||
332
- nestiaCoreMethodKind(segments) != "" ||
333
- (len(segments) != 0 && segments[len(segments)-1] == "WebSocketRoute")
334
- }
335
-
336
- func IsNestiaCoreCall(prog *driver.Program, node *shimast.Node) bool {
337
340
  signature := prog.Checker.GetResolvedSignature(node)
338
341
  if signature == nil || signature.Declaration() == nil {
339
342
  return false
@@ -342,11 +345,7 @@ func IsNestiaCoreCall(prog *driver.Program, node *shimast.Node) bool {
342
345
  if source == nil {
343
346
  return false
344
347
  }
345
- location := filepath.ToSlash(source.FileName())
346
- return strings.Contains(location, "@nestia/core/lib/") ||
347
- strings.Contains(location, "packages/core/lib/") ||
348
- strings.Contains(location, "@nestia/core/src/decorators/") ||
349
- strings.Contains(location, "packages/core/src/decorators/")
348
+ return SourceFilePackageName(prog, source) == "@nestia/core"
350
349
  }
351
350
  func nestiaCoreParameterKind(segments []string) string {
352
351
  suffixes := map[string]string{
@@ -461,6 +460,12 @@ func nestiaCoreParameterArgumentNodes(
461
460
  return output, true, nil
462
461
  }
463
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.
464
469
  func nestiaCoreGenerateTypedBody(
465
470
  prog *driver.Program,
466
471
  importer *nativecontext.ImportProgrammer, ec *shimprinter.EmitContext,
@@ -522,13 +527,12 @@ func nestiaCoreGenerateTypedBody(
522
527
  }
523
528
  }
524
529
 
525
- // nestiaCoreGenerateTypedHeaders intentionally collapses the 10-mode validate
526
- // option down to {assert, is, validate}. Header values are strings keyed by
527
- // name; deep-clone and prune semantics that @TypedBody honors (assertClone,
528
- // assertPrune, validateClone, validatePrune, etc.) have no meaningful effect
529
- // on a flat string→string map. Pass-through to the base programmer is the
530
- // intended behavior, not a fallthrough — matches v6 parity. See also
531
- // 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.
532
536
  func nestiaCoreGenerateTypedHeaders(prog *driver.Program, importer *nativecontext.ImportProgrammer, ec *shimprinter.EmitContext, options nestiaCoreOptions, modulo *shimast.Node, typ *shimchecker.Type) *shimast.Node {
533
537
  context := nestiaCoreTypiaContext(prog, importer, ec, false, false, false)
534
538
  name := nestiaCoreTypeName(prog, typ)
@@ -1085,6 +1089,18 @@ func safeNestiaCoreGenerateNode(generator func() (*shimast.Node, error)) (node *
1085
1089
 
1086
1090
  var nestiaCoreSingleParameterArrowPattern = regexp.MustCompile(`(^|[\s(=,:?])([A-Za-z_$][A-Za-z0-9_$]*) =>`)
1087
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.
1088
1104
  func NestiaCoreMethodReturnType(prog *driver.Program, node *shimast.Node) *shimchecker.Type {
1089
1105
  if typ := nestiaCoreExplicitAsyncReturnType(prog, node); typ != nil {
1090
1106
  return typ
@@ -1099,7 +1115,7 @@ func NestiaCoreMethodReturnType(prog *driver.Program, node *shimast.Node) *shimc
1099
1115
  }
1100
1116
  symbol := typ.Symbol()
1101
1117
  if symbol != nil &&
1102
- nestiaCoreIsAsyncReturnWrapperSymbol(symbol.Name, symbol.Declarations) {
1118
+ nestiaCoreIsAsyncReturnWrapperSymbol(prog, symbol.Name, symbol.Declarations) {
1103
1119
  args := prog.Checker.GetTypeArguments(typ)
1104
1120
  if len(args) == 1 {
1105
1121
  return args[0]
@@ -1120,92 +1136,72 @@ func nestiaCoreExplicitAsyncReturnType(prog *driver.Program, node *shimast.Node)
1120
1136
  if ref == nil || ref.TypeArguments == nil || len(ref.TypeArguments.Nodes) != 1 {
1121
1137
  return nil
1122
1138
  }
1123
- if nestiaCoreIsAsyncReturnWrapperReference(prog, ref.TypeName) == false {
1139
+ if NestiaCoreIsAsyncReturnWrapperReference(prog, ref.TypeName) == false {
1124
1140
  return nil
1125
1141
  }
1126
1142
  return prog.Checker.GetTypeFromTypeNode(ref.TypeArguments.Nodes[0])
1127
1143
  }
1128
1144
 
1129
- 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(
1130
1162
  prog *driver.Program,
1131
1163
  node *shimast.Node,
1132
1164
  ) bool {
1133
- name := nestiaCoreTypeNodeText(node)
1134
- if name == "Promise" {
1135
- return true
1165
+ if node == nil {
1166
+ return false
1136
1167
  }
1137
- if name != "Observable" || prog == nil || prog.Checker == nil {
1168
+ if prog == nil || prog.Checker == nil {
1138
1169
  return false
1139
1170
  }
1140
1171
  symbol := prog.Checker.GetSymbolAtLocation(node)
1141
- return nestiaCoreIsRxjsObservableImport(node) ||
1142
- (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)
1143
1178
  }
1144
1179
 
1145
1180
  func nestiaCoreIsAsyncReturnWrapperSymbol(
1181
+ prog *driver.Program,
1146
1182
  name string,
1147
1183
  declarations []*shimast.Node,
1148
1184
  ) bool {
1149
- if name == "Promise" {
1150
- 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
+ }
1151
1191
  }
1152
- return name == "Observable" && nestiaCoreIsRxjsDeclarations(declarations)
1192
+ return name == "Observable" && nestiaCoreIsRxjsDeclarations(prog, declarations)
1153
1193
  }
1154
1194
 
1155
- func nestiaCoreIsRxjsDeclarations(declarations []*shimast.Node) bool {
1195
+ func nestiaCoreIsRxjsDeclarations(prog *driver.Program, declarations []*shimast.Node) bool {
1156
1196
  for _, decl := range declarations {
1157
1197
  sourceFile := shimast.GetSourceFileOfNode(decl)
1158
- if sourceFile == nil {
1159
- continue
1160
- }
1161
- file := filepath.ToSlash(sourceFile.FileName())
1162
- if strings.Contains(file, "/node_modules/rxjs/") {
1198
+ if sourceFile != nil && SourceFilePackageName(prog, sourceFile) == "rxjs" {
1163
1199
  return true
1164
1200
  }
1165
1201
  }
1166
1202
  return false
1167
1203
  }
1168
1204
 
1169
- func nestiaCoreIsRxjsObservableImport(node *shimast.Node) bool {
1170
- source, ok := SourceFileText(shimast.GetSourceFileOfNode(node))
1171
- return ok && nestiaCoreHasNamedImport(source, "rxjs", "Observable", "Observable")
1172
- }
1173
-
1174
- func nestiaCoreHasNamedImport(
1175
- source string,
1176
- module string,
1177
- imported string,
1178
- local string,
1179
- ) bool {
1180
- for _, match := range nestiaCoreImportFromPattern.FindAllStringSubmatch(source, -1) {
1181
- if len(match) < 3 || match[2] != module {
1182
- continue
1183
- }
1184
- open := strings.Index(match[1], "{")
1185
- close := strings.LastIndex(match[1], "}")
1186
- if open < 0 || close <= open {
1187
- continue
1188
- }
1189
- for _, part := range strings.Split(match[1][open+1:close], ",") {
1190
- fields := strings.Fields(strings.TrimPrefix(strings.TrimSpace(part), "type "))
1191
- if len(fields) == 1 && fields[0] == local && imported == local {
1192
- return true
1193
- }
1194
- if len(fields) == 3 &&
1195
- fields[0] == imported &&
1196
- fields[1] == "as" &&
1197
- fields[2] == local {
1198
- return true
1199
- }
1200
- }
1201
- }
1202
- return false
1203
- }
1204
-
1205
- var nestiaCoreImportFromPattern = regexp.MustCompile(
1206
- `(?s)import\s+(?:type\s+)?(.+?)\s+from\s+["']([^"']+)["']`,
1207
- )
1208
-
1209
1205
  func nestiaCoreTypeNodeText(node *shimast.Node) string {
1210
1206
  if node == nil {
1211
1207
  return ""
@@ -1316,6 +1312,17 @@ func matchClosingParen(text string, pos int) (int, bool) {
1316
1312
  }
1317
1313
  return 0, false
1318
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.
1319
1326
  func NestiaCoreExpressionSegments(node *shimast.Node) []string {
1320
1327
  if node == nil {
1321
1328
  return nil
@@ -2,8 +2,6 @@ package transform
2
2
 
3
3
  import (
4
4
  "fmt"
5
- "path/filepath"
6
- "strings"
7
5
 
8
6
  shimast "github.com/microsoft/typescript-go/shim/ast"
9
7
  shimchecker "github.com/microsoft/typescript-go/shim/checker"
@@ -70,6 +68,15 @@ func validateNestiaCoreWebSocketRoute(
70
68
 
71
69
  // NestiaCoreWebSocketParameterCategory names the WebSocketRoute parameter
72
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.
73
80
  func NestiaCoreWebSocketParameterCategory(prog *driver.Program, param *shimast.Node) string {
74
81
  return nestiaCoreWebSocketParameterCategory(prog, param)
75
82
  }
@@ -103,6 +110,15 @@ func nestiaCoreWebSocketParameterTypeNode(param *shimast.Node) *shimast.Node {
103
110
  // reference, each after the first written in the type alias the one before it
104
111
  // names, and the name tgrid declares the type by; or nil and "" when the
105
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.
106
122
  func NestiaCoreWebSocketTypeReference(prog *driver.Program, node *shimast.Node) ([]*shimast.Node, string) {
107
123
  chain := []*shimast.Node{}
108
124
  for depth := 0; node != nil && depth < 32; depth++ {
@@ -124,7 +140,7 @@ func NestiaCoreWebSocketTypeReference(prog *driver.Program, node *shimast.Node)
124
140
  if symbol == nil {
125
141
  return nil, ""
126
142
  }
127
- if nestiaCoreIsTgridDeclarations(symbol.Declarations) {
143
+ if nestiaCoreIsTgridDeclarations(prog, symbol.Declarations) {
128
144
  return chain, symbol.Name
129
145
  }
130
146
  if symbol.Flags&shimast.SymbolFlagsTypeAlias == 0 {
@@ -162,10 +178,10 @@ func nestiaCoreTypeReferenceName(node *shimast.Node) *shimast.Node {
162
178
  return nil
163
179
  }
164
180
 
165
- func nestiaCoreIsTgridDeclarations(declarations []*shimast.Node) bool {
181
+ func nestiaCoreIsTgridDeclarations(prog *driver.Program, declarations []*shimast.Node) bool {
166
182
  for _, declaration := range declarations {
167
183
  source := shimast.GetSourceFileOfNode(declaration)
168
- if source != nil && strings.Contains(filepath.ToSlash(source.FileName()), "/tgrid/") {
184
+ if source != nil && SourceFilePackageName(prog, source) == "tgrid" {
169
185
  return true
170
186
  }
171
187
  }