@nestia/sdk 13.0.4 → 14.0.1

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 (186) hide show
  1. package/README.md +1 -1
  2. package/assets/bundle/distribute/package.json +2 -2
  3. package/assets/bundle/distribute/tsconfig.json +15 -105
  4. package/lib/INestiaConfig.d.ts +19 -10
  5. package/lib/NestiaSdkApplication.js +58 -27
  6. package/lib/NestiaSdkApplication.js.map +1 -1
  7. package/lib/NestiaSwaggerComposer.js +11 -10
  8. package/lib/NestiaSwaggerComposer.js.map +1 -1
  9. package/lib/analyses/ConfigAnalyzer.js +4 -4
  10. package/lib/analyses/ConfigAnalyzer.js.map +1 -1
  11. package/lib/analyses/ParameterNameAnalyzer.d.ts +20 -0
  12. package/lib/analyses/ParameterNameAnalyzer.js +48 -0
  13. package/lib/analyses/ParameterNameAnalyzer.js.map +1 -0
  14. package/lib/analyses/PathAnalyzer.d.ts +30 -0
  15. package/lib/analyses/PathAnalyzer.js +108 -16
  16. package/lib/analyses/PathAnalyzer.js.map +1 -1
  17. package/lib/analyses/ReflectControllerAnalyzer.js +2 -1
  18. package/lib/analyses/ReflectControllerAnalyzer.js.map +1 -1
  19. package/lib/analyses/ReflectHttpOperationAnalyzer.js +7 -7
  20. package/lib/analyses/ReflectHttpOperationAnalyzer.js.map +1 -1
  21. package/lib/analyses/ReflectHttpOperationParameterAnalyzer.js +122 -23
  22. package/lib/analyses/ReflectHttpOperationParameterAnalyzer.js.map +1 -1
  23. package/lib/analyses/ReflectHttpOperationResponseAnalyzer.js +5 -2
  24. package/lib/analyses/ReflectHttpOperationResponseAnalyzer.js.map +1 -1
  25. package/lib/analyses/ReflectMcpOperationAnalyzer.js +5 -2
  26. package/lib/analyses/ReflectMcpOperationAnalyzer.js.map +1 -1
  27. package/lib/analyses/ReflectWebSocketOperationAnalyzer.js +6 -3
  28. package/lib/analyses/ReflectWebSocketOperationAnalyzer.js.map +1 -1
  29. package/lib/analyses/SecurityAnalyzer.d.ts +10 -0
  30. package/lib/analyses/SecurityAnalyzer.js +24 -19
  31. package/lib/analyses/SecurityAnalyzer.js.map +1 -1
  32. package/lib/analyses/SwaggerExampleAnalyzer.d.ts +15 -0
  33. package/lib/analyses/SwaggerExampleAnalyzer.js +20 -0
  34. package/lib/analyses/SwaggerExampleAnalyzer.js.map +1 -0
  35. package/lib/analyses/TypedHttpRouteAnalyzer.js +30 -21
  36. package/lib/analyses/TypedHttpRouteAnalyzer.js.map +1 -1
  37. package/lib/analyses/TypedWebSocketRouteAnalyzer.js +1 -1
  38. package/lib/analyses/TypedWebSocketRouteAnalyzer.js.map +1 -1
  39. package/lib/executable/internal/NestiaSdkWatcher.js +3 -3
  40. package/lib/executable/internal/NestiaSdkWatcher.js.map +1 -1
  41. package/lib/executable/sdk.js +16 -1
  42. package/lib/executable/sdk.js.map +1 -1
  43. package/lib/factories/ExpressionFactory.d.ts +3 -0
  44. package/lib/factories/ExpressionFactory.js +6 -2
  45. package/lib/factories/ExpressionFactory.js.map +1 -1
  46. package/lib/factories/LiteralFactory.js +3 -1
  47. package/lib/factories/LiteralFactory.js.map +1 -1
  48. package/lib/generates/SdkGenerator.js +3 -3
  49. package/lib/generates/SdkGenerator.js.map +1 -1
  50. package/lib/generates/SwaggerGenerator.js +135 -27
  51. package/lib/generates/SwaggerGenerator.js.map +1 -1
  52. package/lib/generates/internal/FilePrinter.d.ts +14 -0
  53. package/lib/generates/internal/FilePrinter.js +31 -0
  54. package/lib/generates/internal/FilePrinter.js.map +1 -1
  55. package/lib/generates/internal/ImportDictionary.d.ts +2 -0
  56. package/lib/generates/internal/ImportDictionary.js +13 -0
  57. package/lib/generates/internal/ImportDictionary.js.map +1 -1
  58. package/lib/generates/internal/SdkAliasCollection.d.ts +2 -1
  59. package/lib/generates/internal/SdkAliasCollection.js +8 -4
  60. package/lib/generates/internal/SdkAliasCollection.js.map +1 -1
  61. package/lib/generates/internal/SdkDistributionComposer.js +72 -14
  62. package/lib/generates/internal/SdkDistributionComposer.js.map +1 -1
  63. package/lib/generates/internal/SdkFileProgrammer.js +83 -12
  64. package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
  65. package/lib/generates/internal/SdkHttpFunctionProgrammer.js +21 -23
  66. package/lib/generates/internal/SdkHttpFunctionProgrammer.js.map +1 -1
  67. package/lib/generates/internal/SdkHttpNamespaceProgrammer.js +27 -34
  68. package/lib/generates/internal/SdkHttpNamespaceProgrammer.js.map +1 -1
  69. package/lib/generates/internal/SdkHttpParameterProgrammer.d.ts +40 -1
  70. package/lib/generates/internal/SdkHttpParameterProgrammer.js +105 -6
  71. package/lib/generates/internal/SdkHttpParameterProgrammer.js.map +1 -1
  72. package/lib/generates/internal/SdkHttpRouteProgrammer.js +16 -32
  73. package/lib/generates/internal/SdkHttpRouteProgrammer.js.map +1 -1
  74. package/lib/generates/internal/SdkHttpSimulationProgrammer.js +18 -14
  75. package/lib/generates/internal/SdkHttpSimulationProgrammer.js.map +1 -1
  76. package/lib/generates/internal/SdkMcpRouteProgrammer.js +39 -11
  77. package/lib/generates/internal/SdkMcpRouteProgrammer.js.map +1 -1
  78. package/lib/generates/internal/SdkPathTemplate.d.ts +21 -0
  79. package/lib/generates/internal/SdkPathTemplate.js +50 -0
  80. package/lib/generates/internal/SdkPathTemplate.js.map +1 -0
  81. package/lib/generates/internal/SdkTypeProgrammer.js +15 -15
  82. package/lib/generates/internal/SdkTypeProgrammer.js.map +1 -1
  83. package/lib/generates/internal/SdkTypeTagProgrammer.d.ts +20 -1
  84. package/lib/generates/internal/SdkTypeTagProgrammer.js +386 -67
  85. package/lib/generates/internal/SdkTypeTagProgrammer.js.map +1 -1
  86. package/lib/generates/internal/SdkWebSocketNamespaceProgrammer.js +27 -36
  87. package/lib/generates/internal/SdkWebSocketNamespaceProgrammer.js.map +1 -1
  88. package/lib/generates/internal/SdkWebSocketParameterProgrammer.d.ts +35 -1
  89. package/lib/generates/internal/SdkWebSocketParameterProgrammer.js +72 -5
  90. package/lib/generates/internal/SdkWebSocketParameterProgrammer.js.map +1 -1
  91. package/lib/generates/internal/SdkWebSocketRouteProgrammer.js +57 -33
  92. package/lib/generates/internal/SdkWebSocketRouteProgrammer.js.map +1 -1
  93. package/lib/generates/internal/SwaggerOperationComposer.js +19 -8
  94. package/lib/generates/internal/SwaggerOperationComposer.js.map +1 -1
  95. package/lib/generates/internal/SwaggerOperationParameterComposer.d.ts +1 -1
  96. package/lib/generates/internal/SwaggerOperationParameterComposer.js +201 -19
  97. package/lib/generates/internal/SwaggerOperationParameterComposer.js.map +1 -1
  98. package/lib/generates/internal/SwaggerOperationResponseComposer.d.ts +2 -0
  99. package/lib/generates/internal/SwaggerOperationResponseComposer.js +39 -7
  100. package/lib/generates/internal/SwaggerOperationResponseComposer.js.map +1 -1
  101. package/lib/internal/legacy.d.ts +49 -18
  102. package/lib/internal/legacy.js +84 -25
  103. package/lib/internal/legacy.js.map +1 -1
  104. package/lib/structures/IOperationMetadata.d.ts +22 -0
  105. package/lib/structures/IReflectHttpOperationException.d.ts +3 -2
  106. package/lib/structures/IReflectHttpOperationParameter.d.ts +8 -3
  107. package/lib/structures/IReflectHttpOperationSuccess.d.ts +4 -3
  108. package/lib/structures/ITypedHttpRoute.d.ts +8 -0
  109. package/lib/structures/ITypedHttpRouteException.d.ts +3 -1
  110. package/lib/structures/ITypedHttpRouteParameter.d.ts +3 -1
  111. package/lib/structures/ITypedHttpRouteSuccess.d.ts +3 -1
  112. package/lib/structures/ITypedWebSocketRoute.d.ts +8 -0
  113. package/lib/utils/SourceFinder.d.ts +10 -0
  114. package/lib/utils/SourceFinder.js +27 -4
  115. package/lib/utils/SourceFinder.js.map +1 -1
  116. package/lib/utils/VersioningStrategy.d.ts +13 -1
  117. package/lib/utils/VersioningStrategy.js +24 -12
  118. package/lib/utils/VersioningStrategy.js.map +1 -1
  119. package/native/go.mod +1 -1
  120. package/native/go.sum +2 -2
  121. package/native/sdk/sdk_http_rules.go +210 -0
  122. package/native/sdk/sdk_metadata_json.go +73 -3
  123. package/native/sdk/sdk_transform.go +801 -49
  124. package/package.json +8 -8
  125. package/src/INestiaConfig.ts +19 -10
  126. package/src/NestiaSdkApplication.ts +63 -34
  127. package/src/NestiaSwaggerComposer.ts +11 -8
  128. package/src/analyses/ConfigAnalyzer.ts +3 -3
  129. package/src/analyses/ParameterNameAnalyzer.ts +51 -0
  130. package/src/analyses/PathAnalyzer.ts +106 -14
  131. package/src/analyses/ReflectControllerAnalyzer.ts +2 -1
  132. package/src/analyses/ReflectHttpOperationAnalyzer.ts +4 -4
  133. package/src/analyses/ReflectHttpOperationParameterAnalyzer.ts +122 -39
  134. package/src/analyses/ReflectHttpOperationResponseAnalyzer.ts +6 -7
  135. package/src/analyses/ReflectMcpOperationAnalyzer.ts +6 -2
  136. package/src/analyses/ReflectWebSocketOperationAnalyzer.ts +7 -3
  137. package/src/analyses/SecurityAnalyzer.ts +30 -20
  138. package/src/analyses/SwaggerExampleAnalyzer.ts +23 -0
  139. package/src/analyses/TypedHttpRouteAnalyzer.ts +29 -20
  140. package/src/analyses/TypedWebSocketRouteAnalyzer.ts +1 -0
  141. package/src/executable/internal/NestiaSdkWatcher.ts +2 -2
  142. package/src/executable/sdk.ts +24 -1
  143. package/src/factories/ExpressionFactory.ts +8 -2
  144. package/src/factories/LiteralFactory.ts +7 -2
  145. package/src/generates/SdkGenerator.ts +3 -3
  146. package/src/generates/SwaggerGenerator.ts +173 -32
  147. package/src/generates/internal/FilePrinter.ts +28 -0
  148. package/src/generates/internal/ImportDictionary.ts +11 -0
  149. package/src/generates/internal/SdkAliasCollection.ts +11 -4
  150. package/src/generates/internal/SdkDistributionComposer.ts +89 -15
  151. package/src/generates/internal/SdkFileProgrammer.ts +108 -16
  152. package/src/generates/internal/SdkHttpFunctionProgrammer.ts +28 -30
  153. package/src/generates/internal/SdkHttpNamespaceProgrammer.ts +34 -64
  154. package/src/generates/internal/SdkHttpParameterProgrammer.ts +193 -13
  155. package/src/generates/internal/SdkHttpRouteProgrammer.ts +17 -31
  156. package/src/generates/internal/SdkHttpSimulationProgrammer.ts +51 -14
  157. package/src/generates/internal/SdkMcpRouteProgrammer.ts +112 -15
  158. package/src/generates/internal/SdkPathTemplate.ts +73 -0
  159. package/src/generates/internal/SdkTypeProgrammer.ts +19 -22
  160. package/src/generates/internal/SdkTypeTagProgrammer.ts +506 -78
  161. package/src/generates/internal/SdkWebSocketNamespaceProgrammer.ts +43 -68
  162. package/src/generates/internal/SdkWebSocketParameterProgrammer.ts +126 -5
  163. package/src/generates/internal/SdkWebSocketRouteProgrammer.ts +71 -48
  164. package/src/generates/internal/SwaggerOperationComposer.ts +20 -6
  165. package/src/generates/internal/SwaggerOperationParameterComposer.ts +279 -30
  166. package/src/generates/internal/SwaggerOperationResponseComposer.ts +55 -11
  167. package/src/internal/legacy.ts +114 -26
  168. package/src/structures/IOperationMetadata.ts +28 -0
  169. package/src/structures/IReflectHttpOperationException.ts +7 -2
  170. package/src/structures/IReflectHttpOperationParameter.ts +9 -2
  171. package/src/structures/IReflectHttpOperationSuccess.ts +8 -3
  172. package/src/structures/ITypedHttpRoute.ts +8 -0
  173. package/src/structures/ITypedHttpRouteException.ts +4 -1
  174. package/src/structures/ITypedHttpRouteParameter.ts +3 -1
  175. package/src/structures/ITypedHttpRouteSuccess.ts +4 -1
  176. package/src/structures/ITypedWebSocketRoute.ts +8 -0
  177. package/src/utils/SourceFinder.ts +25 -4
  178. package/src/utils/VersioningStrategy.ts +28 -13
  179. package/lib/validators/HttpHeadersValidator.d.ts +0 -11
  180. package/lib/validators/HttpHeadersValidator.js +0 -15
  181. package/lib/validators/HttpHeadersValidator.js.map +0 -1
  182. package/lib/validators/HttpQueryValidator.d.ts +0 -10
  183. package/lib/validators/HttpQueryValidator.js +0 -14
  184. package/lib/validators/HttpQueryValidator.js.map +0 -1
  185. package/src/validators/HttpHeadersValidator.ts +0 -12
  186. package/src/validators/HttpQueryValidator.ts +0 -11
