create-bubbles 0.1.23 → 0.1.24

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 (230) hide show
  1. package/index.js +1 -1
  2. package/package.json +25 -24
  3. package/template-taro-react-oxc/.env.development +1 -1
  4. package/template-taro-react-oxc/.env.production +1 -1
  5. package/template-taro-react-oxc/project.config.json +1 -1
  6. package/template-taro-vue-eslint/project.config.json +2 -2
  7. package/{template-vp-monorepo-react-nestjs → template-vp-monorepo-react-hono}/.spaces/server//347/231/273/345/275/225/JWT/344/270/216RefreshToken/350/275/256/346/215/242/351/211/264/346/235/203/346/236/266/346/236/204/345/256/236/347/216/260/346/214/207/345/215/227.md +588 -1133
  8. package/template-vp-monorepo-react-hono/.vite-hooks/commit-msg +2 -0
  9. package/template-vp-monorepo-react-hono/.vite-hooks/pre-commit +2 -0
  10. package/template-vp-monorepo-react-hono/.vscode/extensions.json +3 -0
  11. package/template-vp-monorepo-react-hono/.vscode/settings.json +23 -0
  12. package/template-vp-monorepo-react-hono/AGENTS.md +16 -0
  13. package/template-vp-monorepo-react-hono/README.md +54 -0
  14. package/template-vp-monorepo-react-hono/apps/server/.env.example +13 -0
  15. package/template-vp-monorepo-react-hono/apps/server/README.md +75 -0
  16. package/template-vp-monorepo-react-hono/apps/server/drizzle/0000_flaky_madame_masque.sql +18 -0
  17. package/template-vp-monorepo-react-hono/apps/server/drizzle/meta/0000_snapshot.json +153 -0
  18. package/template-vp-monorepo-react-hono/apps/server/drizzle/meta/_journal.json +13 -0
  19. package/template-vp-monorepo-react-hono/apps/server/drizzle.config.ts +19 -0
  20. package/template-vp-monorepo-react-hono/apps/server/package.json +46 -0
  21. package/template-vp-monorepo-react-hono/apps/server/src/app.test.ts +57 -0
  22. package/template-vp-monorepo-react-hono/apps/server/src/app.ts +68 -0
  23. package/template-vp-monorepo-react-hono/apps/server/src/common/errors/app-error.ts +23 -0
  24. package/template-vp-monorepo-react-hono/apps/server/src/common/http/response.ts +21 -0
  25. package/template-vp-monorepo-react-hono/apps/server/src/common/logger.ts +30 -0
  26. package/template-vp-monorepo-react-hono/apps/server/src/config/env.ts +54 -0
  27. package/template-vp-monorepo-react-hono/apps/server/src/index.bun.ts +60 -0
  28. package/template-vp-monorepo-react-hono/apps/server/src/index.ts +73 -0
  29. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/client.ts +46 -0
  30. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/schema/index.ts +1 -0
  31. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/schema/job-runs.ts +42 -0
  32. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/queue/connection.ts +6 -0
  33. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/redis/client.ts +39 -0
  34. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/redis/options.ts +28 -0
  35. package/template-vp-monorepo-react-hono/apps/server/src/modules/health/health.routes.ts +17 -0
  36. package/template-vp-monorepo-react-hono/apps/server/src/modules/health/health.service.ts +59 -0
  37. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.queue.ts +46 -0
  38. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.repository.ts +101 -0
  39. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.routes.ts +46 -0
  40. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.schemas.ts +9 -0
  41. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.service.ts +57 -0
  42. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.types.ts +11 -0
  43. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.worker.ts +108 -0
  44. package/template-vp-monorepo-react-hono/apps/server/src/runtime/shutdown.ts +38 -0
  45. package/template-vp-monorepo-react-hono/apps/server/src/worker.ts +48 -0
  46. package/template-vp-monorepo-react-hono/apps/server/tsconfig.json +15 -0
  47. package/template-vp-monorepo-react-hono/apps/server/vite.config.ts +15 -0
  48. package/template-vp-monorepo-react-hono/apps/web/.env +2 -0
  49. package/template-vp-monorepo-react-hono/apps/web/.env.dev +1 -0
  50. package/template-vp-monorepo-react-hono/apps/web/.env.prod +1 -0
  51. package/template-vp-monorepo-react-hono/apps/web/index.html +13 -0
  52. package/template-vp-monorepo-react-hono/apps/web/package.json +35 -0
  53. package/template-vp-monorepo-react-hono/apps/web/public/favicon.svg +1 -0
  54. package/template-vp-monorepo-react-hono/apps/web/public/icons.svg +24 -0
  55. package/template-vp-monorepo-react-hono/apps/web/src/App.tsx +25 -0
  56. package/template-vp-monorepo-react-hono/apps/web/src/assets/hero.png +0 -0
  57. package/template-vp-monorepo-react-hono/apps/web/src/assets/react.svg +1 -0
  58. package/template-vp-monorepo-react-hono/apps/web/src/assets/vite.svg +1 -0
  59. package/template-vp-monorepo-react-hono/apps/web/src/components/Loading/PageLoading.tsx +11 -0
  60. package/template-vp-monorepo-react-hono/apps/web/src/locales/en_US.json +4 -0
  61. package/template-vp-monorepo-react-hono/apps/web/src/locales/zh_CN.json +4 -0
  62. package/template-vp-monorepo-react-hono/apps/web/src/main.tsx +5 -0
  63. package/template-vp-monorepo-react-hono/apps/web/src/pages/home/index.tsx +47 -0
  64. package/template-vp-monorepo-react-hono/apps/web/src/router/index.tsx +25 -0
  65. package/template-vp-monorepo-react-hono/apps/web/src/router/modules/.gitkeep +0 -0
  66. package/template-vp-monorepo-react-hono/apps/web/src/styles/index.css +7 -0
  67. package/template-vp-monorepo-react-hono/apps/web/src/types/auto-imports.d.ts +58 -0
  68. package/template-vp-monorepo-react-hono/apps/web/src/utils/env/index.ts +6 -0
  69. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/core/index.ts +420 -0
  70. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/core/utils.ts +39 -0
  71. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/index.ts +61 -0
  72. package/template-vp-monorepo-react-hono/apps/web/tsconfig.json +18 -0
  73. package/template-vp-monorepo-react-hono/apps/web/vite.config.ts +45 -0
  74. package/template-vp-monorepo-react-hono/commitlint.config.js +1 -0
  75. package/template-vp-monorepo-react-hono/docker-compose.yml +41 -0
  76. package/template-vp-monorepo-react-hono/mise.toml +4 -0
  77. package/template-vp-monorepo-react-hono/package.json +37 -0
  78. package/template-vp-monorepo-react-hono/packages/i18n/core/package.json +20 -0
  79. package/template-vp-monorepo-react-hono/packages/i18n/core/src/format.ts +9 -0
  80. package/template-vp-monorepo-react-hono/packages/i18n/core/src/i18n.ts +73 -0
  81. package/template-vp-monorepo-react-hono/packages/i18n/core/src/index.ts +24 -0
  82. package/template-vp-monorepo-react-hono/packages/i18n/core/src/storage.ts +47 -0
  83. package/template-vp-monorepo-react-hono/packages/i18n/core/src/store.ts +48 -0
  84. package/template-vp-monorepo-react-hono/packages/i18n/core/src/types.ts +36 -0
  85. package/template-vp-monorepo-react-hono/packages/i18n/core/tsconfig.json +4 -0
  86. package/template-vp-monorepo-react-hono/packages/i18n/core/vite.config.ts +13 -0
  87. package/template-vp-monorepo-react-hono/packages/i18n/react/package.json +28 -0
  88. package/template-vp-monorepo-react-hono/packages/i18n/react/src/index.tsx +26 -0
  89. package/template-vp-monorepo-react-hono/packages/i18n/react/src/use-store.ts +14 -0
  90. package/template-vp-monorepo-react-hono/packages/i18n/react/tsconfig.json +9 -0
  91. package/template-vp-monorepo-react-hono/packages/i18n/react/vite.config.ts +16 -0
  92. package/template-vp-monorepo-react-hono/packages/i18n/vue/package.json +27 -0
  93. package/template-vp-monorepo-react-hono/packages/i18n/vue/src/index.ts +52 -0
  94. package/template-vp-monorepo-react-hono/packages/i18n/vue/src/use-store.ts +19 -0
  95. package/template-vp-monorepo-react-hono/packages/i18n/vue/tsconfig.json +8 -0
  96. package/template-vp-monorepo-react-hono/packages/i18n/vue/vite.config.ts +16 -0
  97. package/template-vp-monorepo-react-hono/pnpm-workspace.yaml +57 -0
  98. package/template-vp-monorepo-react-hono/scripts/clean.mjs +48 -0
  99. package/template-vp-monorepo-react-hono/tsconfig.base.json +10 -0
  100. package/template-vp-monorepo-react-hono/tsconfig.bundler.json +12 -0
  101. package/template-vp-monorepo-react-hono/tsconfig.json +11 -0
  102. package/template-vp-monorepo-react-hono/tsconfig.lib.json +10 -0
  103. package/template-vp-monorepo-react-hono/vite.config.ts +20 -0
  104. package/template-vp-monorepo-react-nestjs/.spaces/server/todo.md +991 -0
  105. package/template-vp-monorepo-react-nestjs/.vscode/launch.json +16 -0
  106. package/template-vp-monorepo-react-nestjs/.vscode/settings.json +21 -18
  107. package/template-vp-monorepo-react-nestjs/apps/server/.env.development +3 -39
  108. package/template-vp-monorepo-react-nestjs/apps/server/.swcrc +8 -0
  109. package/template-vp-monorepo-react-nestjs/apps/server/drizzle.config.ts +8 -3
  110. package/template-vp-monorepo-react-nestjs/apps/server/nest-cli.json +8 -1
  111. package/template-vp-monorepo-react-nestjs/apps/server/package.json +11 -6
  112. package/template-vp-monorepo-react-nestjs/apps/server/src/app.controller.ts +2 -0
  113. package/template-vp-monorepo-react-nestjs/apps/server/src/app.module.ts +20 -15
  114. package/template-vp-monorepo-react-nestjs/apps/server/src/common/adapters/fastify.adapter.ts +16 -0
  115. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/auth.ts +2 -0
  116. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/response.ts +0 -4
  117. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/session.constants.ts +28 -0
  118. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/current-auth.decorator.ts +16 -0
  119. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/public.decorator.ts +4 -0
  120. package/template-vp-monorepo-react-nestjs/apps/server/src/common/dto/api-failure.dto.ts +25 -0
  121. package/template-vp-monorepo-react-nestjs/apps/server/src/common/error/common.error.ts +40 -0
  122. package/template-vp-monorepo-react-nestjs/apps/server/src/common/exceptions/app.exception.ts +55 -0
  123. package/template-vp-monorepo-react-nestjs/apps/server/src/common/filters/global-exception.filter.ts +384 -0
  124. package/template-vp-monorepo-react-nestjs/apps/server/src/common/openapi/response-headers.ts +9 -0
  125. package/template-vp-monorepo-react-nestjs/apps/server/src/config/index.ts +1 -1
  126. package/template-vp-monorepo-react-nestjs/apps/server/src/config/session.config.ts +36 -0
  127. package/template-vp-monorepo-react-nestjs/apps/server/src/database/schema.ts +4 -64
  128. package/template-vp-monorepo-react-nestjs/apps/server/src/main.ts +29 -8
  129. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.controller.ts +55 -0
  130. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.errors.ts +42 -0
  131. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.module.ts +26 -0
  132. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.repository.ts +40 -0
  133. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.service.ts +129 -0
  134. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/auth-repsponse.dto.ts +31 -0
  135. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/login.dto.ts +11 -0
  136. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/register.dto.ts +17 -0
  137. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/guards/session-auth.guard.ts +50 -0
  138. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/password.service.ts +24 -0
  139. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session-store.service.ts +198 -0
  140. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session-token.service.ts +29 -0
  141. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session.script.ts +221 -0
  142. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session.types.ts +24 -0
  143. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/db/db.controller.ts +1 -1
  144. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/db/dto/create-user.dto.ts +3 -5
  145. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/redis/redis.service.ts +1 -1
  146. package/template-vp-monorepo-react-nestjs/apps/server/src/utils/env-arr.ts +3 -0
  147. package/template-vp-monorepo-react-nestjs/apps/server/test/error-catalog.spec.ts +30 -0
  148. package/template-vp-monorepo-react-nestjs/apps/server/tsconfig.build.json +11 -1
  149. package/template-vp-monorepo-react-nestjs/apps/server/tsconfig.json +12 -20
  150. package/template-vp-monorepo-react-nestjs/apps/server/vite.config.ts +15 -0
  151. package/template-vp-monorepo-react-nestjs/apps/web/package.json +1 -0
  152. package/template-vp-monorepo-react-nestjs/apps/web/tsconfig.json +5 -15
  153. package/template-vp-monorepo-react-nestjs/apps/web-vue/commitlint.config.js +1 -1
  154. package/template-vp-monorepo-react-nestjs/apps/web-vue/lefthook.yml +1 -1
  155. package/template-vp-monorepo-react-nestjs/apps/web-vue/package.json +1 -3
  156. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/api/index.ts +5 -5
  157. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/components/Icon/svg-icon.vue +3 -3
  158. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/hooks/chart/lib.ts +6 -6
  159. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/hooks/chart/useEcharts.ts +34 -29
  160. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/i18n/instance.ts +2 -0
  161. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/layout/default/index.vue +17 -32
  162. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/locales/en_US.json +4 -1
  163. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/locales/zh_CN.json +4 -1
  164. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/guard/index.ts +3 -3
  165. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/guard/permissionGuard.ts +7 -7
  166. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/index.ts +7 -7
  167. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/interface.ts +6 -6
  168. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/store/index.ts +6 -6
  169. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/store/modules/user.ts +9 -9
  170. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/styles/index.scss +3 -3
  171. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/types/antdv-next.d.ts +1 -0
  172. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/env.ts +1 -1
  173. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/core/index.ts +63 -45
  174. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/core/utils.ts +12 -6
  175. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/index.ts +1 -1
  176. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/echart/config.ts +687 -687
  177. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/echart/index.vue +7 -7
  178. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/tree-chart.vue +2 -7
  179. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/home/index.vue +13 -3
  180. package/template-vp-monorepo-react-nestjs/apps/web-vue/tsconfig.json +5 -17
  181. package/template-vp-monorepo-react-nestjs/apps/web-vue/uno.config.ts +5 -5
  182. package/template-vp-monorepo-react-nestjs/i18n.config.ts +22 -0
  183. package/template-vp-monorepo-react-nestjs/package.json +12 -1
  184. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/README.md +113 -0
  185. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/docs/excel-sync-plan.md +618 -0
  186. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/package.json +31 -0
  187. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/cli.ts +5 -0
  188. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/command.ts +512 -0
  189. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/config.ts +15 -0
  190. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/files.ts +67 -0
  191. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/index.ts +19 -0
  192. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/load-config.ts +228 -0
  193. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/report.ts +57 -0
  194. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/scanner.test.ts +81 -0
  195. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/scanner.ts +290 -0
  196. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/sync.ts +274 -0
  197. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/command.test.ts +209 -0
  198. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/config.test.ts +19 -0
  199. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/load-config.test.ts +194 -0
  200. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/report.test.ts +63 -0
  201. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/sync.test.ts +203 -0
  202. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/temporary-directory.ts +25 -0
  203. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/tsconfig.json +9 -0
  204. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/vite.config.ts +18 -0
  205. package/template-vp-monorepo-react-nestjs/packages/i18n/core/tsconfig.json +1 -12
  206. package/template-vp-monorepo-react-nestjs/packages/i18n/react/tsconfig.json +3 -10
  207. package/template-vp-monorepo-react-nestjs/packages/i18n/vue/tsconfig.json +3 -10
  208. package/template-vp-monorepo-react-nestjs/packages/shared/package.json +40 -0
  209. package/template-vp-monorepo-react-nestjs/packages/shared/src/index.ts +13 -0
  210. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/auth.ts +35 -0
  211. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/common.ts +11 -0
  212. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/index.ts +11 -0
  213. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/account.ts +5 -0
  214. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/index.ts +2 -0
  215. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/session-terminal.ts +11 -0
  216. package/template-vp-monorepo-react-nestjs/packages/shared/tsconfig.json +4 -0
  217. package/template-vp-monorepo-react-nestjs/packages/shared/vite.config.ts +17 -0
  218. package/template-vp-monorepo-react-nestjs/pnpm-workspace.yaml +6 -6
  219. package/template-vp-monorepo-react-nestjs/scripts/clean.mjs +48 -0
  220. package/template-vp-monorepo-react-nestjs/tsconfig.base.json +10 -0
  221. package/template-vp-monorepo-react-nestjs/tsconfig.bundler.json +12 -0
  222. package/template-vp-monorepo-react-nestjs/tsconfig.json +8 -6
  223. package/template-vp-monorepo-react-nestjs/tsconfig.lib.json +10 -0
  224. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/decorator.ts +0 -2
  225. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/bypass.ts +0 -12
  226. package/template-vp-monorepo-react-nestjs/apps/server/src/common/interceptors/transform.ts +0 -34
  227. package/template-vp-monorepo-react-nestjs/apps/server/src/common/model/response.ts +0 -23
  228. package/template-vp-monorepo-react-nestjs/apps/server/src/config/auth.config.ts +0 -64
  229. package/template-vp-monorepo-react-nestjs/apps/web-vue/pnpm-workspace.yaml +0 -16
  230. /package/{template-vp-monorepo-react-nestjs/apps/server/src/config/security.config.ts → template-vp-monorepo-react-hono/.agents/auth.config.ts} +0 -0
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod'
2
+
3
+ const redisUrlSchema = z
4
+ .string()
5
+ .url()
6
+ .refine((value) => value.startsWith('redis://') || value.startsWith('rediss://'), {
7
+ message: 'must use the redis:// or rediss:// protocol',
8
+ })
9
+
10
+ const envSchema = z.object({
11
+ NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
12
+ APP_NAME: z.string().min(1).default('bubbles-server'),
13
+ HOST: z.string().min(1).default('0.0.0.0'),
14
+ PORT: z.coerce.number().int().min(1).max(65_535).default(10_000),
15
+ CORS_ORIGINS: z
16
+ .string()
17
+ .default('http://localhost:9999')
18
+ .transform((value) =>
19
+ value
20
+ .split(',')
21
+ .map((origin) => origin.trim())
22
+ .filter(Boolean),
23
+ )
24
+ .refine((origins) => origins.length > 0, { message: 'must contain at least one origin' }),
25
+ DATABASE_URL: z.string().url().default('postgresql://postgres:ml@localhost:5432/postgres'),
26
+ DATABASE_POOL_MAX: z.coerce.number().int().min(1).max(100).default(10),
27
+ REDIS_URL: redisUrlSchema.default('redis://:ml@localhost:6379/0'),
28
+ REDIS_CONNECT_TIMEOUT_MS: z.coerce.number().int().min(100).default(5000),
29
+ JOB_QUEUE_NAME: z.string().min(1).default('jobs'),
30
+ JOB_QUEUE_PREFIX: z.string().min(1).default('bubbles'),
31
+ JOB_WORKER_CONCURRENCY: z.coerce.number().int().min(1).max(100).default(5),
32
+ SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().min(1000).default(10_000),
33
+ })
34
+
35
+ export type AppEnv = z.infer<typeof envSchema>
36
+
37
+ let cachedEnv: AppEnv | undefined
38
+
39
+ export function getEnv(): AppEnv {
40
+ if (cachedEnv) return cachedEnv
41
+
42
+ const result = envSchema.safeParse(process.env)
43
+
44
+ if (!result.success) {
45
+ const details = result.error.issues
46
+ .map((issue) => (issue.path.join('.') || 'environment') + ': ' + issue.message)
47
+ .join('; ')
48
+
49
+ throw new Error('Invalid server environment: ' + details)
50
+ }
51
+
52
+ cachedEnv = result.data
53
+ return cachedEnv
54
+ }
@@ -0,0 +1,60 @@
1
+ import 'dotenv/config'
2
+ import { createApp } from './app.js'
3
+ import { appLogger } from './common/logger.js'
4
+ import { getEnv } from './config/env.js'
5
+ import { closeDatabase } from './infrastructure/database/client.js'
6
+ import { closeRedis } from './infrastructure/redis/client.js'
7
+ import { closeJobsQueue } from './modules/jobs/jobs.queue.js'
8
+ import { closeAll, withShutdownTimeout } from './runtime/shutdown.js'
9
+
10
+ async function bootstrap(): Promise<void> {
11
+ const env = getEnv()
12
+ const app = createApp()
13
+ const server = Bun.serve({
14
+ hostname: env.HOST,
15
+ port: env.PORT,
16
+ development: env.NODE_ENV !== 'production',
17
+ fetch: (request) => app.fetch(request),
18
+ })
19
+ let shuttingDown = false
20
+
21
+ appLogger.info('Bun HTTP server started', {
22
+ address: server.url.toString(),
23
+ })
24
+
25
+ const shutdown = async (reason: string, exitCode = 0): Promise<void> => {
26
+ if (shuttingDown) return
27
+ shuttingDown = true
28
+ process.exitCode = exitCode
29
+ appLogger.info('Stopping Bun HTTP process', { reason })
30
+
31
+ await withShutdownTimeout('http-bun', async () => {
32
+ await closeAll([
33
+ async () => {
34
+ await server.stop(false)
35
+ },
36
+ ])
37
+ await closeAll([closeJobsQueue, closeRedis, closeDatabase])
38
+ })
39
+ }
40
+
41
+ process.once('SIGINT', () => void shutdown('SIGINT'))
42
+ process.once('SIGTERM', () => void shutdown('SIGTERM'))
43
+ process.once('uncaughtException', (error) => {
44
+ appLogger.error('Uncaught Bun exception', { error: error.message, stack: error.stack })
45
+ void shutdown('uncaughtException', 1)
46
+ })
47
+ process.once('unhandledRejection', (reason) => {
48
+ appLogger.error('Unhandled Bun rejection', {
49
+ error: reason instanceof Error ? reason.message : String(reason),
50
+ })
51
+ void shutdown('unhandledRejection', 1)
52
+ })
53
+ }
54
+
55
+ void bootstrap().catch((error: unknown) => {
56
+ appLogger.error('Bun HTTP process failed to start', {
57
+ error: error instanceof Error ? error.message : String(error),
58
+ })
59
+ process.exitCode = 1
60
+ })
@@ -0,0 +1,73 @@
1
+ import 'dotenv/config'
2
+ import { serve } from '@hono/node-server'
3
+ import type { ServerType } from '@hono/node-server'
4
+ import { createApp } from './app.js'
5
+ import { appLogger } from './common/logger.js'
6
+ import { getEnv } from './config/env.js'
7
+ import { closeDatabase } from './infrastructure/database/client.js'
8
+ import { closeRedis } from './infrastructure/redis/client.js'
9
+ import { closeJobsQueue } from './modules/jobs/jobs.queue.js'
10
+ import { closeAll, withShutdownTimeout } from './runtime/shutdown.js'
11
+
12
+ function closeServer(server: ServerType): Promise<void> {
13
+ return new Promise((resolve, reject) => {
14
+ server.close((error) => {
15
+ if (error) {
16
+ reject(error)
17
+ return
18
+ }
19
+
20
+ resolve()
21
+ })
22
+ })
23
+ }
24
+
25
+ async function bootstrap(): Promise<void> {
26
+ const env = getEnv()
27
+ const app = createApp()
28
+ const server = serve(
29
+ {
30
+ fetch: app.fetch,
31
+ hostname: env.HOST,
32
+ port: env.PORT,
33
+ },
34
+ (info) => {
35
+ appLogger.info('HTTP server started', {
36
+ address: 'http://' + info.address + ':' + info.port,
37
+ })
38
+ },
39
+ )
40
+ let shuttingDown = false
41
+
42
+ const shutdown = async (reason: string, exitCode = 0): Promise<void> => {
43
+ if (shuttingDown) return
44
+ shuttingDown = true
45
+ process.exitCode = exitCode
46
+ appLogger.info('Stopping HTTP process', { reason })
47
+
48
+ await withShutdownTimeout('http', async () => {
49
+ await closeAll([() => closeServer(server)])
50
+ await closeAll([closeJobsQueue, closeRedis, closeDatabase])
51
+ })
52
+ }
53
+
54
+ process.once('SIGINT', () => void shutdown('SIGINT'))
55
+ process.once('SIGTERM', () => void shutdown('SIGTERM'))
56
+ process.once('uncaughtException', (error) => {
57
+ appLogger.error('Uncaught exception', { error: error.message, stack: error.stack })
58
+ void shutdown('uncaughtException', 1)
59
+ })
60
+ process.once('unhandledRejection', (reason) => {
61
+ appLogger.error('Unhandled rejection', {
62
+ error: reason instanceof Error ? reason.message : String(reason),
63
+ })
64
+ void shutdown('unhandledRejection', 1)
65
+ })
66
+ }
67
+
68
+ void bootstrap().catch((error: unknown) => {
69
+ appLogger.error('HTTP process failed to start', {
70
+ error: error instanceof Error ? error.message : String(error),
71
+ })
72
+ process.exitCode = 1
73
+ })
@@ -0,0 +1,46 @@
1
+ import { drizzle, type NodePgDatabase } from 'drizzle-orm/node-postgres'
2
+ import { Pool } from 'pg'
3
+ import { getEnv } from '../../config/env.js'
4
+ import { appLogger } from '../../common/logger.js'
5
+ import * as schema from './schema/index.js'
6
+
7
+ export type Database = NodePgDatabase<typeof schema>
8
+
9
+ let pool: Pool | undefined
10
+ let database: Database | undefined
11
+
12
+ function getPool(): Pool {
13
+ if (pool) return pool
14
+
15
+ const env = getEnv()
16
+ pool = new Pool({
17
+ connectionString: env.DATABASE_URL,
18
+ max: env.DATABASE_POOL_MAX,
19
+ })
20
+ pool.on('error', (error) => {
21
+ appLogger.error('Unexpected PostgreSQL pool error', { error: error.message })
22
+ })
23
+
24
+ return pool
25
+ }
26
+
27
+ export function getDatabase(): Database {
28
+ if (!database) {
29
+ database = drizzle(getPool(), { schema })
30
+ }
31
+
32
+ return database
33
+ }
34
+
35
+ export async function checkDatabase(): Promise<void> {
36
+ await getPool().query('select 1')
37
+ }
38
+
39
+ export async function closeDatabase(): Promise<void> {
40
+ if (!pool) return
41
+
42
+ const currentPool = pool
43
+ pool = undefined
44
+ database = undefined
45
+ await currentPool.end()
46
+ }
@@ -0,0 +1,42 @@
1
+ import {
2
+ index,
3
+ integer,
4
+ jsonb,
5
+ pgEnum,
6
+ pgTable,
7
+ text,
8
+ timestamp,
9
+ uuid,
10
+ varchar,
11
+ } from 'drizzle-orm/pg-core'
12
+
13
+ export const jobRunStatus = pgEnum('job_run_status', [
14
+ 'queued',
15
+ 'processing',
16
+ 'completed',
17
+ 'failed',
18
+ ])
19
+
20
+ export const jobRuns = pgTable(
21
+ 'job_runs',
22
+ {
23
+ id: uuid('id').defaultRandom().primaryKey(),
24
+ queueName: varchar('queue_name', { length: 100 }).notNull(),
25
+ jobName: varchar('job_name', { length: 100 }).notNull(),
26
+ status: jobRunStatus('status').default('queued').notNull(),
27
+ payload: jsonb('payload').$type<Record<string, unknown>>().notNull(),
28
+ result: jsonb('result').$type<Record<string, unknown>>(),
29
+ attempts: integer('attempts').default(0).notNull(),
30
+ error: text('error'),
31
+ createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
32
+ updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
33
+ startedAt: timestamp('started_at', { withTimezone: true }),
34
+ finishedAt: timestamp('finished_at', { withTimezone: true }),
35
+ },
36
+ (table) => [
37
+ index('job_runs_status_created_at_idx').on(table.status, table.createdAt),
38
+ index('job_runs_queue_name_idx').on(table.queueName),
39
+ ],
40
+ )
41
+
42
+ export type JobRun = typeof jobRuns.$inferSelect
@@ -0,0 +1,6 @@
1
+ import type { ConnectionOptions } from 'bullmq'
2
+ import { createRedisOptions } from '../redis/options.js'
3
+
4
+ export function createQueueConnection(role: 'queue' | 'worker'): ConnectionOptions {
5
+ return createRedisOptions(role)
6
+ }
@@ -0,0 +1,39 @@
1
+ import { Redis } from 'ioredis'
2
+ import { appLogger } from '../../common/logger.js'
3
+ import { createRedisOptions } from './options.js'
4
+
5
+ let redis: Redis | undefined
6
+
7
+ export function getRedis(): Redis {
8
+ if (redis) return redis
9
+
10
+ redis = new Redis(createRedisOptions('cache'))
11
+ redis.on('error', (error: Error) => {
12
+ appLogger.error('Redis connection error', { error: error.message })
13
+ })
14
+
15
+ return redis
16
+ }
17
+
18
+ export async function checkRedis(): Promise<void> {
19
+ const client = getRedis()
20
+
21
+ if (client.status === 'wait') {
22
+ await client.connect()
23
+ }
24
+
25
+ await client.ping()
26
+ }
27
+
28
+ export async function closeRedis(): Promise<void> {
29
+ if (!redis) return
30
+
31
+ const client = redis
32
+ redis = undefined
33
+
34
+ try {
35
+ await client.quit()
36
+ } catch {
37
+ client.disconnect()
38
+ }
39
+ }
@@ -0,0 +1,28 @@
1
+ import type { RedisOptions } from 'ioredis'
2
+ import { getEnv } from '../../config/env.js'
3
+
4
+ export type RedisRole = 'cache' | 'queue' | 'worker'
5
+
6
+ export function createRedisOptions(role: RedisRole): RedisOptions {
7
+ const env = getEnv()
8
+ const redisUrl = new URL(env.REDIS_URL)
9
+ const databasePath = redisUrl.pathname.slice(1)
10
+ const database = databasePath ? Number.parseInt(databasePath, 10) : 0
11
+
12
+ if (Number.isNaN(database)) {
13
+ throw new Error('REDIS_URL contains an invalid database number')
14
+ }
15
+
16
+ return {
17
+ host: redisUrl.hostname,
18
+ port: Number.parseInt(redisUrl.port || '6379', 10),
19
+ username: redisUrl.username ? decodeURIComponent(redisUrl.username) : undefined,
20
+ password: redisUrl.password ? decodeURIComponent(redisUrl.password) : undefined,
21
+ db: database,
22
+ tls: redisUrl.protocol === 'rediss:' ? {} : undefined,
23
+ connectTimeout: env.REDIS_CONNECT_TIMEOUT_MS,
24
+ connectionName: env.APP_NAME + ':' + role,
25
+ lazyConnect: role === 'cache',
26
+ maxRetriesPerRequest: role === 'worker' ? null : 1,
27
+ }
28
+ }
@@ -0,0 +1,17 @@
1
+ import { Hono } from 'hono'
2
+ import { apiFailure, apiSuccess } from '../../common/http/response.js'
3
+ import { getLiveness, getReadiness } from './health.service.js'
4
+
5
+ export const healthRoutes = new Hono()
6
+
7
+ healthRoutes.get('/live', (context) => context.json(apiSuccess(getLiveness())))
8
+
9
+ healthRoutes.get('/ready', async (context) => {
10
+ const readiness = await getReadiness()
11
+
12
+ if (readiness.status === 'ok') {
13
+ return context.json(apiSuccess(readiness))
14
+ }
15
+
16
+ return context.json(apiFailure(503, '服务尚未就绪', readiness), 503)
17
+ })
@@ -0,0 +1,59 @@
1
+ import { appLogger } from '../../common/logger.js'
2
+ import { checkDatabase } from '../../infrastructure/database/client.js'
3
+ import { checkRedis } from '../../infrastructure/redis/client.js'
4
+
5
+ interface DependencyHealth {
6
+ status: 'up' | 'down'
7
+ latencyMs: number
8
+ }
9
+
10
+ async function inspectDependency(
11
+ name: string,
12
+ check: () => Promise<void>,
13
+ ): Promise<DependencyHealth> {
14
+ const startedAt = performance.now()
15
+
16
+ try {
17
+ await check()
18
+ return {
19
+ status: 'up',
20
+ latencyMs: Math.round(performance.now() - startedAt),
21
+ }
22
+ } catch (error) {
23
+ appLogger.warn('Health dependency check failed', {
24
+ dependency: name,
25
+ error: error instanceof Error ? error.message : String(error),
26
+ })
27
+
28
+ return {
29
+ status: 'down',
30
+ latencyMs: Math.round(performance.now() - startedAt),
31
+ }
32
+ }
33
+ }
34
+
35
+ export function getLiveness() {
36
+ return {
37
+ status: 'ok' as const,
38
+ timestamp: new Date().toISOString(),
39
+ uptimeSeconds: Math.round(process.uptime()),
40
+ }
41
+ }
42
+
43
+ export async function getReadiness() {
44
+ const [database, redis] = await Promise.all([
45
+ inspectDependency('postgres', checkDatabase),
46
+ inspectDependency('redis', checkRedis),
47
+ ])
48
+ const isReady = database.status === 'up' && redis.status === 'up'
49
+
50
+ return {
51
+ status: isReady ? ('ok' as const) : ('error' as const),
52
+ timestamp: new Date().toISOString(),
53
+ uptimeSeconds: Math.round(process.uptime()),
54
+ dependencies: {
55
+ database,
56
+ redis,
57
+ },
58
+ }
59
+ }
@@ -0,0 +1,46 @@
1
+ import { Queue } from 'bullmq'
2
+ import { getEnv } from '../../config/env.js'
3
+ import { createQueueConnection } from '../../infrastructure/queue/connection.js'
4
+ import { DEMO_JOB_NAME, type DemoJobData, type DemoJobResult } from './jobs.types.js'
5
+
6
+ let jobsQueue: Queue<DemoJobData, DemoJobResult> | undefined
7
+
8
+ export function getJobsQueue(): Queue<DemoJobData, DemoJobResult> {
9
+ if (jobsQueue) return jobsQueue
10
+
11
+ const env = getEnv()
12
+ jobsQueue = new Queue<DemoJobData, DemoJobResult>(env.JOB_QUEUE_NAME, {
13
+ connection: createQueueConnection('queue'),
14
+ prefix: env.JOB_QUEUE_PREFIX,
15
+ defaultJobOptions: {
16
+ attempts: 3,
17
+ backoff: {
18
+ type: 'exponential',
19
+ delay: 1000,
20
+ },
21
+ removeOnComplete: {
22
+ age: 3600,
23
+ count: 1000,
24
+ },
25
+ removeOnFail: {
26
+ age: 86_400,
27
+ count: 5000,
28
+ },
29
+ },
30
+ })
31
+
32
+ return jobsQueue
33
+ }
34
+
35
+ export async function enqueueDemoJob(data: DemoJobData): Promise<string | undefined> {
36
+ const job = await getJobsQueue().add(DEMO_JOB_NAME, data, { jobId: data.jobRunId })
37
+ return job.id
38
+ }
39
+
40
+ export async function closeJobsQueue(): Promise<void> {
41
+ if (!jobsQueue) return
42
+
43
+ const queue = jobsQueue
44
+ jobsQueue = undefined
45
+ await queue.close()
46
+ }
@@ -0,0 +1,101 @@
1
+ import { eq } from 'drizzle-orm'
2
+ import { AppError } from '../../common/errors/app-error.js'
3
+ import { getDatabase } from '../../infrastructure/database/client.js'
4
+ import { jobRuns, type JobRun } from '../../infrastructure/database/schema/index.js'
5
+
6
+ interface CreateJobRunInput {
7
+ queueName: string
8
+ jobName: string
9
+ payload: Record<string, unknown>
10
+ }
11
+
12
+ function requireJobRun(jobRun: JobRun | undefined, id?: string): JobRun {
13
+ if (jobRun) return jobRun
14
+
15
+ throw new AppError({
16
+ status: 404,
17
+ message: id ? '任务记录不存在: ' + id : '任务记录创建失败',
18
+ })
19
+ }
20
+
21
+ export async function createJobRun(input: CreateJobRunInput): Promise<JobRun> {
22
+ const [jobRun] = await getDatabase().insert(jobRuns).values(input).returning()
23
+ return requireJobRun(jobRun)
24
+ }
25
+
26
+ export async function findJobRun(id: string): Promise<JobRun | undefined> {
27
+ const [jobRun] = await getDatabase().select().from(jobRuns).where(eq(jobRuns.id, id)).limit(1)
28
+ return jobRun
29
+ }
30
+
31
+ export async function markJobRunProcessing(id: string, attempts: number): Promise<JobRun> {
32
+ const [jobRun] = await getDatabase()
33
+ .update(jobRuns)
34
+ .set({
35
+ status: 'processing',
36
+ attempts,
37
+ error: null,
38
+ startedAt: new Date(),
39
+ finishedAt: null,
40
+ updatedAt: new Date(),
41
+ })
42
+ .where(eq(jobRuns.id, id))
43
+ .returning()
44
+
45
+ return requireJobRun(jobRun, id)
46
+ }
47
+
48
+ export async function markJobRunQueuedForRetry(
49
+ id: string,
50
+ attempts: number,
51
+ error: string,
52
+ ): Promise<JobRun> {
53
+ const [jobRun] = await getDatabase()
54
+ .update(jobRuns)
55
+ .set({
56
+ status: 'queued',
57
+ attempts,
58
+ error: error.slice(0, 10_000),
59
+ startedAt: null,
60
+ finishedAt: null,
61
+ updatedAt: new Date(),
62
+ })
63
+ .where(eq(jobRuns.id, id))
64
+ .returning()
65
+
66
+ return requireJobRun(jobRun, id)
67
+ }
68
+
69
+ export async function markJobRunCompleted(
70
+ id: string,
71
+ result: Record<string, unknown>,
72
+ ): Promise<JobRun> {
73
+ const [jobRun] = await getDatabase()
74
+ .update(jobRuns)
75
+ .set({
76
+ status: 'completed',
77
+ result,
78
+ error: null,
79
+ finishedAt: new Date(),
80
+ updatedAt: new Date(),
81
+ })
82
+ .where(eq(jobRuns.id, id))
83
+ .returning()
84
+
85
+ return requireJobRun(jobRun, id)
86
+ }
87
+
88
+ export async function markJobRunFailed(id: string, error: string): Promise<JobRun> {
89
+ const [jobRun] = await getDatabase()
90
+ .update(jobRuns)
91
+ .set({
92
+ status: 'failed',
93
+ error: error.slice(0, 10_000),
94
+ finishedAt: new Date(),
95
+ updatedAt: new Date(),
96
+ })
97
+ .where(eq(jobRuns.id, id))
98
+ .returning()
99
+
100
+ return requireJobRun(jobRun, id)
101
+ }
@@ -0,0 +1,46 @@
1
+ import { Hono } from 'hono'
2
+ import { AppError } from '../../common/errors/app-error.js'
3
+ import { apiSuccess } from '../../common/http/response.js'
4
+ import { enqueueDemoJobSchema, jobRunIdSchema } from './jobs.schemas.js'
5
+ import { createDemoJob, getJobRun } from './jobs.service.js'
6
+
7
+ export const jobsRoutes = new Hono()
8
+
9
+ jobsRoutes.post('/demo', async (context) => {
10
+ let body: unknown
11
+
12
+ try {
13
+ body = await context.req.json()
14
+ } catch {
15
+ throw new AppError({
16
+ status: 400,
17
+ message: '请求体必须是合法的 JSON',
18
+ })
19
+ }
20
+
21
+ const result = enqueueDemoJobSchema.safeParse(body)
22
+
23
+ if (!result.success) {
24
+ throw new AppError({
25
+ status: 422,
26
+ message: '请求参数校验失败',
27
+ details: result.error.flatten(),
28
+ })
29
+ }
30
+
31
+ const jobRun = await createDemoJob(result.data.payload)
32
+ return context.json(apiSuccess(jobRun, '任务已入队'), 201)
33
+ })
34
+
35
+ jobsRoutes.get('/:id', async (context) => {
36
+ const id = jobRunIdSchema.safeParse(context.req.param('id'))
37
+
38
+ if (!id.success) {
39
+ throw new AppError({
40
+ status: 400,
41
+ message: '任务 ID 格式不正确',
42
+ })
43
+ }
44
+
45
+ return context.json(apiSuccess(await getJobRun(id.data)))
46
+ })
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod'
2
+
3
+ export const enqueueDemoJobSchema = z
4
+ .object({
5
+ payload: z.record(z.string(), z.unknown()).default({}),
6
+ })
7
+ .strict()
8
+
9
+ export const jobRunIdSchema = z.string().uuid()