create-hest-app 0.1.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 (139) hide show
  1. package/README.md +211 -0
  2. package/dist/index.js +127 -0
  3. package/package.json +50 -0
  4. package/templates/base/.prettierrc +33 -0
  5. package/templates/base/.vscode/extensions.json +79 -0
  6. package/templates/base/.vscode/settings.json +70 -0
  7. package/templates/base/README.md +562 -0
  8. package/templates/base/eslint.config.ts +26 -0
  9. package/templates/base/package.json +62 -0
  10. package/templates/base/src/app.controller.ts +39 -0
  11. package/templates/base/src/app.module.ts +12 -0
  12. package/templates/base/src/app.service.ts +30 -0
  13. package/templates/base/src/common/filters/http-exception.filter.ts +34 -0
  14. package/templates/base/src/common/interceptors/response.interceptor.ts +38 -0
  15. package/templates/base/src/index.ts +35 -0
  16. package/templates/base/src/modules/custom-validation/custom-validation.controller.ts +146 -0
  17. package/templates/base/src/modules/custom-validation/custom-validation.module.ts +10 -0
  18. package/templates/base/src/modules/custom-validation/custom-validation.service.ts +33 -0
  19. package/templates/base/src/modules/custom-validation/dto/custom-validation.dto.ts +132 -0
  20. package/templates/base/src/modules/users/dto/user.dto.ts +64 -0
  21. package/templates/base/src/modules/users/entities/user.entity.ts +9 -0
  22. package/templates/base/src/modules/users/users.controller.ts +68 -0
  23. package/templates/base/src/modules/users/users.module.ts +10 -0
  24. package/templates/base/src/modules/users/users.service.ts +55 -0
  25. package/templates/base/tsconfig.json +19 -0
  26. package/templates/base_scalar/.prettierrc +32 -0
  27. package/templates/base_scalar/.vscode/extensions.json +79 -0
  28. package/templates/base_scalar/.vscode/settings.json +70 -0
  29. package/templates/base_scalar/README.md +562 -0
  30. package/templates/base_scalar/eslint.config.ts +26 -0
  31. package/templates/base_scalar/package.json +63 -0
  32. package/templates/base_scalar/src/app.controller.ts +196 -0
  33. package/templates/base_scalar/src/app.module.ts +12 -0
  34. package/templates/base_scalar/src/app.service.ts +30 -0
  35. package/templates/base_scalar/src/common/filters/http-exception.filter.ts +34 -0
  36. package/templates/base_scalar/src/common/interceptors/response.interceptor.ts +38 -0
  37. package/templates/base_scalar/src/index.ts +67 -0
  38. package/templates/base_scalar/src/modules/custom-validation/custom-validation.controller.ts +146 -0
  39. package/templates/base_scalar/src/modules/custom-validation/custom-validation.module.ts +10 -0
  40. package/templates/base_scalar/src/modules/custom-validation/custom-validation.service.ts +33 -0
  41. package/templates/base_scalar/src/modules/custom-validation/dto/custom-validation.dto.ts +132 -0
  42. package/templates/base_scalar/src/modules/users/dto/user.dto.ts +64 -0
  43. package/templates/base_scalar/src/modules/users/entities/user.entity.ts +9 -0
  44. package/templates/base_scalar/src/modules/users/users.controller.ts +68 -0
  45. package/templates/base_scalar/src/modules/users/users.module.ts +10 -0
  46. package/templates/base_scalar/src/modules/users/users.service.ts +55 -0
  47. package/templates/base_scalar/tsconfig.json +19 -0
  48. package/templates/cqrs/.prettierrc +33 -0
  49. package/templates/cqrs/.vscode/extensions.json +79 -0
  50. package/templates/cqrs/.vscode/settings.json +70 -0
  51. package/templates/cqrs/README.md +234 -0
  52. package/templates/cqrs/eslint.config.ts +26 -0
  53. package/templates/cqrs/package.json +62 -0
  54. package/templates/cqrs/src/app.controller.ts +28 -0
  55. package/templates/cqrs/src/app.module.ts +12 -0
  56. package/templates/cqrs/src/app.service.ts +8 -0
  57. package/templates/cqrs/src/common/filters/http-exception.filter.ts +34 -0
  58. package/templates/cqrs/src/common/interceptors/response.interceptor.ts +38 -0
  59. package/templates/cqrs/src/index.ts +38 -0
  60. package/templates/cqrs/src/modules/custom-validation/custom-validation.controller.ts +146 -0
  61. package/templates/cqrs/src/modules/custom-validation/custom-validation.module.ts +10 -0
  62. package/templates/cqrs/src/modules/custom-validation/custom-validation.service.ts +33 -0
  63. package/templates/cqrs/src/modules/custom-validation/dto/custom-validation.dto.ts +132 -0
  64. package/templates/cqrs/src/modules/users/dto/user.dto.ts +64 -0
  65. package/templates/cqrs/src/modules/users/entities/user.entity.ts +9 -0
  66. package/templates/cqrs/src/modules/users/users.controller.ts +68 -0
  67. package/templates/cqrs/src/modules/users/users.module.ts +10 -0
  68. package/templates/cqrs/src/modules/users/users.service.ts +55 -0
  69. package/templates/cqrs/src/test-error-scenarios.ts +54 -0
  70. package/templates/cqrs/src/users/commands/create-user.command.ts +8 -0
  71. package/templates/cqrs/src/users/commands/index.ts +2 -0
  72. package/templates/cqrs/src/users/commands/update-user.command.ts +11 -0
  73. package/templates/cqrs/src/users/entities/index.ts +1 -0
  74. package/templates/cqrs/src/users/entities/user.entity.ts +22 -0
  75. package/templates/cqrs/src/users/events/index.ts +2 -0
  76. package/templates/cqrs/src/users/events/user-created.event.ts +8 -0
  77. package/templates/cqrs/src/users/events/user-updated.event.ts +8 -0
  78. package/templates/cqrs/src/users/handlers/create-user.handler.ts +26 -0
  79. package/templates/cqrs/src/users/handlers/get-all-users.handler.ts +15 -0
  80. package/templates/cqrs/src/users/handlers/get-user.handler.ts +15 -0
  81. package/templates/cqrs/src/users/handlers/index.ts +6 -0
  82. package/templates/cqrs/src/users/handlers/update-user.handler.ts +33 -0
  83. package/templates/cqrs/src/users/handlers/user-created.handler.ts +15 -0
  84. package/templates/cqrs/src/users/handlers/user-updated.handler.ts +15 -0
  85. package/templates/cqrs/src/users/index.ts +8 -0
  86. package/templates/cqrs/src/users/queries/get-all-users.query.ts +8 -0
  87. package/templates/cqrs/src/users/queries/get-user.query.ts +12 -0
  88. package/templates/cqrs/src/users/queries/index.ts +2 -0
  89. package/templates/cqrs/src/users/repositories/index.ts +1 -0
  90. package/templates/cqrs/src/users/repositories/user.repository.ts +51 -0
  91. package/templates/cqrs/src/users/user.controller.ts +66 -0
  92. package/templates/cqrs/src/users/user.module.ts +30 -0
  93. package/templates/cqrs/tsconfig.json +19 -0
  94. package/templates/cqrs_scalar/.prettierrc +33 -0
  95. package/templates/cqrs_scalar/.vscode/extensions.json +79 -0
  96. package/templates/cqrs_scalar/.vscode/settings.json +70 -0
  97. package/templates/cqrs_scalar/README.md +234 -0
  98. package/templates/cqrs_scalar/eslint.config.ts +26 -0
  99. package/templates/cqrs_scalar/package.json +62 -0
  100. package/templates/cqrs_scalar/src/app.controller.ts +28 -0
  101. package/templates/cqrs_scalar/src/app.module.ts +12 -0
  102. package/templates/cqrs_scalar/src/app.service.ts +8 -0
  103. package/templates/cqrs_scalar/src/common/filters/http-exception.filter.ts +34 -0
  104. package/templates/cqrs_scalar/src/common/interceptors/response.interceptor.ts +38 -0
  105. package/templates/cqrs_scalar/src/index.ts +38 -0
  106. package/templates/cqrs_scalar/src/modules/custom-validation/custom-validation.controller.ts +146 -0
  107. package/templates/cqrs_scalar/src/modules/custom-validation/custom-validation.module.ts +10 -0
  108. package/templates/cqrs_scalar/src/modules/custom-validation/custom-validation.service.ts +33 -0
  109. package/templates/cqrs_scalar/src/modules/custom-validation/dto/custom-validation.dto.ts +132 -0
  110. package/templates/cqrs_scalar/src/modules/users/dto/user.dto.ts +64 -0
  111. package/templates/cqrs_scalar/src/modules/users/entities/user.entity.ts +9 -0
  112. package/templates/cqrs_scalar/src/modules/users/users.controller.ts +68 -0
  113. package/templates/cqrs_scalar/src/modules/users/users.module.ts +10 -0
  114. package/templates/cqrs_scalar/src/modules/users/users.service.ts +55 -0
  115. package/templates/cqrs_scalar/src/test-error-scenarios.ts +54 -0
  116. package/templates/cqrs_scalar/src/users/commands/create-user.command.ts +8 -0
  117. package/templates/cqrs_scalar/src/users/commands/index.ts +2 -0
  118. package/templates/cqrs_scalar/src/users/commands/update-user.command.ts +11 -0
  119. package/templates/cqrs_scalar/src/users/entities/index.ts +1 -0
  120. package/templates/cqrs_scalar/src/users/entities/user.entity.ts +22 -0
  121. package/templates/cqrs_scalar/src/users/events/index.ts +2 -0
  122. package/templates/cqrs_scalar/src/users/events/user-created.event.ts +8 -0
  123. package/templates/cqrs_scalar/src/users/events/user-updated.event.ts +8 -0
  124. package/templates/cqrs_scalar/src/users/handlers/create-user.handler.ts +26 -0
  125. package/templates/cqrs_scalar/src/users/handlers/get-all-users.handler.ts +15 -0
  126. package/templates/cqrs_scalar/src/users/handlers/get-user.handler.ts +15 -0
  127. package/templates/cqrs_scalar/src/users/handlers/index.ts +6 -0
  128. package/templates/cqrs_scalar/src/users/handlers/update-user.handler.ts +33 -0
  129. package/templates/cqrs_scalar/src/users/handlers/user-created.handler.ts +15 -0
  130. package/templates/cqrs_scalar/src/users/handlers/user-updated.handler.ts +15 -0
  131. package/templates/cqrs_scalar/src/users/index.ts +8 -0
  132. package/templates/cqrs_scalar/src/users/queries/get-all-users.query.ts +8 -0
  133. package/templates/cqrs_scalar/src/users/queries/get-user.query.ts +12 -0
  134. package/templates/cqrs_scalar/src/users/queries/index.ts +2 -0
  135. package/templates/cqrs_scalar/src/users/repositories/index.ts +1 -0
  136. package/templates/cqrs_scalar/src/users/repositories/user.repository.ts +51 -0
  137. package/templates/cqrs_scalar/src/users/user.controller.ts +66 -0
  138. package/templates/cqrs_scalar/src/users/user.module.ts +30 -0
  139. package/templates/cqrs_scalar/tsconfig.json +19 -0
