@nestia/sdk 12.0.0 → 13.0.0
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/assets/bundle/distribute/package.json +1 -1
- package/assets/config/nestia.config.ts +19 -19
- package/lib/NestiaSwaggerComposer.js +2 -0
- package/lib/NestiaSwaggerComposer.js.map +1 -1
- package/lib/analyses/ImportAnalyzer.d.ts +6 -5
- package/lib/analyses/ImportAnalyzer.js +30 -15
- package/lib/analyses/ImportAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectControllerAnalyzer.js +18 -9
- package/lib/analyses/ReflectControllerAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectHttpOperationAnalyzer.d.ts +1 -1
- package/lib/analyses/ReflectHttpOperationAnalyzer.js +8 -6
- package/lib/analyses/ReflectHttpOperationAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectHttpOperationExceptionAnalyzer.d.ts +1 -1
- package/lib/analyses/ReflectHttpOperationExceptionAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectHttpOperationParameterAnalyzer.d.ts +1 -1
- package/lib/analyses/ReflectHttpOperationParameterAnalyzer.js +1 -1
- package/lib/analyses/ReflectHttpOperationParameterAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectWebSocketOperationAnalyzer.d.ts +1 -1
- package/lib/analyses/ReflectWebSocketOperationAnalyzer.js +10 -6
- package/lib/analyses/ReflectWebSocketOperationAnalyzer.js.map +1 -1
- package/lib/analyses/TypedHttpRouteAnalyzer.js +13 -7
- package/lib/analyses/TypedHttpRouteAnalyzer.js.map +1 -1
- package/lib/executable/internal/NestiaSdkCommand.js +7 -4
- package/lib/executable/internal/NestiaSdkCommand.js.map +1 -1
- package/lib/generates/E2eGenerator.js +1 -4
- package/lib/generates/E2eGenerator.js.map +1 -1
- package/lib/generates/SdkGenerator.js +7 -8
- package/lib/generates/SdkGenerator.js.map +1 -1
- package/lib/generates/SwaggerGenerator.js +8 -9
- package/lib/generates/SwaggerGenerator.js.map +1 -1
- package/lib/generates/internal/ImportDictionary.js +14 -6
- package/lib/generates/internal/ImportDictionary.js.map +1 -1
- package/lib/generates/internal/SdkDistributionComposer.js +27 -22
- package/lib/generates/internal/SdkDistributionComposer.js.map +1 -1
- package/lib/generates/internal/SdkFileProgrammer.js +1 -4
- package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkHttpCloneProgrammer.js +3 -3
- package/lib/generates/internal/SdkHttpCloneProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkHttpCloneReferencer.js +14 -10
- package/lib/generates/internal/SdkHttpCloneReferencer.js.map +1 -1
- package/lib/generates/internal/SdkTypeProgrammer.js +16 -8
- package/lib/generates/internal/SdkTypeProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkWebSocketCloneProgrammer.js +6 -3
- package/lib/generates/internal/SdkWebSocketCloneProgrammer.js.map +1 -1
- package/lib/generates/internal/SwaggerOperationComposer.d.ts +1 -1
- package/lib/generates/internal/SwaggerOperationComposer.js +35 -7
- package/lib/generates/internal/SwaggerOperationComposer.js.map +1 -1
- package/lib/generates/internal/SwaggerOperationResponseComposer.js +68 -26
- package/lib/generates/internal/SwaggerOperationResponseComposer.js.map +1 -1
- package/lib/internal/legacy.d.ts +44 -46
- package/lib/internal/legacy.js +35 -48
- package/lib/internal/legacy.js.map +1 -1
- package/lib/structures/IReflectHttpOperationException.d.ts +1 -1
- package/lib/structures/IReflectHttpOperationParameter.d.ts +1 -1
- package/lib/structures/IReflectImport.d.ts +2 -0
- package/lib/structures/ITypedHttpRouteParameter.d.ts +1 -1
- package/lib/utils/StringUtil.d.ts +32 -0
- package/lib/utils/StringUtil.js +40 -3
- package/lib/utils/StringUtil.js.map +1 -1
- package/lib/utils/TsConfigReader.js +3 -1
- package/lib/utils/TsConfigReader.js.map +1 -1
- package/lib/validators/HttpHeadersValidator.d.ts +3 -2
- package/lib/validators/HttpHeadersValidator.js +3 -2
- package/lib/validators/HttpHeadersValidator.js.map +1 -1
- package/lib/validators/HttpQueryValidator.d.ts +4 -4
- package/lib/validators/HttpQueryValidator.js +4 -4
- package/native/go.mod +1 -1
- package/native/go.sum +32 -32
- package/native/sdk/register.go +6 -35
- package/native/sdk/sdk_transform.go +80 -304
- package/package.json +9 -9
- package/src/NestiaSwaggerComposer.ts +2 -0
- package/src/analyses/ImportAnalyzer.ts +26 -18
- package/src/analyses/ReflectControllerAnalyzer.ts +19 -10
- package/src/analyses/ReflectHttpOperationAnalyzer.ts +5 -2
- package/src/analyses/ReflectHttpOperationExceptionAnalyzer.ts +2 -2
- package/src/analyses/ReflectHttpOperationParameterAnalyzer.ts +3 -3
- package/src/analyses/ReflectWebSocketOperationAnalyzer.ts +14 -7
- package/src/analyses/TypedHttpRouteAnalyzer.ts +74 -35
- package/src/executable/internal/NestiaSdkCommand.ts +8 -5
- package/src/generates/E2eGenerator.ts +1 -3
- package/src/generates/SdkGenerator.ts +7 -7
- package/src/generates/SwaggerGenerator.ts +15 -7
- package/src/generates/internal/ImportDictionary.ts +23 -11
- package/src/generates/internal/SdkDistributionComposer.ts +25 -23
- package/src/generates/internal/SdkFileProgrammer.ts +1 -3
- package/src/generates/internal/SdkHttpCloneProgrammer.ts +3 -3
- package/src/generates/internal/SdkHttpCloneReferencer.ts +16 -11
- package/src/generates/internal/SdkTypeProgrammer.ts +18 -10
- package/src/generates/internal/SdkWebSocketCloneProgrammer.ts +4 -3
- package/src/generates/internal/SwaggerOperationComposer.ts +40 -8
- package/src/generates/internal/SwaggerOperationResponseComposer.ts +72 -26
- package/src/internal/legacy.ts +83 -73
- package/src/structures/IReflectHttpOperationException.ts +1 -1
- package/src/structures/IReflectHttpOperationParameter.ts +1 -1
- package/src/structures/IReflectImport.ts +2 -0
- package/src/structures/ITypedApplication.ts +0 -1
- package/src/structures/ITypedHttpRouteException.ts +0 -1
- package/src/structures/ITypedHttpRouteParameter.ts +1 -1
- package/src/utils/StringUtil.ts +43 -3
- package/src/utils/TsConfigReader.ts +11 -3
- package/src/validators/HttpHeadersValidator.ts +3 -2
- package/src/validators/HttpQueryValidator.ts +4 -4
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
sizeOf,
|
|
19
19
|
} from "../../internal/legacy";
|
|
20
20
|
import { INestiaProject } from "../../structures/INestiaProject";
|
|
21
|
+
import { StringUtil } from "../../utils/StringUtil";
|
|
21
22
|
import { FilePrinter } from "./FilePrinter";
|
|
22
23
|
import { ImportDictionary } from "./ImportDictionary";
|
|
23
24
|
import { SdkTypeTagProgrammer } from "./SdkTypeTagProgrammer";
|
|
@@ -53,13 +54,12 @@ export namespace SdkTypeProgrammer {
|
|
|
53
54
|
union.push(write_array(project)(importer)(array as MetadataArray));
|
|
54
55
|
for (const object of meta.objects) {
|
|
55
56
|
const target = object.type as MetadataObjectType;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)
|
|
57
|
+
// One definition of "this type has no name to reference". This was a
|
|
58
|
+
// second copy of `StringUtil.isImplicit`, and the copies drifted: only
|
|
59
|
+
// the dotted spelling was listed here, so a duplicated anonymous type
|
|
60
|
+
// -- `__type-o1` under typia's current separator -- was referenced as a
|
|
61
|
+
// module that the declaration side had correctly refused to write.
|
|
62
|
+
if (StringUtil.isImplicit(target.name))
|
|
63
63
|
union.push(write_object(project)(importer)(target));
|
|
64
64
|
else union.push(writeAlias(project)(importer)(target));
|
|
65
65
|
}
|
|
@@ -301,7 +301,13 @@ export namespace SdkTypeProgrammer {
|
|
|
301
301
|
(importer: ImportDictionary) =>
|
|
302
302
|
(meta: MetadataAliasType | MetadataObjectType): TypeNode => {
|
|
303
303
|
importInternalFile(project)(importer)(meta.name);
|
|
304
|
-
|
|
304
|
+
// The reference has to spell the accessor path the declaration was
|
|
305
|
+
// written under, not the raw metadata name: a duplicated name carries
|
|
306
|
+
// typia's `-o<counter>` marker, which declares as a namespace member and
|
|
307
|
+
// therefore refers as `IDirectory.o1`.
|
|
308
|
+
return factory.createTypeReferenceNode(
|
|
309
|
+
StringUtil.accessorsOf(meta.name).join("."),
|
|
310
|
+
);
|
|
305
311
|
};
|
|
306
312
|
|
|
307
313
|
const write_native = (name: string): TypeNode =>
|
|
@@ -377,12 +383,14 @@ const importInternalFile =
|
|
|
377
383
|
(project: INestiaProject) =>
|
|
378
384
|
(importer: ImportDictionary) =>
|
|
379
385
|
(name: string) => {
|
|
380
|
-
|
|
386
|
+
// The file is named after the first accessor, so a duplicated name lands
|
|
387
|
+
// in its base type's file rather than inventing `IDirectory-o1.ts`.
|
|
388
|
+
const top: string = StringUtil.accessorsOf(name)[0]!;
|
|
381
389
|
if (importer.file === `${project.config.output}/structures/${top}.ts`)
|
|
382
390
|
return;
|
|
383
391
|
importer.internal({
|
|
384
392
|
declaration: true,
|
|
385
|
-
file: `${project.config.output}/structures/${
|
|
393
|
+
file: `${project.config.output}/structures/${top}`,
|
|
386
394
|
type: "element",
|
|
387
395
|
name: top,
|
|
388
396
|
});
|
|
@@ -14,9 +14,10 @@ export namespace SdkWebSocketCloneProgrammer {
|
|
|
14
14
|
for (const route of app.routes)
|
|
15
15
|
if (route.protocol === "websocket")
|
|
16
16
|
for (const imp of route.imports)
|
|
17
|
-
for (const
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
for (const local of imp.elements) {
|
|
18
|
+
const imported: string = imp.elementAliases?.[local] ?? local;
|
|
19
|
+
if (await clone(ctx)(imp.file, imported))
|
|
20
|
+
cloned.add(importKey(imp.file, imported));
|
|
20
21
|
}
|
|
21
22
|
return cloned;
|
|
22
23
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { MetadataSchema } from "../../internal/legacy";
|
|
2
1
|
import { OpenApi } from "@typia/interface";
|
|
3
2
|
|
|
4
3
|
import { INestiaConfig } from "../../INestiaConfig";
|
|
5
4
|
import { SecurityAnalyzer } from "../../analyses/SecurityAnalyzer";
|
|
5
|
+
import { MetadataSchema } from "../../internal/legacy";
|
|
6
6
|
import { ITypedHttpRoute } from "../../structures/ITypedHttpRoute";
|
|
7
7
|
import { SwaggerDescriptionComposer } from "./SwaggerDescriptionComposer";
|
|
8
8
|
import { SwaggerOperationParameterComposer } from "./SwaggerOperationParameterComposer";
|
|
@@ -99,13 +99,7 @@ export namespace SwaggerOperationComposer {
|
|
|
99
99
|
}),
|
|
100
100
|
)
|
|
101
101
|
.flat(),
|
|
102
|
-
requestBody: props
|
|
103
|
-
? SwaggerOperationParameterComposer.body({
|
|
104
|
-
schema: props.schema(props.route.body.metadata)!,
|
|
105
|
-
jsDocTags: props.route.jsDocTags,
|
|
106
|
-
parameter: props.route.body,
|
|
107
|
-
})
|
|
108
|
-
: undefined,
|
|
102
|
+
requestBody: writeRequestBody(props),
|
|
109
103
|
responses: SwaggerOperationResponseComposer.compose({
|
|
110
104
|
schema: props.schema,
|
|
111
105
|
route: props.route,
|
|
@@ -117,3 +111,41 @@ export namespace SwaggerOperationComposer {
|
|
|
117
111
|
};
|
|
118
112
|
};
|
|
119
113
|
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Compose an operation's `requestBody`, or omit it when the body has no shape.
|
|
117
|
+
*
|
|
118
|
+
* `props.schema` looks the metadata up in what `SwaggerGenerator.compose`
|
|
119
|
+
* collected, and that collection drops metadata of size zero -- so a `void`,
|
|
120
|
+
* `undefined` or `never` body is absent from it and the lookup answers
|
|
121
|
+
* `undefined`. The call site used to assert the result non-null, which turned a
|
|
122
|
+
* body with nothing to describe into `content: { "application/json": {} }`: a
|
|
123
|
+
* media type that says "send me JSON" and then declines to say what JSON.
|
|
124
|
+
*
|
|
125
|
+
* The Request Body Object's `content` is required by the specification, so the
|
|
126
|
+
* honest document for such a route is not a `requestBody` with an empty
|
|
127
|
+
* `content` -- it is no `requestBody`, which is also what the endpoint
|
|
128
|
+
* accepts.
|
|
129
|
+
*
|
|
130
|
+
* Deliberately not `composeContent`, which decides the same question for
|
|
131
|
+
* responses. The two rules differ and `@typia/utils`' Swagger 2.0 downgrader is
|
|
132
|
+
* where that is visible: a response is described by a schema _or_ an example,
|
|
133
|
+
* while a request body needs the schema -- `{ example }` alone is refused with
|
|
134
|
+
* `request body examples are not representable`, with or without one. Sharing a
|
|
135
|
+
* helper across two rules that disagree would trade one defect for another.
|
|
136
|
+
*/
|
|
137
|
+
const writeRequestBody = (props: {
|
|
138
|
+
schema: (metadata: MetadataSchema) => OpenApi.IJsonSchema | undefined;
|
|
139
|
+
route: ITypedHttpRoute;
|
|
140
|
+
}): OpenApi.IOperation.IRequestBody | undefined => {
|
|
141
|
+
if (props.route.body === null) return undefined;
|
|
142
|
+
const schema: OpenApi.IJsonSchema | undefined = props.schema(
|
|
143
|
+
props.route.body.metadata,
|
|
144
|
+
);
|
|
145
|
+
if (schema === undefined) return undefined;
|
|
146
|
+
return SwaggerOperationParameterComposer.body({
|
|
147
|
+
schema,
|
|
148
|
+
jsDocTags: props.route.jsDocTags,
|
|
149
|
+
parameter: props.route.body,
|
|
150
|
+
});
|
|
151
|
+
};
|
|
@@ -16,13 +16,12 @@ export namespace SwaggerOperationResponseComposer {
|
|
|
16
16
|
for (const [status, error] of Object.entries(props.route.exceptions))
|
|
17
17
|
output[status] = {
|
|
18
18
|
description: error.description ?? undefined,
|
|
19
|
-
content: {
|
|
20
|
-
"application/json"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
19
|
+
content: composeContent({
|
|
20
|
+
contentType: "application/json",
|
|
21
|
+
schema: props.schema(error.metadata),
|
|
22
|
+
example: error.example,
|
|
23
|
+
examples: error.examples,
|
|
24
|
+
}),
|
|
26
25
|
};
|
|
27
26
|
|
|
28
27
|
// FROM COMMENTS
|
|
@@ -52,11 +51,17 @@ export namespace SwaggerOperationResponseComposer {
|
|
|
52
51
|
else if (oldbie === undefined)
|
|
53
52
|
output[status] = {
|
|
54
53
|
description: description,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
// The `{}` schema is what an undescribed payload looks like: the tag
|
|
55
|
+
// names a status and nothing about its body, so the response says a
|
|
56
|
+
// body exists without constraining it. That is a description, and it
|
|
57
|
+
// goes through the same helper as every other response rather than
|
|
58
|
+
// being the one place that still writes a media type by hand.
|
|
59
|
+
content: composeContent({
|
|
60
|
+
contentType: "application/json",
|
|
61
|
+
schema: {},
|
|
62
|
+
example: undefined,
|
|
63
|
+
examples: undefined,
|
|
64
|
+
}),
|
|
60
65
|
};
|
|
61
66
|
}
|
|
62
67
|
|
|
@@ -77,26 +82,67 @@ export namespace SwaggerOperationResponseComposer {
|
|
|
77
82
|
description: props.route.success.encrypted
|
|
78
83
|
? `${warning.get(!!description, props.route.method)}${description ?? ""}`
|
|
79
84
|
: (description ?? ""),
|
|
80
|
-
content:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
},
|
|
92
|
-
}
|
|
93
|
-
: undefined,
|
|
85
|
+
content: composeContent({
|
|
86
|
+
contentType: props.route.success.contentType,
|
|
87
|
+
schema: props.route.success.binary
|
|
88
|
+
? {
|
|
89
|
+
format: "binary",
|
|
90
|
+
type: "string",
|
|
91
|
+
}
|
|
92
|
+
: props.schema(props.route.success.metadata),
|
|
93
|
+
example: props.route.success.example,
|
|
94
|
+
examples: props.route.success.examples,
|
|
95
|
+
}),
|
|
94
96
|
...(props.route.success.encrypted ? { "x-nestia-encrypted": true } : {}),
|
|
95
97
|
};
|
|
96
98
|
return output;
|
|
97
99
|
};
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Compose a response's `content`, or omit it when the response has no body.
|
|
104
|
+
*
|
|
105
|
+
* `content` describes a response body, so it exists exactly when there is one
|
|
106
|
+
* to describe. A response whose metadata yields no schema -- a `void` return
|
|
107
|
+
* type, or a `@TypedException<void>` declaring a status that carries no payload
|
|
108
|
+
* -- has nothing to put under the media type, and writing the media type anyway
|
|
109
|
+
* says "this endpoint replies with JSON" and then declines to say what the JSON
|
|
110
|
+
* is: a client generator emits a parse for a body that never arrives, and the
|
|
111
|
+
* Swagger 2.0 downgrade refuses the whole document because a schemaless
|
|
112
|
+
* `produces` entry carries no information at all.
|
|
113
|
+
*
|
|
114
|
+
* Every response this composer emits goes through here. The success response, a
|
|
115
|
+
* declared exception, and a `@throws` tag all reach the same media type by the
|
|
116
|
+
* same route, so a rule applied to one of them leaves the others still emitting
|
|
117
|
+
* the shape it was written to remove -- which is exactly how the exception
|
|
118
|
+
* responses survived the fix to the success one.
|
|
119
|
+
*
|
|
120
|
+
* The test is what the metadata yields, not how the return type is spelled:
|
|
121
|
+
* `void`, `undefined` and `never` all describe the same absent body. An example
|
|
122
|
+
* without a schema still describes a body, so it keeps the entry, and so does
|
|
123
|
+
* the `{}` schema the `@throws` tag path supplies for an unspecified payload.
|
|
124
|
+
*/
|
|
125
|
+
const composeContent = (props: {
|
|
126
|
+
contentType: string | null | undefined;
|
|
127
|
+
schema: OpenApi.IJsonSchema | undefined;
|
|
128
|
+
example: any;
|
|
129
|
+
examples: Record<string, any> | undefined;
|
|
130
|
+
}): OpenApi.IOperation.IContent | undefined => {
|
|
131
|
+
if (!props.contentType) return undefined;
|
|
132
|
+
const described: boolean =
|
|
133
|
+
props.schema !== undefined ||
|
|
134
|
+
props.example !== undefined ||
|
|
135
|
+
Object.keys(props.examples ?? {}).length !== 0;
|
|
136
|
+
if (described === false) return undefined;
|
|
137
|
+
return {
|
|
138
|
+
[props.contentType]: {
|
|
139
|
+
schema: props.schema,
|
|
140
|
+
example: props.example,
|
|
141
|
+
examples: props.examples,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
100
146
|
const warning = new VariadicSingleton((described: boolean, method: string) => {
|
|
101
147
|
const summary: string = "Response data have been encrypted.";
|
|
102
148
|
const component: string = `[EncryptedRoute.${StringUtil.capitalize(method)}](https://github.com/samchon/@nestia/core#encryptedroute)`;
|
package/src/internal/legacy.ts
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
// cross-reference lookups in O(1). Everything else either reads a
|
|
13
13
|
// pre-baked field or short-circuits because typia's compile-time
|
|
14
14
|
// transform already enforces the invariant.
|
|
15
|
-
|
|
16
15
|
import type {
|
|
17
16
|
IJsonSchemaCollection,
|
|
18
17
|
IMetadataComponents,
|
|
@@ -39,16 +38,15 @@ declare module "@typia/interface" {
|
|
|
39
38
|
/**
|
|
40
39
|
* Plain `IMetadataSchema` augmented with the fields the nestia transform
|
|
41
40
|
* pre-bakes. `size`, `name`, and `empty` replace the methods the legacy
|
|
42
|
-
* `MetadataSchema` class exposed; `jsonSchema` is the OpenAPI 3.1
|
|
43
|
-
*
|
|
44
|
-
* runtime.
|
|
41
|
+
* `MetadataSchema` class exposed; `jsonSchema` is the OpenAPI 3.1 conversion
|
|
42
|
+
* typia's Go-side produces but does not expose to JS at runtime.
|
|
45
43
|
*
|
|
46
44
|
* Fields are declared optional so that nested `IMetadataSchema` values
|
|
47
|
-
* (`metadata.rest`, `metadata.escaped.original`, `IObjectType.value`,
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
45
|
+
* (`metadata.rest`, `metadata.escaped.original`, `IObjectType.value`, …) —
|
|
46
|
+
* which are not top-level route inputs and therefore do not carry the pre-baked
|
|
47
|
+
* overlay — still satisfy the type when passed through legacy utilities. The
|
|
48
|
+
* utilities read the fields via the optional accessor and fall back when
|
|
49
|
+
* absent.
|
|
52
50
|
*/
|
|
53
51
|
export interface IReflectMetadata extends IMetadataSchema {
|
|
54
52
|
size?: number;
|
|
@@ -64,9 +62,9 @@ export interface IReflectJsonSchema {
|
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
/**
|
|
67
|
-
* Cross-reference dictionary produced by `MetadataComponents.from`. Each
|
|
68
|
-
*
|
|
69
|
-
*
|
|
65
|
+
* Cross-reference dictionary produced by `MetadataComponents.from`. Each map is
|
|
66
|
+
* keyed by the entry's `.name`, matching the lookup pattern the legacy
|
|
67
|
+
* `MetadataComponents.dictionary` getter offered.
|
|
70
68
|
*/
|
|
71
69
|
export interface IMetadataDictionary {
|
|
72
70
|
objects: Map<string, IMetadataSchema.IObjectType>;
|
|
@@ -89,9 +87,9 @@ export type MetadataTupleType = IMetadataSchema.ITupleType;
|
|
|
89
87
|
export type MetadataObjectType = IMetadataSchema.IObjectType;
|
|
90
88
|
export type MetadataAtomic = IMetadataSchema.IAtomic;
|
|
91
89
|
/**
|
|
92
|
-
* Flattened constant-value shape: typia v13 splits `IConstant.IValue<T>` by
|
|
93
|
-
*
|
|
94
|
-
*
|
|
90
|
+
* Flattened constant-value shape: typia v13 splits `IConstant.IValue<T>` by the
|
|
91
|
+
* atomic discriminator, but sdk's literal writer just needs the runtime `value`
|
|
92
|
+
* payload, so collapse the union to a single ergonomic shape.
|
|
95
93
|
*/
|
|
96
94
|
export interface MetadataConstantValue {
|
|
97
95
|
value: string | number | bigint | boolean;
|
|
@@ -101,12 +99,12 @@ export type MetadataEscaped = IMetadataSchema.IEscaped;
|
|
|
101
99
|
export type MetadataProperty = IMetadataSchema.IProperty;
|
|
102
100
|
|
|
103
101
|
/**
|
|
104
|
-
* Reference to a named array/tuple/object/alias type. typia v13's
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
102
|
+
* Reference to a named array/tuple/object/alias type. typia v13's plain
|
|
103
|
+
* `IReference` carries only the symbolic `name` + tags; the legacy class
|
|
104
|
+
* additionally exposed `.type` as a getter that resolved against the
|
|
105
|
+
* dictionary. `MetadataSchema.from` walks the metadata tree once and attaches
|
|
106
|
+
* the resolved `.type` field so downstream sdk code can keep its `ref.type`
|
|
107
|
+
* access pattern.
|
|
110
108
|
*/
|
|
111
109
|
export type MetadataArray = IMetadataSchema.IReference & {
|
|
112
110
|
type: MetadataArrayType;
|
|
@@ -138,10 +136,10 @@ export const emptyOf = (m: IMetadataSchema): boolean =>
|
|
|
138
136
|
(m as IReflectMetadata).empty ?? false;
|
|
139
137
|
|
|
140
138
|
/**
|
|
141
|
-
* Equivalent of the legacy `MetadataSchema.isSoleLiteral()` method:
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
139
|
+
* Equivalent of the legacy `MetadataSchema.isSoleLiteral()` method: `true` when
|
|
140
|
+
* the schema represents exactly one constant literal value and nothing else.
|
|
141
|
+
* Used by sdk's type printer to fall back to literal emission instead of a
|
|
142
|
+
* union.
|
|
145
143
|
*/
|
|
146
144
|
export const isSoleLiteralOf = (m: IMetadataSchema): boolean => {
|
|
147
145
|
if (m.any) return false;
|
|
@@ -184,11 +182,11 @@ export namespace MetadataComponents {
|
|
|
184
182
|
|
|
185
183
|
export namespace MetadataSchema {
|
|
186
184
|
/**
|
|
187
|
-
* Walks the metadata tree and attaches the resolved `.type` field to
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
185
|
+
* Walks the metadata tree and attaches the resolved `.type` field to every
|
|
186
|
+
* `IReference` it encounters, using the supplied dictionary as the lookup
|
|
187
|
+
* index. This is idempotent — references whose `.type` has already been
|
|
188
|
+
* resolved are left alone — and mutates the input, matching the in-place
|
|
189
|
+
* resolution model `@typia/core` 12.x used.
|
|
192
190
|
*/
|
|
193
191
|
export const from = (
|
|
194
192
|
plain: IMetadataSchema,
|
|
@@ -202,12 +200,11 @@ export namespace MetadataSchema {
|
|
|
202
200
|
}
|
|
203
201
|
|
|
204
202
|
/**
|
|
205
|
-
* The walk tracks visited
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* `interface Node { children: Node[] }`.
|
|
203
|
+
* The walk tracks visited _targets_ (the IObjectType / IArrayType / … instances
|
|
204
|
+
* reached through the dictionary), not the wrapper schemas. Wrapper schemas are
|
|
205
|
+
* reconstructed on the JS side and are not shared across recursive references,
|
|
206
|
+
* so a `visited<IMetadataSchema>` set never matches and the walk would recurse
|
|
207
|
+
* forever on cycles like `interface Node { children: Node[] }`.
|
|
211
208
|
*/
|
|
212
209
|
const attachTypes = (
|
|
213
210
|
schema: IMetadataSchema | null | undefined,
|
|
@@ -222,7 +219,8 @@ const attachTypes = (
|
|
|
222
219
|
for (const elem of target.elements) attachTypes(elem, dict, visited);
|
|
223
220
|
});
|
|
224
221
|
attachReferences(schema.objects, dict.objects, visited, (target) => {
|
|
225
|
-
for (const prop of target.properties)
|
|
222
|
+
for (const prop of target.properties)
|
|
223
|
+
attachTypes(prop.value, dict, visited);
|
|
226
224
|
});
|
|
227
225
|
attachReferences(schema.aliases, dict.aliases, visited, (target) =>
|
|
228
226
|
attachTypes(target.value, dict, visited),
|
|
@@ -254,8 +252,7 @@ const attachReferences = <Target extends { name: string }>(
|
|
|
254
252
|
const mutable = ref as IMetadataSchema.IReference & { type?: Target };
|
|
255
253
|
if (mutable.type === undefined) {
|
|
256
254
|
const target = index.get(ref.name);
|
|
257
|
-
if (target !== undefined)
|
|
258
|
-
(mutable as { type?: Target }).type = target;
|
|
255
|
+
if (target !== undefined) (mutable as { type?: Target }).type = target;
|
|
259
256
|
}
|
|
260
257
|
if (mutable.type !== undefined && visited.has(mutable.type) === false) {
|
|
261
258
|
visited.add(mutable.type);
|
|
@@ -290,13 +287,12 @@ export namespace MetadataFactory {
|
|
|
290
287
|
|
|
291
288
|
/**
|
|
292
289
|
* Walks the metadata tree once, invoking the provided validator on each
|
|
293
|
-
* visited node, and accumulates the produced messages into `IError`
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
* atomic-only rules, …).
|
|
290
|
+
* visited node, and accumulates the produced messages into `IError` entries.
|
|
291
|
+
* The walk skips back-edges through references so cyclic structures
|
|
292
|
+
* terminate. This is a faithful reimplementation of the legacy `@typia/core`
|
|
293
|
+
* walker, kept lean: the typia native transform has already validated
|
|
294
|
+
* structural invariants, so the validator is only called for SDK-side policy
|
|
295
|
+
* checks (JSON-serializability, query/header atomic-only rules, …).
|
|
300
296
|
*/
|
|
301
297
|
export const validate = (props: {
|
|
302
298
|
options?: unknown;
|
|
@@ -315,39 +311,49 @@ export namespace MetadataFactory {
|
|
|
315
311
|
if (messages.length)
|
|
316
312
|
errors.push({ name: nameOf(metadata), explore, messages });
|
|
317
313
|
for (const obj of metadata.objects) {
|
|
318
|
-
const type = (
|
|
319
|
-
|
|
320
|
-
|
|
314
|
+
const type = (
|
|
315
|
+
obj as IMetadataSchema.IReference & {
|
|
316
|
+
type?: IMetadataSchema.IObjectType;
|
|
317
|
+
}
|
|
318
|
+
).type;
|
|
321
319
|
if (type === undefined || visited.has(type)) continue;
|
|
322
320
|
visited.add(type);
|
|
323
321
|
for (const prop of type.properties)
|
|
324
322
|
visit(prop.value, {
|
|
325
323
|
object: type,
|
|
326
|
-
property:
|
|
324
|
+
property:
|
|
325
|
+
nameOf(prop.key) ||
|
|
326
|
+
String(prop.key.constants[0]?.values[0]?.value ?? ""),
|
|
327
327
|
parameter: null,
|
|
328
328
|
output: explore.output,
|
|
329
329
|
});
|
|
330
330
|
}
|
|
331
331
|
for (const arr of metadata.arrays) {
|
|
332
|
-
const type = (
|
|
333
|
-
|
|
334
|
-
|
|
332
|
+
const type = (
|
|
333
|
+
arr as IMetadataSchema.IReference & {
|
|
334
|
+
type?: IMetadataSchema.IArrayType;
|
|
335
|
+
}
|
|
336
|
+
).type;
|
|
335
337
|
if (type === undefined || visited.has(type)) continue;
|
|
336
338
|
visited.add(type);
|
|
337
339
|
visit(type.value, explore);
|
|
338
340
|
}
|
|
339
341
|
for (const tuple of metadata.tuples) {
|
|
340
|
-
const type = (
|
|
341
|
-
|
|
342
|
-
|
|
342
|
+
const type = (
|
|
343
|
+
tuple as IMetadataSchema.IReference & {
|
|
344
|
+
type?: IMetadataSchema.ITupleType;
|
|
345
|
+
}
|
|
346
|
+
).type;
|
|
343
347
|
if (type === undefined || visited.has(type)) continue;
|
|
344
348
|
visited.add(type);
|
|
345
349
|
for (const elem of type.elements) visit(elem, explore);
|
|
346
350
|
}
|
|
347
351
|
for (const alias of metadata.aliases) {
|
|
348
|
-
const type = (
|
|
349
|
-
|
|
350
|
-
|
|
352
|
+
const type = (
|
|
353
|
+
alias as IMetadataSchema.IReference & {
|
|
354
|
+
type?: IMetadataSchema.IAliasType;
|
|
355
|
+
}
|
|
356
|
+
).type;
|
|
351
357
|
if (type === undefined || visited.has(type)) continue;
|
|
352
358
|
visited.add(type);
|
|
353
359
|
visit(type.value, explore);
|
|
@@ -370,10 +376,10 @@ export namespace MetadataFactory {
|
|
|
370
376
|
export namespace JsonMetadataFactory {
|
|
371
377
|
/**
|
|
372
378
|
* Rejects metadata that cannot be losslessly JSON-serialized. The typia
|
|
373
|
-
* native runtime already screens out most structurally invalid types, so
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
379
|
+
* native runtime already screens out most structurally invalid types, so this
|
|
380
|
+
* only adds the JSON-policy bans the legacy `@typia/core` walker enforced —
|
|
381
|
+
* bare `bigint` payloads, function-typed properties, and `Map` / `Set`
|
|
382
|
+
* containers that have no canonical JSON representation.
|
|
377
383
|
*/
|
|
378
384
|
export const validate: MetadataFactory.Validator = (props) => {
|
|
379
385
|
const messages: string[] = [];
|
|
@@ -414,12 +420,12 @@ export namespace JsonSchemasProgrammer {
|
|
|
414
420
|
/**
|
|
415
421
|
* Consumes the per-metadata `jsonSchema` field the nestia transform
|
|
416
422
|
* pre-bakes. Top-level route metadata (success / parameter / exception)
|
|
417
|
-
* always carries a baked schema; for nested metadata (object property
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
+
* always carries a baked schema; for nested metadata (object property values
|
|
424
|
+
* reached by the decomposed-query path), the bake is absent and this function
|
|
425
|
+
* falls back to a minimal JS-side converter that handles the schema shapes
|
|
426
|
+
* decompose actually emits — atomics, constants, templates, arrays of those,
|
|
427
|
+
* named references — without re-implementing the typia native programmer
|
|
428
|
+
* wholesale.
|
|
423
429
|
*/
|
|
424
430
|
export const writeSchemas = (props: {
|
|
425
431
|
version: "3.0" | "3.1";
|
|
@@ -462,12 +468,16 @@ const schemaFromMetadata = (m: IMetadataSchema): OpenApi.IJsonSchema => {
|
|
|
462
468
|
void tpl;
|
|
463
469
|
}
|
|
464
470
|
for (const arr of m.arrays) {
|
|
465
|
-
const inner = (
|
|
466
|
-
|
|
467
|
-
|
|
471
|
+
const inner = (
|
|
472
|
+
arr as IMetadataSchema.IReference & {
|
|
473
|
+
type?: IMetadataSchema.IArrayType;
|
|
474
|
+
}
|
|
475
|
+
).type;
|
|
468
476
|
union.push({
|
|
469
477
|
type: "array",
|
|
470
|
-
items: inner
|
|
478
|
+
items: inner
|
|
479
|
+
? schemaFromMetadata(inner.value)
|
|
480
|
+
: ({} as OpenApi.IJsonSchema),
|
|
471
481
|
} as unknown as OpenApi.IJsonSchema);
|
|
472
482
|
}
|
|
473
483
|
for (const obj of m.objects)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { MetadataFactory } from "../internal/legacy";
|
|
2
1
|
import { IMetadataComponents, IMetadataSchema } from "@typia/interface";
|
|
3
2
|
|
|
3
|
+
import { MetadataFactory } from "../internal/legacy";
|
|
4
4
|
import { IReflectType } from "./IReflectType";
|
|
5
5
|
|
|
6
6
|
export interface IReflectHttpOperationException {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { MetadataFactory } from "../internal/legacy";
|
|
2
1
|
import {
|
|
3
2
|
IJsDocTagInfo,
|
|
4
3
|
IMetadataComponents,
|
|
5
4
|
IMetadataSchema,
|
|
6
5
|
} from "@typia/interface";
|
|
7
6
|
|
|
7
|
+
import { MetadataFactory } from "../internal/legacy";
|
|
8
8
|
import { IReflectType } from "./IReflectType";
|
|
9
9
|
|
|
10
10
|
export type IReflectHttpOperationParameter =
|
package/src/utils/StringUtil.ts
CHANGED
|
@@ -7,11 +7,51 @@ export namespace StringUtil {
|
|
|
7
7
|
(change: string): string =>
|
|
8
8
|
keep.includes(change) ? escapeDuplicate(keep)(`_${change}`) : change;
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The marker names typia gives a type that has no identity of its own: an
|
|
12
|
+
* anonymous object literal, and the same literal after a duplicate id was
|
|
13
|
+
* minted for it.
|
|
14
|
+
*
|
|
15
|
+
* One definition because the spellings drift. typia qualifies with `.` and
|
|
16
|
+
* disambiguates a duplicate with `-o<counter>`, so the same anonymous type
|
|
17
|
+
* reads `__type`, `__type.o1` or `__type-o1` depending on the release and on
|
|
18
|
+
* whether its name collided. Three call sites carried their own copy of this
|
|
19
|
+
* list, only one of them learned the `-` spelling, and the two that did not
|
|
20
|
+
* declared and referenced modules that do not exist.
|
|
21
|
+
*/
|
|
22
|
+
export const isAnonymous = (str: string): boolean =>
|
|
12
23
|
str === "__type" ||
|
|
13
24
|
str === "__object" ||
|
|
14
25
|
str.startsWith("__type.") ||
|
|
15
26
|
str.startsWith("__object.") ||
|
|
16
|
-
str.
|
|
27
|
+
str.startsWith("__type-") ||
|
|
28
|
+
str.startsWith("__object-");
|
|
29
|
+
|
|
30
|
+
export const isImplicit = (str: string) =>
|
|
31
|
+
str === "object" || isAnonymous(str) || str.includes("readonly [");
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Split a typia metadata name into the accessor path a generated SDK declares
|
|
35
|
+
* it under.
|
|
36
|
+
*
|
|
37
|
+
* Typia separates a qualified name with `.` and a _duplicated_ name from its
|
|
38
|
+
* disambiguating counter with a trailing `-o<counter>`
|
|
39
|
+
* (`MetadataCollection.composeName`). Those are different relations and only
|
|
40
|
+
* the first is a namespace boundary -- which is exactly why typia stopped
|
|
41
|
+
* spelling the second one with a dot, where the two were indistinguishable.
|
|
42
|
+
*
|
|
43
|
+
* A duplicate still has to become some declarable identifier, and the counter
|
|
44
|
+
* is rendered as the last accessor: `IDirectory-o1` declares as `namespace
|
|
45
|
+
* IDirectory { type o1 }`, byte-identical to what the previous
|
|
46
|
+
* `IDirectory.o1` spelling produced. Reading the marker here rather than
|
|
47
|
+
* guessing at the name is what the new separator makes possible -- `-` cannot
|
|
48
|
+
* occur in a qualified name, so unlike the old spelling this cannot be
|
|
49
|
+
* confused with one.
|
|
50
|
+
*/
|
|
51
|
+
export const accessorsOf = (name: string): string[] => {
|
|
52
|
+
const duplicated: RegExpMatchArray | null = name.match(/^(.+)-o(\d+)$/);
|
|
53
|
+
return duplicated === null
|
|
54
|
+
? name.split(".")
|
|
55
|
+
: [...duplicated[1]!.split("."), `o${duplicated[2]!}`];
|
|
56
|
+
};
|
|
17
57
|
}
|