@nestia/core 1.4.4 → 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 +23 -11
  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 +44 -21
  33. package/src/transformers/ParameterDecoratorTransformer.ts +5 -0
@@ -17,13 +17,20 @@ import { TransformError } from "./internal/TransformError";
17
17
  * same with {@link nest.Query}, but it can automatically cast property type following
18
18
  * its DTO definition. Also, `TypedQuery` performs type validation.
19
19
  *
20
- * For referecen, when URL query parameters are different with their promised
21
- * type `T`, `BadRequestException` error (status code: 400) would be thrown.
20
+ * For reference, target type `T` must follw such restriction. Also, if actual URL
21
+ * query parameter values are different with their promised type `T`,
22
+ * `BadRequestException` error (status code: 400) would be thrown.
23
+ *
24
+ * 1. Type `T` must be an object type
25
+ * 2. Do not allow dynamic property
26
+ * 3. Prpoerty value cannot be `undefined`, but `null` is possible
27
+ * 4. Only `boolean`, `bigint`, `number`, `string` or their array types are allowed
28
+ * 5. By the way, union type never be not allowed
22
29
  *
23
30
  * @returns Parameter decorator
24
31
  * @author Jeongho Nam - https://github.com/samchon
25
32
  */
26
- export function TypedQuery<T>(
33
+ export function TypedQuery<T extends object>(
27
34
  decoder?: (params: URLSearchParams) => T,
28
35
  ): ParameterDecorator {
29
36
  if (decoder === undefined) throw TransformError("TypedQuery");
@@ -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(
@@ -91,12 +100,6 @@ export namespace TypedQueryProgrammer {
91
100
  "union type is not allowed",
92
101
  ),
93
102
  );
94
- else if (value.isRequired() === false)
95
- throw new Error(
96
- ErrorMessages.property(obj)(key)(
97
- "array type cannot be optional",
98
- ),
99
- );
100
103
  for (const array of value.arrays)
101
104
  atom.push(...validate(obj)(key)(array.value, depth + 1));
102
105
  for (const tuple of value.tuples)
@@ -145,21 +148,41 @@ export namespace TypedQueryProgrammer {
145
148
  finite: true,
146
149
  },
147
150
  })(modulo)(false)(type);
148
- const output = ts.factory.createIdentifier("output");
151
+ const output: ts.Identifier = ts.factory.createIdentifier("output");
149
152
 
150
- const importer = new FunctionImporter();
153
+ const importer: FunctionImporter = new FunctionImporter();
154
+ const optionalArrays: string[] = [];
151
155
  const statements: ts.Statement[] = [
152
156
  StatementFactory.constant(
153
157
  "output",
154
158
  ts.factory.createObjectLiteralExpression(
155
- object.properties
156
- .filter((prop) => (prop.key as any).isSoleLiteral())
157
- .map((prop) =>
158
- decode_regular_property(importer)(prop),
159
- ),
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
+ }),
160
171
  true,
161
172
  ),
162
173
  ),
174
+ ...optionalArrays.map((key) => {
175
+ const access = IdentifierFactory.access(output)(key);
176
+ return ts.factory.createIfStatement(
177
+ ts.factory.createStrictEquality(
178
+ ts.factory.createNumericLiteral(0),
179
+ IdentifierFactory.access(access)("length"),
180
+ ),
181
+ ts.factory.createExpressionStatement(
182
+ ts.factory.createDeleteExpression(access),
183
+ ),
184
+ );
185
+ }),
163
186
  ts.factory.createReturnStatement(
164
187
  ts.factory.createCallExpression(assert, undefined, [
165
188
  output,
@@ -192,7 +215,9 @@ export namespace TypedQueryProgrammer {
192
215
  : [meta.constants[0]!.type, true];
193
216
  })();
194
217
  return ts.factory.createPropertyAssignment(
195
- key,
218
+ Escaper.variable(key)
219
+ ? key
220
+ : ts.factory.createStringLiteral(key),
196
221
  isArray
197
222
  ? ts.factory.createCallExpression(
198
223
  IdentifierFactory.access(
@@ -240,8 +265,6 @@ namespace ErrorMessages {
240
265
  `Error on nestia.core.TypedQuery<${type.getName()}>(): ${message}`;
241
266
 
242
267
  export const property =
243
- (obj: MetadataObject) => (key: Metadata) => (message: string) =>
244
- `Error on nestia.core.TypedQuery<${
245
- obj.name
246
- }>(): property ${key.getName()} - ${message}`;
268
+ (obj: MetadataObject) => (key: string) => (message: string) =>
269
+ `Error on nestia.core.TypedQuery<${obj.name}>(): property "${key}" - ${message}`;
247
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