@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
@@ -0,0 +1,55 @@
1
+ package transform
2
+
3
+ import (
4
+ "encoding/json"
5
+ "path/filepath"
6
+
7
+ shimast "github.com/microsoft/typescript-go/shim/ast"
8
+ "github.com/samchon/ttsc/packages/ttsc/driver"
9
+ )
10
+
11
+ // SourceFilePackageName returns the nearest package-scope manifest's name for a
12
+ // resolved declaration source. A node_modules boundary ends the scope before
13
+ // its manifest is read. Missing, unreadable or malformed ownership
14
+ // returns an empty name; a foreign or invalid nearest manifest never falls
15
+ // through to an enclosing package.
16
+ //
17
+ // The loaded program filesystem owns path resolution, including its overlays
18
+ // and dependency observations. This performs one ancestor walk without retaining
19
+ // programs or caching an owner across unrelated compilation requests.
20
+ //
21
+ // @evidence contracts/common.md#principled-implementation Node's LOOKUP_PACKAGE_SCOPE ends at the node_modules path component before reading a manifest there. Within that scope the nearest manifest's decoded name decides declaration identity. A present unreadable or invalid manifest also stops resolution, so neither a manifestless dependency nor an invalid foreign owner inherits an enclosing core, rxjs, tgrid or typia owner.
22
+ // @evidence contracts/common.md#clear-and-simple-design One ancestor loop returns one decoded name, with nil, package-scope boundary and filesystem-root termination. All declaration provenance consumers share this operation rather than duplicating ownership policies.
23
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The literal node_modules component establishes Node's documented scope boundary, not package identity. No library-directory spelling, fixture name or type name substitutes for the actual scoped manifest name. Reads use the program filesystem.
24
+ // @evidence contracts/common.md#meaningful-documentation The comment defines scoped nearest-manifest semantics, boundary-before-read termination, empty results, overlay ownership and the absence of cross-program retention. The rule follows Node's ESM resolution LOOKUP_PACKAGE_SCOPE specification.
25
+ // @evidence contracts/portability.md#os-neutral-implementation filepath.Dir, Base and Join represent native path components, and the driver's filesystem performs reads/existence checks. Exact node_modules component comparison follows Node's scope algorithm; no shell path or slash-substring membership test is composed.
26
+ // @evidence contracts/performance.md#efficient-algorithms One read per ancestor and an existence check only after a failed read cost O(directory depth plus the first manifest's bytes). No source graph or dependency tree is recursively searched.
27
+ // @evidence contracts/performance.md#reuse-equivalent-work The program filesystem supplies its own coherent read/overlay observation model. This operation shares no result across programs because manifest inputs and overlays may differ between requests.
28
+ // @evidence contracts/performance.md#bound-retention-and-release-resources Only the current ancestor string and one manifest buffer/decoded name are retained; no program pointer, global owner map, descriptor or filesystem handle survives the call.
29
+ func SourceFilePackageName(prog *driver.Program, source *shimast.SourceFile) string {
30
+ if prog == nil || prog.FS == nil || source == nil {
31
+ return ""
32
+ }
33
+ for directory := filepath.Dir(source.FileName()); ; {
34
+ if filepath.Base(directory) == "node_modules" {
35
+ return ""
36
+ }
37
+ manifest := filepath.Join(directory, "package.json")
38
+ if contents, ok := prog.FS.ReadFile(manifest); ok {
39
+ var pack struct {
40
+ Name string `json:"name"`
41
+ }
42
+ if json.Unmarshal([]byte(contents), &pack) != nil {
43
+ return ""
44
+ }
45
+ return pack.Name
46
+ } else if prog.FS.FileExists(manifest) {
47
+ return ""
48
+ }
49
+ parent := filepath.Dir(directory)
50
+ if parent == directory {
51
+ return ""
52
+ }
53
+ directory = parent
54
+ }
55
+ }
@@ -4,8 +4,6 @@ import (
4
4
  shimast "github.com/microsoft/typescript-go/shim/ast"
5
5
  )
