create-nexwire 4.3.0 → 4.4.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 (326) hide show
  1. package/README.md +10 -5
  2. package/express/src/framework/facade.ts +1 -0
  3. package/express/src/framework/http/logger.ts +13 -4
  4. package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
  5. package/express/src/framework/notification/channels/mail.ts +8 -6
  6. package/express/src/middlewares/auth-middleware.ts +1 -2
  7. package/express/src/middlewares/role-middleware.ts +3 -13
  8. package/express/src/modules/auth/routes/index.ts +1 -2
  9. package/express/src/providers/notification.ts +2 -2
  10. package/express/src/resources/src/components/Switch.vue +60 -65
  11. package/express/src/resources/src/pages/auth/forgetPassword.vue +23 -30
  12. package/express/src/resources/src/pages/auth/login.vue +25 -28
  13. package/express/src/resources/src/pages/auth/register.vue +35 -39
  14. package/express/src/resources/src/pages/auth/resetPassword.vue +29 -37
  15. package/express/src/resources/src/pages/auth/verifyEmail.vue +15 -19
  16. package/express/src/resources/src/plugins/gum.ts +519 -203
  17. package/express/src/resources/src/stores/auth.ts +9 -6
  18. package/express/src/storage/database/nexwire.sqlite +0 -0
  19. package/fastify/.dockerignore +14 -0
  20. package/fastify/.env +44 -0
  21. package/fastify/.env.example +42 -0
  22. package/fastify/.vscode/extensions.json +6 -0
  23. package/fastify/.vscode/settings.json +37 -0
  24. package/fastify/LICENSE +21 -0
  25. package/fastify/README.md +232 -0
  26. package/fastify/biome.json +99 -0
  27. package/fastify/drizzle.config.ts +25 -0
  28. package/fastify/gitignore-stub +31 -0
  29. package/fastify/package.json +130 -0
  30. package/fastify/public/.gitkeep +0 -0
  31. package/fastify/src/config/app.ts +41 -0
  32. package/fastify/src/config/auth.ts +12 -0
  33. package/fastify/src/config/cache.ts +16 -0
  34. package/fastify/src/config/cookie.ts +15 -0
  35. package/fastify/src/config/cors.ts +12 -0
  36. package/fastify/src/config/database.ts +48 -0
  37. package/fastify/src/config/index.ts +83 -0
  38. package/fastify/src/config/jwt.ts +19 -0
  39. package/fastify/src/config/logging.ts +13 -0
  40. package/fastify/src/config/mail.ts +24 -0
  41. package/fastify/src/config/openapi.ts +36 -0
  42. package/fastify/src/config/queue.ts +30 -0
  43. package/fastify/src/config/rateLimit.ts +17 -0
  44. package/fastify/src/config/realtime.ts +15 -0
  45. package/fastify/src/config/redis.ts +18 -0
  46. package/fastify/src/config/security.ts +44 -0
  47. package/fastify/src/config/session.ts +17 -0
  48. package/fastify/src/config/storage.ts +22 -0
  49. package/fastify/src/config/validate.ts +50 -0
  50. package/fastify/src/database/migrations/sqlite/0000_init.sql +48 -0
  51. package/fastify/src/database/migrations/sqlite/meta/0000_snapshot.json +324 -0
  52. package/fastify/src/database/migrations/sqlite/meta/_journal.json +13 -0
  53. package/fastify/src/database/schema.ts +2 -0
  54. package/fastify/src/env.ts +76 -0
  55. package/fastify/src/framework/cache/cache.ts +114 -0
  56. package/fastify/src/framework/circuit-breaker/cb.ts +88 -0
  57. package/fastify/src/framework/database/app-schema.ts +37 -0
  58. package/fastify/src/framework/database/connection.ts +223 -0
  59. package/fastify/src/framework/database/migrate-hooks.ts +87 -0
  60. package/fastify/src/framework/database/paginate.ts +226 -0
  61. package/fastify/src/framework/database/schema.ts +20 -0
  62. package/fastify/src/framework/database/seed.ts +152 -0
  63. package/fastify/src/framework/events/dispatcher.ts +61 -0
  64. package/fastify/src/framework/facade.ts +96 -0
  65. package/fastify/src/framework/http/app.ts +176 -0
  66. package/fastify/src/framework/http/cors.ts +13 -0
  67. package/fastify/src/framework/http/fastify.d.ts +13 -0
  68. package/fastify/src/framework/http/logger.ts +110 -0
  69. package/fastify/src/framework/http/metrics.ts +22 -0
  70. package/fastify/src/framework/http/openapi.ts +48 -0
  71. package/fastify/src/framework/http/ratelimiter.ts +47 -0
  72. package/fastify/src/framework/http/security.ts +25 -0
  73. package/fastify/src/framework/http/static.ts +83 -0
  74. package/fastify/src/framework/http/validation.ts +49 -0
  75. package/fastify/src/framework/kernel.ts +43 -0
  76. package/fastify/src/framework/maker-cli/db/core.mjs +291 -0
  77. package/fastify/src/framework/maker-cli/db/index.mjs +204 -0
  78. package/fastify/src/framework/maker-cli/deploy/core.mjs +1046 -0
  79. package/fastify/src/framework/maker-cli/deploy/index.mjs +91 -0
  80. package/fastify/src/framework/maker-cli/index.mjs +65 -0
  81. package/fastify/src/framework/maker-cli/middleware/core.mjs +41 -0
  82. package/fastify/src/framework/maker-cli/middleware/index.mjs +17 -0
  83. package/fastify/src/framework/maker-cli/module/core.mjs +1066 -0
  84. package/fastify/src/framework/maker-cli/module/index.mjs +211 -0
  85. package/fastify/src/framework/maker-cli/provider/core.mjs +49 -0
  86. package/fastify/src/framework/maker-cli/provider/index.mjs +12 -0
  87. package/fastify/src/framework/maker-cli/runtime/build-ui.mjs +16 -0
  88. package/fastify/src/framework/maker-cli/runtime/core.mjs +560 -0
  89. package/fastify/src/framework/maker-cli/runtime/index.mjs +120 -0
  90. package/fastify/src/framework/maker-cli/runtime/redis-commander-patch.cjs +20 -0
  91. package/fastify/src/framework/maker-cli/stubs/controller/openapi.ts.stub +74 -0
  92. package/fastify/src/framework/maker-cli/stubs/controller/plain.ts.stub +77 -0
  93. package/fastify/src/framework/maker-cli/stubs/controller/standalone.ts.stub +51 -0
  94. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +52 -0
  95. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +55 -0
  96. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +51 -0
  97. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +55 -0
  98. package/fastify/src/framework/maker-cli/stubs/deploy/README.stub +59 -0
  99. package/fastify/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +45 -0
  100. package/fastify/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +45 -0
  101. package/fastify/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +45 -0
  102. package/fastify/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +68 -0
  103. package/fastify/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +68 -0
  104. package/fastify/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +67 -0
  105. package/fastify/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +41 -0
  106. package/fastify/src/framework/maker-cli/stubs/deploy/server/README.stub +78 -0
  107. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +120 -0
  108. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +133 -0
  109. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +131 -0
  110. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +11 -0
  111. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +25 -0
  112. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +25 -0
  113. package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +16 -0
  114. package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +13 -0
  115. package/fastify/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +14 -0
  116. package/fastify/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +7 -0
  117. package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +54 -0
  118. package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +70 -0
  119. package/fastify/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +25 -0
  120. package/fastify/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +24 -0
  121. package/fastify/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
  122. package/fastify/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
  123. package/fastify/src/framework/maker-cli/stubs/job/name.ts.stub +25 -0
  124. package/fastify/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
  125. package/fastify/src/framework/maker-cli/stubs/middleware/name.ts.stub +15 -0
  126. package/fastify/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +10 -0
  127. package/fastify/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +10 -0
  128. package/fastify/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +10 -0
  129. package/fastify/src/framework/maker-cli/stubs/notification/controller.ts.stub +79 -0
  130. package/fastify/src/framework/maker-cli/stubs/notification/job.ts.stub +11 -0
  131. package/fastify/src/framework/maker-cli/stubs/notification/model.ts.stub +29 -0
  132. package/fastify/src/framework/maker-cli/stubs/notification/provider.ts.stub +174 -0
  133. package/fastify/src/framework/maker-cli/stubs/notification/route.api.ts.stub +71 -0
  134. package/fastify/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +15 -0
  135. package/fastify/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +26 -0
  136. package/fastify/src/framework/maker-cli/stubs/notification/schema.ts.stub +26 -0
  137. package/fastify/src/framework/maker-cli/stubs/provider/module-barrel.ts.stub +19 -0
  138. package/fastify/src/framework/maker-cli/stubs/provider/module.ts.stub +21 -0
  139. package/fastify/src/framework/maker-cli/stubs/provider/name.ts.stub +21 -0
  140. package/fastify/src/framework/maker-cli/stubs/route/api.ts.stub +101 -0
  141. package/fastify/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -0
  142. package/fastify/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +120 -0
  143. package/fastify/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +50 -0
  144. package/fastify/src/framework/maker-cli/stubs/schedule/name.ts.stub +36 -0
  145. package/fastify/src/framework/maker-cli/stubs/schema/name.plain.ts.stub +14 -0
  146. package/fastify/src/framework/maker-cli/stubs/schema/name.ts.stub +33 -0
  147. package/fastify/src/framework/maker-cli/stubs/seeder/name.ts.stub +18 -0
  148. package/fastify/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
  149. package/fastify/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
  150. package/fastify/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
  151. package/fastify/src/framework/maker-cli/stubs/test/unit.ts.stub +22 -0
  152. package/fastify/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
  153. package/fastify/src/framework/maker-cli/utils/env-config.mjs +44 -0
  154. package/fastify/src/framework/maker-cli/utils/env-db.mjs +61 -0
  155. package/fastify/src/framework/maker-cli/utils/file-ops.mjs +30 -0
  156. package/fastify/src/framework/maker-cli/utils/flags.mjs +14 -0
  157. package/fastify/src/framework/maker-cli/utils/help.mjs +379 -0
  158. package/fastify/src/framework/maker-cli/utils/naming.mjs +119 -0
  159. package/fastify/src/framework/maker-cli/utils/process.mjs +52 -0
  160. package/fastify/src/framework/maker-cli/utils/project-commands.mjs +201 -0
  161. package/fastify/src/framework/modules/discover.ts +57 -0
  162. package/fastify/src/framework/modules/routes.ts +31 -0
  163. package/fastify/src/framework/notification/channels/broadcast.ts +15 -0
  164. package/fastify/src/framework/notification/channels/mail.ts +42 -0
  165. package/fastify/src/framework/notification/contracts.ts +29 -0
  166. package/fastify/src/framework/notification/index.ts +59 -0
  167. package/fastify/src/framework/notification/registry.ts +15 -0
  168. package/fastify/src/framework/providers/adapter.ts +149 -0
  169. package/fastify/src/framework/providers/contracts.ts +126 -0
  170. package/fastify/src/framework/providers/loader.ts +105 -0
  171. package/fastify/src/framework/providers/registry.ts +88 -0
  172. package/fastify/src/framework/queue/clear.ts +22 -0
  173. package/fastify/src/framework/queue/queue.ts +401 -0
  174. package/fastify/src/framework/queue/ui.ts +194 -0
  175. package/fastify/src/framework/queue/worker.ts +61 -0
  176. package/fastify/src/framework/realtime/broadcast.ts +70 -0
  177. package/fastify/src/framework/realtime/index.ts +2 -0
  178. package/fastify/src/framework/realtime/socket-cookie.ts +72 -0
  179. package/fastify/src/framework/realtime/socket.ts +149 -0
  180. package/fastify/src/framework/realtime/types.ts +6 -0
  181. package/fastify/src/framework/realtime/ui.ts +16 -0
  182. package/fastify/src/framework/redis/client.ts +148 -0
  183. package/fastify/src/framework/runtime/runtime.ts +23 -0
  184. package/fastify/src/framework/runtime/server-adapter.ts +33 -0
  185. package/fastify/src/framework/scheduler/lock.ts +128 -0
  186. package/fastify/src/framework/scheduler/run.ts +48 -0
  187. package/fastify/src/framework/scheduler/scheduler.ts +155 -0
  188. package/fastify/src/framework/server.ts +162 -0
  189. package/fastify/src/framework/session/session.ts +160 -0
  190. package/fastify/src/framework/storage/storage.ts +744 -0
  191. package/fastify/src/framework/support/cookie.ts +71 -0
  192. package/fastify/src/framework/support/jwt.ts +33 -0
  193. package/fastify/src/framework/support/lifecycle.ts +35 -0
  194. package/fastify/src/framework/support/logger.ts +148 -0
  195. package/fastify/src/framework/support/mail.ts +56 -0
  196. package/fastify/src/framework/support/password.ts +23 -0
  197. package/fastify/src/framework/support/status-codes.ts +75 -0
  198. package/fastify/src/framework/support/upload.ts +181 -0
  199. package/fastify/src/framework/support/url.ts +41 -0
  200. package/fastify/src/middlewares/auth-middleware.ts +134 -0
  201. package/fastify/src/middlewares/role-middleware.ts +19 -0
  202. package/fastify/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
  203. package/fastify/src/modules/auth/__tests__/login.test.ts +171 -0
  204. package/fastify/src/modules/auth/__tests__/logout.test.ts +60 -0
  205. package/fastify/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
  206. package/fastify/src/modules/auth/__tests__/me.test.ts +91 -0
  207. package/fastify/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
  208. package/fastify/src/modules/auth/__tests__/register.test.ts +291 -0
  209. package/fastify/src/modules/auth/__tests__/resetpass.test.ts +206 -0
  210. package/fastify/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
  211. package/fastify/src/modules/auth/controllers/auth.ts +252 -0
  212. package/fastify/src/modules/auth/controllers/role.ts +35 -0
  213. package/fastify/src/modules/auth/database/models/role.ts +14 -0
  214. package/fastify/src/modules/auth/database/models/user.ts +46 -0
  215. package/fastify/src/modules/auth/database/seeders/role.ts +18 -0
  216. package/fastify/src/modules/auth/database/seeders/user.ts +35 -0
  217. package/fastify/src/modules/auth/facade.ts +12 -0
  218. package/fastify/src/modules/auth/helpers/auth.ts +115 -0
  219. package/fastify/src/modules/auth/jobs/forgetpass.ts +20 -0
  220. package/fastify/src/modules/auth/jobs/registeruser.ts +23 -0
  221. package/fastify/src/modules/auth/jobs/verifyemail.ts +18 -0
  222. package/fastify/src/modules/auth/routes/index.ts +185 -0
  223. package/fastify/src/modules/auth/routes/role.ts +56 -0
  224. package/fastify/src/modules/auth/schemas/auth.ts +116 -0
  225. package/fastify/src/modules/auth/services/forgetpassword.ts +66 -0
  226. package/fastify/src/modules/auth/services/login.ts +63 -0
  227. package/fastify/src/modules/auth/services/logout.ts +25 -0
  228. package/fastify/src/modules/auth/services/logoutalldevices.ts +19 -0
  229. package/fastify/src/modules/auth/services/me.ts +31 -0
  230. package/fastify/src/modules/auth/services/refreshtoken.ts +110 -0
  231. package/fastify/src/modules/auth/services/register.ts +141 -0
  232. package/fastify/src/modules/auth/services/resetpassword.ts +69 -0
  233. package/fastify/src/modules/auth/services/verifyemail.ts +77 -0
  234. package/fastify/src/modules/auth/types/auth.ts +31 -0
  235. package/fastify/src/providers/README.md +107 -0
  236. package/fastify/src/providers/notification.ts +4 -0
  237. package/fastify/src/resources/index.html +17 -0
  238. package/fastify/src/resources/src/App.vue +5 -0
  239. package/fastify/src/resources/src/assets/css/styles.css +14761 -0
  240. package/fastify/src/resources/src/assets/css/styles.css.map +1 -0
  241. package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin-ext.woff2 +0 -0
  242. package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin.woff2 +0 -0
  243. package/fastify/src/resources/src/assets/images/favicon/favicon.ico +0 -0
  244. package/fastify/src/resources/src/assets/images/logo.png +0 -0
  245. package/fastify/src/resources/src/assets/scss/custom.css +217 -0
  246. package/fastify/src/resources/src/assets/scss/custom.css.map +1 -0
  247. package/fastify/src/resources/src/assets/scss/custom.scss +1346 -0
  248. package/fastify/src/resources/src/components/Button.vue +32 -0
  249. package/fastify/src/resources/src/components/Checkbox.vue +32 -0
  250. package/fastify/src/resources/src/components/Datepicker.vue +106 -0
  251. package/fastify/src/resources/src/components/FeatureButton.vue +53 -0
  252. package/fastify/src/resources/src/components/FloatButton.vue +33 -0
  253. package/fastify/src/resources/src/components/Href.vue +32 -0
  254. package/fastify/src/resources/src/components/Input.vue +243 -0
  255. package/fastify/src/resources/src/components/InputGroup.vue +147 -0
  256. package/fastify/src/resources/src/components/InputPasswordToggle.vue +229 -0
  257. package/fastify/src/resources/src/components/Modal.vue +71 -0
  258. package/fastify/src/resources/src/components/Pagebar.vue +26 -0
  259. package/fastify/src/resources/src/components/Refresh.vue +19 -0
  260. package/fastify/src/resources/src/components/Select.vue +384 -0
  261. package/fastify/src/resources/src/components/Spinner.vue +42 -0
  262. package/fastify/src/resources/src/components/Switch.vue +60 -0
  263. package/fastify/src/resources/src/components/TextArea.vue +117 -0
  264. package/fastify/src/resources/src/components/Toast.vue +56 -0
  265. package/fastify/src/resources/src/components/datatable/DataTableSkeleton.vue +98 -0
  266. package/fastify/src/resources/src/components/datatable/Pagination.vue +147 -0
  267. package/fastify/src/resources/src/components/datatable/SelectOpption.vue +64 -0
  268. package/fastify/src/resources/src/components/datatable/index.vue +199 -0
  269. package/fastify/src/resources/src/composables/useAuth.ts +52 -0
  270. package/fastify/src/resources/src/helpers/assets.ts +51 -0
  271. package/fastify/src/resources/src/helpers/nformatter.ts +10 -0
  272. package/fastify/src/resources/src/helpers/utils.ts +75 -0
  273. package/fastify/src/resources/src/layouts/AuthLayout.vue +20 -0
  274. package/fastify/src/resources/src/layouts/Layout/Footer.vue +23 -0
  275. package/fastify/src/resources/src/layouts/Layout/Header.vue +124 -0
  276. package/fastify/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
  277. package/fastify/src/resources/src/layouts/Layout/index.vue +87 -0
  278. package/fastify/src/resources/src/main.ts +28 -0
  279. package/fastify/src/resources/src/pages/auth/forgetPassword.vue +76 -0
  280. package/fastify/src/resources/src/pages/auth/login.vue +96 -0
  281. package/fastify/src/resources/src/pages/auth/register.vue +129 -0
  282. package/fastify/src/resources/src/pages/auth/resetPassword.vue +110 -0
  283. package/fastify/src/resources/src/pages/auth/verifyEmail.vue +64 -0
  284. package/fastify/src/resources/src/pages/dashboard/index.vue +66 -0
  285. package/fastify/src/resources/src/plugins/axios.ts +31 -0
  286. package/fastify/src/resources/src/plugins/browserDetect.ts +55 -0
  287. package/fastify/src/resources/src/plugins/dialog.ts +216 -0
  288. package/fastify/src/resources/src/plugins/gum.ts +683 -0
  289. package/fastify/src/resources/src/plugins/pulse.ts +157 -0
  290. package/fastify/src/resources/src/plugins/routeProgress.ts +87 -0
  291. package/fastify/src/resources/src/router/index.ts +85 -0
  292. package/fastify/src/resources/src/stores/admin-ui.ts +148 -0
  293. package/fastify/src/resources/src/stores/auth.ts +187 -0
  294. package/fastify/src/resources/tsconfig.json +19 -0
  295. package/fastify/src/resources/vite.config.ts +64 -0
  296. package/fastify/src/storage/app/private/.gitkeep +0 -0
  297. package/fastify/src/storage/app/public/.gitkeep +0 -0
  298. package/fastify/src/storage/app/tmp/generated/.gitkeep +0 -0
  299. package/fastify/src/storage/database/nexwire.sqlite +0 -0
  300. package/fastify/src/storage/logs/.gitkeep +0 -0
  301. package/fastify/src/storage/logs/app.log +0 -0
  302. package/fastify/src/storage/logs/fatal.log +0 -0
  303. package/fastify/src/storage/tmp/.gitkeep +0 -0
  304. package/fastify/src/storage/trash/modules/.gitkeep +0 -0
  305. package/fastify/src/types/global.d.ts +32 -0
  306. package/fastify/tsconfig.json +19 -0
  307. package/fastify/vitest.config.ts +38 -0
  308. package/hono/src/framework/facade.ts +1 -0
  309. package/hono/src/framework/http/logger.ts +86 -81
  310. package/hono/src/framework/maker-cli/runtime/core.mjs +1 -2
  311. package/hono/src/framework/notification/channels/mail.ts +8 -6
  312. package/hono/src/middlewares/auth-middleware.ts +1 -2
  313. package/hono/src/middlewares/role-middleware.ts +3 -13
  314. package/hono/src/modules/auth/routes/index.ts +1 -2
  315. package/hono/src/providers/notification.ts +2 -2
  316. package/hono/src/resources/src/components/Switch.vue +60 -65
  317. package/hono/src/resources/src/pages/auth/forgetPassword.vue +23 -29
  318. package/hono/src/resources/src/pages/auth/login.vue +26 -29
  319. package/hono/src/resources/src/pages/auth/register.vue +36 -40
  320. package/hono/src/resources/src/pages/auth/resetPassword.vue +29 -37
  321. package/hono/src/resources/src/pages/auth/verifyEmail.vue +15 -19
  322. package/hono/src/resources/src/plugins/gum.ts +519 -203
  323. package/hono/src/resources/src/stores/auth.ts +9 -6
  324. package/hono/src/storage/database/nexwire.sqlite +0 -0
  325. package/package.json +3 -2
  326. package/src/index.js +11 -8
