@nestia/sdk 12.0.0-rc.2 → 12.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +21 -21
  2. package/assets/bundle/api/HttpError.ts +1 -1
  3. package/assets/bundle/api/IConnection.ts +1 -1
  4. package/assets/bundle/api/Primitive.ts +1 -1
  5. package/assets/bundle/api/Resolved.ts +1 -1
  6. package/assets/bundle/api/index.ts +4 -4
  7. package/assets/bundle/api/module.ts +6 -6
  8. package/assets/bundle/distribute/README.md +37 -37
  9. package/assets/bundle/distribute/package.json +28 -28
  10. package/assets/bundle/distribute/tsconfig.json +109 -109
  11. package/assets/bundle/e2e/index.ts +42 -42
  12. package/assets/config/nestia.config.ts +97 -97
  13. package/lib/executable/sdk.js +12 -12
  14. package/native/go.mod +35 -52
  15. package/native/go.sum +54 -84
  16. package/native/sdk/register.go +322 -322
  17. package/native/sdk/sdk.go +17 -17
  18. package/native/sdk/sdk_metadata_json.go +327 -327
  19. package/native/sdk/sdk_transform.go +1879 -1879
  20. package/package.json +10 -10
  21. package/src/INestiaConfig.ts +267 -267
  22. package/src/NestiaSwaggerComposer.ts +152 -152
  23. package/src/analyses/AccessorAnalyzer.ts +93 -93
  24. package/src/analyses/ConfigAnalyzer.ts +344 -344
  25. package/src/analyses/ImportAnalyzer.ts +92 -92
  26. package/src/analyses/PathAnalyzer.ts +130 -130
  27. package/src/analyses/ReflectControllerAnalyzer.ts +112 -112
  28. package/src/analyses/ReflectHttpOperationAnalyzer.ts +183 -183
  29. package/src/analyses/ReflectHttpOperationExceptionAnalyzer.ts +90 -90
  30. package/src/analyses/ReflectHttpOperationParameterAnalyzer.ts +350 -350
  31. package/src/analyses/ReflectHttpOperationResponseAnalyzer.ts +163 -163
  32. package/src/analyses/ReflectMcpOperationAnalyzer.ts +124 -124
  33. package/src/analyses/ReflectMetadataAnalyzer.ts +44 -44
  34. package/src/analyses/SecurityAnalyzer.ts +25 -25
  35. package/src/analyses/TypedMcpRouteAnalyzer.ts +34 -34
  36. package/src/decorators/OperationMetadata.ts +29 -29
  37. package/src/executable/internal/CommandParser.ts +15 -15
  38. package/src/executable/internal/NestiaConfigLoader.ts +451 -451
  39. package/src/executable/internal/NestiaSdkCommand.ts +124 -124
  40. package/src/executable/internal/NestiaSdkWatcher.ts +342 -342
  41. package/src/executable/sdk.ts +90 -90
  42. package/src/factories/ExpressionFactory.ts +23 -23
  43. package/src/factories/IdentifierFactory.ts +84 -84
  44. package/src/factories/LiteralFactory.ts +54 -54
  45. package/src/factories/StatementFactory.ts +56 -56
  46. package/src/factories/TypeFactory.ts +27 -27
  47. package/src/generates/CloneGenerator.ts +74 -74
  48. package/src/generates/E2eGenerator.ts +32 -32
  49. package/src/generates/SdkGenerator.ts +176 -176
  50. package/src/generates/SwaggerGenerator.ts +342 -342
  51. package/src/generates/internal/E2eFileProgrammer.ts +246 -246
  52. package/src/generates/internal/FilePrinter.ts +57 -57
  53. package/src/generates/internal/ImportDictionary.ts +207 -207
  54. package/src/generates/internal/SdkAliasCollection.ts +280 -280
  55. package/src/generates/internal/SdkDistributionComposer.ts +123 -123
  56. package/src/generates/internal/SdkFileProgrammer.ts +113 -113
  57. package/src/generates/internal/SdkHttpCloneProgrammer.ts +124 -124
  58. package/src/generates/internal/SdkHttpCloneReferencer.ts +131 -131
  59. package/src/generates/internal/SdkHttpFunctionProgrammer.ts +290 -290
  60. package/src/generates/internal/SdkHttpNamespaceProgrammer.ts +507 -507
  61. package/src/generates/internal/SdkHttpParameterProgrammer.ts +163 -163
  62. package/src/generates/internal/SdkHttpRouteProgrammer.ts +109 -109
  63. package/src/generates/internal/SdkHttpSimulationProgrammer.ts +322 -322
  64. package/src/generates/internal/SdkImportWizard.ts +62 -62
  65. package/src/generates/internal/SdkMcpRouteProgrammer.ts +447 -447
  66. package/src/generates/internal/SdkRouteDirectory.ts +21 -21
  67. package/src/generates/internal/SdkTypeProgrammer.ts +389 -389
  68. package/src/generates/internal/SdkTypeTagProgrammer.ts +110 -110
  69. package/src/generates/internal/SdkWebSocketCloneProgrammer.ts +319 -319
  70. package/src/generates/internal/SdkWebSocketNamespaceProgrammer.ts +383 -383
  71. package/src/generates/internal/SdkWebSocketParameterProgrammer.ts +91 -91
  72. package/src/generates/internal/SdkWebSocketRouteProgrammer.ts +304 -304
  73. package/src/generates/internal/SwaggerDescriptionComposer.ts +64 -64
  74. package/src/generates/internal/SwaggerOperationComposer.ts +119 -119
  75. package/src/generates/internal/SwaggerOperationParameterComposer.ts +175 -175
  76. package/src/generates/internal/SwaggerOperationResponseComposer.ts +115 -115
  77. package/src/generates/internal/SwaggerReadonlyArrayEmender.ts +262 -262
  78. package/src/index.ts +4 -4
  79. package/src/internal/legacy.ts +492 -492
  80. package/src/module.ts +4 -4
  81. package/src/structures/INestiaProject.ts +10 -10
  82. package/src/structures/INestiaSdkInput.ts +27 -27
  83. package/src/structures/IOperationMetadata.ts +41 -41
  84. package/src/structures/IReflectController.ts +18 -18
  85. package/src/structures/IReflectHttpOperation.ts +26 -26
  86. package/src/structures/IReflectHttpOperationException.ts +18 -18
  87. package/src/structures/IReflectHttpOperationParameter.ts +79 -79
  88. package/src/structures/IReflectHttpOperationSuccess.ts +18 -18
  89. package/src/structures/IReflectImport.ts +6 -6
  90. package/src/structures/IReflectMcpOperation.ts +38 -38
  91. package/src/structures/IReflectMcpOperationParameter.ts +27 -27
  92. package/src/structures/IReflectOperationError.ts +26 -26
  93. package/src/structures/IReflectType.ts +4 -4
  94. package/src/structures/IReflectWebSocketOperation.ts +17 -17
  95. package/src/structures/ITypedApplication.ts +12 -12
  96. package/src/structures/ITypedHttpRoute.ts +41 -41
  97. package/src/structures/ITypedHttpRouteException.ts +15 -15
  98. package/src/structures/ITypedHttpRouteParameter.ts +41 -41
  99. package/src/structures/ITypedHttpRouteSuccess.ts +18 -18
  100. package/src/structures/ITypedMcpRoute.ts +33 -33
  101. package/src/structures/ITypedWebSocketRoute.ts +24 -24
  102. package/src/structures/ITypedWebSocketRouteParameter.ts +3 -3
  103. package/src/transform.ts +28 -28
  104. package/src/typings/get-function-location.d.ts +7 -7
  105. package/src/utils/ArrayUtil.ts +26 -26
  106. package/src/utils/EmittedJavaScriptPatcher.ts +88 -88
  107. package/src/utils/FileRetriever.ts +22 -22
  108. package/src/utils/HttpResponseContentTypeUtil.ts +30 -30
  109. package/src/utils/MapUtil.ts +14 -14
  110. package/src/utils/PathUtil.ts +10 -10
  111. package/src/utils/SourceFinder.ts +69 -69
  112. package/src/utils/StringUtil.ts +17 -17
  113. package/src/utils/TsConfigReader.ts +108 -108
  114. package/src/utils/TtscExecutor.ts +68 -68
  115. package/src/utils/VersioningStrategy.ts +28 -28
  116. package/src/validators/HttpHeadersValidator.ts +11 -11
  117. package/src/validators/HttpQueryValidator.ts +11 -11
  118. package/src/validators/TextPlainValidator.ts +17 -17
  119. package/README.md +0 -93