6
6
 
7
- const nestiaKindColonToken = shimast.KindQuestionToken + 1
8
-
9
7
  func normalizeNestiaSyntheticTokens(node *shimast.Node) {
10
8
  if node == nil {
11
9
  return
@@ -17,7 +15,7 @@ func normalizeNestiaSyntheticTokens(node *shimast.Node) {
17
15
  conditional.QuestionToken = factory.NewToken(shimast.KindQuestionToken)
18
16
  }
19
17
  if conditional.ColonToken == nil {
20
- conditional.ColonToken = factory.NewToken(nestiaKindColonToken)
18
+ conditional.ColonToken = factory.NewToken(shimast.KindColonToken)
21
19
  }
22
20
  }
23
21
  node.ForEachChild(func(child *shimast.Node) bool {
@@ -25,6 +25,15 @@ var (
25
25
  // decorator rewrites, then runs any statically linked contributor rewrite
26
26
  // collectors such as the SDK metadata pass when the caller explicitly enables
27
27
  // them.
28
+ //
29
+ // @evidence contracts/common.md#principled-implementation The command runs inside a `recover`, so an unexpected panic becomes one diagnostic line with the same shape as every other diagnostic and exit code 3 instead of a raw Go stack, and the stack is printed only when `NESTIA_NATIVE_DEBUG_STACK` is set.
30
+ // @evidence contracts/common.md#clear-and-simple-design One deferred handler around the private dispatcher.
31
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The recovery reports the failure; it does not retry or hide it, and the exit code stays non-zero.
32
+ // @evidence contracts/common.md#meaningful-documentation The comment explains the recovery envelope, the diagnostic shape, and the debug variable.
33
+ // @evidence contracts/portability.md#os-neutral-implementation Argument vectors are passed as strings to Go flag parsing without shell composition. Native working directories and file outputs are handled by os/filepath and the loaded program filesystem in the dispatched commands.
34
+ // @evidence contracts/performance.md#efficient-algorithms Dispatch itself is constant work; build/transform cost is owned by their program load, diagnostics and per-file emit passes, not by the recovery wrapper.
35
+ // @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.
36
+ // @evidence contracts/performance.md#bound-retention-and-release-resources Commands defer program and publication cleanup; linked collectors remain process-owned. The debug stack is allocated only after a panic.
28
37
  func Run(args []string) (code int) {
29
38
  defer func() {
30
39
  if exp := recover(); exp != nil {
@@ -57,6 +66,15 @@ func Run(args []string) (code int) {
57
66
  // writers are package state, so concurrent calls would interleave; the host runs
58
67
  // one command per process and Go tests in one package run sequentially unless
59
68
  // they opt into t.Parallel.
69
+ //
70
+ // @evidence contracts/common.md#principled-implementation The two package writers are replaced for the duration of one call and restored by a `defer`, so a caller can observe the JSON envelope of the project-mode transform, which an exit code alone cannot show; a nil writer leaves its stream unchanged.
71
+ // @evidence contracts/common.md#clear-and-simple-design One function that swaps the writers, calls `Run`, and restores them.
72
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts This is a test seam kept as an exported function because the external test module cannot reach the private writers; it changes no behavior of the command, but it mutates package state and is therefore not safe for concurrent calls, as its comment says, and it is used only by the Go test modules.
73
+ // @evidence contracts/common.md#meaningful-documentation The comment explains why the seam exists, its restore behavior, and the concurrency limit.
74
+ // @evidence contracts/portability.md#os-neutral-implementation Streams use caller supplied io.Writer values, with native file and process boundaries handled by Run and its dispatcher.
75
+ // @evidence contracts/performance.md#efficient-algorithms Swapping two stream references is constant work; the dispatched command determines compilation cost.
76
+ // @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.
77
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The call borrows writers and restores previous references on return or panic; it neither closes caller writers nor supports concurrent calls.
60
78
  func RunWithOutput(args []string, out io.Writer, errOut io.Writer) int {
61
79
  previousOut, previousErr := stdout, stderr
62
80
  defer func() {
@@ -0,0 +1,213 @@
1
+ package schemaprojection
2
+
3
+ import metadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
4
+
5
+ // Project gives the schema writer an isolated representation
6
+ // of an absorbed metadata graph. Recursive tuples occupy named alias slots
7
+ // so the writer registers their component before visiting its children. Arrays
8
+ // retain their native recursive representation and every name remains the
9
+ // identifier allocated by the original metadata collection.
10
+ //
11
+ // The returned graph shares read-only scalar annotations but no metadata or
12
+ // component node whose traversal or name cache the writer can mutate. The raw
13
+ // graph remains the source of reflected interop data. Callers must use the
14
+ // returned root for both the main schema and decomposed property schemas.
15
+ // This type-only metadata foundation lives in the stable core host module:
16
+ // contributor packages are relocated by ttsc, while their host imports keep
17
+ // this module identity. The SDK owns baking and calls this operation across
18
+ // that production boundary; this package has no SDK module dependency.
19
+ //
20
+ // @evidence contracts/common.md#principled-implementation SDK analysis absorbs aliases, while the schema writer only breaks tuple cycles through named alias components. Replacing recursive tuple uses by aliases of the same collection-allocated semantic slot retains tuple elements and recursive edges without creating names or changing raw reflection. Memoized copies are registered before visiting edges, preserving shared and mutually recursive identities.
21
+ // @evidence contracts/common.md#clear-and-simple-design One graph projection owns the writer representation; a private visitor copies metadata and component edges, and one tuple helper separates a named use from its inline component body.
22
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The mapping follows recursive tuple metadata and original collection names for every input. It does not mutate foreign implementations, change absorption options, recognize fixture names, or catch schema failures.
23
+ // @evidence contracts/common.md#meaningful-documentation The comment explains absorbed input, recursive tuple representation, original name ownership, isolation and the requirement to share the returned root with property schema generation.
24
+ // @evidence contracts/performance.md#efficient-algorithms Pointer maps visit each metadata and component once, and copy each outgoing edge once, for O(V+E+annotation payload) work and space. The projection adds no checker analysis, compiler or host operation.
25
+ // @evidence contracts/performance.md#reuse-equivalent-work Pointer memoization reuses each original metadata and component's projection when several edges reach the same identity within this invocation. The immutable supplied graph establishes equivalence, and the caller reuses that one returned graph for root and property schema writes against shared components. Each different invocation owns fresh maps and does not reuse a projection across changed inputs.
26
+ // @evidence contracts/performance.md#bound-retention-and-release-resources Memo maps and copied nodes belong to one bake invocation and become reclaimable with its result; no global cache, handle or background work is retained.
27
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation The projection visits an in-memory typia metadata graph with no native file identity or subprocess boundary.
28
+ func Project(input *metadata.MetadataSchema) *metadata.MetadataSchema {
29
+ projection := &projection{
30
+ metadatas: map[*metadata.MetadataSchema]*metadata.MetadataSchema{},
31
+ arrays: map[*metadata.MetadataArrayType]*metadata.MetadataArrayType{},
32
+ tuples: map[*metadata.MetadataTupleType]*metadata.MetadataTupleType{},
33
+ objects: map[*metadata.MetadataObjectType]*metadata.MetadataObjectType{},
34
+ aliases: map[*metadata.MetadataAliasType]*metadata.MetadataAliasType{},
35
+ tupleAliases: map[*metadata.MetadataTupleType]*metadata.MetadataAliasType{},
36
+ }
37
+ return projection.visit(input)
38
+ }
39
+
40
+ // projection retains invocation-local graph identities, including
41
+ // separate tuple component bodies and their named recursive use sites.
42
+ type projection struct {
43
+ metadatas map[*metadata.MetadataSchema]*metadata.MetadataSchema
44
+ arrays map[*metadata.MetadataArrayType]*metadata.MetadataArrayType
45
+ tuples map[*metadata.MetadataTupleType]*metadata.MetadataTupleType
46
+ objects map[*metadata.MetadataObjectType]*metadata.MetadataObjectType
47
+ aliases map[*metadata.MetadataAliasType]*metadata.MetadataAliasType
48
+ tupleAliases map[*metadata.MetadataTupleType]*metadata.MetadataAliasType
49
+ }
50
+
51
+ // visit copies every metadata edge before a writer can reach it. Shared scalar
52
+ // tag and documentation payloads are read-only to the schema writer.
53
+ func (p *projection) visit(input *metadata.MetadataSchema) *metadata.MetadataSchema {
54
+ if input == nil {
55
+ return nil
56
+ }
57
+ if output, ok := p.metadatas[input]; ok {
58
+ return output
59
+ }
60
+ output := input.ShallowClone()
61
+ p.metadatas[input] = output
62
+ output.Rest = p.visit(input.Rest)
63
+ if input.Escaped != nil {
64
+ copy := *input.Escaped
65
+ output.Escaped = &copy
66
+ copy.Original, copy.Returns = p.visit(input.Escaped.Original), p.visit(input.Escaped.Returns)
67
+ }
68
+ output.Atomics = copyLeaves(input.Atomics)
69
+ output.Constants = copyLeaves(input.Constants)
70
+ output.Natives = copyLeaves(input.Natives)
71
+ output.Templates = copyLeaves(input.Templates)
72
+ for i, entry := range input.Templates {
73
+ if entry == nil {
74
+ continue
75
+ }
76
+ output.Templates[i].Row = make([]*metadata.MetadataSchema, len(entry.Row))
77
+ for j, child := range entry.Row {
78
+ output.Templates[i].Row[j] = p.visit(child)
79
+ }
80
+ }
81
+ output.Functions = copyLeaves(input.Functions)
82
+ for i, entry := range input.Functions {
83
+ if entry == nil {
84
+ continue
85
+ }
86
+ copy := output.Functions[i]
87
+ copy.Output = p.visit(entry.Output)
88
+ copy.Parameters = copyLeaves(entry.Parameters)
89
+ for j, parameter := range entry.Parameters {
90
+ if parameter != nil {
91
+ copy.Parameters[j].Type = p.visit(parameter.Type)
92
+ }
93
+ }
94
+ }
95
+ output.Sets = copyLeaves(input.Sets)
96
+ for i, entry := range input.Sets {
97
+ if entry != nil {
98
+ output.Sets[i].Value = p.visit(entry.Value)
99
+ }
100
+ }
101
+ output.Maps = copyLeaves(input.Maps)
102
+ for i, entry := range input.Maps {
103
+ if entry != nil {
104
+ output.Maps[i].Key, output.Maps[i].Value = p.visit(entry.Key), p.visit(entry.Value)
105
+ }
106
+ }
107
+ output.Arrays = copyLeaves(input.Arrays)
108
+ for i, entry := range input.Arrays {
109
+ if entry == nil || entry.Type == nil {
110
+ continue
111
+ }
112
+ component, ok := p.arrays[entry.Type]
113
+ if !ok {
114
+ copy := *entry.Type
115
+ component = &copy
116
+ p.arrays[entry.Type] = component
117
+ component.Value = p.visit(entry.Type.Value)
118
+ }
119
+ output.Arrays[i].Type = component
120
+ }
121
+ output.Objects = copyLeaves(input.Objects)
122
+ for i, entry := range input.Objects {
123
+ if entry == nil || entry.Type == nil {
124
+ continue
125
+ }
126
+ component, ok := p.objects[entry.Type]
127
+ if !ok {
128
+ copy := *entry.Type
129
+ component = &copy
130
+ p.objects[entry.Type] = component
131
+ component.Properties = copyLeaves(entry.Type.Properties)
132
+ for j, property := range entry.Type.Properties {
133
+ if property != nil {
134
+ component.Properties[j].Key, component.Properties[j].Value = p.visit(property.Key), p.visit(property.Value)
135
+ }
136
+ }
137
+ }
138
+ output.Objects[i].Type = component
139
+ }
140
+ output.Aliases = copyLeaves(input.Aliases)
141
+ for i, entry := range input.Aliases {
142
+ if entry == nil || entry.Type == nil {
143
+ continue
144
+ }
145
+ component, ok := p.aliases[entry.Type]
146
+ if !ok {
147
+ copy := *entry.Type
148
+ component = &copy
149
+ p.aliases[entry.Type] = component
150
+ component.Value = p.visit(entry.Type.Value)
151
+ }
152
+ output.Aliases[i].Type = component
153
+ }
154
+ output.Tuples = make([]*metadata.MetadataTuple, 0, len(input.Tuples))
155
+ for _, entry := range input.Tuples {
156
+ if entry == nil || entry.Type == nil {
157
+ output.Tuples = append(output.Tuples, entry)
158
+ continue
159
+ }
160
+ if entry.Type.Recursive {
161
+ alias, ok := p.tupleAliases[entry.Type]
162
+ if !ok {
163
+ alias = metadata.MetadataAliasType_create(metadata.MetadataAliasType{
164
+ Name: entry.Type.Name, DisplayName: entry.Type.DisplayName, Recursive: true, Nullables: entry.Type.Nullables,
165
+ })
166
+ p.tupleAliases[entry.Type] = alias
167
+ body := metadata.MetadataSchema_initialize()
168
+ alias.Value = body
169
+ copy := *entry
170
+ copy.Type = p.tuple(entry.Type)
171
+ body.Tuples = []*metadata.MetadataTuple{&copy}
172
+ }
173
+ output.Aliases = append(output.Aliases, metadata.MetadataAlias_create(metadata.MetadataAlias{Type: alias, Tags: entry.Tags}))
174
+ } else {
175
+ copy := *entry
176
+ copy.Type = p.tuple(entry.Type)
177
+ output.Tuples = append(output.Tuples, &copy)
178
+ }
179
+ }
180
+ return output
181
+ }
182
+
183
+ // tuple copies a structural component after registering its identity, allowing
184
+ // its child metadata to return through the already registered named alias.
185
+ func (p *projection) tuple(input *metadata.MetadataTupleType) *metadata.MetadataTupleType {
186
+ if output, ok := p.tuples[input]; ok {
187
+ return output
188
+ }
189
+ copy := *input
190
+ output := &copy
191
+ p.tuples[input] = output
192
+ output.Elements = make([]*metadata.MetadataSchema, len(input.Elements))
193
+ for i, element := range input.Elements {
194
+ output.Elements[i] = p.visit(element)
195
+ }
196
+ return output
197
+ }
198
+
199
+ // copyLeaves isolates node-local name caches. Scalar annotation
200
+ // slices remain shared because the public schema writer only reads them.
201
+ func copyLeaves[T any](input []*T) []*T {
202
+ if input == nil {
203
+ return nil
204
+ }
205
+ output := make([]*T, len(input))
206
+ for i, item := range input {
207
+ if item != nil {
208
+ copy := *item
209
+ output[i] = &copy
210
+ }
211
+ }
212
+ return output
213
+ }
@@ -7,7 +7,7 @@ import (
7
7
  "fmt"
8
8
  "os"
9
9
  "path/filepath"
10
- "regexp"
10
+ "reflect"
11
11
  "strings"
12
12
 
13
13
  shimast "github.com/microsoft/typescript-go/shim/ast"
@@ -169,7 +169,14 @@ func runTransformProject(
169
169
  sf := prog.SourceFile(fileName)
170
170
  return sf != nil && prog.TSProgram.IsLibFile(sf)
171
171
  })
172
- schemametadata.MetadataDependency_listen(prog.Checker, collector.Touch)
172
+ // `File` alone is the whole listener this host needs. `Callee` falls back
173
+ // to it, and the two channels differ only in whether a default library
174
+ // withholds a completeness declaration, which this host never makes;
175
+ // `Unbounded` is ignorable for the same reason, since an unreported input
176
+ // is still watched through the reference graph.
177
+ schemametadata.MetadataDependency_listen(prog.Checker, schemametadata.MetadataDependency_IListener{
178
+ File: collector.Touch,
179
+ })
173
180
  defer schemametadata.MetadataDependency_release(prog.Checker)
174
181
  for _, sf := range prog.SourceFiles() {
175
182
  if sf.IsDeclarationFile {
@@ -245,6 +252,19 @@ func writeSingleOutput(text, outPath string) int {
245
252
  return 0
246
253
  }
247
254
 
255
+ // SourceFileText returns the text supplied by a present Text() provider.
256
+ // Missing values, including typed nil receivers, and values without Text()
257
+ // return empty text and false without invoking a method. Errors from a present
258
+ // provider's Text() method propagate to its caller.
259
+ //
260
+ // @evidence contracts/common.md#principled-implementation Structural Text() support establishes the provider operation, while a guarded reflect nil-identity check establishes that a receiver exists before dispatch. Every nil-capable kind follows the same absent-source rule; non-nil providers return their own text unchanged.
261
+ // @evidence contracts/common.md#clear-and-simple-design One structural type assertion, one constant-time receiver-presence check restricted to the kinds for which Go permits IsNil, and one method dispatch separate absent sources from actual provider behavior.
262
+ // @evidence contracts/common.md#prohibited-implementation-shortcuts The nil rule applies to all structural providers without compiler-type names or recovery. It does not invoke missing receivers, mutate foreign providers or conceal errors raised by present providers.
263
+ // @evidence contracts/common.md#meaningful-documentation The comment states exact absent-value behavior, typed nil receiver treatment and propagation of present-provider errors.
264
+ // @evidenceExclude contracts/portability.md#os-neutral-implementation The Text provider is an in-memory structural interface; the helper does not read a native source file.
265
+ // @evidence contracts/performance.md#efficient-algorithms Type and nil checks are constant work; obtaining the string has the provider-defined cost.
266
+ // @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.
267
+ // @evidence contracts/performance.md#bound-retention-and-release-resources The provider owns source text and the returned string may retain those bytes; no global provider reference is stored.
248
268
  func SourceFileText(target any) (string, bool) {
249
269
  type sourceText interface {
250
270
  Text() string
@@ -253,25 +273,16 @@ func SourceFileText(target any) (string, bool) {
253
273
  if !ok {
254
274
  return "", false
255
275
  }
276
+ value := reflect.ValueOf(file)
277
+ switch value.Kind() {
278
+ case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice, reflect.UnsafePointer:
279
+ if value.IsNil() {
280
+ return "", false
281
+ }
282
+ }
256
283
  return file.Text(), true
257
284
  }
258
285
 
259
- // These patterns run once per transformed file in cleanupTypeScriptTransformText
260
- // / normalizeParenthesizedTypeAnnotations. Compiling them at package scope keeps
261
- // the per-file cost to a match instead of a recompile (the SDK `transform` path
262
- // runs this for every emitted source file).
263
- var (
264
- cleanupImportTypeBlockPattern = regexp.MustCompile(`(?m)^import type \{([^{}\n]+)\} from`)
265
- cleanupImportTypeLinePattern = regexp.MustCompile(`^import type \{\s*([^{}\n]+?)\s*\} from`)
266
- cleanupImportBlankLinePattern = regexp.MustCompile(`(?m)(^import [^\n]+;\n)\n+(const |let |var |export )`)
267
- cleanupInputIsParenPattern = regexp.MustCompile(`input is \(([A-Za-z_$][A-Za-z0-9_$.]*)\)`)
268
- cleanupCollapseBlankCallPattern = regexp.MustCompile(`\n\n([A-Za-z_$][A-Za-z0-9_$]*\([^;\n]*\);?)`)
269
- )
270
- var (
271
- normalizeParenArrowTypePattern = regexp.MustCompile(`: \(([A-Za-z_$][A-Za-z0-9_$.]*(<[^()\n;{}]*>)?)\)(\s*=>)`)
272
- normalizeParenNullishPattern = regexp.MustCompile(`\| \((null|undefined)\)`)
273
- )
274
-
275
286
  func transformDiagnosticToCompilerDiagnostic(
276
287
  diag Diagnostic,
277
288
  ) transformCompilerDiagnostic {
@@ -1,6 +1,8 @@
1
1
  package transform
2
2
 
3
3
  import (
4
+ "sync"
5
+
4
6
  shimast "github.com/microsoft/typescript-go/shim/ast"
5
7
  shimprinter "github.com/microsoft/typescript-go/shim/printer"
6
8
  shimscanner "github.com/microsoft/typescript-go/shim/scanner"
@@ -22,6 +24,10 @@ func nestiaTypiaNodeTransform(
22
24
  ) driver.PluginTransform {
23
25
  transformOptions := pluginOptions.TransformOptions()
24
26
  extras := nativecontext.ITypiaContext_Extras{
27
+ // One cache for the whole program, as typia's own hosts share it: left
28
+ // nil, typia makes one per file, and a program-wide analysis such as
29
+ // `llm.evaluation`'s `@probability` scan reruns for every file.
30
+ Shared: &sync.Map{},
25
31
  AddDiagnostic: func(diag *nativecontext.ITypiaDiagnostic) int {
26
32
  addDiagnostic(nestiaTypiaDiagnosticFrom(diag))
27
33
  return 1
@@ -16,16 +16,65 @@ const path = require("node:path");
16
16
  // - When the `@nestia/core` plugin itself is disabled, this descriptor is
17
17
  // never evaluated, so the SDK contributor is never linked either.
18
18
  function createTtscPlugin(context) {
19
+ const typia = assertTypiaVersion(context);
19
20
  const plugin = {
20
21
  name: "@nestia/core",
21
22
  source: path.resolve(__dirname, "cmd", "ttsc-nestia"),
22
23
  composes: ["typia/lib/transform"],
24
+ // a typia upgrade must evaluate the version check again
25
+ hostInputs: typia,
23
26
  };
24
27
  const sdk = resolveSdkContributorSource(context);
25
28
  if (sdk !== null) plugin.contributors = [{ name: "sdk", source: sdk }];
26
29
  return plugin;
27
30
  }
28
31
 
32
+ // The typia transform this host runs is compiled from the typia Go source
33
+ // nestia's own `go.mod` pins, never from the typia package the project installs,
34
+ // while the code it emits calls that installed package's runtime. A project
35
+ // resolving another typia version would run mismatched code with no error until
36
+ // a call reaches the difference (#1663), so the build stops and names both
37
+ // versions. The version nestia is built for is the typia @nestia/core itself
38
+ // resolves, which its exact dependency holds at the Go pin's release.
39
+ //
40
+ // Returns the typia manifests read, for ttsc to watch.
41
+ function assertTypiaVersion(context) {
42
+ const expected = resolveTypiaManifest([__dirname]);
43
+ const actual =
44
+ context && typeof context.projectRoot === "string"
45
+ ? resolveTypiaManifest([context.projectRoot])
46
+ : null;
47
+ if (
48
+ expected !== null &&
49
+ actual !== null &&
50
+ expected.version !== actual.version
51
+ )
52
+ throw new Error(
53
+ [
54
+ `@nestia/core runs the typia ${expected.version} transform, but this project resolves typia ${actual.version} (${actual.file}).`,
55
+ `The generated code would call typia ${actual.version}'s runtime with typia ${expected.version}'s output.`,
56
+ `Install typia@${expected.version}, or a @nestia/core release built for typia ${actual.version}.`,
57
+ ].join(" "),
58
+ );
59
+ return [
60
+ ...new Set(
61
+ [expected, actual]
62
+ .filter((manifest) => manifest !== null)
63
+ .map((manifest) => manifest.file),
64
+ ),
65
+ ];
66
+ }
67
+
68
+ function resolveTypiaManifest(paths) {
69
+ try {
70
+ const file = require.resolve("typia/package.json", { paths });
71
+ const version = JSON.parse(fs.readFileSync(file, "utf8")).version;
72
+ return typeof version === "string" ? { file, version } : null;
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+
29
78
  function resolveSdkContributorSource(context) {
30
79
  const paths = [__dirname];
31
80
  if (context && typeof context.projectRoot === "string")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestia/core",
3
- "version": "14.0.0",
3
+ "version": "14.0.2",
4
4
  "description": "Super-fast validation decorators of NestJS",
5
5
  "main": "lib/index.js",
6
6
  "exports": {
@@ -8,10 +8,7 @@
8
8
  "types": "./lib/index.d.ts",
9
9
  "default": "./lib/index.js"
10
10
  },
11
- "./lib/transform": {
12
- "types": "./lib/transform.d.ts",
13
- "default": "./lib/transform.js"
14
- },
11
+ "./lib/transform": "./native/transform.cjs",
15
12
  "./native/transform.cjs": "./native/transform.cjs",
16
13
  "./package.json": "./package.json"
17
14
  },
@@ -49,24 +46,24 @@
49
46
  "@typia/utils": "^15.0.0",
50
47
  "get-function-location": "^2.0.0",
51
48
  "glob": "^11.0.3",
52
- "path-parser": "^6.1.0",
49
+ "path-to-regexp": "^8.4.2",
53
50
  "raw-body": "^2.0.0",
54
51
  "reflect-metadata": ">=0.1.12",
55
- "rxjs": ">=6.0.3",
52
+ "rxjs": ">=7.1.0",
56
53
  "tgrid": "^1.1.0",
57
- "typia": "^15.0.0",
54
+ "typia": "15.0.0",
58
55
  "ws": "^7.5.3",
59
- "@nestia/fetcher": "^14.0.0"
56
+ "@nestia/fetcher": "^14.0.2"
60
57
  },
61
58
  "peerDependencies": {
62
59
  "@modelcontextprotocol/sdk": "^1.18.0",
63
- "@nestjs/common": ">=7.0.1",
64
- "@nestjs/core": ">=7.0.1",
60
+ "@nestjs/common": ">=11.0.0",
61
+ "@nestjs/core": ">=11.0.0",
65
62
  "reflect-metadata": ">=0.1.12",
66
- "rxjs": ">=6.0.3",
63
+ "rxjs": ">=7.1.0",
67
64
  "ttsc": ">=0.19.2",
68
- "typia": "^15.0.0",
69
- "@nestia/fetcher": "^14.0.0"
65
+ "typia": "15.0.0",
66
+ "@nestia/fetcher": "^14.0.2"
70
67
  },
71
68
  "peerDependenciesMeta": {
72
69
  "@modelcontextprotocol/sdk": {
@@ -110,7 +107,8 @@
110
107
  "scripts": {
111
108
  "build": "rimraf lib && ttsc",
112
109
  "dev": "ttsc --watch",
113
- "test:go": "cd test && go test ./..."
110
+ "test:go": "cd test && go test -count=1 ./...",
111
+ "evidence": "evidence"
114
112
  },
115
113
  "types": "lib/index.d.ts"
116
114
  }