@@ -0,0 +1,401 @@
1
+ import { type Job, type JobsOptions, Queue, QueueEvents, Worker } from "bullmq";
2
+ import { type DurableContext, type DurableJob, DurableWorker, RedisStateStore } from "bullmq-durable";
3
+ import { queueConfig, redisConfig } from "@/config/index.js";
4
+ import { discoverModuleFiles, importFile } from "@/framework/modules/discover.js";
5
+ import { redisClientIfReady } from "@/framework/redis/client.js";
6
+ import { logger } from "@/framework/support/logger.js";
7
+
8
+ export type QueueJob = {
9
+ name: string;
10
+ data: any;
11
+ };
12
+
13
+ export type QueueHandler = (job: Job) => Promise<any>;
14
+ export type DurableQueueHandler = (job: DurableJob, ctx: DurableContext) => Promise<any>;
15
+ export type AnyQueueHandler = QueueHandler | DurableQueueHandler;
16
+
17
+ const queues = new Map<string, Queue>();
18
+ const events = new Map<string, QueueEvents>();
19
+ const handlers = new Map<string, QueueHandler>();
20
+ const durableHandlers = new Map<string, DurableQueueHandler>();
21
+ const workers: Worker[] = [];
22
+ /**
23
+ * Queue names this process already has a worker for. Each BullMQ `Worker` holds
24
+ * its own blocking Redis connection, so starting the runtime twice for one queue
25
+ * would silently double that connection for no benefit. Cleared by
26
+ * `stopQueueRuntime` so a later boot in the same process still works.
27
+ */
28
+ const workedQueues = new Set<string>();
29
+ let durableStateStore: RedisStateStore | null = null;
30
+
31
+ /**
32
+ * Why: Jobs already reported as dropped, so that one unreachable queue cannot
33
+ * flood the log from a request hot path.
34
+ * When: A call to queueJob finds no Redis.
35
+ * Where: Queue facade module scope.
36
+ * How: Keyed by queue + job name, because that pair is what identifies a queued
37
+ * job - two queues running the same job name are two separate failures and
38
+ * must each be reported once.
39
+ */
40
+ const droppedJobs = new Set<string>();
41
+
42
+ function queuePrefix() {
43
+ return queueConfig.prefix;
44
+ }
45
+
46
+ function key(queue: string, job: string) {
47
+ return `${queue}:${job}`;
48
+ }
49
+
50
+ /**
51
+ * Why: BullMQ manages its own connections. We pass the URL string,
52
+ * not the shared client, so BullMQ doesn't crash if the shared client dies.
53
+ */
54
+ function connectionOptions() {
55
+ return { url: redisConfig.url };
56
+ }
57
+
58
+ /**
59
+ * Why: Registers a job handler for a queue + job name.
60
+ * When: Module job files are loaded during boot.
61
+ * Where: Module job definition files under `src/modules/<module>/jobs`.
62
+ * How: Stores handler in an in-memory lookup key. Pass `{ durable: true }` to
63
+ * register a checkpointed handler `(job, ctx)` powered by bullmq-durable;
64
+ * such a handler can split its work into `ctx.step(...)` checkpoints that
65
+ * survive crashes/restarts instead of re-running from scratch.
66
+ */
67
+ export function shouldQueue(job: string, queue: string, handler: AnyQueueHandler, options: { durable?: boolean } = {}) {
68
+ const target = key(queue || "default", job);
69
+ if (options.durable) {
70
+ durableHandlers.set(target, handler as DurableQueueHandler);
71
+ } else {
72
+ handlers.set(target, handler as QueueHandler);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Why: Gets or lazily creates a BullMQ queue instance.
78
+ * When: Enqueueing jobs or ensuring queue resources.
79
+ * Where: Queue facade and dispatcher.
80
+ * How: Reuses shared Redis connection and memoized queue map.
81
+ */
82
+ export function getQueue(queue = "default") {
83
+ // Check if Redis is supposed to be enabled
84
+ if (!redisConfig.enabled) return null;
85
+
86
+ if (!queues.has(queue)) {
87
+ queues.set(
88
+ queue,
89
+ new Queue(queue, {
90
+ connection: connectionOptions(), // Let BullMQ handle its own connection
91
+ prefix: queuePrefix(),
92
+ defaultJobOptions: {
93
+ attempts: 3,
94
+ backoff: { type: "exponential", delay: 3000 },
95
+ removeOnComplete: 1000,
96
+ removeOnFail: 5000
97
+ }
98
+ })
99
+ );
100
+ }
101
+
102
+ return queues.get(queue)!;
103
+ }
104
+
105
+ /**
106
+ * Why: Provides the shared Redis-backed state store for durable queues.
107
+ * When: Durable queue/worker construction.
108
+ * Where: Queue facade internals.
109
+ * How: Creates one `RedisStateStore` reusing the shared Redis client so durable
110
+ * state lives in Redis. Note the store opens its own client on first use -
111
+ * bullmq-durable does this so that merely constructing a store costs no
112
+ * connection - which is why `stopQueueRuntime` must call `close()` on it.
113
+ */
114
+ function durableStateStoreIfReady() {
115
+ const client = redisClientIfReady();
116
+ if (!client) return null;
117
+
118
+ if (!durableStateStore) {
119
+ durableStateStore = new RedisStateStore({ connection: client as never, prefix: queueConfig.durablePrefix });
120
+ }
121
+ return durableStateStore;
122
+ }
123
+
124
+ /**
125
+ * Why: Facade-friendly alias for getQueue.
126
+ * When: Consumers want fluent queue access.
127
+ * Where: Framework facade/API usage.
128
+ * How: Delegates directly to getQueue.
129
+ */
130
+ export function queue(queueName = "default") {
131
+ return getQueue(queueName);
132
+ }
133
+
134
+ /**
135
+ * Why: Ensures configured queues exist before UI/runtime usage.
136
+ * When: Queue dashboard setup or startup warmup.
137
+ * Where: Queue UI/bootstrap code.
138
+ * How: Iterates names and resolves queue instances.
139
+ */
140
+ export function ensureQueues(queueNames: string[]) {
141
+ for (const queueName of queueNames) {
142
+ getQueue(queueName);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Why: Returns all created queue instances.
148
+ * When: Queue introspection and dashboard binding.
149
+ * Where: Queue dashboard setup code.
150
+ * How: Converts internal queue map to array.
151
+ */
152
+ export function getAllQueues() {
153
+ return Array.from(queues.values());
154
+ }
155
+
156
+ /**
157
+ * Why: Explains that a job was lost instead of letting it vanish.
158
+ * When: queueJob is called and no Redis connection is ready.
159
+ * Where: Called from queueJob.
160
+ * How: Warns once per queue + job. The message names the job and its queue,
161
+ * says plainly that it never ran, and gives both the Redis fix and the
162
+ * in-process alternative, so a caller reading only this line knows what
163
+ * to do without opening framework source.
164
+ */
165
+ function warnDroppedJob(queueName: string, job: string, delay?: number) {
166
+ const id = key(queueName, job);
167
+ if (droppedJobs.has(id)) return;
168
+ droppedJobs.add(id);
169
+
170
+ const schedule = delay ? ` It was due in ${delay}s, so it was never even scheduled.` : "";
171
+ console.warn(
172
+ `[queue] Dropped job "${job}" on queue "${queueName}". Redis is not available, so this job was never queued and never ran.${schedule} ` +
173
+ `Enable REDIS in .env and run a queue worker, or call dispatchCommand("${job}", payload) without { async: true } to run it in-process now.`
174
+ );
175
+ }
176
+
177
+ /**
178
+ * Why: Adds a job to BullMQ with project defaults.
179
+ * When: Commands/events need background execution.
180
+ * Where: Dispatcher and feature modules.
181
+ * How: Resolves queue then calls add with retry/backoff options. When Redis is
182
+ * absent there is nowhere to put the job, so it returns null and says so -
183
+ * previously it returned null with no output at all, which meant silently
184
+ * losing the work.
185
+ */
186
+ export async function queueJob(
187
+ job: string,
188
+ data: any,
189
+ options: {
190
+ queue?: string;
191
+ delay?: number;
192
+ attempts?: number;
193
+ jobId?: string;
194
+ priority?: number;
195
+ removeOnComplete?: JobsOptions["removeOnComplete"];
196
+ removeOnFail?: JobsOptions["removeOnFail"];
197
+ backoff?: JobsOptions["backoff"];
198
+ } = {}
199
+ ) {
200
+ const queueName = options.queue || "default";
201
+ const queue = getQueue(queueName);
202
+ if (!queue) {
203
+ warnDroppedJob(queueName, job, options.delay);
204
+ return null;
205
+ }
206
+
207
+ try {
208
+ return await queue.add(job, data, {
209
+ delay: options.delay ? options.delay * 1000 : 0,
210
+ attempts: options.attempts ?? 3,
211
+ jobId: options.jobId,
212
+ priority: options.priority,
213
+ removeOnComplete: options.removeOnComplete ?? 1000,
214
+ removeOnFail: options.removeOnFail ?? 5000,
215
+ backoff: options.backoff ?? { type: "exponential", delay: 3000 }
216
+ });
217
+ } catch (error) {
218
+ logger.error(`Failed to queue job ${job}`, { error });
219
+ return null;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Why: Loads module job files so handlers are registered.
225
+ * When: Worker start and explicit queue boot flows.
226
+ * Where: Worker/runtime bootstrap.
227
+ * How: Discovers jobs glob and imports each file.
228
+ */
229
+ export async function bootQueueJobs() {
230
+ const files = await discoverModuleFiles("**/jobs/*.{ts,js}");
231
+ for (const file of files) await importFile(file);
232
+ return files.length;
233
+ }
234
+
235
+ /**
236
+ * Why: Deletes phantom queues whose names are neither configured nor worked by
237
+ * this process.
238
+ * When: Queue worker boot, after workers start.
239
+ * Where: Worker runtime bootstrap.
240
+ * How: Scans Redis for `{prefix}*:meta` keys, and for each queue name that is
241
+ * not in queueConfig.queues nor in the queues this worker was launched
242
+ * with (--queue), obliterates it only when it holds no live jobs
243
+ * (waiting/active/delayed/paused/prioritized/waiting-children). Historical
244
+ * completed/failed entries are ignored so leftover history cannot keep a
245
+ * dead queue alive. Recreates nothing permanent: BullMQ re-creates a
246
+ * queue's keys on demand if any code path still dispatches to it.
247
+ */
248
+ export async function pruneStaleQueues(protectedQueues: string[] = []) {
249
+ if (!queueConfig.autoPruneQueues || !redisConfig.enabled) return;
250
+
251
+ const client = redisClientIfReady();
252
+ if (!client) return;
253
+
254
+ const configured = new Set([...queueConfig.queues, ...protectedQueues]);
255
+ const prefix = `${queuePrefix()}:`;
256
+ const candidates = new Set<string>();
257
+
258
+ try {
259
+ let cursor = "0";
260
+ do {
261
+ const [nextCursor, keys] = await client.scan(cursor, "MATCH", `${prefix}*:meta`, "COUNT", 100);
262
+ cursor = nextCursor;
263
+ for (const key of keys) {
264
+ const name = key.slice(prefix.length, -5);
265
+ if (name && !configured.has(name)) candidates.add(name);
266
+ }
267
+ } while (cursor !== "0");
268
+
269
+ for (const name of candidates) {
270
+ try {
271
+ const queue = getQueue(name);
272
+ if (!queue) continue;
273
+
274
+ const counts = await queue.getJobCounts("waiting", "active", "delayed", "paused", "prioritized", "waiting-children");
275
+ const live = Object.values(counts).reduce((sum, n) => sum + (Number(n) || 0), 0);
276
+ if (live !== 0) continue;
277
+
278
+ await queue.obliterate({ force: true });
279
+ logger.info(`Pruned stale queue "${name}"`);
280
+ } catch (error: any) {
281
+ logger.warn(`Could not prune stale queue "${name}": ${error?.message || error}`);
282
+ }
283
+ }
284
+ } catch (error: any) {
285
+ logger.warn(`Could not scan queues for stale cleanup: ${error?.message || error}`);
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Why: Starts BullMQ workers for selected queues.
291
+ * When: Queue worker process boots.
292
+ * Where: Worker runtime entrypoint.
293
+ * How: Boots handlers, creates QueueEvents, and creates workers.
294
+ */
295
+ export async function startQueueWorker(queueNames = ["default"]) {
296
+ await bootQueueJobs();
297
+ if (!redisConfig.enabled) throw new Error("Redis is required for queue workers");
298
+
299
+ for (const queueName of queueNames) {
300
+ if (workedQueues.has(queueName)) continue;
301
+ workedQueues.add(queueName);
302
+
303
+ if (!events.has(queueName)) {
304
+ // Let BullMQ manage its own connection for events
305
+ events.set(queueName, new QueueEvents(queueName, { connection: connectionOptions(), prefix: queuePrefix() }));
306
+ }
307
+
308
+ const durable = [...durableHandlers.keys()].some((k) => k.startsWith(`${queueName}:`));
309
+
310
+ if (durable) {
311
+ // Durable worker still needs a shared client for its state store in this specific lib
312
+ const store = durableStateStoreIfReady();
313
+ if (!store) throw new Error("Redis is required for durable queue workers");
314
+
315
+ const map: Record<string, AnyQueueHandler> = {};
316
+ for (const [k, h] of handlers) {
317
+ if (k.startsWith(`${queueName}:`)) map[k.slice(queueName.length + 1)] = h;
318
+ }
319
+ for (const [k, h] of durableHandlers) {
320
+ if (k.startsWith(`${queueName}:`)) map[k.slice(queueName.length + 1)] = h;
321
+ }
322
+
323
+ const worker = new DurableWorker(queueName, map as never, {
324
+ connection: connectionOptions(),
325
+ prefix: queuePrefix(),
326
+ stateStore: store,
327
+ concurrency: 10
328
+ });
329
+
330
+ worker.on("completed", (job) => logger.info(`Processed: ${job.name} (${queueName})`));
331
+ worker.on("failed", (job, error) => logger.warn(`Failed: ${job?.name ?? "unknown"} (${queueName}) - ${error.message}`));
332
+
333
+ workers.push(worker as unknown as Worker);
334
+ continue;
335
+ }
336
+
337
+ const worker = new Worker(
338
+ queueName,
339
+ async (job) => {
340
+ logger.info(`Processing: ${job.name} (${queueName})`);
341
+ const handler = handlers.get(key(queueName, job.name));
342
+ if (!handler) throw new Error(`No handler registered for ${queueName}:${job.name}`);
343
+ return await handler(job);
344
+ },
345
+ { connection: connectionOptions(), prefix: queuePrefix(), concurrency: 10 }
346
+ );
347
+
348
+ worker.on("completed", (job) => logger.info(`Processed: ${job.name} (${queueName})`));
349
+ worker.on("failed", (job, error) => logger.warn(`Failed: ${job?.name ?? "unknown"} (${queueName}) - ${error.message}`));
350
+
351
+ workers.push(worker);
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Why: Hard-clears ALL queue-system state for reset workflows.
357
+ * When: Local cleanup and maintenance commands.
358
+ * Where: `queue:clear` command.
359
+ * How: Wipes every BullMQ queue key (including jobs, job schedulers, and
360
+ * repeat keys for queues not listed in `queues`, e.g. `demo`), plus the
361
+ * durable key space, by scanning each prefix and deleting in batches.
362
+ * Cache/session/rate-limit keys are intentionally left alone.
363
+ */
364
+ export async function clearQueue() {
365
+ if (!redisConfig.enabled) return;
366
+ const client = redisClientIfReady();
367
+ if (!client) return;
368
+
369
+ for (const prefix of [queuePrefix(), queueConfig.durablePrefix]) {
370
+ const stream = client.scanStream({ match: `${prefix}*`, count: 100 });
371
+ for await (const keys of stream) {
372
+ if (keys.length) await client.del(keys);
373
+ }
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Why: Gracefully stops all workers/events/queues.
379
+ * When: Process shutdown.
380
+ * Where: Server/worker lifecycle hooks.
381
+ * How: Closes resources with Promise.allSettled and clears registries. The
382
+ * durable state store is closed too: it owns a Redis client of its own, and
383
+ * dropping the reference without closing it would leak that connection on
384
+ * every restart until Redis runs out of clients.
385
+ */
386
+ export async function stopQueueRuntime() {
387
+ await Promise.allSettled(workers.map((worker) => worker.close()));
388
+ workers.length = 0;
389
+ workedQueues.clear();
390
+
391
+ await Promise.allSettled(Array.from(events.values()).map((queueEvents) => queueEvents.close()));
392
+ events.clear();
393
+
394
+ await Promise.allSettled(Array.from(queues.values()).map((queue) => queue.close()));
395
+ queues.clear();
396
+
397
+ if (durableStateStore) {
398
+ await Promise.allSettled([durableStateStore.close()]);
399
+ durableStateStore = null;
400
+ }
401
+ }
@@ -0,0 +1,194 @@
1
+ import { createBullBoard } from "@bull-board/api";
2
+ import { BullMQAdapter } from "@bull-board/api/bullMQAdapter";
3
+ import { FastifyAdapter } from "@bull-board/fastify";
4
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
5
+ import { queueConfig, redisConfig } from "@/config/index.js";
6
+ import { ensureQueues, getQueue } from "@/framework/queue/queue.js";
7
+ import { redisClientIfReady } from "@/framework/redis/client.js";
8
+ import { cookie } from "@/framework/support/cookie.js";
9
+ import { jwt } from "@/framework/support/jwt.js";
10
+ import { parseCsvOrFallback } from "@/framework/support/lifecycle.js";
11
+
12
+ const BASE_PATH = queueConfig.queueUi || "/queues";
13
+
14
+ const POLL_INTERVAL_MS = 5_000;
15
+
16
+ let pollTimer: ReturnType<typeof setInterval> | null = null;
17
+ let syncInFlight = false;
18
+
19
+ const dashboardQueueNames = new Set<string>();
20
+
21
+ const serverAdapter = new FastifyAdapter();
22
+ serverAdapter.setBasePath(BASE_PATH);
23
+
24
+ const bullBoard = createBullBoard({
25
+ queues: [],
26
+ serverAdapter,
27
+ options: {
28
+ uiConfig: {
29
+ boardTitle: "Queue Dashboard"
30
+ }
31
+ }
32
+ });
33
+
34
+ export function stopQueueDashboard() {
35
+ if (pollTimer) {
36
+ clearInterval(pollTimer);
37
+ pollTimer = null;
38
+ }
39
+ }
40
+
41
+ async function refreshDashboardQueues() {
42
+ if (syncInFlight) return;
43
+ syncInFlight = true;
44
+
45
+ try {
46
+ const client = redisClientIfReady();
47
+ if (!client) return;
48
+
49
+ const discovered = (await discoverQueueNames()).sort();
50
+ ensureQueues(discovered);
51
+
52
+ for (const name of discovered) {
53
+ if (dashboardQueueNames.has(name)) continue;
54
+ const queue = getQueue(name);
55
+ if (!queue) continue;
56
+ bullBoard.addQueue(new BullMQAdapter(queue));
57
+ dashboardQueueNames.add(name);
58
+ }
59
+
60
+ for (const name of Array.from(dashboardQueueNames)) {
61
+ if (!discovered.includes(name)) {
62
+ bullBoard.removeQueue(name);
63
+ dashboardQueueNames.delete(name);
64
+ }
65
+ }
66
+ } catch {
67
+ // Keep the previous queue list if a refresh fails.
68
+ } finally {
69
+ syncInFlight = false;
70
+ }
71
+ }
72
+
73
+ function queuePrefix() {
74
+ return queueConfig.prefix;
75
+ }
76
+
77
+ function allowedQueueDashboardEmails() {
78
+ const emails = parseCsvOrFallback(queueConfig.allowedEmails, []);
79
+ return new Set(emails.map((email) => email.toLowerCase()));
80
+ }
81
+
82
+ function dashboardAuth() {
83
+ const allowedEmails = allowedQueueDashboardEmails();
84
+
85
+ return async (request: FastifyRequest, reply: FastifyReply) => {
86
+ /**
87
+ * Why: The email allowlist is the ONLY gate on the queue dashboard UI.
88
+ * When: No `allowedEmails` configured → the dashboard is open to everyone
89
+ * in both dev and production (no login required). When configured,
90
+ * only the listed email(s) pass — everyone else is blocked.
91
+ * Where: BullMQ dashboard route hook.
92
+ * How: An empty allowlist short-circuits straight through; otherwise the
93
+ * requester's email (from the validated access token) must be in the
94
+ * set. This keeps BullMQ workers (mail delivery etc.) always running
95
+ * and gating strictly UI-only.
96
+ */
97
+ if (allowedEmails.size === 0) return;
98
+
99
+ const rawToken = await cookie.getAuth(request);
100
+ if (!rawToken) {
101
+ return reply.code(401).send({ message: "Unauthorized" });
102
+ }
103
+
104
+ const payload = await jwt.verifyToken(rawToken, "access");
105
+ if (!payload) {
106
+ return reply.code(401).send({ message: "Unauthorized" });
107
+ }
108
+
109
+ const normalized = payload as Record<string, unknown>;
110
+ const email = String(normalized.email ?? "")
111
+ .trim()
112
+ .toLowerCase();
113
+
114
+ if (!allowedEmails.has(email)) {
115
+ return reply.code(401).send({ message: "Unauthorized" });
116
+ }
117
+ };
118
+ }
119
+
120
+ async function discoverQueueNames(): Promise<string[]> {
121
+ const client = redisClientIfReady();
122
+ if (!client) return [];
123
+
124
+ const prefix = `${queuePrefix()}:`;
125
+ const names = new Set<string>();
126
+ let cursor = 0;
127
+
128
+ do {
129
+ const [nextCursor, keys] = await client.scan(cursor, "MATCH", `${prefix}*:meta`, "COUNT", 100);
130
+ cursor = Number(nextCursor);
131
+ for (const key of keys) {
132
+ const name = key.slice(prefix.length, -5);
133
+ if (name) names.add(name);
134
+ }
135
+ } while (cursor !== 0);
136
+
137
+ return Array.from(names);
138
+ }
139
+
140
+ function unavailableHtml() {
141
+ return `
142
+ <!doctype html>
143
+ <html>
144
+ <head>
145
+ <title>Queue Dashboard Unavailable</title>
146
+ <style>
147
+ body { font-family: system-ui, sans-serif; padding: 48px; text-align: center; }
148
+ h1 { color: #dc2626; }
149
+ </style>
150
+ </head>
151
+ <body>
152
+ <h1>Queue Dashboard Unavailable</h1>
153
+ <p>Redis is not connected. Check the Redis URL in src/config/redis.ts and make sure Redis is running.</p>
154
+ <p>Redis URL: ${redisConfig.url}</p>
155
+ </body>
156
+ </html>
157
+ `;
158
+ }
159
+
160
+ /**
161
+ * Why: Mounts the BullMQ dashboard, or a fallback page when Redis is down.
162
+ * When: Kernel boots after queue jobs are registered.
163
+ * Where: Framework startup.
164
+ * How: Registers the BullBoard Fastify adapter at `BASE_PATH`, wrapped in a
165
+ * scope that applies the email allowlist hook before any dashboard route.
166
+ */
167
+ export async function setupQueueDashboard() {
168
+ if (!redisClientIfReady()) {
169
+ const route = (app: FastifyInstance) => {
170
+ const handler = async (_request: FastifyRequest, reply: FastifyReply) => reply.code(200).type("text/html").send(unavailableHtml());
171
+ app.get(BASE_PATH, { schema: { hide: true } }, handler);
172
+ app.get(`${BASE_PATH}/*`, { schema: { hide: true } }, handler);
173
+ };
174
+
175
+ return { basePath: BASE_PATH, enabled: false, route };
176
+ }
177
+
178
+ await refreshDashboardQueues();
179
+ if (!pollTimer) {
180
+ pollTimer = setInterval(refreshDashboardQueues, POLL_INTERVAL_MS);
181
+ }
182
+
183
+ const route = async (app: FastifyInstance) => {
184
+ await app.register(
185
+ async (scope) => {
186
+ scope.addHook("onRequest", dashboardAuth());
187
+ await scope.register(serverAdapter.registerPlugin());
188
+ },
189
+ { prefix: BASE_PATH }
190
+ );
191
+ };
192
+
193
+ return { basePath: BASE_PATH, enabled: true, route };
194
+ }
@@ -0,0 +1,61 @@
1
+ import { queueConfig } from "@/config/index.js";
2
+ import { closeDatabase, initDatabase } from "@/framework/database/connection.js";
3
+ import { pruneStaleQueues, startQueueWorker as startQueueWorkers, stopQueueRuntime } from "@/framework/queue/queue.js";
4
+ import { closeRedis, initRedis, redisError, redisReady } from "@/framework/redis/client.js";
5
+ import { parseCsvOrFallback, registerShutdownSignals, type ShutdownSignal } from "@/framework/support/lifecycle.js";
6
+ import { logger } from "@/framework/support/logger.js";
7
+
8
+ export interface WorkerHandle {
9
+ queues: string[];
10
+ shutdown: (signal: ShutdownSignal) => Promise<void>;
11
+ }
12
+
13
+ /**
14
+ * Why: Starts the queue worker and wires DB/Redis shutdown.
15
+ * When: CLI `queue:work` command or E2E test bootstrap.
16
+ * Where: Worker entrypoint.
17
+ * How: Parses --queue= args, inits DB/Redis, starts workers, registers signals.
18
+ * When called as the main module, also prints the startup banner.
19
+ */
20
+ export async function startQueueWorkerRuntime(queues: string[] = []): Promise<WorkerHandle> {
21
+ if (!queues.length) {
22
+ const queuesArg = process.argv.find((arg) => arg.startsWith("--queue="));
23
+ const queueNames = queuesArg?.split("=")[1];
24
+ queues = parseCsvOrFallback(queueNames, queueConfig.queues);
25
+ }
26
+
27
+ await initDatabase();
28
+ await initRedis();
29
+ if (!redisReady()) {
30
+ logger.error("Queue worker cannot start because Redis is unavailable", {
31
+ error: redisError()
32
+ });
33
+ process.exit(1);
34
+ }
35
+
36
+ await startQueueWorkers(queues);
37
+ await pruneStaleQueues(queues);
38
+ console.log(`Queue worker started: ${queues.join(", ")}`);
39
+
40
+ let shuttingDown = false;
41
+
42
+ async function shutdown(signal: ShutdownSignal) {
43
+ if (shuttingDown) return;
44
+ shuttingDown = true;
45
+
46
+ logger.info("Queue worker shutdown signal received", { signal });
47
+ await Promise.allSettled([stopQueueRuntime(), closeDatabase()]);
48
+ await closeRedis();
49
+
50
+ process.exit(0);
51
+ }
52
+
53
+ registerShutdownSignals(shutdown);
54
+
55
+ return { queues, shutdown };
56
+ }
57
+
58
+ const isMain = process.argv[1]?.endsWith("worker.ts") || process.argv[1]?.endsWith("worker.js");
59
+ if (isMain) {
60
+ await startQueueWorkerRuntime();
61
+ }