@@ -1,322 +1,322 @@
1
- package sdk
2
-
3
- import (
4
- "fmt"
5
- "os"
6
- "path/filepath"
7
- "strings"
8
-
9
- shimast "github.com/microsoft/typescript-go/shim/ast"
10
- shimprinter "github.com/microsoft/typescript-go/shim/printer"
11
- "github.com/samchon/nestia/packages/core/native/plugin"
12
- "github.com/samchon/nestia/packages/core/native/transform"
13
- "github.com/samchon/ttsc/packages/ttsc/driver"
14
- )
15
-
16
- // sdkMetadataNamespace is the import alias the legacy (text/program-mutation)
17
- // path references. The emit-context path lets tsgo's module-transform pick the
18
- // generated alias itself, so it does not use this constant.
19
- const sdkMetadataNamespace = "__OperationMetadata"
20
-
21
- // sdkMetadataModule is the runtime package the injected decorator is imported
22
- // from.
23
- const sdkMetadataModule = "@nestia/sdk"
24
-
25
- // sdkMetadataMember is the decorator factory exported by sdkMetadataModule.
26
- const sdkMetadataMember = "OperationMetadata"
27
-
28
- // init registers the SDK metadata transform with both native entry paths.
29
- //
30
- // ttsc classifies this package (name != "main") as a linked transform and
31
- // statically links it into the `@nestia/core` host binary — but only for
32
- // projects that depend on `@nestia/sdk`. The direct linked-plugin path keeps
33
- // old plugin plans working, while the contributor collectors let the aggregate
34
- // core host run SDK metadata rewrites without starting a second native backend.
35
- func init() {
36
- driver.RegisterPlugin(linkedPlugin{})
37
- transform.RegisterBuildOutputRewriteCollector(collectSDKBuildOutputRewriter)
38
- transform.RegisterSourceRewriteCollector(collectSDKSourceRewriteMap)
39
- transform.RegisterEmitTransformCollector(collectSDKEmitTransform)
40
- }
41
-
42
- type linkedPlugin struct{}
43
-
44
- // ApplyProgram injects the `@OperationMetadata("<json>")` decorator that the
45
- // SDK / Swagger / e2e generators read at runtime, plus the namespace import it
46
- // references.
47
- //
48
- // Insertion is done with synthesized AST nodes (NodeFlagsSynthesized): ttsc's
49
- // emit prints those structurally, without slicing the original source text, so
50
- // no source-text rewrite is needed. The metadata is carried as a single JSON
51
- // string literal — the `OperationMetadata` decorator `JSON.parse`s it — which
52
- // keeps the constructed AST to one literal node instead of a deep object tree.
53
- func (linkedPlugin) ApplyProgram(prog *driver.Program, _ driver.PluginContext) error {
54
- sites, diags := collectNestiaSDKSites(prog)
55
- if len(diags) > 0 {
56
- messages := make([]string, 0, len(diags))
57
- for _, diag := range diags {
58
- messages = append(messages, diag.String(""))
59
- }
60
- return fmt.Errorf("%s", strings.Join(messages, "\n"))
61
- }
62
- if len(sites) == 0 {
63
- return nil
64
- }
65
- factory := shimast.NewNodeFactory(shimast.NodeFactoryHooks{})
66
- touched := map[*shimast.SourceFile]bool{}
67
- for _, site := range sites {
68
- if site.Method == nil || site.File == nil {
69
- continue
70
- }
71
- injectOperationMetadataDecorator(factory, site.Method, site.Metadata)
72
- touched[site.File] = true
73
- }
74
- for file := range touched {
75
- injectOperationMetadataImport(factory, file)
76
- }
77
- return nil
78
- }
79
-
80
- // EmitTransform builds the SDK metadata pass as an emit-phase AST transformer
81
- // (the AST-integration path that mirrors typia's `transform.Transform`). It
82
- // collects every controller-method site once for the program, then returns a
83
- // per-file `driver.PluginTransform` that injects the
84
- // `@<ns>.OperationMetadata("<json>")` decorator plus a namespace import of
85
- // `@nestia/sdk`, both built with ec.Factory and referenced through
86
- // NewGeneratedNameForNode(modSpec), so tsgo's builtin module-transform emits the
87
- // `require("@nestia/sdk")` and aliases the reference itself — no hand-rolled
88
- // `__OperationMetadata` namespace and no text-splice.
89
- //
90
- // Core's build command wires this into `prog.EmitWithPluginTransformers`
91
- // alongside the typia and core transforms, exactly as the typia build command
92
- // wires `nativetransform.Transform`. A site-collection failure is returned as
93
- // diagnostics; the returned transform is nil when there are no sites.
94
- func EmitTransform(prog *driver.Program) (driver.PluginTransform, []transform.Diagnostic) {
95
- sites, diagnostics := collectNestiaSDKSites(prog)
96
- if len(diagnostics) > 0 {
97
- return nil, diagnostics
98
- }
99
- if len(sites) == 0 {
100
- return nil, nil
101
- }
102
- byFile := map[string][]nestiaSDKSite{}
103
- for _, site := range sites {
104
- byFile[filepath.ToSlash(site.FilePath)] = append(byFile[filepath.ToSlash(site.FilePath)], site)
105
- }
106
- return func(ec *shimprinter.EmitContext, sf *shimast.SourceFile) *shimast.SourceFile {
107
- if sf == nil {
108
- return sf
109
- }
110
- fileSites := byFile[filepath.ToSlash(sf.FileName())]
111
- if len(fileSites) == 0 {
112
- return sf
113
- }
114
- // One module-specifier literal per file, shared between the injected
115
- // import declaration and every decorator reference, so
116
- // NewGeneratedNameForNode binds them to the same alias tsgo's
117
- // module-transform emits for the require.
118
- modSpec := ec.Factory.NewStringLiteral(sdkMetadataModule, shimast.TokenFlagsNone)
119
- // Sites were collected from the parse-tree program, so site.Method is an
120
- // original method node. By the time this contributor runs, typia and core
121
- // have already rebuilt every decorated method into a synthetic copy with a
122
- // fresh modifier list, and the original node is no longer in the emitted
123
- // tree. Map each original method to its synthetic counterpart (via the
124
- // emit context's original link) so the injected decorator lands on the
125
- // node that is actually printed; fall back to the original when the method
126
- // was untouched.
127
- synthByOriginal := map[*shimast.Node]*shimast.Node{}
128
- var collect func(node *shimast.Node)
129
- collect = func(node *shimast.Node) {
130
- if node == nil {
131
- return
132
- }
133
- if node.Kind == shimast.KindMethodDeclaration {
134
- if original := ec.MostOriginal(node); original != nil {
135
- synthByOriginal[original] = node
136
- }
137
- }
138
- node.ForEachChild(func(child *shimast.Node) bool {
139
- collect(child)
140
- return false
141
- })
142
- }
143
- collect(sf.AsNode())
144
- for _, site := range fileSites {
145
- if site.Method == nil {
146
- continue
147
- }
148
- method := site.Method
149
- if synth, ok := synthByOriginal[site.Method]; ok {
150
- method = synth
151
- }
152
- injectOperationMetadataDecoratorEC(ec, modSpec, method, site.Metadata)
153
- }
154
- injectOperationMetadataImportEC(ec, modSpec, sf)
155
- return sf
156
- }, nil
157
- }
158
-
159
- // collectSDKEmitTransform exposes EmitTransform to the core `transform`
160
- // subcommand's contributor registry, the node-path twin of
161
- // collectSDKSourceRewriteMap. It is gated by the same NESTIA_SDK_TRANSFORM flag
162
- // so the SDK metadata pass only participates when the caller opts in.
163
- func collectSDKEmitTransform(
164
- prog *driver.Program,
165
- _ plugin.Plan,
166
- ) (driver.PluginTransform, []transform.Diagnostic) {
167
- if shouldRunSDKContributorTransform() == false {
168
- return nil, nil
169
- }
170
- return EmitTransform(prog)
171
- }
172
-
173
- func collectSDKBuildOutputRewriter(
174
- prog *driver.Program,
175
- plan plugin.Plan,
176
- ) (*transform.BuildOutputRewriter, []transform.Diagnostic) {
177
- if shouldRunSDKContributorTransform() == false {
178
- return nil, nil
179
- }
180
- plan.SDK = true
181
- set, diagnostics := collectNestiaSDKBuildRewrites(prog, plan)
182
- return &transform.BuildOutputRewriter{
183
- Len: set.Len,
184
- Apply: set.Apply,
185
- }, diagnostics
186
- }
187
-
188
- func collectSDKSourceRewriteMap(
189
- prog *driver.Program,
190
- plan plugin.Plan,
191
- onlyFile string,
192
- ) (map[string][]transform.SourceRewrite, []transform.Diagnostic) {
193
- if shouldRunSDKContributorTransform() == false {
194
- return map[string][]transform.SourceRewrite{}, nil
195
- }
196
- plan.SDK = true
197
- return collectNestiaSDKSourceRewriteMap(prog, plan, onlyFile)
198
- }
199
-
200
- func shouldRunSDKContributorTransform() bool {
201
- return os.Getenv("NESTIA_SDK_TRANSFORM") == "1"
202
- }
203
-
204
- func synthesized(node *shimast.Node) *shimast.Node {
205
- if node != nil {
206
- node.Flags |= shimast.NodeFlagsSynthesized
207
- }
208
- return node
209
- }
210
-
211
- // injectOperationMetadataDecorator prepends a synthesized
212
- // `@__OperationMetadata.OperationMetadata("<json>")` decorator to a controller
213
- // method. Every site is a method that already carries `@nestia/core`
214
- // decorators, so its modifier list is non-nil and can be mutated in place.
215
- func injectOperationMetadataDecorator(
216
- factory *shimast.NodeFactory,
217
- method *shimast.Node,
218
- metadataJSON string,
219
- ) {
220
- modifiers := method.Modifiers()
221
- if modifiers == nil {
222
- return
223
- }
224
- namespaceID := synthesized(factory.NewIdentifier(sdkMetadataNamespace))
225
- memberID := synthesized(factory.NewIdentifier("OperationMetadata"))
226
- access := synthesized(factory.NewPropertyAccessExpression(
227
- namespaceID,
228
- nil,
229
- memberID,
230
- shimast.NodeFlagsNone,
231
- ))
232
- namespaceID.Parent = access
233
- memberID.Parent = access
234
- argument := synthesized(factory.NewStringLiteral(metadataJSON, shimast.TokenFlagsNone))
235
- call := synthesized(factory.NewCallExpression(
236
- access,
237
- nil,
238
- nil,
239
- factory.NewNodeList([]*shimast.Node{argument}),
240
- shimast.NodeFlagsNone,
241
- ))
242
- access.Parent = call
243
- argument.Parent = call
244
- decorator := synthesized(factory.NewDecorator(call))
245
- call.Parent = decorator
246
- decorator.Parent = method
247
- modifiers.Nodes = append([]*shimast.Node{decorator}, modifiers.Nodes...)
248
- }
249
-
250
- // injectOperationMetadataImport prepends a synthesized
251
- // `import * as __OperationMetadata from "@nestia/sdk"` to a touched file.
252
- func injectOperationMetadataImport(
253
- factory *shimast.NodeFactory,
254
- file *shimast.SourceFile,
255
- ) {
256
- if file == nil || file.Statements == nil {
257
- return
258
- }
259
- namespaceID := synthesized(factory.NewIdentifier(sdkMetadataNamespace))
260
- namespace := synthesized(factory.NewNamespaceImport(namespaceID))
261
- namespaceID.Parent = namespace
262
- clause := synthesized(factory.NewImportClause(shimast.KindUnknown, nil, namespace))
263
- namespace.Parent = clause
264
- specifier := synthesized(factory.NewStringLiteral("@nestia/sdk", shimast.TokenFlagsNone))
265
- declaration := synthesized(factory.NewImportDeclaration(nil, clause, specifier, nil))
266
- clause.Parent = declaration
267
- specifier.Parent = declaration
268
- declaration.Parent = file.AsNode()
269
- file.Statements.Nodes = append([]*shimast.Node{declaration}, file.Statements.Nodes...)
270
- }
271
-
272
- // injectOperationMetadataDecoratorEC is the emit-context (AST-integration)
273
- // twin of injectOperationMetadataDecorator. The decorator's namespace reference
274
- // is `ec.Factory.NewGeneratedNameForNode(modSpec)`, the same generated name
275
- // tsgo's module-transform binds to `require("@nestia/sdk")`, so no hand-rolled
276
- // `__OperationMetadata` alias is needed.
277
- func injectOperationMetadataDecoratorEC(
278
- ec *shimprinter.EmitContext,
279
- modSpec *shimast.Node,
280
- method *shimast.Node,
281
- metadataJSON string,
282
- ) {
283
- modifiers := method.Modifiers()
284
- if modifiers == nil {
285
- return
286
- }
287
- access := ec.Factory.NewPropertyAccessExpression(
288
- ec.Factory.NewGeneratedNameForNode(modSpec),
289
- nil,
290
- ec.Factory.NewIdentifier(sdkMetadataMember),
291
- shimast.NodeFlagsNone,
292
- )
293
- argument := ec.Factory.NewStringLiteral(metadataJSON, shimast.TokenFlagsNone)
294
- call := ec.Factory.NewCallExpression(
295
- access,
296
- nil,
297
- nil,
298
- ec.Factory.NewNodeList([]*shimast.Node{argument}),
299
- shimast.NodeFlagsNone,
300
- )
301
- decorator := ec.Factory.NewDecorator(call)
302
- modifiers.Nodes = append([]*shimast.Node{decorator}, modifiers.Nodes...)
303
- }
304
-
305
- // injectOperationMetadataImportEC is the emit-context twin of
306
- // injectOperationMetadataImport. It prepends `import * as <gen> from
307
- // "@nestia/sdk"` built with ec.Factory, reusing modSpec so the namespace alias
308
- // matches every decorator reference; tsgo's module-transform turns it into the
309
- // `const <gen> = require("@nestia/sdk")` binding.
310
- func injectOperationMetadataImportEC(
311
- ec *shimprinter.EmitContext,
312
- modSpec *shimast.Node,
313
- file *shimast.SourceFile,
314
- ) {
315
- if file == nil || file.Statements == nil {
316
- return
317
- }
318
- namespace := ec.Factory.NewNamespaceImport(ec.Factory.NewGeneratedNameForNode(modSpec))
319
- clause := ec.Factory.NewImportClause(shimast.KindUnknown, nil, namespace)
320
- declaration := ec.Factory.NewImportDeclaration(nil, clause, modSpec, nil)
321
- file.Statements.Nodes = append([]*shimast.Node{declaration}, file.Statements.Nodes...)
322
- }
1
+ package sdk
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+
9
+ shimast "github.com/microsoft/typescript-go/shim/ast"
10
+ shimprinter "github.com/microsoft/typescript-go/shim/printer"
11
+ "github.com/samchon/nestia/packages/core/native/plugin"
12
+ "github.com/samchon/nestia/packages/core/native/transform"
13
+ "github.com/samchon/ttsc/packages/ttsc/driver"
14
+ )
15
+
16
+ // sdkMetadataNamespace is the import alias the legacy (text/program-mutation)
17
+ // path references. The emit-context path lets tsgo's module-transform pick the
18
+ // generated alias itself, so it does not use this constant.
19
+ const sdkMetadataNamespace = "__OperationMetadata"
20
+
21
+ // sdkMetadataModule is the runtime package the injected decorator is imported
22
+ // from.
23
+ const sdkMetadataModule = "@nestia/sdk"
24
+
25
+ // sdkMetadataMember is the decorator factory exported by sdkMetadataModule.
26
+ const sdkMetadataMember = "OperationMetadata"
27
+
28
+ // init registers the SDK metadata transform with both native entry paths.
29
+ //
30
+ // ttsc classifies this package (name != "main") as a linked transform and
31
+ // statically links it into the `@nestia/core` host binary — but only for
32
+ // projects that depend on `@nestia/sdk`. The direct linked-plugin path keeps
33
+ // old plugin plans working, while the contributor collectors let the aggregate
34
+ // core host run SDK metadata rewrites without starting a second native backend.
35
+ func init() {
36
+ driver.RegisterPlugin(linkedPlugin{})
37
+ transform.RegisterBuildOutputRewriteCollector(collectSDKBuildOutputRewriter)
38
+ transform.RegisterSourceRewriteCollector(collectSDKSourceRewriteMap)
39
+ transform.RegisterEmitTransformCollector(collectSDKEmitTransform)
40
+ }
41
+
42
+ type linkedPlugin struct{}
43
+
44
+ // ApplyProgram injects the `@OperationMetadata("<json>")` decorator that the
45
+ // SDK / Swagger / e2e generators read at runtime, plus the namespace import it
46
+ // references.
47
+ //
48
+ // Insertion is done with synthesized AST nodes (NodeFlagsSynthesized): ttsc's
49
+ // emit prints those structurally, without slicing the original source text, so
50
+ // no source-text rewrite is needed. The metadata is carried as a single JSON
51
+ // string literal — the `OperationMetadata` decorator `JSON.parse`s it — which
52
+ // keeps the constructed AST to one literal node instead of a deep object tree.
53
+ func (linkedPlugin) ApplyProgram(prog *driver.Program, _ driver.PluginContext) error {
54
+ sites, diags := collectNestiaSDKSites(prog)
55
+ if len(diags) > 0 {
56
+ messages := make([]string, 0, len(diags))
57
+ for _, diag := range diags {
58
+ messages = append(messages, diag.String(""))
59
+ }
60
+ return fmt.Errorf("%s", strings.Join(messages, "\n"))
61
+ }
62
+ if len(sites) == 0 {
63
+ return nil
64
+ }
65
+ factory := shimast.NewNodeFactory(shimast.NodeFactoryHooks{})
66
+ touched := map[*shimast.SourceFile]bool{}
67
+ for _, site := range sites {
68
+ if site.Method == nil || site.File == nil {
69
+ continue
70
+ }
71
+ injectOperationMetadataDecorator(factory, site.Method, site.Metadata)
72
+ touched[site.File] = true
73
+ }
74
+ for file := range touched {
75
+ injectOperationMetadataImport(factory, file)
76
+ }
77
+ return nil
78
+ }
79
+
80
+ // EmitTransform builds the SDK metadata pass as an emit-phase AST transformer
81
+ // (the AST-integration path that mirrors typia's `transform.Transform`). It
82
+ // collects every controller-method site once for the program, then returns a
83
+ // per-file `driver.PluginTransform` that injects the
84
+ // `@<ns>.OperationMetadata("<json>")` decorator plus a namespace import of
85
+ // `@nestia/sdk`, both built with ec.Factory and referenced through
86
+ // NewGeneratedNameForNode(modSpec), so tsgo's builtin module-transform emits the
87
+ // `require("@nestia/sdk")` and aliases the reference itself — no hand-rolled
88
+ // `__OperationMetadata` namespace and no text-splice.
89
+ //
90
+ // Core's build command wires this into `prog.EmitWithPluginTransformers`
91
+ // alongside the typia and core transforms, exactly as the typia build command
92
+ // wires `nativetransform.Transform`. A site-collection failure is returned as
93
+ // diagnostics; the returned transform is nil when there are no sites.
94
+ func EmitTransform(prog *driver.Program) (driver.PluginTransform, []transform.Diagnostic) {
95
+ sites, diagnostics := collectNestiaSDKSites(prog)
96
+ if len(diagnostics) > 0 {
97
+ return nil, diagnostics
98
+ }
99
+ if len(sites) == 0 {
100
+ return nil, nil
101
+ }
102
+ byFile := map[string][]nestiaSDKSite{}
103
+ for _, site := range sites {
104
+ byFile[filepath.ToSlash(site.FilePath)] = append(byFile[filepath.ToSlash(site.FilePath)], site)
105
+ }
106
+ return func(ec *shimprinter.EmitContext, sf *shimast.SourceFile) *shimast.SourceFile {
107
+ if sf == nil {
108
+ return sf
109
+ }
110
+ fileSites := byFile[filepath.ToSlash(sf.FileName())]
111
+ if len(fileSites) == 0 {
112
+ return sf
113
+ }
114
+ // One module-specifier literal per file, shared between the injected
115
+ // import declaration and every decorator reference, so
116
+ // NewGeneratedNameForNode binds them to the same alias tsgo's
117
+ // module-transform emits for the require.
118
+ modSpec := ec.Factory.NewStringLiteral(sdkMetadataModule, shimast.TokenFlagsNone)
119
+ // Sites were collected from the parse-tree program, so site.Method is an
120
+ // original method node. By the time this contributor runs, typia and core
121
+ // have already rebuilt every decorated method into a synthetic copy with a
122
+ // fresh modifier list, and the original node is no longer in the emitted
123
+ // tree. Map each original method to its synthetic counterpart (via the
124
+ // emit context's original link) so the injected decorator lands on the
125
+ // node that is actually printed; fall back to the original when the method
126
+ // was untouched.
127
+ synthByOriginal := map[*shimast.Node]*shimast.Node{}
128
+ var collect func(node *shimast.Node)
129
+ collect = func(node *shimast.Node) {
130
+ if node == nil {
131
+ return
132
+ }
133
+ if node.Kind == shimast.KindMethodDeclaration {
134
+ if original := ec.MostOriginal(node); original != nil {
135
+ synthByOriginal[original] = node
136
+ }
137
+ }
138
+ node.ForEachChild(func(child *shimast.Node) bool {
139
+ collect(child)
140
+ return false
141
+ })
142
+ }
143
+ collect(sf.AsNode())
144
+ for _, site := range fileSites {
145
+ if site.Method == nil {
146
+ continue
147
+ }
148
+ method := site.Method
149
+ if synth, ok := synthByOriginal[site.Method]; ok {
150
+ method = synth
151
+ }
152
+ injectOperationMetadataDecoratorEC(ec, modSpec, method, site.Metadata)
153
+ }
154
+ injectOperationMetadataImportEC(ec, modSpec, sf)
155
+ return sf
156
+ }, nil
157
+ }
158
+
159
+ // collectSDKEmitTransform exposes EmitTransform to the core `transform`
160
+ // subcommand's contributor registry, the node-path twin of
161
+ // collectSDKSourceRewriteMap. It is gated by the same NESTIA_SDK_TRANSFORM flag
162
+ // so the SDK metadata pass only participates when the caller opts in.
163
+ func collectSDKEmitTransform(
164
+ prog *driver.Program,
165
+ _ plugin.Plan,
166
+ ) (driver.PluginTransform, []transform.Diagnostic) {
167
+ if shouldRunSDKContributorTransform() == false {
168
+ return nil, nil
169
+ }
170
+ return EmitTransform(prog)
171
+ }
172
+
173
+ func collectSDKBuildOutputRewriter(
174
+ prog *driver.Program,
175
+ plan plugin.Plan,
176
+ ) (*transform.BuildOutputRewriter, []transform.Diagnostic) {
177
+ if shouldRunSDKContributorTransform() == false {
178
+ return nil, nil
179
+ }
180
+ plan.SDK = true
181
+ set, diagnostics := collectNestiaSDKBuildRewrites(prog, plan)
182
+ return &transform.BuildOutputRewriter{
183
+ Len: set.Len,
184
+ Apply: set.Apply,
185
+ }, diagnostics
186
+ }
187
+
188
+ func collectSDKSourceRewriteMap(
189
+ prog *driver.Program,
190
+ plan plugin.Plan,
191
+ onlyFile string,
192
+ ) (map[string][]transform.SourceRewrite, []transform.Diagnostic) {
193
+ if shouldRunSDKContributorTransform() == false {
194
+ return map[string][]transform.SourceRewrite{}, nil
195
+ }
196
+ plan.SDK = true
197
+ return collectNestiaSDKSourceRewriteMap(prog, plan, onlyFile)
198
+ }
199
+
200
+ func shouldRunSDKContributorTransform() bool {
201
+ return os.Getenv("NESTIA_SDK_TRANSFORM") == "1"
202
+ }
203
+
204
+ func synthesized(node *shimast.Node) *shimast.Node {
205
+ if node != nil {
206
+ node.Flags |= shimast.NodeFlagsSynthesized
207
+ }
208
+ return node
209
+ }
210
+
211
+ // injectOperationMetadataDecorator prepends a synthesized
212
+ // `@__OperationMetadata.OperationMetadata("<json>")` decorator to a controller
213
+ // method. Every site is a method that already carries `@nestia/core`
214
+ // decorators, so its modifier list is non-nil and can be mutated in place.
215
+ func injectOperationMetadataDecorator(
216
+ factory *shimast.NodeFactory,
217
+ method *shimast.Node,
218
+ metadataJSON string,
219
+ ) {
220
+ modifiers := method.Modifiers()
221
+ if modifiers == nil {
222
+ return
223
+ }
224
+ namespaceID := synthesized(factory.NewIdentifier(sdkMetadataNamespace))
225
+ memberID := synthesized(factory.NewIdentifier("OperationMetadata"))
226
+ access := synthesized(factory.NewPropertyAccessExpression(
227
+ namespaceID,
228
+ nil,
229
+ memberID,
230
+ shimast.NodeFlagsNone,
231
+ ))
232
+ namespaceID.Parent = access
233
+ memberID.Parent = access
234
+ argument := synthesized(factory.NewStringLiteral(metadataJSON, shimast.TokenFlagsNone))
235
+ call := synthesized(factory.NewCallExpression(
236
+ access,
237
+ nil,
238
+ nil,
239
+ factory.NewNodeList([]*shimast.Node{argument}),
240
+ shimast.NodeFlagsNone,
241
+ ))
242
+ access.Parent = call
243
+ argument.Parent = call
244
+ decorator := synthesized(factory.NewDecorator(call))
245
+ call.Parent = decorator
246
+ decorator.Parent = method
247
+ modifiers.Nodes = append([]*shimast.Node{decorator}, modifiers.Nodes...)
248
+ }
249
+
250
+ // injectOperationMetadataImport prepends a synthesized
251
+ // `import * as __OperationMetadata from "@nestia/sdk"` to a touched file.
252
+ func injectOperationMetadataImport(
253
+ factory *shimast.NodeFactory,
254
+ file *shimast.SourceFile,
255
+ ) {
256
+ if file == nil || file.Statements == nil {
257
+ return
258
+ }
259
+ namespaceID := synthesized(factory.NewIdentifier(sdkMetadataNamespace))
260
+ namespace := synthesized(factory.NewNamespaceImport(namespaceID))
261
+ namespaceID.Parent = namespace
262
+ clause := synthesized(factory.NewImportClause(shimast.KindUnknown, nil, namespace))
263
+ namespace.Parent = clause
264
+ specifier := synthesized(factory.NewStringLiteral("@nestia/sdk", shimast.TokenFlagsNone))
265
+ declaration := synthesized(factory.NewImportDeclaration(nil, clause, specifier, nil))
266
+ clause.Parent = declaration
267
+ specifier.Parent = declaration
268
+ declaration.Parent = file.AsNode()
269
+ file.Statements.Nodes = append([]*shimast.Node{declaration}, file.Statements.Nodes...)
270
+ }
271
+
272
+ // injectOperationMetadataDecoratorEC is the emit-context (AST-integration)
273
+ // twin of injectOperationMetadataDecorator. The decorator's namespace reference
274
+ // is `ec.Factory.NewGeneratedNameForNode(modSpec)`, the same generated name
275
+ // tsgo's module-transform binds to `require("@nestia/sdk")`, so no hand-rolled
276
+ // `__OperationMetadata` alias is needed.
277
+ func injectOperationMetadataDecoratorEC(
278
+ ec *shimprinter.EmitContext,
279
+ modSpec *shimast.Node,
280
+ method *shimast.Node,
281
+ metadataJSON string,
282
+ ) {
283
+ modifiers := method.Modifiers()
284
+ if modifiers == nil {
285
+ return
286
+ }
287
+ access := ec.Factory.NewPropertyAccessExpression(
288
+ ec.Factory.NewGeneratedNameForNode(modSpec),
289
+ nil,
290
+ ec.Factory.NewIdentifier(sdkMetadataMember),
291
+ shimast.NodeFlagsNone,
292
+ )
293
+ argument := ec.Factory.NewStringLiteral(metadataJSON, shimast.TokenFlagsNone)
294
+ call := ec.Factory.NewCallExpression(
295
+ access,
296
+ nil,
297
+ nil,
298
+ ec.Factory.NewNodeList([]*shimast.Node{argument}),
299
+ shimast.NodeFlagsNone,
300
+ )
301
+ decorator := ec.Factory.NewDecorator(call)
302
+ modifiers.Nodes = append([]*shimast.Node{decorator}, modifiers.Nodes...)
303
+ }
304
+
305
+ // injectOperationMetadataImportEC is the emit-context twin of
306
+ // injectOperationMetadataImport. It prepends `import * as <gen> from
307
+ // "@nestia/sdk"` built with ec.Factory, reusing modSpec so the namespace alias
308
+ // matches every decorator reference; tsgo's module-transform turns it into the
309
+ // `const <gen> = require("@nestia/sdk")` binding.
310
+ func injectOperationMetadataImportEC(
311
+ ec *shimprinter.EmitContext,
312
+ modSpec *shimast.Node,
313
+ file *shimast.SourceFile,
314
+ ) {
315
+ if file == nil || file.Statements == nil {
316
+ return
317
+ }
318
+ namespace := ec.Factory.NewNamespaceImport(ec.Factory.NewGeneratedNameForNode(modSpec))
319
+ clause := ec.Factory.NewImportClause(shimast.KindUnknown, nil, namespace)
320
+ declaration := ec.Factory.NewImportDeclaration(nil, clause, modSpec, nil)
321
+ file.Statements.Nodes = append([]*shimast.Node{declaration}, file.Statements.Nodes...)
322
+ }