@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
@@ -14,6 +14,16 @@ const (
14
14
  TypiaTransform = "typia/lib/transform"
15
15
  )
16
16
 
17
+ // Entry is one plugin of the ordered list that ttsc hands to the host: its name, stage, the `transform` specifier of its configuration, and the whole configuration.
18
+ //
19
+ // @evidence contracts/common.md#principled-implementation The fields are the parts of the payload that classification and option reading need, with the transform specifier lifted out of the configuration map once so every later comparison reads a string.
20
+ // @evidence contracts/common.md#clear-and-simple-design A plain record with no behavior; the two methods answer questions about it.
21
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts It carries the payload as given, and no plugin name or option is special-cased here.
22
+ // @evidence contracts/common.md#meaningful-documentation The comment names the fields and where the value comes from.
23
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation Plugin specifiers are module identifiers and configuration is decoded data, not native file identity.
24
+ // @evidenceExclude contracts/performance.md#efficient-algorithms This record defines data rather than choosing a processing algorithm.
25
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work This record does not coordinate computations across consumers.
26
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources The parsed entry stores configuration but does not own its consumers lifetimes.
17
27
  type Entry struct {
18
28
  Name string
19
29
  Stage string
@@ -21,6 +31,16 @@ type Entry struct {
21
31
  Config map[string]any
22
32
  }
23
33
 
34
+ // Plan is the parsed plugin list: the entries in order and one flag for each plugin family present.
35
+ //
36
+ // @evidence contracts/common.md#principled-implementation The three flags are computed once by `ParsePlan` from the classification of every entry, so the callers ask a boolean instead of rescanning the list.
37
+ // @evidence contracts/common.md#clear-and-simple-design A record of three flags and the ordered entries, with one derived accessor.
38
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts It holds parsed data only, and the flags follow from the entries.
39
+ // @evidence contracts/common.md#meaningful-documentation The comment states what each part of the plan means.
40
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation The ordered plugin list and family flags contain no native paths or process handles.
41
+ // @evidenceExclude contracts/performance.md#efficient-algorithms This record defines data rather than choosing a processing algorithm.
42
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work This record does not coordinate computations across consumers.
43
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources The plan stores parsed entries; the command owns their lifetime.
24
44
  type Plan struct {
25
45
  Core bool
26
46
  SDK bool
@@ -28,12 +48,31 @@ type Plan struct {
28
48
  Entries []Entry
29
49
  }
30
50
 
51
+ // UsesNestia reports whether the plan contains a `@nestia/core` or a `@nestia/sdk` entry, that is, whether any nestia rewrite is wanted.
52
+ //
53
+ // @evidence contracts/common.md#principled-implementation The result is the disjunction of the two family flags, which is exactly the condition under which the nestia passes run.
54
+ // @evidence contracts/common.md#clear-and-simple-design A one-line accessor over two fields, so the condition has one definition.
55
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts It reads the flags and nothing else.
56
+ // @evidence contracts/common.md#meaningful-documentation The comment states the condition.
57
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation The family-flag predicate reads only booleans.
58
+ // @evidence contracts/performance.md#efficient-algorithms Reading two family flags takes constant work and allocates no collection.
59
+ // @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.
60
+ // @evidence contracts/performance.md#bound-retention-and-release-resources No state survives the boolean result.
31
61
  func (p Plan) UsesNestia() bool {
32
62
  return p.Core || p.SDK
33
63
  }
34
64
 
35
65
  // Kind reports which plugin family an entry belongs to: "core", "sdk", "typia",
36
66
  // or "" when it is none of them.
67
+ //
68
+ // @evidence contracts/common.md#principled-implementation The family is decided by the entry name or by the `transform` specifier compared with the known published specifiers, so an entry written under either spelling of the plugin is recognized, and any other entry is not a family member.
69
+ // @evidence contracts/common.md#clear-and-simple-design A one-line method over the private classifier, so one function owns the naming rules.
70
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The names are the published package specifiers, which are contract constants, and no consumer or path is special-cased.
71
+ // @evidence contracts/common.md#meaningful-documentation The comment lists the four possible results.
72
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation Classification compares published module specifiers, not native paths.
73
+ // @evidence contracts/performance.md#efficient-algorithms Classification trims and compares the name and transform strings, linear in their bytes.
74
+ // @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.
75
+ // @evidence contracts/performance.md#bound-retention-and-release-resources Only temporary trimmed strings are used; no registry entry is retained.
37
76
  func (e Entry) Kind() string {
38
77
  return classify(e)
39
78
  }
@@ -41,6 +80,15 @@ func (e Entry) Kind() string {
41
80
  // BoolConfig reads a boolean option from an entry's resolved plugin config. The
42
81
  // second result distinguishes an option explicitly set to false from one that is
43
82
  // absent, which the tri-state options depend on.
83
+ //
84
+ // @evidence contracts/common.md#principled-implementation A boolean read from the configuration returns its value and a presence flag, so an option explicitly set to false is distinct from an absent one, which the tri-state options need; a non-boolean value reads as absent.
85
+ // @evidence contracts/common.md#clear-and-simple-design One map lookup and one type assertion.
86
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts It reads only the configuration of the entry it is called on.
87
+ // @evidence contracts/common.md#meaningful-documentation The comment states why the second result exists.
88
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation The lookup reads an in-memory JSON option map.
89
+ // @evidence contracts/performance.md#efficient-algorithms One map lookup and type assertion use expected constant work.
90
+ // @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.
91
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The lookup retains no state or handle.
44
92
  func (e Entry) BoolConfig(key string) (bool, bool) {
45
93
  if e.Config == nil {
46
94
  return false, false
@@ -49,13 +97,25 @@ func (e Entry) BoolConfig(key string) (bool, bool) {
49
97
  return value, ok
50
98
  }
51
99
 
100
+ // ParsePlan parses the ordered plugin payload that ttsc passes as `--plugins-json`.
101
+ //
102
+ // An empty payload is an empty plan. A payload that is not a JSON list of plugin objects is an error naming the flag. JSON null is not an empty list, and a null list entry is not a plugin object. An entry without a stage is a `transform` stage entry.
103
+ //
104
+ // @evidence contracts/common.md#principled-implementation The standard JSON decoder checks the list and object field types. A nil decoded list identifies JSON null rather than an empty array; pointer elements retain null entries for explicit rejection. Each object keeps its order, defaults its stage, supplies its transform string and contributes its classified family flag. A rejected list returns a zero plan even when a prior element was valid.
105
+ // @evidence contracts/common.md#clear-and-simple-design One function that decodes, normalizes, and classifies; the string reading and the classification are private helpers.
106
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The defaults are the ttsc protocol's, and the function trusts the payload only as far as decoding it.
107
+ // @evidence contracts/common.md#meaningful-documentation The comment states the accepted payloads, the error, and the stage default.
108
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation The command payload is a JSON string; plugin transform specifiers remain module identifiers rather than native paths.
109
+ // @evidence contracts/performance.md#efficient-algorithms Decoding is linear in payload bytes and one classification is performed per entry; the output holds one entry per plugin.
110
+ // @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.
111
+ // @evidence contracts/performance.md#bound-retention-and-release-resources Decoded maps and the result list belong to the returned plan and become reclaimable with its caller.
52
112
  func ParsePlan(payload string) (Plan, error) {
53
113
  payload = strings.TrimSpace(payload)
54
114
  if payload == "" {
55
115
  return Plan{}, nil
56
116
  }
57
117
 
58
- var raws []struct {
118
+ var raws []*struct {
59
119
  Name string `json:"name"`
60
120
  Stage string `json:"stage"`
61
121
  Config map[string]any `json:"config"`
@@ -63,11 +123,17 @@ func ParsePlan(payload string) (Plan, error) {
63
123
  if err := json.Unmarshal([]byte(payload), &raws); err != nil {
64
124
  return Plan{}, fmt.Errorf("parse plugins-json: %w", err)
65
125
  }
126
+ if raws == nil {
127
+ return Plan{}, fmt.Errorf("parse plugins-json: expected a JSON list of plugin objects")
128
+ }
66
129
 
67
130
  plan := Plan{
68
131
  Entries: make([]Entry, 0, len(raws)),
69
132
  }
70
- for _, raw := range raws {
133
+ for index, raw := range raws {
134
+ if raw == nil {
135
+ return Plan{}, fmt.Errorf("parse plugins-json: entry %d must be a JSON object", index)
136
+ }
71
137
  stage := raw.Stage
72
138
  if stage == "" {
73
139
  stage = "transform"
@@ -7,15 +7,35 @@ import (
7
7
  )
8
8
 
9
9
  // NodeName returns the trimmed identifier text of a named AST node
10
- // (method, parameter, ...). Empty when the node has no name.
10
+ // (method, parameter, ...). Empty when the node has no name. A name that is no
11
+ // identifier, a destructured parameter's `{ a }` or a computed method name's
12
+ // `[key]`, reads as its source, since typescript-go's `Text()` panics on it.
13
+ //
14
+ // @evidence contracts/common.md#principled-implementation The identifier text is read through the shim's `NodeText` so that a destructured parameter or a computed method name, whose `Text()` panics in the compiler port, reads as its source, and the quotes of a string-literal name are trimmed.
15
+ // @evidence contracts/common.md#clear-and-simple-design One function with a nil check, one call, and one trim.
16
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts It handles the node kinds the compiler API defines, with no fixture names.
17
+ // @evidence contracts/common.md#meaningful-documentation The comment states the empty result, the source fallback for non-identifiers, and the reason.
18
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation The compiler node name is source text, not a filesystem path.
19
+ // @evidence contracts/performance.md#efficient-algorithms Reading NodeText and trimming quote characters cost at most the name text length.
20
+ // @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.
21
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The returned string borrows or contains source text; the helper retains no node globally.
11
22
  func NodeName(node *shimast.Node) string {
12
23
  if node == nil || node.Name() == nil {
13
24
  return ""
14
25
  }
15
- return strings.Trim(node.Name().Text(), "\"'")
26
+ return strings.Trim(shimast.NodeText(node.Name()), "\"'")
16
27
  }
17
28
 
18
- // NodeText returns the verbatim source slice spanned by a node.
29
+ // NodeText returns the source slice spanned by a node with surrounding whitespace removed.
30
+ //
31
+ // @evidence contracts/common.md#principled-implementation The node's position range is applied to the source text of its file after a bounds check, and the slice is trimmed, so a synthesized node or a range outside the source returns an empty string instead of panicking.
32
+ // @evidence contracts/common.md#clear-and-simple-design One function over the source text accessor.
33
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts It reads the text of the file the node belongs to and adds nothing to it.
34
+ // @evidence contracts/common.md#meaningful-documentation The comment identifies the source range and whitespace trimming.
35
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation Source text and node byte offsets are compiler data rather than native filesystem access.
36
+ // @evidence contracts/performance.md#efficient-algorithms Bounds checks are constant work and TrimSpace scans at most the selected source range.
37
+ // @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.
38
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The returned source substring can keep its source string alive until the caller releases it; no global state or handle is acquired.
19
39
  func NodeText(node *shimast.Node) string {
20
40
  if node == nil {
21
41
  return ""
@@ -2,17 +2,16 @@ package transform
2
2
 
3
3
  import (
4
4
  "context"
5
- "encoding/json"
6
5
  "flag"
7
6
  "fmt"
8
7
  "io"
9
8
  "os"
10
9
  "path/filepath"
11
- "sync"
12
10
  "time"
13
11
 
14
12
  shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
15
13
  "github.com/samchon/nestia/packages/core/native/plugin"
14
+ "github.com/samchon/nestia/packages/core/native/transform/buildoutput"
16
15
  "github.com/samchon/ttsc/packages/ttsc/driver"
17
16
  typiaadapter "github.com/samchon/typia/packages/typia/native/adapter"
18
17
  )
@@ -117,18 +116,14 @@ func runBuild(args []string) int {
117
116
  defer prog.Close()
118
117
  releaseTypiaRegistries := registerTypiaDefaultLibraryClassifier(prog)
119
118
  defer releaseTypiaRegistries()
120
- if !*quiet {
121
- fmt.Fprintf(
122
- stdout,
123
- "// ttsc-nestia build: tsconfig=%s cwd=%s core=%v sdk=%v typia=%v emit=%v\n",
124
- *tsconfigPath,
125
- cwd,
126
- plan.Core,
127
- plan.SDK,
128
- plan.Typia,
129
- shouldEmit,
130
- )
131
- }
119
+ publication := buildoutput.New(buildoutput.Options{
120
+ TSConfig: *tsconfigPath, CWD: cwd, Manifest: *manifestPath,
121
+ Core: plan.Core, SDK: plan.SDK, Typia: plan.Typia,
122
+ Emit: shouldEmit, Quiet: *quiet,
123
+ }, stdout, driver.DefaultWriteFile)
124
+ // Publication owns all generated bytes until actual diagnostics accept them.
125
+ // Every earlier return terminates this session without committing its buffer.
126
+ defer publication.Finish(false)
132
127
 
133
128
  // AST-integration emit: typia's per-file transformer and @nestia/core's own
134
129
  // per-file transformer both run inside tsgo's emit pipeline (sharing the
@@ -141,7 +136,13 @@ func runBuild(args []string) int {
141
136
  if profile {
142
137
  started = time.Now()
143
138
  }
144
- newPathsRewriter(prog).applyAll(prog.SourceFiles())
139
+ // Path remapping serves published output, not type analysis. The private
140
+ // ForceEmit program used by noEmit has not run semantic diagnostics, so its
141
+ // checker still resolves imports lazily. Changing those imports to output
142
+ // paths would make it look for JavaScript files that do not exist.
143
+ if shouldEmit {
144
+ newPathsRewriter(prog).applyAll(prog.SourceFiles())
145
+ }
145
146
  profileBuildStep(profile, "paths-rewrite", started)
146
147
 
147
148
  typiaTransform := nestiaTypiaNodeTransform(prog, readTypiaPluginOptions(plan), addDiagnostic)
@@ -160,20 +161,9 @@ func runBuild(args []string) int {
160
161
  }
161
162
  transforms := append([]driver.PluginTransform{typiaTransform, coreTransform}, contributorTransforms...)
162
163
 
163
- emitted := []string{}
164
- pending := []buildPendingOutput{}
165
- var writeMu sync.Mutex
166
164
  writeFile := shimcompiler.WriteFile(func(fileName, text string, data *shimcompiler.WriteFileData) error {
167
165
  _ = data
168
- if shouldEmit {
169
- // TypeScript-Go emits declarations in parallel. Serialize the shared
170
- // slices so buffering cannot lose an artifact or race while the
171
- // declaration workers call this callback concurrently.
172
- writeMu.Lock()
173
- defer writeMu.Unlock()
174
- emitted = append(emitted, fileName)
175
- pending = append(pending, buildPendingOutput{FileName: fileName, Text: text})
176
- }
166
+ publication.Write(fileName, text)
177
167
  return nil
178
168
  })
179
169
 
@@ -215,41 +205,15 @@ func runBuild(args []string) int {
215
205
  if emitHasError {
216
206
  return 3
217
207
  }
218
- if shouldEmit {
219
- for _, output := range pending {
220
- if err := driver.DefaultWriteFile(output.FileName, output.Text); err != nil {
221
- fmt.Fprintf(stderr, "ttsc-nestia build: emit write failed: %v\n", err)
222
- return 3
223
- }
224
- }
225
- if *manifestPath != "" {
226
- data, err := json.Marshal(emitted)
227
- if err != nil {
228
- fmt.Fprintf(stderr, "ttsc-nestia build: manifest marshal failed: %v\n", err)
229
- return 3
230
- }
231
- if err := os.MkdirAll(filepath.Dir(*manifestPath), 0o755); err != nil {
232
- fmt.Fprintf(stderr, "ttsc-nestia build: manifest mkdir failed: %v\n", err)
233
- return 3
234
- }
235
- if err := os.WriteFile(*manifestPath, data, 0o644); err != nil {
236
- fmt.Fprintf(stderr, "ttsc-nestia build: manifest write failed: %v\n", err)
237
- return 3
238
- }
239
- }
240
- if !*quiet {
241
- fmt.Fprintf(stdout, "// ttsc-nestia build: emitted=%d files\n", len(emitted))
242
- }
208
+ if err := publication.Finish(true); err != nil {
209
+ fmt.Fprintf(stderr, "ttsc-nestia build: %v\n", err)
210
+ return 3
243
211
  }
212
+
244
213
  profileBuildStep(profile, "total", totalStarted)
245
214
  return 0
246
215
  }
247
216
 
248
- type buildPendingOutput struct {
249
- FileName string
250
- Text string
251
- }
252
-
253
217
  // emitDeclarations runs tsgo's standard declaration emitter for the program.
254
218
  // tsgo's MarkLinkedReferences pass can nil-panic on some cross-module reference
255
219
  // shapes (e.g. a nestia.config.ts that calls NestFactory.create, compiled by the
@@ -275,6 +239,16 @@ func runCheck(args []string) int {
275
239
  return runBuild(append([]string{"--noEmit"}, args...))
276
240
  }
277
241
 
242
+ // Diagnostic is one transform error: its file, line and column when known, its code, and its message.
243
+ //
244
+ // @evidence contracts/common.md#principled-implementation The record carries what the diagnostic line and the compiler envelope need, and a zero line marks a diagnostic without a position.
245
+ // @evidence contracts/common.md#clear-and-simple-design A five-field record with one formatting method.
246
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The values come from the transforms that report them.
247
+ // @evidence contracts/common.md#meaningful-documentation The comment names the fields and the meaning of an absent position.
248
+ // @evidence contracts/portability.md#os-neutral-implementation File holds the compiler supplied native source path; formatting delegates native relative-path conversion to filepath.Rel instead of treating it as a URL.
249
+ // @evidenceExclude contracts/performance.md#efficient-algorithms This record defines data rather than choosing a processing algorithm.
250
+ // @evidenceExclude contracts/performance.md#reuse-equivalent-work This record does not coordinate computations across consumers.
251
+ // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources A diagnostic record stores a path and message; the reporting command owns the collection lifetime.
278
252
  type Diagnostic struct {
279
253
  File string
280
254
  Line int
@@ -283,6 +257,16 @@ type Diagnostic struct {
283
257
  Message string
284
258
  }
285
259
 
260
+ // String formats the diagnostic as `file:line:column - error TS(code): message`, with the file made relative to `cwd` when possible, and without the position when the line is unknown.
261
+ //
262
+ // @evidence contracts/common.md#principled-implementation The path is made relative with `filepath.Rel` and the position part is printed only for a positive line, which is the same shape as every other nestia and typia diagnostic.
263
+ // @evidence contracts/common.md#clear-and-simple-design One function with two format strings.
264
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The shape is the protocol that ttsc reads from stderr, not a test-specific format.
265
+ // @evidence contracts/common.md#meaningful-documentation The comment states the two formats and the relative path rule.
266
+ // @evidence contracts/portability.md#os-neutral-implementation filepath.Rel represents native path differences, including cross-volume failure which keeps the original path. The position format is diagnostic protocol text.
267
+ // @evidence contracts/performance.md#efficient-algorithms Relative path calculation and formatting are linear in path and message bytes.
268
+ // @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.
269
+ // @evidence contracts/performance.md#bound-retention-and-release-resources Only the formatted string is returned; no file or writer is opened.
286
270
  func (d Diagnostic) String(cwd string) string {
287
271
  file := d.File
288
272
  if rel, err := filepath.Rel(cwd, file); err == nil {
@@ -294,6 +278,16 @@ func (d Diagnostic) String(cwd string) string {
294
278
  return fmt.Sprintf("%s - error TS(%s): %s", file, d.Code, d.Message)
295
279
  }
296
280
 
281
+ // WriteTypiaTransformDiagnostics writes each diagnostic on its own line to the writer, with files relative to `cwd`.
282
+ //
283
+ // @evidence contracts/common.md#principled-implementation Each diagnostic is written by its own `String`, one per line in the given order, so the output is the concatenation of the formatted diagnostics.
284
+ // @evidence contracts/common.md#clear-and-simple-design One loop over one formatter.
285
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts It writes what it is given, without filtering or reordering.
286
+ // @evidence contracts/common.md#meaningful-documentation The comment states the format and the ordering.
287
+ // @evidence contracts/portability.md#os-neutral-implementation Each diagnostic uses filepath.Rel for native paths and writes through io.Writer; no shell or executable spelling is assembled.
288
+ // @evidence contracts/performance.md#efficient-algorithms Each diagnostic is formatted and written once; work is linear in diagnostic count and total text bytes.
289
+ // @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.
290
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The writer is caller-owned and remains open; the function retains no diagnostic collection after returning.
297
291
  func WriteTypiaTransformDiagnostics(out io.Writer, diagnostics []Diagnostic, cwd string) {
298
292
  for _, diag := range diagnostics {
299
293
  fmt.Fprintln(out, diag.String(cwd))
@@ -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)
@@ -1,8 +1,6 @@
1
1
  package transform
2
2
 
3
3
  import (
4
- "fmt"
5
-
6
4
  shimast "github.com/microsoft/typescript-go/shim/ast"
7
5
  shimchecker "github.com/microsoft/typescript-go/shim/checker"
8
6
  shimprinter "github.com/microsoft/typescript-go/shim/printer"
@@ -44,7 +42,13 @@ func nestiaCoreHttpQuerifyProgrammer(prog *driver.Program, ec *shimprinter.EmitC
44
42
  Type: typ,
45
43
  })
46
44
  if result.Success == false {
47
- panic(fmt.Errorf("failed to analyze query-string metadata: %d error(s)", len(result.Errors)))
45
+ panic(nativecontext.TransformerError_from(struct {
46
+ Code string
47
+ Errors []nativecontext.TransformerError_MetadataFactory_IError
48
+ }{
49
+ Code: "nestia.core.TypedQueryRoute",
50
+ Errors: nestiaCoreMetadataErrors(result.Errors),
51
+ }))
48
52
  }
49
53
  statements := []*shimast.Node{
50
54
  nativefactories.StatementFactory.Constant(nativefactories.StatementFactory_ConstantProps{
@@ -62,9 +66,7 @@ func nestiaCoreHttpQuerifyProgrammer(prog *driver.Program, ec *shimprinter.EmitC
62
66
  if ok == false || property == nil || property.Value == nil {
63
67
  continue
64
68
  }
65
- statements = append(statements, nestiaCoreFactory.NewExpressionStatement(
66
- nestiaCoreHttpQuerifyDecode(key, property.Value, ec),
67
- ))
69
+ statements = append(statements, nestiaCoreHttpQuerifyDecode(key, property.Value, ec))
68
70
  }
69
71
  }
70
72
  statements = append(statements, nestiaCoreFactory.NewReturnStatement(nestiaCoreFactory.NewIdentifier("output")))
@@ -149,12 +151,28 @@ func nestiaCoreHttpValidateQuerifyProgrammer(prog *driver.Program, importer *nat
149
151
  Name: "query",
150
152
  Value: nestiaCoreHttpQuerifyProgrammer(prog, ec, typ),
151
153
  }),
154
+ // the object is validated, then querified: validating the
155
+ // URLSearchParams against the object type found every property absent
152
156
  nativefactories.StatementFactory.Constant(nativefactories.StatementFactory_ConstantProps{
153
- Name: "output",
154
- Value: nestiaCoreCall("query", nestiaCoreFactory.NewIdentifier("input")),
157
+ Name: "result",
158
+ Value: nestiaCoreCall("validate", nestiaCoreFactory.NewIdentifier("input")),
155
159
  }),
156
160
  nestiaCoreFactory.NewReturnStatement(nestiaCoreFactory.NewAsExpression(
157
- nestiaCoreCall("validate", nestiaCoreFactory.NewIdentifier("output")),
161
+ nestiaCoreFactory.NewConditionalExpression(
162
+ nestiaCoreFactory.NewPropertyAccessExpression(
163
+ nestiaCoreFactory.NewIdentifier("result"),
164
+ nil,
165
+ nestiaCoreFactory.NewIdentifier("success"),
166
+ shimast.NodeFlagsNone,
167
+ ),
168
+ nil,
169
+ nestiaCoreFactory.NewObjectLiteralExpression(nestiaCoreFactory.NewNodeList([]*shimast.Node{
170
+ nestiaCoreProperty("success", nestiaCoreFactory.NewKeywordExpression(shimast.KindTrueKeyword), ec),
171
+ nestiaCoreProperty("data", nestiaCoreCall("query", nestiaCoreFactory.NewIdentifier("input")), ec),
172
+ }), false),
173
+ nil,
174
+ nestiaCoreFactory.NewIdentifier("result"),
175
+ ),
158
176
  nativefactories.TypeFactory.Keyword("any"),
159
177
  )),
160
178
  })
@@ -174,7 +192,43 @@ func nestiaCoreQueryWrapperArrow(statements []*shimast.Node) *shimast.Node {
174
192
  )
175
193
  }
176
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.
177
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 {
178
232
  if len(value.Arrays) != 0 {
179
233
  return nestiaCoreFactory.NewCallExpression(
180
234
  nativefactories.IdentifierFactory.Access(