create-hazejs 0.0.0-stage → 0.1.2

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 (187) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -2
  3. package/bin/create-hazejs.mjs +2 -0
  4. package/dist/index.d.mts +1 -0
  5. package/dist/index.mjs +74 -0
  6. package/dist/scaffold.d.mts +29 -0
  7. package/dist/scaffold.mjs +76 -0
  8. package/package.json +77 -3
  9. package/templates/api/.env +4 -0
  10. package/templates/api/.env.test +1 -0
  11. package/templates/api/README.md +383 -0
  12. package/templates/api/_gitignore +9 -0
  13. package/templates/api/app/controller/AuthController.ts +45 -0
  14. package/templates/api/app/controller/GreetingController.ts +23 -0
  15. package/templates/api/app/controller/UserController.ts +38 -0
  16. package/templates/api/app/event/UserRegistered.ts +6 -0
  17. package/templates/api/app/exception/Handler.ts +13 -0
  18. package/templates/api/app/job/SendWelcomeEmail.ts +19 -0
  19. package/templates/api/app/listener/BroadcastRegistration.ts +7 -0
  20. package/templates/api/app/listener/QueueWelcomeEmail.ts +6 -0
  21. package/templates/api/app/mail/WelcomeMail.ts +17 -0
  22. package/templates/api/app/model/User.ts +11 -0
  23. package/templates/api/app/policy/UserPolicy.ts +14 -0
  24. package/templates/api/app/provider/AppProvider.ts +9 -0
  25. package/templates/api/app/provider/DatabaseProvider.ts +4 -0
  26. package/templates/api/app/response/Formatter.ts +17 -0
  27. package/templates/api/app/service/GreetingService.ts +6 -0
  28. package/templates/api/app/validate/auth.ts +20 -0
  29. package/templates/api/app/validate/greeting.ts +5 -0
  30. package/templates/api/app/validate/user.ts +12 -0
  31. package/templates/api/config/app.ts +7 -0
  32. package/templates/api/config/auth.ts +23 -0
  33. package/templates/api/config/cache.ts +23 -0
  34. package/templates/api/config/cors.ts +11 -0
  35. package/templates/api/config/database.ts +17 -0
  36. package/templates/api/config/filesystem.ts +32 -0
  37. package/templates/api/config/mail.ts +31 -0
  38. package/templates/api/config/middleware.ts +10 -0
  39. package/templates/api/config/queue.ts +21 -0
  40. package/templates/api/config/throttle.ts +12 -0
  41. package/templates/api/config/websocket.ts +12 -0
  42. package/templates/api/database/migrations/0000_create_users.sql +9 -0
  43. package/templates/api/database/migrations/0001_add_user_password.sql +1 -0
  44. package/templates/api/database/migrations/0002_add_user_role.sql +1 -0
  45. package/templates/api/database/migrations/meta/0000_snapshot.json +71 -0
  46. package/templates/api/database/migrations/meta/0001_snapshot.json +78 -0
  47. package/templates/api/database/migrations/meta/0002_snapshot.json +86 -0
  48. package/templates/api/database/migrations/meta/_journal.json +27 -0
  49. package/templates/api/database/schema/users.ts +17 -0
  50. package/templates/api/database/seeders/01-users.ts +15 -0
  51. package/templates/api/haze.config.ts +5 -0
  52. package/templates/api/package.json +38 -0
  53. package/templates/api/route/api.ts +22 -0
  54. package/templates/api/route/ws.ts +18 -0
  55. package/templates/api/schedule/app.ts +11 -0
  56. package/templates/api/tests/auth.test.ts +77 -0
  57. package/templates/api/tests/greeting.test.ts +29 -0
  58. package/templates/api/tests/user.test.ts +58 -0
  59. package/templates/api/tsconfig.json +34 -0
  60. package/templates/api/vitest.config.ts +4 -0
  61. package/templates/react/.env +8 -0
  62. package/templates/react/.env.test +1 -0
  63. package/templates/react/README.md +374 -0
  64. package/templates/react/_gitignore +9 -0
  65. package/templates/react/haze.config.ts +8 -0
  66. package/templates/react/index.html +12 -0
  67. package/templates/react/package.json +46 -0
  68. package/templates/react/server/app/controller/AuthController.ts +45 -0
  69. package/templates/react/server/app/controller/GreetingController.ts +23 -0
  70. package/templates/react/server/app/controller/UserController.ts +38 -0
  71. package/templates/react/server/app/event/UserRegistered.ts +6 -0
  72. package/templates/react/server/app/exception/Handler.ts +13 -0
  73. package/templates/react/server/app/job/SendWelcomeEmail.ts +19 -0
  74. package/templates/react/server/app/listener/BroadcastRegistration.ts +7 -0
  75. package/templates/react/server/app/listener/QueueWelcomeEmail.ts +6 -0
  76. package/templates/react/server/app/mail/WelcomeMail.ts +17 -0
  77. package/templates/react/server/app/model/User.ts +11 -0
  78. package/templates/react/server/app/policy/UserPolicy.ts +14 -0
  79. package/templates/react/server/app/provider/AppProvider.ts +9 -0
  80. package/templates/react/server/app/provider/DatabaseProvider.ts +4 -0
  81. package/templates/react/server/app/provider/SocketProvider.ts +4 -0
  82. package/templates/react/server/app/response/Formatter.ts +17 -0
  83. package/templates/react/server/app/service/GreetingService.ts +6 -0
  84. package/templates/react/server/app/socket/chat.ts +17 -0
  85. package/templates/react/server/app/validate/auth.ts +20 -0
  86. package/templates/react/server/app/validate/greeting.ts +5 -0
  87. package/templates/react/server/app/validate/user.ts +12 -0
  88. package/templates/react/server/config/app.ts +7 -0
  89. package/templates/react/server/config/auth.ts +23 -0
  90. package/templates/react/server/config/cache.ts +23 -0
  91. package/templates/react/server/config/database.ts +17 -0
  92. package/templates/react/server/config/filesystem.ts +32 -0
  93. package/templates/react/server/config/mail.ts +31 -0
  94. package/templates/react/server/config/middleware.ts +11 -0
  95. package/templates/react/server/config/queue.ts +21 -0
  96. package/templates/react/server/config/socketio.ts +13 -0
  97. package/templates/react/server/config/throttle.ts +12 -0
  98. package/templates/react/server/config/websocket.ts +12 -0
  99. package/templates/react/server/database/migrations/0000_create_users.sql +9 -0
  100. package/templates/react/server/database/migrations/0001_add_user_password.sql +1 -0
  101. package/templates/react/server/database/migrations/0002_add_user_role.sql +1 -0
  102. package/templates/react/server/database/migrations/meta/0000_snapshot.json +71 -0
  103. package/templates/react/server/database/migrations/meta/0001_snapshot.json +78 -0
  104. package/templates/react/server/database/migrations/meta/0002_snapshot.json +86 -0
  105. package/templates/react/server/database/migrations/meta/_journal.json +27 -0
  106. package/templates/react/server/database/schema/users.ts +17 -0
  107. package/templates/react/server/database/seeders/01-users.ts +15 -0
  108. package/templates/react/server/route/api.ts +24 -0
  109. package/templates/react/server/route/ws.ts +18 -0
  110. package/templates/react/server/schedule/app.ts +11 -0
  111. package/templates/react/src/App.css +22 -0
  112. package/templates/react/src/App.tsx +72 -0
  113. package/templates/react/src/api.ts +48 -0
  114. package/templates/react/src/main.tsx +9 -0
  115. package/templates/react/src/socket.ts +5 -0
  116. package/templates/react/src/vite-env.d.ts +5 -0
  117. package/templates/react/tests/auth.test.ts +77 -0
  118. package/templates/react/tests/greeting.test.ts +29 -0
  119. package/templates/react/tests/user.test.ts +58 -0
  120. package/templates/react/tsconfig.app.json +22 -0
  121. package/templates/react/tsconfig.json +7 -0
  122. package/templates/react/tsconfig.server.json +34 -0
  123. package/templates/react/vite.config.ts +18 -0
  124. package/templates/react/vitest.config.ts +8 -0
  125. package/templates/vue/.env +8 -0
  126. package/templates/vue/.env.test +1 -0
  127. package/templates/vue/README.md +374 -0
  128. package/templates/vue/_gitignore +9 -0
  129. package/templates/vue/haze.config.ts +8 -0
  130. package/templates/vue/index.html +12 -0
  131. package/templates/vue/package.json +44 -0
  132. package/templates/vue/server/app/controller/AuthController.ts +45 -0
  133. package/templates/vue/server/app/controller/GreetingController.ts +23 -0
  134. package/templates/vue/server/app/controller/UserController.ts +38 -0
  135. package/templates/vue/server/app/event/UserRegistered.ts +6 -0
  136. package/templates/vue/server/app/exception/Handler.ts +13 -0
  137. package/templates/vue/server/app/job/SendWelcomeEmail.ts +19 -0
  138. package/templates/vue/server/app/listener/BroadcastRegistration.ts +7 -0
  139. package/templates/vue/server/app/listener/QueueWelcomeEmail.ts +6 -0
  140. package/templates/vue/server/app/mail/WelcomeMail.ts +17 -0
  141. package/templates/vue/server/app/model/User.ts +11 -0
  142. package/templates/vue/server/app/policy/UserPolicy.ts +14 -0
  143. package/templates/vue/server/app/provider/AppProvider.ts +9 -0
  144. package/templates/vue/server/app/provider/DatabaseProvider.ts +4 -0
  145. package/templates/vue/server/app/provider/SocketProvider.ts +4 -0
  146. package/templates/vue/server/app/response/Formatter.ts +17 -0
  147. package/templates/vue/server/app/service/GreetingService.ts +6 -0
  148. package/templates/vue/server/app/socket/chat.ts +17 -0
  149. package/templates/vue/server/app/validate/auth.ts +20 -0
  150. package/templates/vue/server/app/validate/greeting.ts +5 -0
  151. package/templates/vue/server/app/validate/user.ts +12 -0
  152. package/templates/vue/server/config/app.ts +7 -0
  153. package/templates/vue/server/config/auth.ts +23 -0
  154. package/templates/vue/server/config/cache.ts +23 -0
  155. package/templates/vue/server/config/database.ts +17 -0
  156. package/templates/vue/server/config/filesystem.ts +32 -0
  157. package/templates/vue/server/config/mail.ts +31 -0
  158. package/templates/vue/server/config/middleware.ts +11 -0
  159. package/templates/vue/server/config/queue.ts +21 -0
  160. package/templates/vue/server/config/socketio.ts +13 -0
  161. package/templates/vue/server/config/throttle.ts +12 -0
  162. package/templates/vue/server/config/websocket.ts +12 -0
  163. package/templates/vue/server/database/migrations/0000_create_users.sql +9 -0
  164. package/templates/vue/server/database/migrations/0001_add_user_password.sql +1 -0
  165. package/templates/vue/server/database/migrations/0002_add_user_role.sql +1 -0
  166. package/templates/vue/server/database/migrations/meta/0000_snapshot.json +71 -0
  167. package/templates/vue/server/database/migrations/meta/0001_snapshot.json +78 -0
  168. package/templates/vue/server/database/migrations/meta/0002_snapshot.json +86 -0
  169. package/templates/vue/server/database/migrations/meta/_journal.json +27 -0
  170. package/templates/vue/server/database/schema/users.ts +17 -0
  171. package/templates/vue/server/database/seeders/01-users.ts +15 -0
  172. package/templates/vue/server/route/api.ts +24 -0
  173. package/templates/vue/server/route/ws.ts +18 -0
  174. package/templates/vue/server/schedule/app.ts +11 -0
  175. package/templates/vue/src/App.vue +92 -0
  176. package/templates/vue/src/api.ts +48 -0
  177. package/templates/vue/src/env.d.ts +5 -0
  178. package/templates/vue/src/main.ts +4 -0
  179. package/templates/vue/src/socket.ts +5 -0
  180. package/templates/vue/tests/auth.test.ts +77 -0
  181. package/templates/vue/tests/greeting.test.ts +29 -0
  182. package/templates/vue/tests/user.test.ts +58 -0
  183. package/templates/vue/tsconfig.app.json +22 -0
  184. package/templates/vue/tsconfig.json +7 -0
  185. package/templates/vue/tsconfig.server.json +34 -0
  186. package/templates/vue/vite.config.ts +18 -0
  187. package/templates/vue/vitest.config.ts +8 -0
