@nestia/core 1.4.5 → 1.4.6

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 (33) hide show
  1. package/lib/decorators/EncryptedRoute.d.ts +4 -4
  2. package/lib/decorators/EncryptedRoute.js +4 -4
  3. package/lib/decorators/TypedBody.d.ts +1 -1
  4. package/lib/decorators/TypedBody.js +1 -1
  5. package/lib/decorators/TypedHeaders.d.ts +42 -0
  6. package/lib/decorators/TypedHeaders.js +114 -0
  7. package/lib/decorators/TypedHeaders.js.map +1 -0
  8. package/lib/decorators/TypedQuery.d.ts +10 -3
  9. package/lib/decorators/TypedQuery.js +9 -2
  10. package/lib/decorators/TypedQuery.js.map +1 -1
  11. package/lib/decorators/TypedRoute.d.ts +5 -4
  12. package/lib/decorators/TypedRoute.js +5 -4
  13. package/lib/decorators/TypedRoute.js.map +1 -1
  14. package/lib/module.d.ts +1 -0
  15. package/lib/module.js +1 -0
  16. package/lib/module.js.map +1 -1
  17. package/lib/programmers/TypedHeadersProgrammer.d.ts +5 -0
  18. package/lib/programmers/TypedHeadersProgrammer.js +300 -0
  19. package/lib/programmers/TypedHeadersProgrammer.js.map +1 -0
  20. package/lib/programmers/TypedQueryProgrammer.js +11 -8
  21. package/lib/programmers/TypedQueryProgrammer.js.map +1 -1
  22. package/lib/transformers/ParameterDecoratorTransformer.js +6 -0
  23. package/lib/transformers/ParameterDecoratorTransformer.js.map +1 -1
  24. package/package.json +3 -3
  25. package/src/decorators/EncryptedRoute.ts +4 -4
  26. package/src/decorators/TypedBody.ts +1 -1
  27. package/src/decorators/TypedHeaders.ts +95 -0
  28. package/src/decorators/TypedQuery.ts +10 -3
  29. package/src/decorators/TypedRoute.ts +5 -4
  30. package/src/module.ts +1 -0
  31. package/src/programmers/TypedHeadersProgrammer.ts +304 -0
  32. package/src/programmers/TypedQueryProgrammer.ts +31 -25
  33. package/src/transformers/ParameterDecoratorTransformer.ts +5 -0
@@ -29,13 +29,14 @@ import { route_error } from "./internal/route_error";
29
29
  * Type safe router decorator functions.
30
30
  *
31
31
  * `TypedRoute` is a module containing router decorator functions which can boost up
32
- * JSON string conversion speed about 50x times faster than `class-transformer`.
32
+ * JSON string conversion speed about 200x times faster than `class-transformer`.
33
33
  * Furthermore, such JSON string conversion is even type safe through
