@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.
Files changed (103) hide show
  1. package/assets/bundle/distribute/package.json +1 -1
  2. package/assets/config/nestia.config.ts +19 -19
  3. package/lib/NestiaSwaggerComposer.js +2 -0
  4. package/lib/NestiaSwaggerComposer.js.map +1 -1
  5. package/lib/analyses/ImportAnalyzer.d.ts +6 -5
  6. package/lib/analyses/ImportAnalyzer.js +30 -15
  7. package/lib/analyses/ImportAnalyzer.js.map +1 -1
  8. package/lib/analyses/ReflectControllerAnalyzer.js +18 -9
  9. package/lib/analyses/ReflectControllerAnalyzer.js.map +1 -1
  10. package/lib/analyses/ReflectHttpOperationAnalyzer.d.ts +1 -1
  11. package/lib/analyses/ReflectHttpOperationAnalyzer.js +8 -6
  12. package/lib/analyses/ReflectHttpOperationAnalyzer.js.map +1 -1
  13. package/lib/analyses/ReflectHttpOperationExceptionAnalyzer.d.ts +1 -1
  14. package/lib/analyses/ReflectHttpOperationExceptionAnalyzer.js.map +1 -1
  15. package/lib/analyses/ReflectHttpOperationParameterAnalyzer.d.ts +1 -1
  16. package/lib/analyses/ReflectHttpOperationParameterAnalyzer.js +1 -1
  17. package/lib/analyses/ReflectHttpOperationParameterAnalyzer.js.map +1 -1
  18. package/lib/analyses/ReflectWebSocketOperationAnalyzer.d.ts +1 -1
  19. package/lib/analyses/ReflectWebSocketOperationAnalyzer.js +10 -6
  20. package/lib/analyses/ReflectWebSocketOperationAnalyzer.js.map +1 -1
  21. package/lib/analyses/TypedHttpRouteAnalyzer.js +13 -7
  22. package/lib/analyses/TypedHttpRouteAnalyzer.js.map +1 -1
  23. package/lib/executable/internal/NestiaSdkCommand.js +7 -4
  24. package/lib/executable/internal/NestiaSdkCommand.js.map +1 -1
  25. package/lib/generates/E2eGenerator.js +1 -4
  26. package/lib/generates/E2eGenerator.js.map +1 -1
  27. package/lib/generates/SdkGenerator.js +7 -8
  28. package/lib/generates/SdkGenerator.js.map +1 -1
  29. package/lib/generates/SwaggerGenerator.js +8 -9
  30. package/lib/generates/SwaggerGenerator.js.map +1 -1
  31. package/lib/generates/internal/ImportDictionary.js +14 -6
  32. package/lib/generates/internal/ImportDictionary.js.map +1 -1
  33. package/lib/generates/internal/SdkDistributionComposer.js +27 -22
  34. package/lib/generates/internal/SdkDistributionComposer.js.map +1 -1
  35. package/lib/generates/internal/SdkFileProgrammer.js +1 -4
  36. package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
  37. package/lib/generates/internal/SdkHttpCloneProgrammer.js +3 -3
  38. package/lib/generates/internal/SdkHttpCloneProgrammer.js.map +1 -1
  39. package/lib/generates/internal/SdkHttpCloneReferencer.js +14 -10
  40. package/lib/generates/internal/SdkHttpCloneReferencer.js.map +1 -1
  41. package/lib/generates/internal/SdkTypeProgrammer.js +16 -8
  42. package/lib/generates/internal/SdkTypeProgrammer.js.map +1 -1
  43. package/lib/generates/internal/SdkWebSocketCloneProgrammer.js +6 -3
  44. package/lib/generates/internal/SdkWebSocketCloneProgrammer.js.map +1 -1
  45. package/lib/generates/internal/SwaggerOperationComposer.d.ts +1 -1
  46. package/lib/generates/internal/SwaggerOperationComposer.js +35 -7
  47. package/lib/generates/internal/SwaggerOperationComposer.js.map +1 -1
  48. package/lib/generates/internal/SwaggerOperationResponseComposer.js +68 -26
  49. package/lib/generates/internal/SwaggerOperationResponseComposer.js.map +1 -1
  50. package/lib/internal/legacy.d.ts +44 -46
  51. package/lib/internal/legacy.js +35 -48
  52. package/lib/internal/legacy.js.map +1 -1
  53. package/lib/structures/IReflectHttpOperationException.d.ts +1 -1
  54. package/lib/structures/IReflectHttpOperationParameter.d.ts +1 -1
  55. package/lib/structures/IReflectImport.d.ts +2 -0
  56. package/lib/structures/ITypedHttpRouteParameter.d.ts +1 -1
  57. package/lib/utils/StringUtil.d.ts +32 -0
  58. package/lib/utils/StringUtil.js +40 -3
  59. package/lib/utils/StringUtil.js.map +1 -1
  60. package/lib/utils/TsConfigReader.js +3 -1
  61. package/lib/utils/TsConfigReader.js.map +1 -1
  62. package/lib/validators/HttpHeadersValidator.d.ts +3 -2
  63. package/lib/validators/HttpHeadersValidator.js +3 -2
  64. package/lib/validators/HttpHeadersValidator.js.map +1 -1
  65. package/lib/validators/HttpQueryValidator.d.ts +4 -4
  66. package/lib/validators/HttpQueryValidator.js +4 -4
  67. package/native/go.mod +1 -1
  68. package/native/go.sum +32 -32
  69. package/native/sdk/register.go +6 -35
  70. package/native/sdk/sdk_transform.go +80 -304
  71. package/package.json +9 -9
  72. package/src/NestiaSwaggerComposer.ts +2 -0
  73. package/src/analyses/ImportAnalyzer.ts +26 -18
  74. package/src/analyses/ReflectControllerAnalyzer.ts +19 -10
  75. package/src/analyses/ReflectHttpOperationAnalyzer.ts +5 -2
  76. package/src/analyses/ReflectHttpOperationExceptionAnalyzer.ts +2 -2
  77. package/src/analyses/ReflectHttpOperationParameterAnalyzer.ts +3 -3
  78. package/src/analyses/ReflectWebSocketOperationAnalyzer.ts +14 -7
  79. package/src/analyses/TypedHttpRouteAnalyzer.ts +74 -35
  80. package/src/executable/internal/NestiaSdkCommand.ts +8 -5
  81. package/src/generates/E2eGenerator.ts +1 -3
  82. package/src/generates/SdkGenerator.ts +7 -7
  83. package/src/generates/SwaggerGenerator.ts +15 -7
  84. package/src/generates/internal/ImportDictionary.ts +23 -11
  85. package/src/generates/internal/SdkDistributionComposer.ts +25 -23
  86. package/src/generates/internal/SdkFileProgrammer.ts +1 -3
  87. package/src/generates/internal/SdkHttpCloneProgrammer.ts +3 -3
  88. package/src/generates/internal/SdkHttpCloneReferencer.ts +16 -11
  89. package/src/generates/internal/SdkTypeProgrammer.ts +18 -10
  90. package/src/generates/internal/SdkWebSocketCloneProgrammer.ts +4 -3
  91. package/src/generates/internal/SwaggerOperationComposer.ts +40 -8
  92. package/src/generates/internal/SwaggerOperationResponseComposer.ts +72 -26
  93. package/src/internal/legacy.ts +83 -73
  94. package/src/structures/IReflectHttpOperationException.ts +1 -1
  95. package/src/structures/IReflectHttpOperationParameter.ts +1 -1
  96. package/src/structures/IReflectImport.ts +2 -0
  97. package/src/structures/ITypedApplication.ts +0 -1
  98. package/src/structures/ITypedHttpRouteException.ts +0 -1
  99. package/src/structures/ITypedHttpRouteParameter.ts +1 -1
  100. package/src/utils/StringUtil.ts +43 -3
  101. package/src/utils/TsConfigReader.ts +11 -3
  102. package/src/validators/HttpHeadersValidator.ts +3 -2
  103. 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