@@ -0,0 +1,20 @@
1
+ import * as v from 'valibot'
2
+
3
+ export const RegisterSchema = v.object({
4
+ name: v.pipe(v.string('名称必填'), v.trim(), v.minLength(1, '名称不能为空')),
5
+ email: v.pipe(v.string('邮箱必填'), v.email('邮箱格式不正确')),
6
+ password: v.pipe(v.string('密码必填'), v.minLength(8, '密码至少 8 位')),
7
+ })
8
+
9
+ export const LoginSchema = v.object({
10
+ email: v.pipe(v.string('邮箱必填'), v.email('邮箱格式不正确')),
11
+ password: v.pipe(v.string('密码必填'), v.minLength(1, '密码必填')),
12
+ })
13
+
14
+ export const AvatarSchema = v.object({
15
+ avatar: v.pipe(
16
+ v.file('请选择图片'),
17
+ v.mimeType(['image/png', 'image/jpeg', 'image/webp'], '只支持 png / jpeg / webp'),
18
+ v.maxSize(2 * 1024 * 1024, '图片不能超过 2MB'),
19
+ ),
20
+ })
@@ -0,0 +1,5 @@
1
+ import * as v from 'valibot'
2
+
3
+ export const GreetSchema = v.object({
4
+ name: v.pipe(v.string('name 必填'), v.trim(), v.minLength(1, 'name 不能为空'), v.maxLength(50, 'name 最多 50 个字符')),
5
+ })
@@ -0,0 +1,12 @@
1
+ import * as v from 'valibot'
2
+
3
+ export const UserIdParams = v.object({
4
+ id: v.pipe(v.string(), v.transform(Number), v.integer('用户 ID 必须是整数')),
5
+ })
6
+
7
+ export const CreateUserSchema = v.object({
8
+ name: v.pipe(v.string('名称必填'), v.trim(), v.minLength(1, '名称不能为空')),
9
+ email: v.pipe(v.string('邮箱必填'), v.email('邮箱格式不正确')),
10
+ })
11
+
12
+ export const UpdateUserSchema = v.partial(CreateUserSchema)
@@ -0,0 +1,7 @@
1
+ import { defineConfig, env } from '@hazejs/core'
2
+
3
+ // 读取环境变量时使用函数形式,确保在 .env 加载之后求值。
4
+ // app.debug 默认仅在开发环境开启(错误页、堆栈);如需强制开启,可在此设置 debug: env('APP_DEBUG', false)
5
+ export default defineConfig(() => ({
6
+ name: env('APP_NAME', '{{name}}'),
7
+ }))
@@ -0,0 +1,23 @@
1
+ import { defineAuthConfig, env } from '@hazejs/core'
2
+ import { modelUserProvider } from '@hazejs/orm'
3
+ import { User } from '~/app/model/User'
4
+
5
+ /**
6
+ * 认证:guards 决定“怎么认”(jwt:Authorization: Bearer;session:会话 Cookie),
7
+ * providers 决定“用户从哪来”。路由上用 .middleware('auth') / 'auth:web,api' 保护。
8
+ */
9
+ export default defineAuthConfig(() => ({
10
+ default: 'api',
11
+ guards: {
12
+ // 密钥默认为 APP_KEY;也可配置 algorithm: 'ES256' 与 privateKey / publicKey
13
+ api: {
14
+ driver: 'jwt',
15
+ provider: 'users',
16
+ expiresIn: env('JWT_TTL', '2h'),
17
+ },
18
+ // 浏览器同源场景可改用 session:{ driver: 'session', provider: 'users' }
19
+ },
20
+ providers: {
21
+ users: modelUserProvider(() => User, { username: 'email', password: 'password' }),
22
+ },
23
+ }))
@@ -0,0 +1,23 @@
1
+ import { defineCacheConfig, env } from '@hazejs/core'
2
+
3
+ /**
4
+ * 缓存:Cache.remember('key', '10m', () => ...)、Cache.increment()、Cache.lock()。
5
+ * 多实例部署时设置 CACHE_STORE=redis(REDIS_URL),限流(config/throttle.ts)与 Session 也会共享计数 / 数据。
6
+ */
7
+ export default defineCacheConfig(() => ({
8
+ default: env('CACHE_STORE', 'memory'),
9
+ // 多个应用共用一个 Redis 时用前缀区分
10
+ prefix: env('CACHE_PREFIX', '{{name}}:'),
11
+ stores: {
12
+ memory: { driver: 'memory' },
13
+ // 需要安装 ioredis
14
+ redis: {
15
+ driver: 'redis',
16
+ url: env('REDIS_URL', 'redis://127.0.0.1:6379'),
17
+ },
18
+ // Edge / Serverless:Upstash(HTTP,需要安装 @upstash/redis)
19
+ // upstash: { driver: 'upstash', url: env('UPSTASH_REDIS_REST_URL', ''), token: env('UPSTASH_REDIS_REST_TOKEN', '') },
20
+ // Cloudflare Workers:KV 绑定(最终一致,适合缓存与 Session,不适合精确限流)
21
+ // kv: { driver: 'kv', binding: 'CACHE' },
22
+ },
23
+ }))
@@ -0,0 +1,11 @@
1
+ import { defineCorsConfig, env } from '@hazejs/core'
2
+
3
+ // 跨域:CORS_ORIGIN 为逗号分隔的来源列表,默认允许任意来源(不携带 Cookie)
4
+ export default defineCorsConfig(() => {
5
+ const origin = env('CORS_ORIGIN', '*')
6
+ return {
7
+ origin: origin === '*'
8
+ ? '*'
9
+ : origin.split(',').map(item => item.trim()),
10
+ }
11
+ })
@@ -0,0 +1,17 @@
1
+ import { env } from '@hazejs/core'
2
+ import { defineDatabaseConfig } from '@hazejs/orm'
3
+
4
+ /**
5
+ * 数据库连接。driver:sqlite(libsql)| turso(HTTP)| d1(Cloudflare)| postgres | pglite | mysql
6
+ * 切换数据库:安装对应驱动(postgres / mysql2),修改 driver 与 url,并改用对应方言的表结构。
7
+ */
8
+ export default defineDatabaseConfig(() => ({
9
+ default: 'main',
10
+ connections: {
11
+ main: {
12
+ driver: 'sqlite',
13
+ url: env('DATABASE_URL', 'file:./data/app.db'),
14
+ logging: env('DB_LOGGING', false),
15
+ },
16
+ },
17
+ }))
@@ -0,0 +1,32 @@
1
+ import { defineFilesystemConfig, env } from '@hazejs/core'
2
+
3
+ /**
4
+ * 文件存储:Storage.put('reports/a.csv', csv)、Storage.disk('public').putFile('avatars', file)、
5
+ * 上传文件 `await file.store('avatars', 'public')`。
6
+ * public 磁盘的文件经 /storage/<path> 访问;私有文件用 Storage.temporaryUrl(path, '10m') 生成签名链接。
7
+ */
8
+ export default defineFilesystemConfig(() => ({
9
+ default: env('FILESYSTEM_DISK', 'local'),
10
+ disks: {
11
+ local: { driver: 'local', root: 'storage/app' },
12
+ public: {
13
+ driver: 'local',
14
+ root: 'storage/app/public',
15
+ url: '/storage',
16
+ serve: true,
17
+ },
18
+ // AWS S3 与 S3 兼容服务(R2 的 S3 接口、MinIO、RustFS 等),需要 pnpm add @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
19
+ s3: {
20
+ driver: 's3',
21
+ bucket: env('S3_BUCKET', ''),
22
+ region: env('S3_REGION', 'us-east-1'),
23
+ endpoint: env('S3_ENDPOINT', '') || undefined,
24
+ accessKeyId: env('S3_ACCESS_KEY_ID', ''),
25
+ secretAccessKey: env('S3_SECRET_ACCESS_KEY', ''),
26
+ // 公共读的桶 / CDN 地址;私有桶用 temporaryUrl() 预签名
27
+ url: env('S3_URL', '') || undefined,
28
+ },
29
+ // Cloudflare Workers:R2 绑定(wrangler.json 的 r2_buckets)
30
+ // r2: { driver: 'r2', binding: 'BUCKET', url: 'https://files.example.com' },
31
+ },
32
+ }))
@@ -0,0 +1,31 @@
1
+ import { defineMailConfig, env } from '@hazejs/core'
2
+
3
+ /**
4
+ * 邮件:await Mail.to(user.email).send(WelcomeMail, { name }),排队发送用 .queue() / .later('10m', ...)。
5
+ * 开发环境默认 log(只写日志);本地查看真实邮件可运行 Mailpit 并设置 MAIL_MAILER=smtp MAIL_PORT=1025。
6
+ */
7
+ export default defineMailConfig(() => ({
8
+ default: env('MAIL_MAILER', 'log'),
9
+ from: {
10
+ address: env('MAIL_FROM_ADDRESS', 'hello@example.com'),
11
+ name: env('MAIL_FROM_NAME', '{{name}}'),
12
+ },
13
+ mailers: {
14
+ // 需要 pnpm add nodemailer(Cloudflare Workers 不支持 SMTP,请用下面的 HTTP 接口驱动)
15
+ smtp: {
16
+ driver: 'smtp',
17
+ host: env('MAIL_HOST', '127.0.0.1'),
18
+ port: env('MAIL_PORT', 1025),
19
+ username: env('MAIL_USERNAME', ''),
20
+ password: env('MAIL_PASSWORD', ''),
21
+ },
22
+ resend: {
23
+ driver: 'resend',
24
+ key: env('RESEND_API_KEY', ''),
25
+ },
26
+ // postmark: { driver: 'postmark', token: env('POSTMARK_TOKEN', '') },
27
+ // mailgun: { driver: 'mailgun', key: env('MAILGUN_SECRET', ''), domain: env('MAILGUN_DOMAIN', '') },
28
+ // 需要 pnpm add @aws-sdk/client-sesv2
29
+ // ses: { driver: 'ses', region: env('AWS_REGION', 'us-east-1') },
30
+ },
31
+ }))
@@ -0,0 +1,10 @@
1
+ import { defineMiddlewareConfig } from '@hazejs/core'
2
+
3
+ // 请求 ID(x-request-id)与访问日志由框架内置,无需注册
4
+ // 内置中间件:cors、secureHeaders、compress、throttle:60,60、bodyLimit:10mb、session、auth / auth:api、guest
5
+ export default defineMiddlewareConfig({
6
+ // 全局中间件,按顺序作用于所有请求
7
+ global: ['cors', 'secureHeaders', 'compress'],
8
+ // 别名,在路由中按名引用:.middleware('auth');用 haze make:middleware 生成
9
+ alias: {},
10
+ })
@@ -0,0 +1,21 @@
1
+ import { defineQueueConfig, env } from '@hazejs/core'
2
+
3
+ /**
4
+ * 队列:app/job 下的任务,await SendWelcomeEmail.dispatch({ userId })。
5
+ * memory 在本进程消费(开发 / 单实例);生产多实例设置 QUEUE_CONNECTION=redis(需要 pnpm add bullmq ioredis),
6
+ * 并运行 haze queue:work(构建产物:node dist/…/worker.mjs queue)。
7
+ */
8
+ export default defineQueueConfig(() => ({
9
+ default: env('QUEUE_CONNECTION', 'memory'),
10
+ connections: {
11
+ memory: { driver: 'memory', concurrency: 2 },
12
+ sync: { driver: 'sync' },
13
+ redis: {
14
+ driver: 'redis',
15
+ url: env('REDIS_URL', 'redis://127.0.0.1:6379'),
16
+ queues: ['default'],
17
+ },
18
+ // Cloudflare Workers:Queues 绑定(wrangler.json 的 queues.producers / consumers)
19
+ // cloudflare: { driver: 'cloudflare', binding: 'QUEUE' },
20
+ },
21
+ }))
@@ -0,0 +1,12 @@
1
+ import { defineThrottleConfig, env } from '@hazejs/core'
2
+
3
+ // 限流计数存在 config/cache.ts 的存储中(CACHE_STORE=redis 时多实例共享)
4
+ export default defineThrottleConfig(() => ({
5
+ store: env('CACHE_STORE', 'memory'),
6
+ // 命名规则:.middleware('throttle:api')
7
+ limiters: {
8
+ api: { max: 60, window: '1m' },
9
+ // Cloudflare Workers:使用 Rate Limiting 绑定(wrangler.json 的 ratelimits)
10
+ // api: { max: 60, window: '1m', binding: 'API_RATE_LIMITER' },
11
+ },
12
+ }))
@@ -0,0 +1,12 @@
1
+ import { defineWebSocketConfig, env } from '@hazejs/core'
2
+
3
+ // 多实例部署时设置 WS_BROADCAST=redis:Socket.publish / peer.publish 经 Redis 转发到所有实例(需要 ioredis)
4
+ export default defineWebSocketConfig(() => env('WS_BROADCAST', '') === 'redis'
5
+ ? {
6
+ broadcast: {
7
+ driver: 'redis',
8
+ url: env('REDIS_URL', 'redis://127.0.0.1:6379'),
9
+ channel: '{{name}}:ws',
10
+ },
11
+ }
12
+ : {})
@@ -0,0 +1,9 @@
1
+ CREATE TABLE `users` (
2
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3
+ `name` text NOT NULL,
4
+ `email` text NOT NULL,
5
+ `created_at` integer,
6
+ `updated_at` integer
7
+ );
8
+ --> statement-breakpoint
9
+ CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);
@@ -0,0 +1 @@
1
+ ALTER TABLE `users` ADD `password` text;
@@ -0,0 +1 @@
1
+ ALTER TABLE `users` ADD `role` text DEFAULT 'member' NOT NULL;
@@ -0,0 +1,71 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "1b28388a-9186-40de-9d67-bb08f562b58c",
5
+ "prevId": "00000000-0000-0000-0000-000000000000",
6
+ "tables": {
7
+ "users": {
8
+ "name": "users",
9
+ "columns": {
10
+ "id": {
11
+ "name": "id",
12
+ "type": "integer",
13
+ "primaryKey": true,
14
+ "notNull": true,
15
+ "autoincrement": true
16
+ },
17
+ "name": {
18
+ "name": "name",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "autoincrement": false
23
+ },
24
+ "email": {
25
+ "name": "email",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": true,
29
+ "autoincrement": false
30
+ },
31
+ "created_at": {
32
+ "name": "created_at",
33
+ "type": "integer",
34
+ "primaryKey": false,
35
+ "notNull": false,
36
+ "autoincrement": false
37
+ },
38
+ "updated_at": {
39
+ "name": "updated_at",
40
+ "type": "integer",
41
+ "primaryKey": false,
42
+ "notNull": false,
43
+ "autoincrement": false
44
+ }
45
+ },
46
+ "indexes": {
47
+ "users_email_unique": {
48
+ "name": "users_email_unique",
49
+ "columns": [
50
+ "email"
51
+ ],
52
+ "isUnique": true
53
+ }
54
+ },
55
+ "foreignKeys": {},
56
+ "compositePrimaryKeys": {},
57
+ "uniqueConstraints": {},
58
+ "checkConstraints": {}
59
+ }
60
+ },
61
+ "views": {},
62
+ "enums": {},
63
+ "_meta": {
64
+ "schemas": {},
65
+ "tables": {},
66
+ "columns": {}
67
+ },
68
+ "internal": {
69
+ "indexes": {}
70
+ }
71
+ }
@@ -0,0 +1,78 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "1138ad71-fa8c-4b78-a94a-5dbce374d676",
5
+ "prevId": "1b28388a-9186-40de-9d67-bb08f562b58c",
6
+ "tables": {
7
+ "users": {
8
+ "name": "users",
9
+ "columns": {
10
+ "id": {
11
+ "name": "id",
12
+ "type": "integer",
13
+ "primaryKey": true,
14
+ "notNull": true,
15
+ "autoincrement": true
16
+ },
17
+ "name": {
18
+ "name": "name",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "autoincrement": false
23
+ },
24
+ "email": {
25
+ "name": "email",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": true,
29
+ "autoincrement": false
30
+ },
31
+ "password": {
32
+ "name": "password",
33
+ "type": "text",
34
+ "primaryKey": false,
35
+ "notNull": false,
36
+ "autoincrement": false
37
+ },
38
+ "created_at": {
39
+ "name": "created_at",
40
+ "type": "integer",
41
+ "primaryKey": false,
42
+ "notNull": false,
43
+ "autoincrement": false
44
+ },
45
+ "updated_at": {
46
+ "name": "updated_at",
47
+ "type": "integer",
48
+ "primaryKey": false,
49
+ "notNull": false,
50
+ "autoincrement": false
51
+ }
52
+ },
53
+ "indexes": {
54
+ "users_email_unique": {
55
+ "name": "users_email_unique",
56
+ "columns": [
57
+ "email"
58
+ ],
59
+ "isUnique": true
60
+ }
61
+ },
62
+ "foreignKeys": {},
63
+ "compositePrimaryKeys": {},
64
+ "uniqueConstraints": {},
65
+ "checkConstraints": {}
66
+ }
67
+ },
68
+ "views": {},
69
+ "enums": {},
70
+ "_meta": {
71
+ "schemas": {},
72
+ "tables": {},
73
+ "columns": {}
74
+ },
75
+ "internal": {
76
+ "indexes": {}
77
+ }
78
+ }
@@ -0,0 +1,86 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "d41d3fc5-5742-40b2-9f99-4f4870f40ebf",
5
+ "prevId": "1138ad71-fa8c-4b78-a94a-5dbce374d676",
6
+ "tables": {
7
+ "users": {
8
+ "name": "users",
9
+ "columns": {
10
+ "id": {
11
+ "name": "id",
12
+ "type": "integer",
13
+ "primaryKey": true,
14
+ "notNull": true,
15
+ "autoincrement": true
16
+ },
17
+ "name": {
18
+ "name": "name",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "autoincrement": false
23
+ },
24
+ "email": {
25
+ "name": "email",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": true,
29
+ "autoincrement": false
30
+ },
31
+ "password": {
32
+ "name": "password",
33
+ "type": "text",
34
+ "primaryKey": false,
35
+ "notNull": false,
36
+ "autoincrement": false
37
+ },
38
+ "role": {
39
+ "name": "role",
40
+ "type": "text",
41
+ "primaryKey": false,
42
+ "notNull": true,
43
+ "autoincrement": false,
44
+ "default": "'member'"
45
+ },
46
+ "created_at": {
47
+ "name": "created_at",
48
+ "type": "integer",
49
+ "primaryKey": false,
50
+ "notNull": false,
51
+ "autoincrement": false
52
+ },
53
+ "updated_at": {
54
+ "name": "updated_at",
55
+ "type": "integer",
56
+ "primaryKey": false,
57
+ "notNull": false,
58
+ "autoincrement": false
59
+ }
60
+ },
61
+ "indexes": {
62
+ "users_email_unique": {
63
+ "name": "users_email_unique",
64
+ "columns": [
65
+ "email"
66
+ ],
67
+ "isUnique": true
68
+ }
69
+ },
70
+ "foreignKeys": {},
71
+ "compositePrimaryKeys": {},
72
+ "uniqueConstraints": {},
73
+ "checkConstraints": {}
74
+ }
75
+ },
76
+ "views": {},
77
+ "enums": {},
78
+ "_meta": {
79
+ "schemas": {},
80
+ "tables": {},
81
+ "columns": {}
82
+ },
83
+ "internal": {
84
+ "indexes": {}
85
+ }
86
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "6",
8
+ "when": 1793072281689,
9
+ "tag": "0000_create_users",
10
+ "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "6",
15
+ "when": 1793084099573,
16
+ "tag": "0001_add_user_password",
17
+ "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "6",
22
+ "when": 1793086076027,
23
+ "tag": "0002_add_user_role",
24
+ "breakpoints": true
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,17 @@
1
+ import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
2
+
3
+ // 表结构定义(drizzle):此文件只依赖 drizzle-orm,不要使用 ~ 别名(drizzle-kit 需要直接读取)
4
+ // 修改后运行 haze db:generate 生成迁移,再 haze db:migrate
5
+ export const users = sqliteTable('users', {
6
+ id: integer('id').primaryKey({ autoIncrement: true }),
7
+ name: text('name').notNull(),
8
+ email: text('email').notNull().unique(),
9
+ // 密码哈希(Hash.make()),通过 /api/auth/register 注册的用户才有
10
+ password: text('password'),
11
+ // 角色:admin 可以管理所有用户(app/policy/UserPolicy.ts)
12
+ role: text('role', {
13
+ enum: ['member', 'admin'],
14
+ }).notNull().default('member'),
15
+ createdAt: integer('created_at', { mode: 'timestamp' }),
16
+ updatedAt: integer('updated_at', { mode: 'timestamp' }),
17
+ })
@@ -0,0 +1,15 @@
1
+ import { Hash } from '@hazejs/core'
2
+ import { User } from '~/app/model/User'
3
+
4
+ /** haze db:seed:按文件名顺序执行 database/seeders 下的文件 */
5
+ export default async function seed(): Promise<void> {
6
+ if (await User.where('email', 'admin@example.com').exists())
7
+ return
8
+ // 开发用账号,上线前请修改密码
9
+ await User.create({
10
+ name: 'Admin',
11
+ email: 'admin@example.com',
12
+ password: await Hash.make('password'),
13
+ role: 'admin',
14
+ })
15
+ }
@@ -0,0 +1,5 @@
1
+ import { defineHazeConfig } from '@hazejs/vite'
2
+
3
+ export default defineHazeConfig({
4
+ port: 3000,
5
+ })
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "haze dev",
8
+ "build": "haze build",
9
+ "start": "haze start",
10
+ "preview": "haze preview",
11
+ "queue": "haze queue:work",
12
+ "schedule": "haze schedule:work",
13
+ "test": "haze test",
14
+ "routes": "haze route:list",
15
+ "typecheck": "tsc --noEmit",
16
+ "typegen": "haze typegen",
17
+ "db:generate": "haze db:generate",
18
+ "db:migrate": "haze db:migrate",
19
+ "db:seed": "haze db:seed"
20
+ },
21
+ "dependencies": {
22
+ "@hazejs/core": "{{version}}",
23
+ "@hazejs/orm": "{{version}}",
24
+ "@libsql/client": "^0.18.0",
25
+ "drizzle-orm": "^0.45.4",
26
+ "valibot": "^1.1.0"
27
+ },
28
+ "devDependencies": {
29
+ "@hazejs/cli": "{{version}}",
30
+ "@hazejs/testing": "{{version}}",
31
+ "@hazejs/vite": "{{version}}",
32
+ "@types/node": "^24.0.0",
33
+ "drizzle-kit": "^0.31.11",
34
+ "typescript": "~6.0.3",
35
+ "vite": "^8.3.5",
36
+ "vitest": "^5.0.5"
37
+ }
38
+ }
@@ -0,0 +1,22 @@
1
+ import { defineRoutes } from '@hazejs/core'
2
+ import { AuthController } from '~/app/controller/AuthController'
3
+ import { GreetingController } from '~/app/controller/GreetingController'
4
+ import { UserController } from '~/app/controller/UserController'
5
+ import { GreetSchema } from '~/app/validate/greeting'
6
+ import { UserIdParams } from '~/app/validate/user'
7
+
8
+ export default defineRoutes((r) => {
9
+ r.group({ prefix: '/api', as: 'api.' }, (r) => {
10
+ r.get('/hello', [GreetingController, 'index']).name('hello')
11
+ r.post('/hello', [GreetingController, 'store']).validate({ body: GreetSchema })
12
+ r.resource('/users', UserController).validateMember({ params: UserIdParams })
13
+
14
+ r.group({ prefix: '/auth', as: 'auth.' }, (r) => {
15
+ r.post('/register', [AuthController, 'register']).name('register')
16
+ // 登录限流:同一 IP 每分钟最多 10 次
17
+ r.post('/login', [AuthController, 'login']).middleware('throttle:10,60').name('login')
18
+ r.get('/me', [AuthController, 'me']).middleware('auth').name('me')
19
+ r.post('/avatar', [AuthController, 'avatar']).middleware(['auth', 'bodyLimit:3mb']).name('avatar')
20
+ })
21
+ })
22
+ })
@@ -0,0 +1,18 @@
1
+ import { defineRoutes } from '@hazejs/core'
2
+
3
+ /**
4
+ * WebSocket:ws://<host>/api/ws。升级请求同样经过中间件,需要登录时加 .middleware('auth')。
5
+ * 服务端任意位置用 Socket.publish('users', data) 推送给订阅者(多实例见 config/websocket.ts)
6
+ */
7
+ export default defineRoutes((r) => {
8
+ r.ws('/api/ws', {
9
+ open(peer) {
10
+ peer.subscribe('users')
11
+ peer.send(JSON.stringify({ type: 'connected' }))
12
+ },
13
+ message(peer, message) {
14
+ if (message.text() === 'ping')
15
+ peer.send(JSON.stringify({ type: 'pong' }))
16
+ },
17
+ })
18
+ })