@@ -2,40 +2,47 @@ import { type TypeNode, factory } from "@ttsc/factory";
2
2
  import { IMetadataTypeTag } from "@typia/interface";
3
3
 
4
4
  import { LiteralFactory } from "../../factories/LiteralFactory";
5
+ import { decodeTagValue } from "../../internal/legacy";
5
6
  import { ImportDictionary } from "./ImportDictionary";
6
7
 
7
8
  export namespace SdkTypeTagProgrammer {
9
+ export type Target =
10
+ | "object"
11
+ | "array"
12
+ | "boolean"
13
+ | "number"
14
+ | "bigint"
15
+ | "string";
16
+
17
+ /**
18
+ * Writes a type tag of a cloned DTO.
19
+ *
20
+ * A tag prints as the predefined typia tag it matches, `tags.Minimum<3>`,
21
+ * only when that tag accepts the argument and, given it, expands to exactly
22
+ * the tag the metadata carries; then the two are the same type. Anything else
23
+ * prints in the generic `tags.TagBase<{ ... }>` form, which is always the
24
+ * same type.
25
+ *
26
+ * The tag's name cannot decide it. The SDK names a tag after its object type
27
+ * with the brackets and quotes a component name drops, `Minimum3` or
28
+ * `Formatuuid`, so a name no longer tells `tags.Minimum<3>` from a user's own
29
+ * tag named alike (#1662). The expansion can: it is typia's declaration of
30
+ * each predefined tag, and should typia change one, the tag only falls back
31
+ * to the `TagBase` form.
32
+ */
8
33
  export const write = (
9
34
  importer: ImportDictionary,
10
- from: "object" | "array" | "boolean" | "number" | "bigint" | "string",
35
+ from: Target,
11
36
  tag: IMetadataTypeTag,
12
37
  ): TypeNode => {
13
- const name: string = tag.name.split("<")[0]!;
14
- if (PREDEFINED[from]?.has(name) === true)
15
- return factory.createTypeReferenceNode(
16
- factory.createQualifiedName(
17
- factory.createIdentifier(
18
- importer.external({
19
- declaration: true,
20
- file: `typia`,
21
- type: "element",
22
- name: "tags",
23
- }),
24
- ),
25
- factory.createIdentifier(name),
26
- ),
27
- [factory.createLiteralTypeNode(LiteralFactory.write(tag.value) as any)],
28
- );
38
+ const predefined: IPredefined | null =
39
+ comment(from, tag) ?? recognize(from, tag);
40
+ if (predefined !== null)
41
+ return writePredefined(importer, predefined.name, predefined.argument);
42
+ const value: unknown = decodeTagValue(tag);
29
43
  return factory.createTypeReferenceNode(
30
44
  factory.createQualifiedName(
31
- factory.createIdentifier(
32
- importer.external({
33
- declaration: true,
34
- file: `typia`,
35
- type: "element",
36
- name: "tags",
37
- }),
38
- ),
45
+ factory.createIdentifier(tagsOf(importer)),
39
46
  factory.createIdentifier("TagBase"),
40
47
  ),
41
48
  [
@@ -43,7 +50,7 @@ export namespace SdkTypeTagProgrammer {
43
50
  LiteralFactory.write({
44
51
  target: from,
45
52
  kind: tag.kind,
46
- value: tag.value,
53
+ value: Number.isNaN(value) ? null : value,
47
54
  validate: tag.validate,
48
55
  exclusive: tag.exclusive,
49
56
  schema: tag.schema,
@@ -52,59 +59,480 @@ export namespace SdkTypeTagProgrammer {
52
59
  ],
53
60
  );
54
61
  };
62
+
63
+ /** Writes the predefined typia tag `tags.<name><argument>`. */
64
+ export const writePredefined = (
65
+ importer: ImportDictionary,
66
+ name: string,
67
+ argument: unknown,
68
+ ): TypeNode =>
69
+ factory.createTypeReferenceNode(
70
+ factory.createQualifiedName(
71
+ factory.createIdentifier(tagsOf(importer)),
72
+ factory.createIdentifier(name),
73
+ ),
74
+ [factory.createLiteralTypeNode(LiteralFactory.write(argument) as any)],
75
+ );
76
+
77
+ const tagsOf = (importer: ImportDictionary): string =>
78
+ importer.external({
79
+ declaration: true,
80
+ file: `typia`,
81
+ type: "element",
82
+ name: "tags",
83
+ });
55
84
  }
56
85
 
57
- const COMMON_KINDS = ["Default", "Example", "Examples", "Sequence"];
58
- const PREDEFINED = {
59
- object: new Set([...COMMON_KINDS]),
60
- array: new Set([...COMMON_KINDS, "MinItems", "MaxItems", "UniqueItems"]),
61
- boolean: new Set([...COMMON_KINDS]),
62
- number: new Set([
63
- ...COMMON_KINDS,
64
- "Minimum",
65
- "Maximum",
66
- "ExclusiveMinimum",
67
- "ExclusiveMaximum",
68
- "MultipleOf",
69
- "Type",
70
- ]),
71
- bigint: new Set([
72
- ...COMMON_KINDS,
73
- "Minimum",
74
- "Maximum",
75
- "ExclusiveMinimum",
76
- "ExclusiveMaximum",
77
- "MultipleOf",
78
- "Type",
79
- ]),
80
- string: new Set([
81
- ...COMMON_KINDS,
82
- "ContentMediaType",
83
- "Format",
84
- "MaxLength",
85
- "MinLength",
86
- "Pattern",
87
- ]),
86
+ interface IPredefined {
87
+ name: string;
88
+ argument: unknown;
89
+ }
90
+
91
+ /**
92
+ * The predefined tag a JSDoc comment tag stands for.
93
+ *
94
+ * Typia names a comment tag, `@format uri`, after that tag with its brackets,
95
+ * `Format<"uri">`, while a type tag's name has none, as the SDK's component
96
+ * names drop them. The source has no type tag to equal, and a comment tag may
97
+ * validate its own way, such as a format's inlined expression, so it prints as
98
+ * the tag the comment names whenever the argument suits the tagged type.
99
+ */
100
+ const comment = (
101
+ from: SdkTypeTagProgrammer.Target,
102
+ tag: IMetadataTypeTag,
103
+ ): IPredefined | null => {
104
+ if (tag.name.includes("<") === false) return null;
105
+ const definition: IDefinition | undefined = DEFINITIONS[tag.kind];
106
+ if (definition === undefined || tag.name.split("<")[0] !== definition.name)
107
+ return null;
108
+ const argument: unknown = argumentOf(definition, tag);
109
+ return argument !== undefined && definition.expand(from, argument) !== null
110
+ ? { name: definition.name, argument }
111
+ : null;
112
+ };
113
+
114
+ /**
115
+ * The typia tag the metadata's type tag is, with the argument that makes it, or
116
+ * `null` when no predefined tag expands to it exactly.
117
+ */
118
+ const recognize = (
119
+ from: SdkTypeTagProgrammer.Target,
120
+ tag: IMetadataTypeTag,
121
+ ): IPredefined | null => {
122
+ const definition: IDefinition | undefined = DEFINITIONS[tag.kind];
123
+ if (definition === undefined) return null;
124
+ const argument: unknown = argumentOf(definition, tag);
125
+ if (argument === undefined) return null;
126
+ const expected: IExpansion | null = definition.expand(from, argument);
127
+ if (expected === null) return null;
128
+ // the metadata holds the schema as JSON holds it
129
+ return equals(expected.validate, tag.validate) &&
130
+ equals(expected.exclusive, tag.exclusive) &&
131
+ equals(json(expected.schema), tag.schema)
132
+ ? { name: definition.name, argument }
133
+ : null;
88
134
  };
89
135
 
90
- // export * from "./Constant";
91
- // export * from "./ContentMediaType";
92
- // export * from "./Default";
93
- // export * from "./Example";
94
- // export * from "./Examples";
95
- // export * from "./ExclusiveMaximum";
96
- // export * from "./ExclusiveMinimum";
97
- // export * from "./Format";
98
- // export * from "./JsonSchemaPlugin";
99
- // export * from "./Maximum";
100
- // export * from "./MaxItems";
101
- // export * from "./MaxLength";
102
- // export * from "./Minimum";
103
- // export * from "./MinItems";
104
- // export * from "./MinLength";
105
- // export * from "./MultipleOf";
106
- // export * from "./Pattern";
107
- // export * from "./Sequence";
108
- // export * from "./TagBase";
109
- // export * from "./Type";
110
- // export * from "./UniqueItems";
136
+ /**
137
+ * The type argument a tag was written with: its value, or, for a tag whose
138
+ * value is no literal, the schema member holding it. NaN has no literal type to
139
+ * write, so it gives none.
140
+ */
141
+ const argumentOf = (
142
+ definition: IDefinition,
143
+ tag: IMetadataTypeTag,
144
+ ): unknown => {
145
+ const value: unknown = decodeTagValue(tag);
146
+ const argument: unknown =
147
+ value === null || value === undefined
148
+ ? definition.fallback?.(tag.schema as Record<string, unknown>)
149
+ : value;
150
+ return Number.isNaN(argument) ? undefined : argument;
151
+ };
152
+
153
+ /**
154
+ * What a predefined tag, given its argument, bakes into the metadata. A tag
155
+ * whose type parameter constraint rejects the argument expands to nothing.
156
+ */
157
+ interface IExpansion {
158
+ validate: string | undefined;
159
+ exclusive: boolean | string[];
160
+ schema: object;
161
+ }
162
+
163
+ interface IDefinition {
164
+ name: string;
165
+ expand: (
166
+ from: SdkTypeTagProgrammer.Target,
167
+ argument: unknown,
168
+ ) => IExpansion | null;
169
+ fallback?: (schema: Record<string, unknown> | undefined) => unknown;
170
+ }
171
+
172
+ /** `${Value}` of a numeric type argument, as a TypeScript template prints it. */
173
+ const numeral = (value: number | bigint): string =>
174
+ typeof value === "bigint" ? `BigInt(${value})` : String(value);
175
+
176
+ const isNumeric = (value: unknown): value is number | bigint =>
177
+ typeof value === "number" || typeof value === "bigint";
178
+
179
+ /**
180
+ * A value as JSON holds it: a bigint as its number, as typia's `Numeric<Value>`
181
+ * does, and a non-finite number as null, as `JSON.stringify` writes it.
182
+ */
183
+ const json = (value: unknown): unknown =>
184
+ typeof value === "bigint"
185
+ ? Number(value)
186
+ : typeof value === "number" && Number.isFinite(value) === false
187
+ ? null
188
+ : Array.isArray(value)
189
+ ? value.map(json)
190
+ : typeof value === "object" && value !== null
191
+ ? Object.fromEntries(
192
+ Object.entries(value).map(([key, v]) => [key, json(v)]),
193
+ )
194
+ : value;
195
+
196
+ const bound =
197
+ (
198
+ kind: string,
199
+ exclusive: string[],
200
+ validate: (value: string) => string,
201
+ ): IDefinition["expand"] =>
202
+ (from, argument) =>
203
+ isNumeric(argument) &&
204
+ from === (typeof argument === "bigint" ? "bigint" : "number")
205
+ ? {
206
+ validate: validate(numeral(argument)),
207
+ exclusive,
208
+ schema: { [kind]: json(argument) },
209
+ }
210
+ : null;
211
+
212
+ const TYPE_VALIDATE: Record<string, string | Record<string, string>> = {
213
+ int8: `$importInternal("isTypeInt8")($input)`,
214
+ uint8: `$importInternal("isTypeUint8")($input)`,
215
+ int16: `$importInternal("isTypeInt16")($input)`,
216
+ uint16: `$importInternal("isTypeUint16")($input)`,
217
+ int32: `$importInternal("isTypeInt32")($input)`,
218
+ uint32: `$importInternal("isTypeUint32")($input)`,
219
+ int64: {
220
+ number: `$importInternal("isTypeInt64")($input)`,
221
+ bigint: `$importInternal("isTypeInt64Bigint")($input)`,
222
+ },
223
+ uint64: {
224
+ number: `$importInternal("isTypeUint64")($input)`,
225
+ bigint: `$importInternal("isTypeUint64Bigint")($input)`,
226
+ },
227
+ float: `$importInternal("isTypeFloat")($input)`,
228
+ double: `true`,
229
+ };
230
+
231
+ /** `Format.Value` of typia's `Format` tag. */
232
+ const FORMATS: Set<string> = new Set([
233
+ "byte",
234
+ "password",
235
+ "regex",
236
+ "uuid",
237
+ "email",
238
+ "hostname",
239
+ "idn-email",
240
+ "idn-hostname",
241
+ "iri",
242
+ "iri-reference",
243
+ "ipv4",
244
+ "ipv6",
245
+ "uri",
246
+ "uri-reference",
247
+ "uri-template",
248
+ "url",
249
+ "date-time",
250
+ "date",
251
+ "time",
252
+ "duration",
253
+ "json-pointer",
254
+ "relative-json-pointer",
255
+ ]);
256
+
257
+ /** `DefaultAtomic` of typia's `Default` tag. */
258
+ const isDefaultAtomic = (value: unknown): boolean =>
259
+ typeof value === "boolean" ||
260
+ typeof value === "bigint" ||
261
+ typeof value === "number" ||
262
+ typeof value === "string";
263
+
264
+ /** `PascalizeString` of typia's `Format` tag: `date-time` as `DateTime`. */
265
+ const pascalize = (value: string): string =>
266
+ value.includes("-")
267
+ ? value
268
+ .split("-")
269
+ .filter((word) => word.length !== 0)
270
+ .map(
271
+ (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
272
+ )
273
+ .join("")
274
+ : value.charAt(0).toUpperCase() + value.slice(1);
275
+
276
+ /** `Serialize` of typia's `Pattern` tag, escaping as a string literal. */
277
+ const serialize = (value: string): string =>
278
+ value.replace(
279
+ /["\\\b\f\n\r\t]/g,
280
+ (char) =>
281
+ ({
282
+ '"': '\\"',
283
+ "\\": "\\\\",
284
+ "\b": "\\b",
285
+ "\f": "\\f",
286
+ "\n": "\\n",
287
+ "\r": "\\r",
288
+ "\t": "\\t",
289
+ })[char]!,
290
+ );
291
+
292
+ /**
293
+ * Every predefined typia tag, keyed by its kind, as `@typia/interface` declares
294
+ * it.
295
+ */
296
+ const DEFINITIONS: Record<string, IDefinition> = {
297
+ minimum: {
298
+ name: "Minimum",
299
+ expand: bound(
300
+ "minimum",
301
+ ["minimum", "exclusiveMinimum"],
302
+ (v) => `${v} <= $input`,
303
+ ),
304
+ },
305
+ maximum: {
306
+ name: "Maximum",
307
+ expand: bound(
308
+ "maximum",
309
+ ["maximum", "exclusiveMaximum"],
310
+ (v) => `$input <= ${v}`,
311
+ ),
312
+ },
313
+ exclusiveMinimum: {
314
+ name: "ExclusiveMinimum",
315
+ expand: bound(
316
+ "exclusiveMinimum",
317
+ ["exclusiveMinimum", "minimum"],
318
+ (v) => `${v} < $input`,
319
+ ),
320
+ },
321
+ exclusiveMaximum: {
322
+ name: "ExclusiveMaximum",
323
+ expand: bound(
324
+ "exclusiveMaximum",
325
+ ["exclusiveMaximum", "maximum"],
326
+ (v) => `$input < ${v}`,
327
+ ),
328
+ },
329
+ multipleOf: {
330
+ name: "MultipleOf",
331
+ expand: (from, argument) =>
332
+ isNumeric(argument) &&
333
+ from === (typeof argument === "bigint" ? "bigint" : "number")
334
+ ? {
335
+ validate:
336
+ typeof argument === "bigint"
337
+ ? `$input % ${numeral(argument)} === ${numeral(BigInt(0))}`
338
+ : `$importInternal("_isMultipleOf")($input, ${argument})`,
339
+ exclusive: true,
340
+ schema: { multipleOf: json(argument) },
341
+ }
342
+ : null,
343
+ },
344
+ type: {
345
+ name: "Type",
346
+ expand: (from, argument) => {
347
+ if (typeof argument !== "string") return null;
348
+ const validate = TYPE_VALIDATE[argument];
349
+ if (validate === undefined) return null;
350
+ const wide: boolean = argument === "int64" || argument === "uint64";
351
+ if (from !== "number" && (wide === false || from !== "bigint"))
352
+ return null;
353
+ return {
354
+ validate: typeof validate === "string" ? validate : validate[from],
355
+ exclusive: true,
356
+ schema: argument.startsWith("uint")
357
+ ? { type: "integer", minimum: 0 }
358
+ : { type: argument.startsWith("int") ? "integer" : "number" },
359
+ };
360
+ },
361
+ },
362
+ format: {
363
+ name: "Format",
364
+ expand: (from, argument) =>
365
+ from === "string" && typeof argument === "string" && FORMATS.has(argument)
366
+ ? {
367
+ validate: `$importInternal("isFormat${pascalize(argument)}")($input)`,
368
+ exclusive: ["format", "pattern"],
369
+ schema: { format: argument },
370
+ }
371
+ : null,
372
+ },
373
+ pattern: {
374
+ name: "Pattern",
375
+ expand: (from, argument) =>
376
+ from === "string" && typeof argument === "string"
377
+ ? {
378
+ validate: `RegExp("${serialize(argument)}").test($input)`,
379
+ exclusive: ["format", "pattern"],
380
+ schema: { pattern: argument },
381
+ }
382
+ : null,
383
+ },
384
+ minLength: {
385
+ name: "MinLength",
386
+ expand: (from, argument) =>
387
+ from === "string" && typeof argument === "number"
388
+ ? {
389
+ validate: `$importInternal("_stringLengthGte")($input, ${argument})`,
390
+ exclusive: true,
391
+ schema: { minLength: argument },
392
+ }
393
+ : null,
394
+ },
395
+ maxLength: {
396
+ name: "MaxLength",
397
+ expand: (from, argument) =>
398
+ from === "string" && typeof argument === "number"
399
+ ? {
400
+ validate: `$importInternal("_stringLengthLte")($input, ${argument})`,
401
+ exclusive: true,
402
+ schema: { maxLength: argument },
403
+ }
404
+ : null,
405
+ },
406
+ minItems: {
407
+ name: "MinItems",
408
+ expand: (from, argument) =>
409
+ from === "array" && typeof argument === "number"
410
+ ? {
411
+ validate: `${argument} <= $input.length`,
412
+ exclusive: true,
413
+ schema: { minItems: argument },
414
+ }
415
+ : null,
416
+ },
417
+ maxItems: {
418
+ name: "MaxItems",
419
+ expand: (from, argument) =>
420
+ from === "array" && typeof argument === "number"
421
+ ? {
422
+ validate: `$input.length <= ${argument}`,
423
+ exclusive: true,
424
+ schema: { maxItems: argument },
425
+ }
426
+ : null,
427
+ },
428
+ uniqueItems: {
429
+ name: "UniqueItems",
430
+ expand: (from, argument) =>
431
+ from === "array" && typeof argument === "boolean"
432
+ ? {
433
+ validate: argument
434
+ ? `$importInternal("isUniqueItems")($input)`
435
+ : undefined,
436
+ exclusive: true,
437
+ schema: { uniqueItems: true },
438
+ }
439
+ : null,
440
+ },
441
+ contentMediaType: {
442
+ name: "ContentMediaType",
443
+ expand: (from, argument) =>
444
+ from === "string" && typeof argument === "string"
445
+ ? {
446
+ validate: undefined,
447
+ exclusive: false,
448
+ schema: { contentMediaType: argument },
449
+ }
450
+ : null,
451
+ fallback: (schema) => schema?.contentMediaType,
452
+ },
453
+ default: {
454
+ name: "Default",
455
+ expand: (from, argument) =>
456
+ from ===
457
+ (Array.isArray(argument)
458
+ ? argument.every(isDefaultAtomic)
459
+ ? "array"
460
+ : null
461
+ : typeof argument === "boolean"
462
+ ? "boolean"
463
+ : typeof argument === "bigint"
464
+ ? "bigint"
465
+ : typeof argument === "number"
466
+ ? "number"
467
+ : typeof argument === "string"
468
+ ? "string"
469
+ : null)
470
+ ? {
471
+ validate: undefined,
472
+ exclusive: true,
473
+ schema: { default: json(argument) },
474
+ }
475
+ : null,
476
+ fallback: (schema) => schema?.default,
477
+ },
478
+ example: {
479
+ name: "Example",
480
+ expand: (_from, argument) => ({
481
+ validate: undefined,
482
+ exclusive: true,
483
+ schema: {
484
+ example: typeof argument === "bigint" ? Number(argument) : argument,
485
+ },
486
+ }),
487
+ fallback: (schema) => schema?.example,
488
+ },
489
+ examples: {
490
+ name: "Examples",
491
+ expand: (_from, argument) =>
492
+ typeof argument === "object" &&
493
+ argument !== null &&
494
+ Array.isArray(argument) === false &&
495
+ Object.values(argument).every((value) => value !== undefined)
496
+ ? {
497
+ validate: undefined,
498
+ exclusive: true,
499
+ schema: { examples: argument },
500
+ }
501
+ : null,
502
+ fallback: (schema) => schema?.examples,
503
+ },
504
+ sequence: {
505
+ name: "Sequence",
506
+ expand: (_from, argument) =>
507
+ typeof argument === "number"
508
+ ? {
509
+ validate: undefined,
510
+ exclusive: false,
511
+ schema: { "x-protobuf-sequence": argument },
512
+ }
513
+ : null,
514
+ },
515
+ };
516
+
517
+ /** Structural equality of metadata values, which hold bigints. */
518
+ const equals = (x: unknown, y: unknown): boolean => {
519
+ if (x === y) return true;
520
+ if (typeof x !== "object" || typeof y !== "object") return false;
521
+ if (x === null || y === null) return false;
522
+ if (Array.isArray(x) !== Array.isArray(y)) return false;
523
+ const xKeys: string[] = Object.keys(x).filter(
524
+ (key) => (x as Record<string, unknown>)[key] !== undefined,
525
+ );
526
+ const yKeys: string[] = Object.keys(y).filter(
527
+ (key) => (y as Record<string, unknown>)[key] !== undefined,
528
+ );
529
+ return (
530
+ xKeys.length === yKeys.length &&
531
+ xKeys.every((key) =>
532
+ equals(
533
+ (x as Record<string, unknown>)[key],
534
+ (y as Record<string, unknown>)[key],
535
+ ),
536
+ )
537
+ );
538
+ };