@@ -0,0 +1,146 @@
1
+ import { Controller, Get, Post } from '@hestjs/core';
2
+ import { Body } from '@hestjs/validation';
3
+ import { CustomValidationDto, SearchQueryDto } from './dto/custom-validation.dto';
4
+ import { CustomValidationService } from './custom-validation.service';
5
+
6
+ /**
7
+ * 自定义验证控制器 - 展示 TypeBox 自定义验证功能
8
+ */
9
+ @Controller('/api/custom')
10
+ export class CustomValidationController {
11
+ constructor(
12
+ private readonly customValidationService: CustomValidationService,
13
+ ) {}
14
+
15
+ @Get('/')
16
+ async getInfo() {
17
+ return {
18
+ success: true,
19
+ message: 'HestJS 自定义验证功能示例',
20
+ features: [
21
+ '🔧 TypeBox 自定义 Schema 验证',
22
+ '🏗️ SchemaFactory 便捷构建器',
23
+ '📦 CommonValidators 常用验证',
24
+ '🔗 联合类型验证',
25
+ '📐 嵌套对象验证',
26
+ '📱 中国手机号验证',
27
+ '🔗 UUID 验证',
28
+ '📍 地理坐标验证',
29
+ ],
30
+ endpoints: {
31
+ 'POST /api/custom/validate': '测试自定义验证',
32
+ 'POST /api/custom/search': '测试搜索参数验证',
33
+ 'GET /api/custom/examples': '获取验证示例',
34
+ },
35
+ };
36
+ }
37
+
38
+ @Post('/validate')
39
+ async validateCustomData(
40
+ @Body(CustomValidationDto) data: CustomValidationDto,
41
+ ) {
42
+ const result = this.customValidationService.processCustomData(data);
43
+ return {
44
+ success: true,
45
+ message: '自定义验证通过!',
46
+ result,
47
+ validationInfo: {
48
+ username: `用户名 "${data.username}" 通过正则验证`,
49
+ role: `角色 "${data.role}" 通过联合类型验证`,
50
+ userId: `UUID "${data.userId}" 格式验证通过`,
51
+ phoneNumber: data.phoneNumber
52
+ ? `手机号 "${data.phoneNumber}" 通过中国手机号验证`
53
+ : '未提供手机号',
54
+ location: data.location
55
+ ? `坐标 (${data.location.lat}, ${data.location.lng}) 验证通过`
56
+ : '未提供坐标',
57
+ emails: data.emails
58
+ ? `邮箱列表包含 ${data.emails.length} 个地址`
59
+ : '未提供邮箱',
60
+ },
61
+ };
62
+ }
63
+
64
+ @Post('/search')
65
+ async searchWithValidation(@Body(SearchQueryDto) query: SearchQueryDto) {
66
+ const result = this.customValidationService.processSearch(query);
67
+ return {
68
+ success: true,
69
+ message: '搜索参数验证通过!',
70
+ result,
71
+ validationInfo: {
72
+ query: query.q || '无搜索关键词',
73
+ pagination: query.pagination || '使用默认分页',
74
+ },
75
+ };
76
+ }
77
+
78
+ @Get('/examples')
79
+ async getValidationExamples() {
80
+ return {
81
+ success: true,
82
+ message: '自定义验证示例',
83
+ examples: {
84
+ customValidation: {
85
+ description: 'POST /api/custom/validate',
86
+ validExample: {
87
+ username: 'john_doe123',
88
+ role: 'user',
89
+ userId: '123e4567-e89b-12d3-a456-426614174000',
90
+ phoneNumber: '13812345678',
91
+ location: { lat: 39.9042, lng: 116.4074 },
92
+ emails: ['john@example.com', 'john.doe@company.com'],
93
+ },
94
+ invalidExample: {
95
+ username: 'a', // 太短
96
+ role: 'invalid_role', // 不在联合类型中
97
+ userId: 'not-a-uuid', // 无效 UUID
98
+ phoneNumber: '123456', // 无效手机号
99
+ location: { lat: 200, lng: 200 }, // 超出范围
100
+ emails: ['invalid-email', 'another@invalid'], // 无效邮箱
101
+ },
102
+ },
103
+ searchValidation: {
104
+ description: 'POST /api/custom/search',
105
+ validExample: {
106
+ q: 'TypeScript',
107
+ pagination: {
108
+ page: 1,
109
+ limit: 10,
110
+ sort: 'createdAt',
111
+ order: 'desc',
112
+ },
113
+ },
114
+ invalidExample: {
115
+ q: 123, // 应该是字符串
116
+ pagination: {
117
+ page: 0, // 应该 >= 1
118
+ limit: 1000, // 应该 <= 100
119
+ order: 'invalid', // 应该是 'asc' 或 'desc'
120
+ },
121
+ },
122
+ },
123
+ },
124
+ typeboxFeatures: {
125
+ basicTypes: [
126
+ 'Type.String()',
127
+ 'Type.Number()',
128
+ 'Type.Boolean()',
129
+ 'Type.Array()',
130
+ ],
131
+ advancedTypes: [
132
+ 'Type.Union()',
133
+ 'Type.Object()',
134
+ 'Type.Intersect()',
135
+ 'Type.Optional()',
136
+ ],
137
+ formats: ['email', 'date', 'date-time', 'uri', 'uuid'],
138
+ patterns: ['正则表达式验证', '长度限制', '数值范围', '枚举值'],
139
+ customValidators: [
140
+ 'SchemaFactory 便捷方法',
141
+ 'CommonValidators 常用验证',
142
+ ],
143
+ },
144
+ };
145
+ }
146
+ }
@@ -0,0 +1,10 @@
1
+ import { Module } from '@hestjs/core';
2
+ import { CustomValidationController } from './custom-validation.controller';
3
+ import { CustomValidationService } from './custom-validation.service';
4
+
5
+ @Module({
6
+ controllers: [CustomValidationController],
7
+ providers: [CustomValidationService],
8
+ exports: [CustomValidationService],
9
+ })
10
+ export class CustomValidationModule {}
@@ -0,0 +1,33 @@
1
+ import { Injectable } from '@hestjs/core';
2
+ import type { CustomValidationDto, SearchQueryDto } from './dto/custom-validation.dto';
3
+
4
+ /**
5
+ * 自定义验证服务
6
+ */
7
+ @Injectable()
8
+ export class CustomValidationService {
9
+ processCustomData(data: CustomValidationDto) {
10
+ // 模拟处理逻辑
11
+ return {
12
+ processed: true,
13
+ data: {
14
+ ...data,
15
+ processedAt: new Date().toISOString(),
16
+ validation: 'TypeBox 自定义验证通过',
17
+ },
18
+ };
19
+ }
20
+
21
+ processSearch(query: SearchQueryDto) {
22
+ // 模拟搜索逻辑
23
+ return {
24
+ results: [
25
+ { id: 1, title: '搜索结果 1', relevance: 0.95 },
26
+ { id: 2, title: '搜索结果 2', relevance: 0.87 },
27
+ ],
28
+ query,
29
+ total: 2,
30
+ searchedAt: new Date().toISOString(),
31
+ };
32
+ }
33
+ }
@@ -0,0 +1,132 @@
1
+ import {
2
+ CommonValidators,
3
+ Custom,
4
+ IsOptional,
5
+ IsString,
6
+ SchemaFactory,
7
+ } from '@hestjs/validation';
8
+ import { Type } from '@sinclair/typebox';
9
+
10
+ /**
11
+ * 展示自定义验证功能的简化 DTO
12
+ */
13
+ export class CustomValidationDto {
14
+ // 使用基础的 TypeBox API
15
+ @Custom(
16
+ Type.String({ minLength: 3, maxLength: 20, pattern: '^[a-zA-Z0-9_]+$' }),
17
+ {
18
+ message: '用户名必须是3-20位字母、数字或下划线',
19
+ },
20
+ )
21
+ username!: string;
22
+
23
+ // 使用联合类型
24
+ @Custom(
25
+ Type.Union([
26
+ Type.Literal('admin'),
27
+ Type.Literal('user'),
28
+ Type.Literal('guest'),
29
+ ]),
30
+ {
31
+ message: '角色必须是 admin、user 或 guest',
32
+ },
33
+ )
34
+ role!: 'admin' | 'user' | 'guest';
35
+
36
+ // 使用 CommonValidators 的便捷方法
37
+ @CommonValidators.UUID({ message: '必须是有效的 UUID' })
38
+ userId!: string;
39
+
40
+ // 使用 SchemaFactory 的便捷方法
41
+ @Custom(SchemaFactory.chinesePhoneNumber(), {
42
+ message: '必须是有效的中国手机号',
43
+ optional: true,
44
+ })
45
+ phoneNumber?: string;
46
+
47
+ // 自定义对象验证
48
+ @Custom(
49
+ Type.Object({
50
+ lat: Type.Number({ minimum: -90, maximum: 90 }),
51
+ lng: Type.Number({ minimum: -180, maximum: 180 }),
52
+ }),
53
+ { optional: true, message: '坐标必须在有效范围内' },
54
+ )
55
+ location?: {
56
+ lat: number;
57
+ lng: number;
58
+ };
59
+
60
+ // 数组验证
61
+ @Custom(Type.Array(Type.String({ format: 'email' })), {
62
+ optional: true,
63
+ message: '邮箱列表必须是有效的邮箱地址数组',
64
+ })
65
+ emails?: string[];
66
+ }
67
+
68
+ /**
69
+ * 搜索查询 DTO - 展示复杂对象验证
70
+ */
71
+ export class SearchQueryDto {
72
+ @IsOptional()
73
+ @IsString({ message: '搜索关键词必须是字符串' })
74
+ q?: string;
75
+
76
+ // 使用 SchemaFactory 构建复杂的分页对象
77
+ @Custom(
78
+ Type.Object({
79
+ page: Type.Number({ minimum: 1 }),
80
+ limit: Type.Number({ minimum: 1, maximum: 100 }),
81
+ sort: Type.Optional(Type.String()),
82
+ order: Type.Optional(
83
+ Type.Union([Type.Literal('asc'), Type.Literal('desc')]),
84
+ ),
85
+ }),
86
+ {
87
+ optional: true,
88
+ message: '分页参数格式错误',
89
+ },
90
+ )
91
+ pagination?: {
92
+ page: number;
93
+ limit: number;
94
+ sort?: string;
95
+ order?: 'asc' | 'desc';
96
+ };
97
+
98
+ // 使用联合类型进行复杂验证
99
+ @Custom(
100
+ Type.Union([
101
+ Type.Array(Type.String()),
102
+ Type.String(),
103
+ Type.Null(),
104
+ Type.Undefined(),
105
+ ]),
106
+ {
107
+ optional: true,
108
+ message: '标签可以是字符串、字符串数组或空值',
109
+ },
110
+ )
111
+ tags?: string | string[] | null;
112
+
113
+ // 使用数值范围验证
114
+ @Custom(Type.Number({ minimum: 0, maximum: 100 }), {
115
+ optional: true,
116
+ message: '评分必须在 0-100 之间',
117
+ })
118
+ score?: number;
119
+
120
+ // 使用日期字符串验证
121
+ @Custom(Type.String({ format: 'date' }), {
122
+ optional: true,
123
+ message: '开始日期格式必须是 YYYY-MM-DD',
124
+ })
125
+ startDate?: string;
126
+
127
+ @Custom(Type.String({ format: 'date' }), {
128
+ optional: true,
129
+ message: '结束日期格式必须是 YYYY-MM-DD',
130
+ })
131
+ endDate?: string;
132
+ }
@@ -0,0 +1,64 @@
1
+ import {
2
+ IsEmail,
3
+ IsNumber,
4
+ IsOptional,
5
+ IsString,
6
+ Length,
7
+ Max,
8
+ Min,
9
+ } from '@hestjs/validation';
10
+
11
+ /**
12
+ * 创建用户 DTO
13
+ */
14
+ export class CreateUserDto {
15
+ @IsString({
16
+ minLength: 2,
17
+ maxLength: 50,
18
+ message: '用户名长度必须在2-50字符之间',
19
+ })
20
+ name!: string;
21
+
22
+ @IsEmail({ message: '请输入有效的邮箱地址' })
23
+ email!: string;
24
+
25
+ @IsNumber({ message: '年龄必须是数字' })
26
+ @Min(0, { message: '年龄不能小于0' })
27
+ @Max(120, { message: '年龄不能大于120' })
28
+ age!: number;
29
+
30
+ @IsString({ message: '密码必须是字符串' })
31
+ @Length(8, 100, { message: '密码长度必须在8-100字符之间' })
32
+ password!: string;
33
+
34
+ @IsOptional()
35
+ @IsString({ message: '个人简介必须是字符串' })
36
+ bio?: string;
37
+ }
38
+
39
+ /**
40
+ * 更新用户 DTO
41
+ */
42
+ export class UpdateUserDto {
43
+ @IsOptional()
44
+ @IsString({
45
+ minLength: 2,
46
+ maxLength: 50,
47
+ message: '用户名长度必须在2-50字符之间',
48
+ })
49
+ name?: string;
50
+
51
+ @IsOptional()
52
+ @IsEmail({ message: '请输入有效的邮箱地址' })
53
+ email?: string;
54
+
55
+ @IsOptional()
56
+ @IsNumber({ message: '年龄必须是数字' })
57
+ @Min(0, { message: '年龄不能小于0' })
58
+ @Max(120, { message: '年龄不能大于120' })
59
+ age?: number;
60
+
61
+ @IsOptional()
62
+ @IsString({ message: '个人简介必须是字符串' })
63
+ bio?: string;
64
+ }
@@ -0,0 +1,9 @@
1
+ // 定义用户类型
2
+ export interface User {
3
+ id: number;
4
+ name: string;
5
+ email: string;
6
+ age: number;
7
+ password?: string;
8
+ bio?: string;
9
+ }
@@ -0,0 +1,68 @@
1
+ import type { HestContext } from '@hestjs/core';
2
+ import { Context, Controller, Get, Post } from '@hestjs/core';
3
+ import { Body } from '@hestjs/validation';
4
+ import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
5
+ import { UsersService } from './users.service';
6
+
7
+ /**
8
+ * 用户控制器 - 展示验证功能
9
+ */
10
+ @Controller('/users')
11
+ export class UsersController {
12
+ constructor(private readonly usersService: UsersService) {}
13
+
14
+ @Get('/')
15
+ async getAllUsers() {
16
+ return {
17
+ success: true,
18
+ data: this.usersService.findAll(),
19
+ message: 'Users retrieved successfully',
20
+ };
21
+ }
22
+
23
+ @Get('/:id')
24
+ async getUser(@Context() c: HestContext) {
25
+ const id = parseInt(c.req.param('id'));
26
+ const user = this.usersService.findOne(id);
27
+
28
+ if (!user) {
29
+ return c.json(
30
+ {
31
+ success: false,
32
+ message: 'User not found',
33
+ },
34
+ 404,
35
+ );
36
+ }
37
+
38
+ return {
39
+ success: true,
40
+ data: user,
41
+ message: 'User retrieved successfully',
42
+ };
43
+ }
44
+
45
+ @Post('/')
46
+ async createUser(@Body(CreateUserDto) createUserDto: CreateUserDto) {
47
+ const newUser = this.usersService.create(createUserDto);
48
+ return {
49
+ success: true,
50
+ data: newUser,
51
+ message: 'User created successfully',
52
+ };
53
+ }
54
+
55
+ @Post('/:id')
56
+ async updateUser(
57
+ @Context() c: HestContext,
58
+ @Body(UpdateUserDto) updateUserDto: UpdateUserDto,
59
+ ) {
60
+ const id = parseInt(c.req.param('id'));
61
+ const updatedUser = this.usersService.update(id, updateUserDto);
62
+ return {
63
+ success: true,
64
+ data: updatedUser,
65
+ message: 'User updated successfully',
66
+ };
67
+ }
68
+ }
@@ -0,0 +1,10 @@
1
+ import { Module } from '@hestjs/core';
2
+ import { UsersController } from './users.controller';
3
+ import { UsersService } from './users.service';
4
+
5
+ @Module({
6
+ controllers: [UsersController],
7
+ providers: [UsersService],
8
+ exports: [UsersService],
9
+ })
10
+ export class UsersModule {}
@@ -0,0 +1,55 @@
1
+ import { Injectable } from '@hestjs/core';
2
+ import type { User } from './entities/user.entity';
3
+ import type { CreateUserDto, UpdateUserDto } from './dto/user.dto';
4
+
5
+ /**
6
+ * 用户服务
7
+ */
8
+ @Injectable()
9
+ export class UsersService {
10
+ private users: User[] = [
11
+ { id: 1, name: 'John Doe', email: 'john@example.com', age: 30 },
12
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com', age: 25 },
13
+ ];
14
+
15
+ findAll(): User[] {
16
+ return this.users;
17
+ }
18
+
19
+ findOne(id: number): User | undefined {
20
+ return this.users.find(user => user.id === id);
21
+ }
22
+
23
+ create(createUserDto: CreateUserDto): Omit<User, 'password'> {
24
+ const newUser: User = {
25
+ id: this.users.length + 1,
26
+ ...createUserDto,
27
+ };
28
+ this.users.push(newUser);
29
+
30
+ // 不返回密码
31
+ const { password: _password, ...userWithoutPassword } = newUser;
32
+ return userWithoutPassword;
33
+ }
34
+
35
+ update(id: number, updateUserDto: UpdateUserDto): User {
36
+ const userIndex = this.users.findIndex(user => user.id === id);
37
+ if (userIndex === -1) {
38
+ throw new Error('User not found');
39
+ }
40
+
41
+ this.users[userIndex] = { ...this.users[userIndex], ...updateUserDto };
42
+ return this.users[userIndex];
43
+ }
44
+
45
+ remove(id: number): User {
46
+ const userIndex = this.users.findIndex(user => user.id === id);
47
+ if (userIndex === -1) {
48
+ throw new Error('User not found');
49
+ }
50
+
51
+ const deletedUser = this.users[userIndex];
52
+ this.users.splice(userIndex, 1);
53
+ return deletedUser;
54
+ }
55
+ }
@@ -0,0 +1,54 @@
1
+ import { logger } from '@hestjs/logger';
2
+
3
+ // 模拟应用启动时的错误处理
4
+ async function testErrorScenarios() {
5
+ console.log('=== Testing Real-world Error Scenarios ===\n');
6
+
7
+ // 场景 1: 数据库连接错误
8
+ try {
9
+ throw new Error('Database connection failed');
10
+ } catch (error) {
11
+ logger.error(
12
+ '❌ Failed to connect to database:',
13
+ error instanceof Error ? error : new Error(String(error)),
14
+ );
15
+ }
16
+
17
+ // 场景 2: API 调用失败
18
+ try {
19
+ const apiError = new Error('API request timeout');
20
+ (apiError as any).status = 504;
21
+ (apiError as any).endpoint = '/api/users';
22
+ throw apiError;
23
+ } catch (error) {
24
+ logger.error(
25
+ '❌ API call failed:',
26
+ error instanceof Error ? error : new Error(String(error)),
27
+ {
28
+ requestId: 'req-12345',
29
+ userId: 'user-67890',
30
+ },
31
+ );
32
+ }
33
+
34
+ // 场景 3: 验证错误
35
+ try {
36
+ const validationError = new Error('Validation failed');
37
+ (validationError as any).fields = ['email', 'password'];
38
+ (validationError as any).code = 'VALIDATION_ERROR';
39
+ throw validationError;
40
+ } catch (error) {
41
+ logger.error(
42
+ '❌ Validation failed:',
43
+ error instanceof Error ? error : new Error(String(error)),
44
+ );
45
+ }
46
+
47
+ // 场景 4: 使用简化的语法
48
+ const simpleError = new Error('Something went wrong');
49
+ logger.error('❌ Simple error logging:', simpleError);
50
+
51
+ console.log('\n=== Test Complete ===');
52
+ }
53
+
54
+ testErrorScenarios();
@@ -0,0 +1,8 @@
1
+ import { Command } from '@hestjs/cqrs';
2
+ import { CreateUserData, User } from '../entities';
3
+
4
+ export class CreateUserCommand extends Command<User> {
5
+ constructor(public readonly userData: CreateUserData) {
6
+ super();
7
+ }
8
+ }
@@ -0,0 +1,2 @@
1
+ export * from './create-user.command';
2
+ export * from './update-user.command';
@@ -0,0 +1,11 @@
1
+ import { Command } from '@hestjs/cqrs';
2
+ import { UpdateUserData, User } from '../entities';
3
+
4
+ export class UpdateUserCommand extends Command<User> {
5
+ constructor(
6
+ public readonly userId: string,
7
+ public readonly userData: UpdateUserData,
8
+ ) {
9
+ super();
10
+ }
11
+ }
@@ -0,0 +1 @@
1
+ export * from './user.entity';
@@ -0,0 +1,22 @@
1
+ import { IQueryResult } from '@hestjs/cqrs';
2
+
3
+ export interface User extends IQueryResult {
4
+ id: string;
5
+ name: string;
6
+ email: string;
7
+ age?: number;
8
+ createdAt: Date;
9
+ updatedAt: Date;
10
+ }
11
+
12
+ export interface CreateUserData {
13
+ name: string;
14
+ email: string;
15
+ age?: number;
16
+ }
17
+
18
+ export interface UpdateUserData {
19
+ name?: string;
20
+ email?: string;
21
+ age?: number;
22
+ }
@@ -0,0 +1,2 @@
1
+ export * from './user-created.event';
2
+ export * from './user-updated.event';
@@ -0,0 +1,8 @@
1
+ import { Event } from '@hestjs/cqrs';
2
+ import { User } from '../entities';
3
+
4
+ export class UserCreatedEvent extends Event {
5
+ constructor(public readonly user: User) {
6
+ super();
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ import { Event } from '@hestjs/cqrs';
2
+ import { User } from '../entities';
3
+
4
+ export class UserUpdatedEvent extends Event {
5
+ constructor(public readonly user: User) {
6
+ super();
7
+ }
8
+ }
@@ -0,0 +1,26 @@
1
+ import { Injectable } from '@hestjs/core';
2
+ import { CommandHandler, EventBus, ICommandHandler } from '@hestjs/cqrs';
3
+ import { CreateUserCommand } from '../commands';
4
+ import { User } from '../entities';
5
+ import { UserCreatedEvent } from '../events';
6
+ import { UserRepository } from '../repositories';
7
+
8
+ @Injectable()
9
+ @CommandHandler(CreateUserCommand)
10
+ export class CreateUserHandler
11
+ implements ICommandHandler<CreateUserCommand, User>
12
+ {
13
+ constructor(
14
+ private readonly userRepository: UserRepository,
15
+ private readonly eventBus: EventBus,
16
+ ) {}
17
+
18
+ async execute(command: CreateUserCommand): Promise<User> {
19
+ const user = await this.userRepository.create(command.userData);
20
+
21
+ // 发布用户创建事件
22
+ await this.eventBus.publish(new UserCreatedEvent(user));
23
+
24
+ return user;
25
+ }
26
+ }