- if (
57
- target.name === "object" ||
58
- target.name === "__type" ||
59
- target.name.startsWith("__type.") ||
60
- target.name === "__object" ||
61
- target.name.startsWith("__object.")
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
- return factory.createTypeReferenceNode(meta.name);
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
- const top: string = name.split(".")[0]!;
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/${name.split(".")[0]}`,
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 name of imp.elements) {
18
- if (await clone(ctx)(imp.file, name))
19
- cloned.add(importKey(imp.file, name));
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.route.body
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
- schema: props.schema(error.metadata),
22
- example: error.example,
23
- examples: error.examples,
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
- content: {
56
- "application/json": {
57
- schema: {},
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: props.route.success.contentType
81
- ? {
82
- [props.route.success.contentType]: {
83
- schema: props.route.success.binary
84
- ? {
85
- format: "binary",
86
- type: "string",
87
- }
88
- : props.schema(props.route.success.metadata),
89
- example: props.route.success.example,
90
- examples: props.route.success.examples,
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)`;
@@ -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
- * conversion typia's Go-side produces but does not expose to JS at
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
- * …) — which are not top-level route inputs and therefore do not carry
49
- * the pre-baked overlay — still satisfy the type when passed through
50
- * legacy utilities. The utilities read the fields via the optional
51
- * accessor and fall back when absent.
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
- * map is keyed by the entry's `.name`, matching the lookup pattern the
69
- * legacy `MetadataComponents.dictionary` getter offered.
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
- * the atomic discriminator, but sdk's literal writer just needs the runtime
94
- * `value` payload, so collapse the union to a single ergonomic shape.
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
- * plain `IReference` carries only the symbolic `name` + tags; the
106
- * legacy class additionally exposed `.type` as a getter that resolved
107
- * against the dictionary. `MetadataSchema.from` walks the metadata
108
- * tree once and attaches the resolved `.type` field so downstream
109
- * sdk code can keep its `ref.type` access pattern.
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
- * `true` when the schema represents exactly one constant literal value
143
- * and nothing else. Used by sdk's type printer to fall back to literal
144
- * emission instead of a union.
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
- * every `IReference` it encounters, using the supplied dictionary as
189
- * the lookup index. This is idempotent — references whose `.type`
190
- * has already been resolved are left alone — and mutates the input,
191
- * matching the in-place resolution model `@typia/core` 12.x used.
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 *targets* (the IObjectType / IArrayType / …
206
- * instances reached through the dictionary), not the wrapper schemas.
207
- * Wrapper schemas are reconstructed on the JS side and are not shared
208
- * across recursive references, so a `visited<IMetadataSchema>` set never
209
- * matches and the walk would recurse forever on cycles like
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) attachTypes(prop.value, dict, visited);
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
- * entries. The walk skips back-edges through references so cyclic
295
- * structures terminate. This is a faithful reimplementation of the
296
- * legacy `@typia/core` walker, kept lean: the typia native transform
297
- * has already validated structural invariants, so the validator is only
298
- * called for SDK-side policy checks (JSON-serializability, query/header
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 = (obj as IMetadataSchema.IReference & {
319
- type?: IMetadataSchema.IObjectType;
320
- }).type;
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: nameOf(prop.key) || String(prop.key.constants[0]?.values[0]?.value ?? ""),
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 = (arr as IMetadataSchema.IReference & {
333
- type?: IMetadataSchema.IArrayType;
334
- }).type;
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 = (tuple as IMetadataSchema.IReference & {
341
- type?: IMetadataSchema.ITupleType;
342
- }).type;
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 = (alias as IMetadataSchema.IReference & {
349
- type?: IMetadataSchema.IAliasType;
350
- }).type;
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
- * this only adds the JSON-policy bans the legacy `@typia/core` walker
375
- * enforced — bare `bigint` payloads, function-typed properties, and
376
- * `Map` / `Set` containers that have no canonical JSON representation.
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
- * values reached by the decomposed-query path), the bake is absent and
419
- * this function falls back to a minimal JS-side converter that handles
420
- * the schema shapes decompose actually emits — atomics, constants,
421
- * templates, arrays of those, named references — without re-implementing
422
- * the typia native programmer wholesale.
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 = (arr as IMetadataSchema.IReference & {
466
- type?: IMetadataSchema.IArrayType;
467
- }).type;
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 ? schemaFromMetadata(inner.value) : ({} as OpenApi.IJsonSchema),
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 =
@@ -3,4 +3,6 @@ export interface IReflectImport {
3
3
  asterisk: string | null;
4
4
  default: string | null;
5
5
  elements: string[];
6
+ /** Maps a local named binding to the name exported by its source module. */
7
+ elementAliases?: Record<string, string>;
6
8
  }