34
34
  * [typia](https://github.com/samchon/typia).
35
35
  *
36
- * For reference, router functions of `TypedRoute` can convert custom error classes to
37
- * the regular {@link nest.HttpException} class automatically, through
38
- * {@link ExceptionManager}.
36
+ * For reference, if you try to invalid data that is not following the promised
37
+ * type `T`, 500 internal server error would be thrown. Also, as `TypedRoute` composes
38
+ * JSON string through `typia.assertStringify<T>()` function, it is not possible to
39
+ * modify response data through interceptors.
39
40
  *
40
41
  * @author Jeongho Nam - https://github.com/samchon
41
42
  */
package/src/module.ts CHANGED
@@ -6,6 +6,7 @@ export * from "./decorators/EncryptedRoute";
6
6
  export * from "./utils/ExceptionManager";
7
7
  export * from "./decorators/PlainBody";
8
8
  export * from "./decorators/TypedBody";
9
+ export * from "./decorators/TypedHeaders";
9
10
  export * from "./decorators/TypedParam";
10
11
  export * from "./decorators/TypedRoute";
11
12
  export * from "./decorators/TypedQuery";
@@ -0,0 +1,304 @@
1
+ import ts from "typescript";
2
+
3
+ import { IdentifierFactory } from "typia/lib/factories/IdentifierFactory";
4
+ import { MetadataCollection } from "typia/lib/factories/MetadataCollection";
5
+ import { MetadataFactory } from "typia/lib/factories/MetadataFactory";
6
+ import { StatementFactory } from "typia/lib/factories/StatementFactory";
7
+ import { Metadata } from "typia/lib/metadata/Metadata";
8
+ import { MetadataObject } from "typia/lib/metadata/MetadataObject";
9
+ import { MetadataProperty } from "typia/lib/metadata/MetadataProperty";
10
+ import { AssertProgrammer } from "typia/lib/programmers/AssertProgrammer";
11
+ import { FunctionImporter } from "typia/lib/programmers/helpers/FunctionImporeter";
12
+ import { Atomic } from "typia/lib/typings/Atomic";
13
+ import { Escaper } from "typia/lib/utils/Escaper";
14
+
15
+ import { INestiaTransformProject } from "../options/INestiaTransformProject";
16
+
17
+ export namespace TypedHeadersProgrammer {
18
+ export const generate =
19
+ (project: INestiaTransformProject) =>
20
+ (modulo: ts.LeftHandSideExpression) =>
21
+ (type: ts.Type): ts.Expression => {
22
+ const object: MetadataObject = getObject(project.checker)(type);
23
+ return decode(project, modulo)(type, object);
24
+ };
25
+
26
+ const getObject =
27
+ (checker: ts.TypeChecker) =>
28
+ (type: ts.Type): MetadataObject => {
29
+ const collection: MetadataCollection = new MetadataCollection();
30
+ const metadata: Metadata = MetadataFactory.analyze(checker)({
31
+ resolve: false,
32
+ constant: true,
33
+ absorb: true,
34
+ })(collection)(type);
35
+ if (metadata.objects.length !== 1 || metadata.bucket() !== 1)
36
+ throw new Error(
37
+ ErrorMessages.object(metadata)(
38
+ "only one object type is allowed.",
39
+ ),
40
+ );
41
+ else if (metadata.nullable === true)
42
+ throw new Error(
43
+ ErrorMessages.object(metadata)(
44
+ "query parameter cannot be null.",
45
+ ),
46
+ );
47
+ else if (metadata.isRequired() === false)
48
+ throw new Error(
49
+ ErrorMessages.object(metadata)(
50
+ "query parameter cannot be undefined.",
51
+ ),
52
+ );
53
+
54
+ const object: MetadataObject = metadata.objects[0]!;
55
+ if (object.properties.some((p) => !(p.key as any).isSoleLiteral()))
56
+ throw new Error(
57
+ ErrorMessages.object(metadata)(
58
+ "dynamic property is not allowed.",
59
+ ),
60
+ );
61
+
62
+ for (const property of object.properties) {
63
+ const key: string = property.key.constants[0]
64
+ .values[0] as string;
65
+ const value: Metadata = property.value;
66
+ validate(object)(key)(value, 0);
67
+ }
68
+ return object;
69
+ };
70
+
71
+ const validate =
72
+ (obj: MetadataObject) =>
73
+ (key: string) =>
74
+ (value: Metadata, depth: number): string[] => {
75
+ if (depth === 1 && value.isRequired() === false)
76
+ throw new Error(
77
+ ErrorMessages.property(obj)(key)(
78
+ "optional type is not allowed in array.",
79
+ ),
80
+ );
81
+ else if (value.nullable === true)
82
+ throw new Error(
83
+ ErrorMessages.property(obj)(key)(
84
+ "nullable type is not allowed.",
85
+ ),
86
+ );
87
+ else if (
88
+ value.maps.length ||
89
+ value.sets.length ||
90
+ value.objects.length
91
+ )
92
+ throw new Error(
93
+ ErrorMessages.property(obj)(key)(
94
+ "object type is not allowed",
95
+ ),
96
+ );
97
+
98
+ const atom: string[] = [];
99
+ for (const type of value.atomics) atom.push(type);
100
+ for (const { type } of value.constants) atom.push(type);
101
+
102
+ if (depth === 0 && (value.arrays.length || value.arrays.length)) {
103
+ if (atom.length)
104
+ throw new Error(
105
+ ErrorMessages.property(obj)(key)(
106
+ "union type is not allowed",
107
+ ),
108
+ );
109
+ for (const array of value.arrays)
110
+ atom.push(...validate(obj)(key)(array.value, depth + 1));
111
+ for (const tuple of value.tuples)
112
+ for (const elem of tuple.elements)
113
+ atom.push(...validate(obj)(key)(elem, depth + 1));
114
+ } else if (value.arrays.length || value.tuples.length)
115
+ throw new Error(
116
+ ErrorMessages.property(obj)(key)(
117
+ "double-array type is not allowed",
118
+ ),
119
+ );
120
+
121
+ const size: number = new Set(atom).size;
122
+ if (size === 0)
123
+ throw new Error(
124
+ ErrorMessages.property(obj)(key)("unknown type"),
125
+ );
126
+ else if (size > 1)
127
+ throw new Error(
128
+ ErrorMessages.property(obj)(key)(
129
+ "union type is not allowed",
130
+ ),
131
+ );
132
+ return atom;
133
+ };
134
+
135
+ const decode =
136
+ (project: INestiaTransformProject, modulo: ts.LeftHandSideExpression) =>
137
+ (type: ts.Type, object: MetadataObject): ts.ArrowFunction =>
138
+ ts.factory.createArrowFunction(
139
+ undefined,
140
+ undefined,
141
+ [IdentifierFactory.parameter("input")],
142
+ undefined,
143
+ undefined,
144
+ decode_object(project, modulo)(type, object),
145
+ );
146
+
147
+ const decode_object =
148
+ (project: INestiaTransformProject, modulo: ts.LeftHandSideExpression) =>
149
+ (type: ts.Type, object: MetadataObject): ts.ConciseBody => {
150
+ const assert: ts.ArrowFunction = AssertProgrammer.write({
151
+ ...project,
152
+ options: {
153
+ numeric: true,
154
+ finite: true,
155
+ },
156
+ })(modulo)(false)(type);
157
+ const output: ts.Identifier = ts.factory.createIdentifier("output");
158
+
159
+ const importer: FunctionImporter = new FunctionImporter();
160
+ const statements: ts.Statement[] = [
161
+ StatementFactory.constant(
162
+ "output",
163
+ ts.factory.createObjectLiteralExpression(
164
+ object.properties.map((prop) =>
165
+ decode_regular_property(importer)(object)(prop),
166
+ ),
167
+ true,
168
+ ),
169
+ ),
170
+ ts.factory.createReturnStatement(
171
+ ts.factory.createCallExpression(assert, undefined, [
172
+ output,
173
+ ]),
174
+ ),
175
+ ];
176
+
177
+ return ts.factory.createBlock(
178
+ [...importer.declare(modulo), ...statements],
179
+ true,
180
+ );
181
+ };
182
+
183
+ const decode_regular_property =
184
+ (importer: FunctionImporter) =>
185
+ (object: MetadataObject) =>
186
+ (property: MetadataProperty): ts.PropertyAssignment => {
187
+ const key: string = property.key.constants[0]!.values[0] as string;
188
+ const value: Metadata = property.value;
189
+
190
+ const [type, isArray]: [Atomic.Literal, boolean] = value.atomics
191
+ .length
192
+ ? [value.atomics[0], false]
193
+ : value.constants.length
194
+ ? [value.constants[0]!.type, false]
195
+ : (() => {
196
+ const meta =
197
+ value.arrays[0]?.value ?? value.tuples[0].elements[0];
198
+ return meta.atomics.length
199
+ ? [meta.atomics[0], true]
200
+ : [meta.constants[0]!.type, true];
201
+ })();
202
+ if (key.toLowerCase() !== key)
203
+ throw new Error(
204
+ ErrorMessages.property(object)(key)(
205
+ `property "${key}" must be lower case.`,
206
+ ),
207
+ );
208
+ else if (isArray && SINGULAR.has(key))
209
+ throw new Error(
210
+ ErrorMessages.property(object)(key)(
211
+ `property "${key}" cannot be array.`,
212
+ ),
213
+ );
214
+ else if (!isArray && key === "set-cookie")
215
+ throw new Error(
216
+ ErrorMessages.property(object)(key)(
217
+ `property "${key}" must be array.`,
218
+ ),
219
+ );
220
+
221
+ const accessor = IdentifierFactory.access(
222
+ ts.factory.createIdentifier("input"),
223
+ )(key);
224
+
225
+ return ts.factory.createPropertyAssignment(
226
+ Escaper.variable(key)
227
+ ? key
228
+ : ts.factory.createStringLiteral(key),
229
+ isArray
230
+ ? key === "set-cookie"
231
+ ? accessor
232
+ : ts.factory.createCallChain(
233
+ ts.factory.createPropertyAccessChain(
234
+ ts.factory.createCallChain(
235
+ ts.factory.createPropertyAccessChain(
236
+ accessor,
237
+ ts.factory.createToken(
238
+ ts.SyntaxKind.QuestionDotToken,
239
+ ),
240
+ ts.factory.createIdentifier("split"),
241
+ ),
242
+ undefined,
243
+ undefined,
244
+ [
245
+ ts.factory.createStringLiteral(
246
+ key === "cookie" ? "; " : ", ",
247
+ ),
248
+ ],
249
+ ),
250
+ ts.factory.createToken(
251
+ ts.SyntaxKind.QuestionDotToken,
252
+ ),
253
+ ts.factory.createIdentifier("map"),
254
+ ),
255
+ undefined,
256
+ undefined,
257
+ [importer.use(type)],
258
+ )
259
+ : decode_value(importer)(type)(accessor),
260
+ );
261
+ };
262
+
263
+ const decode_value =
264
+ (importer: FunctionImporter) =>
265
+ (type: Atomic.Literal) =>
266
+ (value: ts.Expression) =>
267
+ type === "string"
268
+ ? value
269
+ : ts.factory.createCallExpression(
270
+ importer.use(type),
271
+ undefined,
272
+ [value],
273
+ );
274
+ }
275
+
276
+ namespace ErrorMessages {
277
+ export const object = (type: Metadata) => (message: string) =>
278
+ `Error on nestia.core.TypedHeaders<${type.getName()}>(): ${message}`;
279
+
280
+ export const property =
281
+ (obj: MetadataObject) => (key: string) => (message: string) =>
282
+ `Error on nestia.core.TypedHeaders<${obj.name}>(): property "${key}" - ${message}`;
283
+ }
284
+
285
+ const SINGULAR: Set<string> = new Set([
286
+ "age",
287
+ "authorization",
288
+ "content-length",
289
+ "content-type",
290
+ "etag",
291
+ "expires",
292
+ "from",
293
+ "host",
294
+ "if-modified-since",
295
+ "if-unmodified-since",
296
+ "last-modified",
297
+ "location",
298
+ "max-forwards",
299
+ "proxy-authorization",
300
+ "referer",
301
+ "retry-after",
302
+ "server",
303
+ "user-agent",
304
+ ]);
@@ -10,6 +10,7 @@ import { MetadataProperty } from "typia/lib/metadata/MetadataProperty";
10
10
  import { AssertProgrammer } from "typia/lib/programmers/AssertProgrammer";
11
11
  import { FunctionImporter } from "typia/lib/programmers/helpers/FunctionImporeter";
12
12
  import { Atomic } from "typia/lib/typings/Atomic";
13
+ import { Escaper } from "typia/lib/utils/Escaper";
13
14
 
14
15
  import { INestiaTransformProject } from "../options/INestiaTransformProject";
15
16
 
@@ -50,9 +51,17 @@ export namespace TypedQueryProgrammer {
50
51
  ),
51
52
  );
