@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.
- package/MIGRATION.md +20 -0
- package/README.md +1 -1
- package/lib/adaptors/McpAdaptor.d.ts +29 -3
- package/lib/adaptors/McpAdaptor.js +150 -24
- package/lib/adaptors/McpAdaptor.js.map +1 -1
- package/lib/adaptors/WebSocketAdaptor.d.ts +37 -0
- package/lib/adaptors/WebSocketAdaptor.js +184 -48
- package/lib/adaptors/WebSocketAdaptor.js.map +1 -1
- package/lib/adaptors/internal/RoutePathMatcher.d.ts +1 -0
- package/lib/adaptors/internal/RoutePathMatcher.js +88 -0
- package/lib/adaptors/internal/RoutePathMatcher.js.map +1 -0
- package/lib/adaptors/internal/external_context.d.ts +1 -0
- package/lib/adaptors/internal/external_context.js +56 -0
- package/lib/adaptors/internal/external_context.js.map +1 -0
- package/lib/decorators/DynamicModule.d.ts +12 -0
- package/lib/decorators/DynamicModule.js +12 -0
- package/lib/decorators/DynamicModule.js.map +1 -1
- package/lib/decorators/EncryptedBody.d.ts +14 -6
- package/lib/decorators/EncryptedBody.js +39 -16
- package/lib/decorators/EncryptedBody.js.map +1 -1
- package/lib/decorators/EncryptedController.d.ts +6 -1
- package/lib/decorators/EncryptedController.js +7 -2
- package/lib/decorators/EncryptedController.js.map +1 -1
- package/lib/decorators/EncryptedModule.d.ts +25 -6
- package/lib/decorators/EncryptedModule.js +87 -18
- package/lib/decorators/EncryptedModule.js.map +1 -1
- package/lib/decorators/EncryptedRoute.d.ts +20 -11
- package/lib/decorators/EncryptedRoute.js +27 -13
- package/lib/decorators/EncryptedRoute.js.map +1 -1
- package/lib/decorators/HumanRoute.d.ts +5 -0
- package/lib/decorators/HumanRoute.js +5 -0
- package/lib/decorators/HumanRoute.js.map +1 -1
- package/lib/decorators/McpRoute.d.ts +31 -19
- package/lib/decorators/McpRoute.js +12 -1
- package/lib/decorators/McpRoute.js.map +1 -1
- package/lib/decorators/PlainBody.d.ts +7 -1
- package/lib/decorators/PlainBody.js +16 -8
- package/lib/decorators/PlainBody.js.map +1 -1
- package/lib/decorators/SwaggerCustomizer.d.ts +15 -0
- package/lib/decorators/SwaggerCustomizer.js +5 -0
- package/lib/decorators/SwaggerCustomizer.js.map +1 -1
- package/lib/decorators/SwaggerExample.d.ts +51 -22
- package/lib/decorators/SwaggerExample.js +35 -22
- package/lib/decorators/SwaggerExample.js.map +1 -1
- package/lib/decorators/TypedBody.d.ts +10 -3
- package/lib/decorators/TypedBody.js +20 -10
- package/lib/decorators/TypedBody.js.map +1 -1
- package/lib/decorators/TypedException.d.ts +10 -0
- package/lib/decorators/TypedFormData.d.ts +81 -9
- package/lib/decorators/TypedFormData.js +121 -31
- package/lib/decorators/TypedFormData.js.map +1 -1
- package/lib/decorators/TypedHeaders.d.ts +5 -0
- package/lib/decorators/TypedHeaders.js +5 -0
- package/lib/decorators/TypedHeaders.js.map +1 -1
- package/lib/decorators/TypedParam.d.ts +12 -3
- package/lib/decorators/TypedParam.js +12 -3
- package/lib/decorators/TypedParam.js.map +1 -1
- package/lib/decorators/TypedQuery.d.ts +17 -1
- package/lib/decorators/TypedQuery.js +25 -4
- package/lib/decorators/TypedQuery.js.map +1 -1
- package/lib/decorators/TypedRoute.d.ts +25 -10
- package/lib/decorators/TypedRoute.js +19 -10
- package/lib/decorators/TypedRoute.js.map +1 -1
- package/lib/decorators/WebSocketRoute.d.ts +18 -1
- package/lib/decorators/WebSocketRoute.js +18 -1
- package/lib/decorators/WebSocketRoute.js.map +1 -1
- package/lib/decorators/doNotThrowTransformError.d.ts +14 -0
- package/lib/decorators/doNotThrowTransformError.js +14 -0
- package/lib/decorators/doNotThrowTransformError.js.map +1 -1
- package/lib/decorators/internal/EncryptedConstant.js +13 -2
- package/lib/decorators/internal/EncryptedConstant.js.map +1 -1
- package/lib/decorators/internal/IRequestBodyValidation.d.ts +66 -0
- package/lib/decorators/internal/IRequestBodyValidation.js +3 -0
- package/lib/decorators/internal/IRequestBodyValidation.js.map +1 -0
- package/lib/decorators/internal/IWebSocketRouteReflect.d.ts +93 -1
- package/lib/decorators/internal/get_encryption_password.d.ts +1 -0
- package/lib/decorators/internal/get_encryption_password.js +14 -0
- package/lib/decorators/internal/get_encryption_password.js.map +1 -0
- package/lib/decorators/internal/get_path_and_querify.js +23 -9
- package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
- package/lib/decorators/internal/get_path_and_stringify.js +3 -7
- package/lib/decorators/internal/get_path_and_stringify.js.map +1 -1
- package/lib/decorators/internal/get_text_body.js +9 -3
- package/lib/decorators/internal/get_text_body.js.map +1 -1
- package/lib/decorators/internal/group_entries.d.ts +1 -0
- package/lib/decorators/internal/group_entries.js +19 -0
- package/lib/decorators/internal/group_entries.js.map +1 -0
- package/lib/decorators/internal/load_controller.d.ts +14 -0
- package/lib/decorators/internal/load_controller.js +14 -0
- package/lib/decorators/internal/load_controller.js.map +1 -1
- package/lib/decorators/internal/split_route_arguments.d.ts +1 -0
- package/lib/decorators/internal/split_route_arguments.js +22 -0
- package/lib/decorators/internal/split_route_arguments.js.map +1 -0
- package/lib/decorators/internal/validate_request_body.js +68 -25
- package/lib/decorators/internal/validate_request_body.js.map +1 -1
- package/lib/decorators/internal/validate_request_form_data.js +3 -1
- package/lib/decorators/internal/validate_request_form_data.js.map +1 -1
- package/lib/decorators/internal/validate_request_headers.js +2 -1
- package/lib/decorators/internal/validate_request_headers.js.map +1 -1
- package/lib/decorators/internal/validate_request_query.js +3 -1
- package/lib/decorators/internal/validate_request_query.js.map +1 -1
- package/lib/options/IRequestBodyValidator.d.ts +71 -0
- package/lib/options/IRequestFormDataProps.d.ts +80 -0
- package/lib/options/IRequestHeadersValidator.d.ts +67 -0
- package/lib/options/IRequestQueryValidator.d.ts +68 -0
- package/lib/options/IResponseBodyQuerifier.d.ts +125 -1
- package/lib/options/IResponseBodyStringifier.d.ts +107 -0
- package/lib/typings/Creator.d.ts +10 -0
- package/lib/utils/ArrayUtil.d.ts +21 -0
- package/lib/utils/ArrayUtil.js +22 -1
- package/lib/utils/ArrayUtil.js.map +1 -1
- package/lib/utils/ExceptionManager.d.ts +43 -4
- package/lib/utils/ExceptionManager.js +44 -6
- package/lib/utils/ExceptionManager.js.map +1 -1
- package/lib/utils/SourceFinder.d.ts +22 -0
- package/lib/utils/SourceFinder.js +60 -10
- package/lib/utils/SourceFinder.js.map +1 -1
- package/lib/utils/VersioningStrategy.d.ts +45 -2
- package/lib/utils/VersioningStrategy.js +40 -7
- package/lib/utils/VersioningStrategy.js.map +1 -1
- package/native/go.mod +1 -1
- package/native/go.sum +2 -2
- package/native/plugin/plan.go +68 -2
- package/native/transform/ast.go +23 -3
- package/native/transform/build.go +51 -57
- package/native/transform/buildoutput/output.go +147 -0
- package/native/transform/contributor.go +9 -0
- package/native/transform/core_querify.go +63 -9
- package/native/transform/core_transform.go +180 -98
- package/native/transform/core_websocket.go +111 -14
- package/native/transform/node_transform.go +17 -8
- package/native/transform/package.go +55 -0
- package/native/transform/printer.go +1 -3
- package/native/transform/run.go +18 -0
- package/native/transform/schemaprojection/project.go +213 -0
- package/native/transform/transform.go +29 -18
- package/native/transform/typia_fast.go +6 -0
- package/native/transform.cjs +49 -0
- package/package.json +13 -15
- package/src/adaptors/McpAdaptor.ts +215 -33
- package/src/adaptors/WebSocketAdaptor.ts +202 -52
- package/src/adaptors/internal/RoutePathMatcher.ts +91 -0
- package/src/adaptors/internal/external_context.ts +81 -0
- package/src/decorators/DynamicModule.ts +12 -0
- package/src/decorators/EncryptedBody.ts +39 -15
- package/src/decorators/EncryptedController.ts +12 -3
- package/src/decorators/EncryptedModule.ts +89 -18
- package/src/decorators/EncryptedRoute.ts +27 -16
- package/src/decorators/HumanRoute.ts +5 -0
- package/src/decorators/McpRoute.ts +31 -19
- package/src/decorators/PlainBody.ts +20 -10
- package/src/decorators/SwaggerCustomizer.ts +15 -0
- package/src/decorators/SwaggerExample.ts +51 -22
- package/src/decorators/TypedBody.ts +18 -9
- package/src/decorators/TypedException.ts +10 -0
- package/src/decorators/TypedFormData.ts +207 -45
- package/src/decorators/TypedHeaders.ts +5 -0
- package/src/decorators/TypedParam.ts +12 -3
- package/src/decorators/TypedQuery.ts +35 -7
- package/src/decorators/TypedRoute.ts +25 -10
- package/src/decorators/WebSocketRoute.ts +18 -1
- package/src/decorators/doNotThrowTransformError.ts +14 -0
- package/src/decorators/internal/EncryptedConstant.ts +14 -1
- package/src/decorators/internal/IMcpRouteReflect.ts +7 -1
- package/src/decorators/internal/IRequestBodyValidation.ts +65 -0
- package/src/decorators/internal/IWebSocketRouteReflect.ts +95 -1
- package/src/decorators/internal/get_encryption_password.ts +19 -0
- package/src/decorators/internal/get_path_and_querify.ts +31 -10
- package/src/decorators/internal/get_path_and_stringify.ts +3 -9
- package/src/decorators/internal/get_text_body.ts +8 -4
- package/src/decorators/internal/group_entries.ts +17 -0
- package/src/decorators/internal/load_controller.ts +14 -0
- package/src/decorators/internal/split_route_arguments.ts +20 -0
- package/src/decorators/internal/validate_request_body.ts +87 -34
- package/src/decorators/internal/validate_request_form_data.ts +3 -1
- package/src/decorators/internal/validate_request_headers.ts +2 -2
- package/src/decorators/internal/validate_request_query.ts +3 -2
- package/src/options/IRequestBodyValidator.ts +71 -0
- package/src/options/IRequestFormDataProps.ts +80 -0
- package/src/options/IRequestHeadersValidator.ts +67 -0
- package/src/options/IRequestQueryValidator.ts +68 -0
- package/src/options/IResponseBodyQuerifier.ts +126 -1
- package/src/options/IResponseBodyStringifier.ts +107 -0
- package/src/typings/Creator.ts +10 -0
- package/src/utils/ArrayUtil.ts +22 -3
- package/src/utils/ExceptionManager.ts +50 -6
- package/src/utils/SourceFinder.ts +62 -10
- package/src/utils/VersioningStrategy.ts +55 -11
- package/lib/transform.d.ts +0 -10
- package/lib/transform.js +0 -25
- package/lib/transform.js.map +0 -1
- package/src/transform.ts +0 -26
package/native/plugin/plan.go
CHANGED
|
@@ -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
|
|
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"
|
package/native/transform/ast.go
CHANGED
|
@@ -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()
|
|
26
|
+
return strings.Trim(shimast.NodeText(node.Name()), "\"'")
|
|
16
27
|
}
|
|
17
28
|
|
|
18
|
-
// NodeText returns the
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
219
|
-
|
|
220
|
-
|
|
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(
|
|
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,
|
|
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: "
|
|
154
|
-
Value: nestiaCoreCall("
|
|
157
|
+
Name: "result",
|
|
158
|
+
Value: nestiaCoreCall("validate", nestiaCoreFactory.NewIdentifier("input")),
|
|
155
159
|
}),
|
|
156
160
|
nestiaCoreFactory.NewReturnStatement(nestiaCoreFactory.NewAsExpression(
|
|
157
|
-
|
|
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(
|