@@ -1,5 +1,4 @@
1
1
  import { IMetadataDictionary } from "../internal/legacy";
2
-
3
2
  import { INestiaProject } from "./INestiaProject";
4
3
  import { ITypedHttpRoute } from "./ITypedHttpRoute";
5
4
  import { ITypedMcpRoute } from "./ITypedMcpRoute";
@@ -1,5 +1,4 @@
1
1
  import { MetadataSchema } from "../internal/legacy";
2
-
3
2
  import { IReflectType } from "./IReflectType";
4
3
 
5
4
  export interface ITypedHttpRouteException {
@@ -1,6 +1,6 @@
1
- import { MetadataSchema } from "../internal/legacy";
2
1
  import { IJsDocTagInfo } from "typia";
3
2
 
3
+ import { MetadataSchema } from "../internal/legacy";
4
4
  import { IReflectType } from "./IReflectType";
5
5
 
6
6
  export type ITypedHttpRouteParameter =
@@ -7,11 +7,51 @@ export namespace StringUtil {
7
7
  (change: string): string =>
8
8
  keep.includes(change) ? escapeDuplicate(keep)(`_${change}`) : change;
9
9
 
10
- export const isImplicit = (str: string) =>
11
- str === "object" ||
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.includes("readonly [");
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
  }