52
53
 
53
- const object = metadata.objects[0]!;
54
+ const object: MetadataObject = metadata.objects[0]!;
55
+ if (object.properties.some((p) => !(p.key as any).isSoleLiteral()))
56
+ throw new Error(
57
+ ErrorMessages.object(metadata)(
58
+ "dynamic property is not allowed.",
59
+ ),
60
+ );
61
+
54
62
  for (const property of object.properties) {
55
- const key: Metadata = property.key;
63
+ const key: string = property.key.constants[0]
64
+ .values[0] as string;
56
65
  const value: Metadata = property.value;
57
66
  validate(object)(key)(value, 0);
58
67
  }
@@ -61,7 +70,7 @@ export namespace TypedQueryProgrammer {
61
70
 
62
71
  const validate =
63
72
  (obj: MetadataObject) =>
64
- (key: Metadata) =>
73
+ (key: string) =>
65
74
  (value: Metadata, depth: number): string[] => {
66
75
  if (depth === 1 && value.isRequired() === false)
67
76
  throw new Error(
@@ -139,29 +148,26 @@ export namespace TypedQueryProgrammer {
139
148
  finite: true,
140
149
  },
141
150
  })(modulo)(false)(type);
142
- const output = ts.factory.createIdentifier("output");
151
+ const output: ts.Identifier = ts.factory.createIdentifier("output");
143
152
 
144
- const importer = new FunctionImporter();
153
+ const importer: FunctionImporter = new FunctionImporter();
145
154
  const optionalArrays: string[] = [];
146
155
  const statements: ts.Statement[] = [
147
156
  StatementFactory.constant(
148
157
  "output",
149
158
  ts.factory.createObjectLiteralExpression(
150
- object.properties
151
- .filter((prop) => (prop.key as any).isSoleLiteral())
152
- .map((prop) => {
153
- if (
154
- !prop.value.isRequired() &&
155
- prop.value.arrays.length +
156
- prop.value.tuples.length >
157
- 0
158
- )
159
- optionalArrays.push(
160
- prop.key.constants[0]!
161
- .values[0] as string,
162
- );
163
- return decode_regular_property(importer)(prop);
164
- }),
159
+ object.properties.map((prop) => {
160
+ if (
161
+ !prop.value.isRequired() &&
162
+ prop.value.arrays.length +
163
+ prop.value.tuples.length >
164
+ 0
165
+ )
166
+ optionalArrays.push(
167
+ prop.key.constants[0]!.values[0] as string,
168
+ );
169
+ return decode_regular_property(importer)(prop);
170
+ }),
165
171
  true,
166
172
  ),
167
173
  ),
@@ -209,7 +215,9 @@ export namespace TypedQueryProgrammer {
209
215
  : [meta.constants[0]!.type, true];
210
216
  })();
211
217
  return ts.factory.createPropertyAssignment(
212
- key,
218
+ Escaper.variable(key)
219
+ ? key
220
+ : ts.factory.createStringLiteral(key),
213
221
  isArray
214
222
  ? ts.factory.createCallExpression(
215
223
  IdentifierFactory.access(
@@ -257,8 +265,6 @@ namespace ErrorMessages {
257
265
  `Error on nestia.core.TypedQuery<${type.getName()}>(): ${message}`;
258
266
 
259
267
  export const property =
260
- (obj: MetadataObject) => (key: Metadata) => (message: string) =>
261
- `Error on nestia.core.TypedQuery<${
262
- obj.name
263
- }>(): property ${key.getName()} - ${message}`;
268
+ (obj: MetadataObject) => (key: string) => (message: string) =>
269
+ `Error on nestia.core.TypedQuery<${obj.name}>(): property "${key}" - ${message}`;
264
270
  }
@@ -4,6 +4,7 @@ import ts from "typescript";
4
4
  import { INestiaTransformProject } from "../options/INestiaTransformProject";
5
5
  import { PlainBodyProgrammer } from "../programmers/PlainBodyProgrammer";
6
6
  import { TypedBodyProgrammer } from "../programmers/TypedBodyProgrammer";
7
+ import { TypedHeadersProgrammer } from "../programmers/TypedHeadersProgrammer";
7
8
  import { TypedParamProgrammer } from "../programmers/TypedParamProgrammer";
8
9
  import { TypedQueryProgrammer } from "../programmers/TypedQueryProgrammer";
9
10
 
@@ -78,6 +79,10 @@ const FUNCTORS: Record<string, Programmer> = {
78
79
  parameters.length
79
80
  ? parameters
80
81
  : [TypedBodyProgrammer.generate(project)(modulo)(type)],
82
+ TypedHeaders: (project) => (modulo) => (parameters) => (type) =>
83
+ parameters.length
84
+ ? parameters
85
+ : [TypedHeadersProgrammer.generate(project)(modulo)(type)],
81
86
  TypedParam: (project) => () => TypedParamProgrammer.generate(project),
82
87
  TypedQuery: (project) => (modulo) => (parameters) => (type) =>
83
88
  parameters.length