create-nexwire 4.2.5 → 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 (399) hide show
  1. package/README.md +29 -12
  2. package/express/.env +1 -1
  3. package/express/package.json +1 -1
  4. package/express/src/config/database.ts +48 -29
  5. package/express/src/config/index.ts +83 -83
  6. package/express/src/config/openapi.ts +6 -24
  7. package/express/src/config/queue.ts +30 -30
  8. package/express/src/config/validate.ts +50 -40
  9. package/express/src/database/migrations/sqlite/0000_init.sql +47 -47
  10. package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  11. package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
  12. package/express/src/framework/cache/cache.ts +40 -8
  13. package/express/src/framework/circuit-breaker/cb.ts +88 -60
  14. package/express/src/framework/database/connection.ts +52 -27
  15. package/express/src/framework/events/dispatcher.ts +61 -49
  16. package/express/src/framework/facade.ts +47 -3
  17. package/express/src/framework/http/app.ts +28 -22
  18. package/express/src/framework/http/logger.ts +37 -17
  19. package/express/src/framework/http/openapi.ts +9 -11
  20. package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
  21. package/express/src/framework/modules/routes.ts +26 -21
  22. package/express/src/framework/notification/channels/broadcast.ts +15 -0
  23. package/express/src/framework/notification/channels/mail.ts +42 -0
  24. package/express/src/framework/notification/contracts.ts +29 -0
  25. package/express/src/framework/notification/index.ts +19 -94
  26. package/express/src/framework/notification/registry.ts +15 -0
  27. package/express/src/framework/providers/adapter.ts +27 -17
  28. package/express/src/framework/queue/queue.ts +43 -44
  29. package/express/src/framework/realtime/broadcast.ts +29 -5
  30. package/express/src/framework/realtime/socket.ts +149 -119
  31. package/express/src/framework/redis/client.ts +148 -151
  32. package/express/src/framework/runtime/server-adapter.ts +39 -34
  33. package/express/src/framework/scheduler/lock.ts +128 -116
  34. package/express/src/framework/server.ts +33 -49
  35. package/express/src/framework/session/session.ts +69 -14
  36. package/express/src/framework/storage/storage.ts +744 -785
  37. package/express/src/framework/support/mail.ts +15 -7
  38. package/express/src/middlewares/auth-middleware.ts +1 -2
  39. package/express/src/middlewares/role-middleware.ts +3 -13
  40. package/express/src/modules/auth/routes/index.ts +1 -2
  41. package/express/src/providers/notification.ts +4 -0
  42. package/express/src/resources/src/components/Switch.vue +60 -65
  43. package/express/src/resources/src/layouts/Layout/Header.vue +124 -105
  44. package/express/src/resources/src/pages/auth/forgetPassword.vue +23 -30
  45. package/express/src/resources/src/pages/auth/login.vue +25 -28
  46. package/express/src/resources/src/pages/auth/register.vue +35 -39
  47. package/express/src/resources/src/pages/auth/resetPassword.vue +29 -37
  48. package/express/src/resources/src/pages/auth/verifyEmail.vue +15 -19
  49. package/express/src/resources/src/plugins/gum.ts +519 -203
  50. package/express/src/resources/src/plugins/pulse.ts +157 -147
  51. package/express/src/resources/src/stores/auth.ts +9 -6
  52. package/express/src/resources/vite.config.ts +54 -43
  53. package/express/src/storage/database/nexwire.sqlite +0 -0
  54. package/express/src/storage/logs/app.log +0 -0
  55. package/express/src/storage/logs/fatal.log +0 -0
  56. package/express/src/types/global.d.ts +32 -26
  57. package/fastify/.dockerignore +14 -0
  58. package/fastify/.env +44 -0
  59. package/fastify/.env.example +42 -0
  60. package/fastify/.vscode/extensions.json +6 -0
  61. package/fastify/.vscode/settings.json +37 -0
  62. package/fastify/LICENSE +21 -0
  63. package/fastify/README.md +232 -0
  64. package/fastify/biome.json +99 -0
  65. package/fastify/drizzle.config.ts +25 -0
  66. package/fastify/gitignore-stub +31 -0
  67. package/fastify/package.json +130 -0
  68. package/fastify/public/.gitkeep +0 -0
  69. package/fastify/src/config/app.ts +41 -0
  70. package/fastify/src/config/auth.ts +12 -0
  71. package/fastify/src/config/cache.ts +16 -0
  72. package/fastify/src/config/cookie.ts +15 -0
  73. package/fastify/src/config/cors.ts +12 -0
  74. package/fastify/src/config/database.ts +48 -0
  75. package/fastify/src/config/index.ts +83 -0
  76. package/fastify/src/config/jwt.ts +19 -0
  77. package/fastify/src/config/logging.ts +13 -0
  78. package/fastify/src/config/mail.ts +24 -0
  79. package/fastify/src/config/openapi.ts +36 -0
  80. package/fastify/src/config/queue.ts +30 -0
  81. package/fastify/src/config/rateLimit.ts +17 -0
  82. package/fastify/src/config/realtime.ts +15 -0
  83. package/fastify/src/config/redis.ts +18 -0
  84. package/fastify/src/config/security.ts +44 -0
  85. package/fastify/src/config/session.ts +17 -0
  86. package/fastify/src/config/storage.ts +22 -0
  87. package/fastify/src/config/validate.ts +50 -0
  88. package/fastify/src/database/migrations/sqlite/0000_init.sql +48 -0
  89. package/fastify/src/database/migrations/sqlite/meta/0000_snapshot.json +324 -0
  90. package/fastify/src/database/migrations/sqlite/meta/_journal.json +13 -0
  91. package/fastify/src/database/schema.ts +2 -0
  92. package/fastify/src/env.ts +76 -0
  93. package/fastify/src/framework/cache/cache.ts +114 -0
  94. package/fastify/src/framework/circuit-breaker/cb.ts +88 -0
  95. package/fastify/src/framework/database/app-schema.ts +37 -0
  96. package/fastify/src/framework/database/connection.ts +223 -0
  97. package/fastify/src/framework/database/migrate-hooks.ts +87 -0
  98. package/fastify/src/framework/database/paginate.ts +226 -0
  99. package/fastify/src/framework/database/schema.ts +20 -0
  100. package/fastify/src/framework/database/seed.ts +152 -0
  101. package/fastify/src/framework/events/dispatcher.ts +61 -0
  102. package/fastify/src/framework/facade.ts +96 -0
  103. package/fastify/src/framework/http/app.ts +176 -0
  104. package/fastify/src/framework/http/cors.ts +13 -0
  105. package/fastify/src/framework/http/fastify.d.ts +13 -0
  106. package/fastify/src/framework/http/logger.ts +110 -0
  107. package/fastify/src/framework/http/metrics.ts +22 -0
  108. package/fastify/src/framework/http/openapi.ts +48 -0
  109. package/fastify/src/framework/http/ratelimiter.ts +47 -0
  110. package/fastify/src/framework/http/security.ts +25 -0
  111. package/fastify/src/framework/http/static.ts +83 -0
  112. package/fastify/src/framework/http/validation.ts +49 -0
  113. package/fastify/src/framework/kernel.ts +43 -0
  114. package/fastify/src/framework/maker-cli/db/core.mjs +291 -0
  115. package/fastify/src/framework/maker-cli/db/index.mjs +204 -0
  116. package/fastify/src/framework/maker-cli/deploy/core.mjs +1046 -0
  117. package/fastify/src/framework/maker-cli/deploy/index.mjs +91 -0
  118. package/fastify/src/framework/maker-cli/index.mjs +65 -0
  119. package/fastify/src/framework/maker-cli/middleware/core.mjs +41 -0
  120. package/fastify/src/framework/maker-cli/middleware/index.mjs +17 -0
  121. package/fastify/src/framework/maker-cli/module/core.mjs +1066 -0
  122. package/fastify/src/framework/maker-cli/module/index.mjs +211 -0
  123. package/fastify/src/framework/maker-cli/provider/core.mjs +49 -0
  124. package/fastify/src/framework/maker-cli/provider/index.mjs +12 -0
  125. package/fastify/src/framework/maker-cli/runtime/build-ui.mjs +16 -0
  126. package/fastify/src/framework/maker-cli/runtime/core.mjs +560 -0
  127. package/fastify/src/framework/maker-cli/runtime/index.mjs +120 -0
  128. package/fastify/src/framework/maker-cli/runtime/redis-commander-patch.cjs +20 -0
  129. package/fastify/src/framework/maker-cli/stubs/controller/openapi.ts.stub +74 -0
  130. package/fastify/src/framework/maker-cli/stubs/controller/plain.ts.stub +77 -0
  131. package/fastify/src/framework/maker-cli/stubs/controller/standalone.ts.stub +51 -0
  132. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +52 -0
  133. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +55 -0
  134. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +51 -0
  135. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +55 -0
  136. package/fastify/src/framework/maker-cli/stubs/deploy/README.stub +59 -0
  137. package/fastify/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +45 -0
  138. package/fastify/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +45 -0
  139. package/fastify/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +45 -0
  140. package/fastify/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +68 -0
  141. package/fastify/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +68 -0
  142. package/fastify/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +67 -0
  143. package/fastify/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +41 -0
  144. package/fastify/src/framework/maker-cli/stubs/deploy/server/README.stub +78 -0
  145. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +120 -0
  146. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +133 -0
  147. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +131 -0
  148. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +11 -0
  149. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +25 -0
  150. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +25 -0
  151. package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +16 -0
  152. package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +13 -0
  153. package/fastify/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +14 -0
  154. package/fastify/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +7 -0
  155. package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +54 -0
  156. package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +70 -0
  157. package/fastify/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +25 -0
  158. package/fastify/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +24 -0
  159. package/fastify/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
  160. package/fastify/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
  161. package/fastify/src/framework/maker-cli/stubs/job/name.ts.stub +25 -0
  162. package/fastify/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
  163. package/fastify/src/framework/maker-cli/stubs/middleware/name.ts.stub +15 -0
  164. package/fastify/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +10 -0
  165. package/fastify/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +10 -0
  166. package/fastify/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +10 -0
  167. package/fastify/src/framework/maker-cli/stubs/notification/controller.ts.stub +79 -0
  168. package/fastify/src/framework/maker-cli/stubs/notification/job.ts.stub +11 -0
  169. package/fastify/src/framework/maker-cli/stubs/notification/model.ts.stub +29 -0
  170. package/fastify/src/framework/maker-cli/stubs/notification/provider.ts.stub +174 -0
  171. package/fastify/src/framework/maker-cli/stubs/notification/route.api.ts.stub +71 -0
  172. package/fastify/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +15 -0
  173. package/fastify/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +26 -0
  174. package/fastify/src/framework/maker-cli/stubs/notification/schema.ts.stub +26 -0
  175. package/fastify/src/framework/maker-cli/stubs/provider/module-barrel.ts.stub +19 -0
  176. package/fastify/src/framework/maker-cli/stubs/provider/module.ts.stub +21 -0
  177. package/fastify/src/framework/maker-cli/stubs/provider/name.ts.stub +21 -0
  178. package/fastify/src/framework/maker-cli/stubs/route/api.ts.stub +101 -0
  179. package/fastify/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -0
  180. package/fastify/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +120 -0
  181. package/fastify/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +50 -0
  182. package/fastify/src/framework/maker-cli/stubs/schedule/name.ts.stub +36 -0
  183. package/fastify/src/framework/maker-cli/stubs/schema/name.plain.ts.stub +14 -0
  184. package/fastify/src/framework/maker-cli/stubs/schema/name.ts.stub +33 -0
  185. package/fastify/src/framework/maker-cli/stubs/seeder/name.ts.stub +18 -0
  186. package/fastify/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
  187. package/fastify/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
  188. package/fastify/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
  189. package/fastify/src/framework/maker-cli/stubs/test/unit.ts.stub +22 -0
  190. package/fastify/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
  191. package/fastify/src/framework/maker-cli/utils/env-config.mjs +44 -0
  192. package/fastify/src/framework/maker-cli/utils/env-db.mjs +61 -0
  193. package/fastify/src/framework/maker-cli/utils/file-ops.mjs +30 -0
  194. package/fastify/src/framework/maker-cli/utils/flags.mjs +14 -0
  195. package/fastify/src/framework/maker-cli/utils/help.mjs +379 -0
  196. package/fastify/src/framework/maker-cli/utils/naming.mjs +119 -0
  197. package/fastify/src/framework/maker-cli/utils/process.mjs +52 -0
  198. package/fastify/src/framework/maker-cli/utils/project-commands.mjs +201 -0
  199. package/fastify/src/framework/modules/discover.ts +57 -0
  200. package/fastify/src/framework/modules/routes.ts +31 -0
  201. package/fastify/src/framework/notification/channels/broadcast.ts +15 -0
  202. package/fastify/src/framework/notification/channels/mail.ts +42 -0
  203. package/fastify/src/framework/notification/contracts.ts +29 -0
  204. package/fastify/src/framework/notification/index.ts +59 -0
  205. package/fastify/src/framework/notification/registry.ts +15 -0
  206. package/fastify/src/framework/providers/adapter.ts +149 -0
  207. package/fastify/src/framework/providers/contracts.ts +126 -0
  208. package/fastify/src/framework/providers/loader.ts +105 -0
  209. package/fastify/src/framework/providers/registry.ts +88 -0
  210. package/fastify/src/framework/queue/clear.ts +22 -0
  211. package/fastify/src/framework/queue/queue.ts +401 -0
  212. package/fastify/src/framework/queue/ui.ts +194 -0
  213. package/fastify/src/framework/queue/worker.ts +61 -0
  214. package/fastify/src/framework/realtime/broadcast.ts +70 -0
  215. package/fastify/src/framework/realtime/index.ts +2 -0
  216. package/fastify/src/framework/realtime/socket-cookie.ts +72 -0
  217. package/fastify/src/framework/realtime/socket.ts +149 -0
  218. package/fastify/src/framework/realtime/types.ts +6 -0
  219. package/fastify/src/framework/realtime/ui.ts +16 -0
  220. package/fastify/src/framework/redis/client.ts +148 -0
  221. package/fastify/src/framework/runtime/runtime.ts +23 -0
  222. package/fastify/src/framework/runtime/server-adapter.ts +33 -0
  223. package/fastify/src/framework/scheduler/lock.ts +128 -0
  224. package/fastify/src/framework/scheduler/run.ts +48 -0
  225. package/fastify/src/framework/scheduler/scheduler.ts +155 -0
  226. package/fastify/src/framework/server.ts +162 -0
  227. package/fastify/src/framework/session/session.ts +160 -0
  228. package/fastify/src/framework/storage/storage.ts +744 -0
  229. package/fastify/src/framework/support/cookie.ts +71 -0
  230. package/fastify/src/framework/support/jwt.ts +33 -0
  231. package/fastify/src/framework/support/lifecycle.ts +35 -0
  232. package/fastify/src/framework/support/logger.ts +148 -0
  233. package/fastify/src/framework/support/mail.ts +56 -0
  234. package/fastify/src/framework/support/password.ts +23 -0
  235. package/fastify/src/framework/support/status-codes.ts +75 -0
  236. package/fastify/src/framework/support/upload.ts +181 -0
  237. package/fastify/src/framework/support/url.ts +41 -0
  238. package/fastify/src/middlewares/auth-middleware.ts +134 -0
  239. package/fastify/src/middlewares/role-middleware.ts +19 -0
  240. package/fastify/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
  241. package/fastify/src/modules/auth/__tests__/login.test.ts +171 -0
  242. package/fastify/src/modules/auth/__tests__/logout.test.ts +60 -0
  243. package/fastify/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
  244. package/fastify/src/modules/auth/__tests__/me.test.ts +91 -0
  245. package/fastify/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
  246. package/fastify/src/modules/auth/__tests__/register.test.ts +291 -0
  247. package/fastify/src/modules/auth/__tests__/resetpass.test.ts +206 -0
  248. package/fastify/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
  249. package/fastify/src/modules/auth/controllers/auth.ts +252 -0
  250. package/fastify/src/modules/auth/controllers/role.ts +35 -0
  251. package/fastify/src/modules/auth/database/models/role.ts +14 -0
  252. package/fastify/src/modules/auth/database/models/user.ts +46 -0
  253. package/fastify/src/modules/auth/database/seeders/role.ts +18 -0
  254. package/fastify/src/modules/auth/database/seeders/user.ts +35 -0
  255. package/fastify/src/modules/auth/facade.ts +12 -0
  256. package/fastify/src/modules/auth/helpers/auth.ts +115 -0
  257. package/fastify/src/modules/auth/jobs/forgetpass.ts +20 -0
  258. package/fastify/src/modules/auth/jobs/registeruser.ts +23 -0
  259. package/fastify/src/modules/auth/jobs/verifyemail.ts +18 -0
  260. package/fastify/src/modules/auth/routes/index.ts +185 -0
  261. package/fastify/src/modules/auth/routes/role.ts +56 -0
  262. package/fastify/src/modules/auth/schemas/auth.ts +116 -0
  263. package/fastify/src/modules/auth/services/forgetpassword.ts +66 -0
  264. package/fastify/src/modules/auth/services/login.ts +63 -0
  265. package/fastify/src/modules/auth/services/logout.ts +25 -0
  266. package/fastify/src/modules/auth/services/logoutalldevices.ts +19 -0
  267. package/fastify/src/modules/auth/services/me.ts +31 -0
  268. package/fastify/src/modules/auth/services/refreshtoken.ts +110 -0
  269. package/fastify/src/modules/auth/services/register.ts +141 -0
  270. package/fastify/src/modules/auth/services/resetpassword.ts +69 -0
  271. package/fastify/src/modules/auth/services/verifyemail.ts +77 -0
  272. package/fastify/src/modules/auth/types/auth.ts +31 -0
  273. package/fastify/src/providers/README.md +107 -0
  274. package/fastify/src/providers/notification.ts +4 -0
  275. package/fastify/src/resources/index.html +17 -0
  276. package/fastify/src/resources/src/App.vue +5 -0
  277. package/fastify/src/resources/src/assets/css/styles.css +14761 -0
  278. package/fastify/src/resources/src/assets/css/styles.css.map +1 -0
  279. package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin-ext.woff2 +0 -0
  280. package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin.woff2 +0 -0
  281. package/fastify/src/resources/src/assets/images/favicon/favicon.ico +0 -0
  282. package/fastify/src/resources/src/assets/images/logo.png +0 -0
  283. package/fastify/src/resources/src/assets/scss/custom.css +217 -0
  284. package/fastify/src/resources/src/assets/scss/custom.css.map +1 -0
  285. package/fastify/src/resources/src/assets/scss/custom.scss +1346 -0
  286. package/fastify/src/resources/src/components/Button.vue +32 -0
  287. package/fastify/src/resources/src/components/Checkbox.vue +32 -0
  288. package/fastify/src/resources/src/components/Datepicker.vue +106 -0
  289. package/fastify/src/resources/src/components/FeatureButton.vue +53 -0
  290. package/fastify/src/resources/src/components/FloatButton.vue +33 -0
  291. package/fastify/src/resources/src/components/Href.vue +32 -0
  292. package/fastify/src/resources/src/components/Input.vue +243 -0
  293. package/fastify/src/resources/src/components/InputGroup.vue +147 -0
  294. package/fastify/src/resources/src/components/InputPasswordToggle.vue +229 -0
  295. package/fastify/src/resources/src/components/Modal.vue +71 -0
  296. package/fastify/src/resources/src/components/Pagebar.vue +26 -0
  297. package/fastify/src/resources/src/components/Refresh.vue +19 -0
  298. package/fastify/src/resources/src/components/Select.vue +384 -0
  299. package/fastify/src/resources/src/components/Spinner.vue +42 -0
  300. package/fastify/src/resources/src/components/Switch.vue +60 -0
  301. package/fastify/src/resources/src/components/TextArea.vue +117 -0
  302. package/fastify/src/resources/src/components/Toast.vue +56 -0
  303. package/fastify/src/resources/src/components/datatable/DataTableSkeleton.vue +98 -0
  304. package/fastify/src/resources/src/components/datatable/Pagination.vue +147 -0
  305. package/fastify/src/resources/src/components/datatable/SelectOpption.vue +64 -0
  306. package/fastify/src/resources/src/components/datatable/index.vue +199 -0
  307. package/fastify/src/resources/src/composables/useAuth.ts +52 -0
  308. package/fastify/src/resources/src/helpers/assets.ts +51 -0
  309. package/fastify/src/resources/src/helpers/nformatter.ts +10 -0
  310. package/fastify/src/resources/src/helpers/utils.ts +75 -0
  311. package/fastify/src/resources/src/layouts/AuthLayout.vue +20 -0
  312. package/fastify/src/resources/src/layouts/Layout/Footer.vue +23 -0
  313. package/fastify/src/resources/src/layouts/Layout/Header.vue +124 -0
  314. package/fastify/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
  315. package/fastify/src/resources/src/layouts/Layout/index.vue +87 -0
  316. package/fastify/src/resources/src/main.ts +28 -0
  317. package/fastify/src/resources/src/pages/auth/forgetPassword.vue +76 -0
  318. package/fastify/src/resources/src/pages/auth/login.vue +96 -0
  319. package/fastify/src/resources/src/pages/auth/register.vue +129 -0
  320. package/fastify/src/resources/src/pages/auth/resetPassword.vue +110 -0
  321. package/fastify/src/resources/src/pages/auth/verifyEmail.vue +64 -0
  322. package/fastify/src/resources/src/pages/dashboard/index.vue +66 -0
  323. package/fastify/src/resources/src/plugins/axios.ts +31 -0
  324. package/fastify/src/resources/src/plugins/browserDetect.ts +55 -0
  325. package/fastify/src/resources/src/plugins/dialog.ts +216 -0
  326. package/fastify/src/resources/src/plugins/gum.ts +683 -0
  327. package/fastify/src/resources/src/plugins/pulse.ts +157 -0
  328. package/fastify/src/resources/src/plugins/routeProgress.ts +87 -0
  329. package/fastify/src/resources/src/router/index.ts +85 -0
  330. package/fastify/src/resources/src/stores/admin-ui.ts +148 -0
  331. package/fastify/src/resources/src/stores/auth.ts +187 -0
  332. package/fastify/src/resources/tsconfig.json +19 -0
  333. package/fastify/src/resources/vite.config.ts +64 -0
  334. package/fastify/src/storage/app/private/.gitkeep +0 -0
  335. package/fastify/src/storage/app/public/.gitkeep +0 -0
  336. package/fastify/src/storage/app/tmp/generated/.gitkeep +0 -0
  337. package/fastify/src/storage/database/nexwire.sqlite +0 -0
  338. package/fastify/src/storage/logs/.gitkeep +0 -0
  339. package/fastify/src/storage/logs/app.log +0 -0
  340. package/fastify/src/storage/logs/fatal.log +0 -0
  341. package/fastify/src/storage/tmp/.gitkeep +0 -0
  342. package/fastify/src/storage/trash/modules/.gitkeep +0 -0
  343. package/fastify/src/types/global.d.ts +32 -0
  344. package/fastify/tsconfig.json +19 -0
  345. package/fastify/vitest.config.ts +38 -0
  346. package/hono/.env +1 -1
  347. package/hono/package.json +1 -1
  348. package/hono/src/config/database.ts +48 -29
  349. package/hono/src/config/index.ts +83 -83
  350. package/hono/src/config/openapi.ts +6 -24
  351. package/hono/src/config/queue.ts +30 -30
  352. package/hono/src/config/validate.ts +50 -40
  353. package/hono/src/database/migrations/sqlite/0000_init.sql +47 -47
  354. package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  355. package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
  356. package/hono/src/framework/cache/cache.ts +40 -8
  357. package/hono/src/framework/circuit-breaker/cb.ts +88 -60
  358. package/hono/src/framework/database/connection.ts +52 -27
  359. package/hono/src/framework/events/dispatcher.ts +61 -49
  360. package/hono/src/framework/facade.ts +39 -1
  361. package/hono/src/framework/http/logger.ts +86 -81
  362. package/hono/src/framework/http/openapi.ts +7 -9
  363. package/hono/src/framework/maker-cli/runtime/core.mjs +1 -2
  364. package/hono/src/framework/notification/channels/broadcast.ts +15 -0
  365. package/hono/src/framework/notification/channels/mail.ts +42 -0
  366. package/hono/src/framework/notification/contracts.ts +29 -0
  367. package/hono/src/framework/notification/index.ts +19 -94
  368. package/hono/src/framework/notification/registry.ts +15 -0
  369. package/hono/src/framework/providers/adapter.ts +27 -17
  370. package/hono/src/framework/queue/queue.ts +43 -44
  371. package/hono/src/framework/realtime/broadcast.ts +29 -5
  372. package/hono/src/framework/realtime/socket.ts +217 -196
  373. package/hono/src/framework/redis/client.ts +148 -151
  374. package/hono/src/framework/scheduler/lock.ts +128 -116
  375. package/hono/src/framework/server.ts +27 -29
  376. package/hono/src/framework/session/session.ts +62 -14
  377. package/hono/src/framework/storage/storage.ts +744 -785
  378. package/hono/src/framework/support/mail.ts +56 -48
  379. package/hono/src/middlewares/auth-middleware.ts +1 -2
  380. package/hono/src/middlewares/role-middleware.ts +3 -13
  381. package/hono/src/modules/auth/routes/index.ts +1 -2
  382. package/hono/src/providers/notification.ts +4 -0
  383. package/hono/src/resources/src/components/Switch.vue +60 -65
  384. package/hono/src/resources/src/layouts/Layout/Header.vue +124 -105
  385. package/hono/src/resources/src/pages/auth/forgetPassword.vue +23 -29
  386. package/hono/src/resources/src/pages/auth/login.vue +26 -29
  387. package/hono/src/resources/src/pages/auth/register.vue +36 -40
  388. package/hono/src/resources/src/pages/auth/resetPassword.vue +29 -37
  389. package/hono/src/resources/src/pages/auth/verifyEmail.vue +15 -19
  390. package/hono/src/resources/src/plugins/gum.ts +519 -203
  391. package/hono/src/resources/src/plugins/pulse.ts +157 -147
  392. package/hono/src/resources/src/stores/auth.ts +9 -6
  393. package/hono/src/resources/vite.config.ts +54 -43
  394. package/hono/src/storage/database/nexwire.sqlite +0 -0
  395. package/hono/src/storage/logs/app.log +0 -0
  396. package/hono/src/storage/logs/fatal.log +0 -0
  397. package/hono/src/types/global.d.ts +32 -26
  398. package/package.json +4 -3
  399. package/src/index.js +143 -15
@@ -0,0 +1,71 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ import { appConfig, cookieConfig, jwtConfig } from "@/config/index.js";
3
+
4
+ function originOf(url?: string) {
5
+ if (!url) return "";
6
+ try {
7
+ return new URL(url).origin;
8
+ } catch {
9
+ return "";
10
+ }
11
+ }
12
+
13
+ function needsCrossSiteCookie() {
14
+ const appOrigin = originOf(appConfig.url);
15
+ const frontendOrigin = originOf(appConfig.frontendUrl);
16
+ return Boolean(appOrigin && frontendOrigin && appOrigin !== frontendOrigin);
17
+ }
18
+
19
+ function baseOptions() {
20
+ const crossSiteCookie = needsCrossSiteCookie();
21
+ const sameSite: "none" | "lax" = crossSiteCookie ? "none" : "lax";
22
+ return {
23
+ httpOnly: true,
24
+ secure: crossSiteCookie,
25
+ sameSite,
26
+ path: "/"
27
+ };
28
+ }
29
+
30
+ /** Reads a signed cookie, returning the value only when the signature is valid. */
31
+ function readSigned(request: FastifyRequest, name: string) {
32
+ const raw = request.cookies?.[name];
33
+ if (!raw) return undefined;
34
+ const result = request.unsignCookie(raw);
35
+ return result.valid ? result.value : undefined;
36
+ }
37
+
38
+ export const cookie = {
39
+ async setAuth(reply: FastifyReply, token: string) {
40
+ reply.setCookie(`${cookieConfig.name}_access`, token, {
41
+ ...baseOptions(),
42
+ // @fastify/cookie expects maxAge in seconds.
43
+ maxAge: jwtConfig.accessExpirySeconds,
44
+ signed: true
45
+ });
46
+ },
47
+
48
+ async setRefresh(reply: FastifyReply, token: string, maxAge?: number) {
49
+ reply.setCookie(`${cookieConfig.name}_refresh`, token, {
50
+ ...baseOptions(),
51
+ maxAge: typeof maxAge === "number" ? maxAge : jwtConfig.refreshExpirySeconds,
52
+ signed: true
53
+ });
54
+ },
55
+
56
+ async getAuth(request: FastifyRequest) {
57
+ return readSigned(request, `${cookieConfig.name}_access`);
58
+ },
59
+
60
+ async getRefresh(request: FastifyRequest) {
61
+ return readSigned(request, `${cookieConfig.name}_refresh`);
62
+ },
63
+
64
+ deleteAuth(reply: FastifyReply) {
65
+ reply.clearCookie(`${cookieConfig.name}_access`, { path: "/" });
66
+ },
67
+
68
+ deleteRefresh(reply: FastifyReply) {
69
+ reply.clearCookie(`${cookieConfig.name}_refresh`, { path: "/" });
70
+ }
71
+ };
@@ -0,0 +1,33 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import jwtLib from "jsonwebtoken";
3
+ import { jwtConfig } from "@/config/index.js";
4
+
5
+ export const jwt = {
6
+ async generateToken(payload: any, type: "access" | "refresh", expirySeconds?: number) {
7
+ const now = Math.floor(Date.now() / 1000);
8
+ const exp =
9
+ now +
10
+ (typeof expirySeconds === "number"
11
+ ? expirySeconds
12
+ : type === "refresh"
13
+ ? jwtConfig.refreshExpirySeconds
14
+ : jwtConfig.accessExpirySeconds);
15
+ const secret = type === "refresh" ? jwtConfig.refreshSecret : jwtConfig.accessSecret;
16
+ const jti = type === "refresh" ? randomUUID() : undefined;
17
+
18
+ const tokenPayload = { ...payload, type, iat: now, exp, ...(jti ? { jti } : {}) };
19
+ const token = await jwtLib.sign(tokenPayload, secret, { algorithm: jwtConfig.algorithm });
20
+
21
+ return { token, jti, exp };
22
+ },
23
+
24
+ async verifyToken(token: string, type: "access" | "refresh") {
25
+ try {
26
+ const secret = type === "refresh" ? jwtConfig.refreshSecret : jwtConfig.accessSecret;
27
+ const payload = (await jwtLib.verify(token, secret, { algorithms: [jwtConfig.algorithm] })) as Record<string, unknown>;
28
+ return payload.type === type ? payload : null;
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+ };
@@ -0,0 +1,35 @@
1
+ const shutdownSignals = ["SIGINT", "SIGTERM"] as const;
2
+ export type ShutdownSignal = (typeof shutdownSignals)[number];
3
+
4
+ type ShutdownHandler = (signal: ShutdownSignal) => Promise<void>;
5
+
6
+ /**
7
+ * Why: Avoid duplicated graceful-shutdown wiring across runtime entrypoints.
8
+ * When: Server/worker/scheduler need signal handling.
9
+ * Where: Runtime bootstrap files.
10
+ * How: Registers SIGINT/SIGTERM handlers and forwards signal to async callback.
11
+ */
12
+ export function registerShutdownSignals(handler: ShutdownHandler) {
13
+ for (const signal of shutdownSignals) {
14
+ process.on(signal, () => {
15
+ void handler(signal);
16
+ });
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Why: Keep comma-separated CLI/env parsing consistent.
22
+ * When: Runtime options are provided as a single CSV string.
23
+ * Where: Worker and other entrypoints.
24
+ * How: Trims tokens, removes blanks, and falls back when empty.
25
+ */
26
+ export function parseCsvOrFallback(value: string | undefined, fallback: string[]) {
27
+ if (!value) return fallback;
28
+
29
+ const parsed = value
30
+ .split(",")
31
+ .map((item) => item.trim())
32
+ .filter(Boolean);
33
+
34
+ return parsed.length ? parsed : fallback;
35
+ }
@@ -0,0 +1,148 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { Writable } from "node:stream";
4
+ import pino from "pino";
5
+ import { appConfig, loggingConfig } from "@/config/index.js";
6
+
7
+ const logDir = path.resolve(process.cwd(), "src", "storage", "logs");
8
+ const MAX_FILE_BYTES = 10 * 1024 * 1024;
9
+
10
+ if (!fs.existsSync(logDir)) {
11
+ fs.mkdirSync(logDir, { recursive: true });
12
+ }
13
+
14
+ const LEVEL_COLORS: Record<number, string> = {
15
+ 10: "\x1b[90m",
16
+ 20: "\x1b[90m",
17
+ 30: "\x1b[32m",
18
+ 40: "\x1b[33m",
19
+ 50: "\x1b[31m",
20
+ 60: "\x1b[31m"
21
+ };
22
+
23
+ const RESET = "\x1b[0m";
24
+
25
+ function formatLine(chunk: string, colorize: boolean): string {
26
+ const line = chunk.trim();
27
+ if (!line) return "";
28
+
29
+ let record: Record<string, unknown>;
30
+ try {
31
+ record = JSON.parse(line);
32
+ } catch {
33
+ return colorize ? line : `${line}\n`;
34
+ }
35
+
36
+ const time = typeof record.time === "number" ? new Date(record.time) : new Date(String(record.time));
37
+ const timestamp = Number.isNaN(time.getTime()) ? "" : `[${time.toLocaleTimeString("en-GB", { hour12: false })}] `;
38
+ const levelNumber = typeof record.level === "number" ? record.level : 30;
39
+ const label = pino.levels.labels[levelNumber] ?? "info";
40
+ const message = typeof record.msg === "string" ? record.msg : "";
41
+
42
+ const ignored = new Set(["time", "level", "msg", "pid", "hostname", "service"]);
43
+ const meta: Record<string, unknown> = {};
44
+ for (const [key, value] of Object.entries(record)) {
45
+ if (!ignored.has(key)) meta[key] = value;
46
+ }
47
+
48
+ const level = colorize ? `${LEVEL_COLORS[levelNumber] ?? ""}${label}${RESET}` : label;
49
+ const metaString = Object.keys(meta).length ? `\n${JSON.stringify(meta, null, 2)}` : "";
50
+ return `${timestamp}${level} -> ${message}${metaString}\n`;
51
+ }
52
+
53
+ function consoleWriter() {
54
+ return new Writable({
55
+ write(chunk, _encoding, callback) {
56
+ const text = formatLine(chunk.toString("utf8"), true);
57
+ if (text) process.stdout.write(text);
58
+ callback();
59
+ }
60
+ });
61
+ }
62
+
63
+ function rotate(filePath: string, maxFiles: number) {
64
+ for (let index = maxFiles - 1; index >= 1; index--) {
65
+ const from = `${filePath}.${index}`;
66
+ const to = `${filePath}.${index + 1}`;
67
+ if (fs.existsSync(from)) fs.renameSync(from, to);
68
+ }
69
+ if (fs.existsSync(filePath)) fs.renameSync(filePath, `${filePath}.1`);
70
+ }
71
+
72
+ function fileWriter(filePath: string, maxFiles: number) {
73
+ let bytes = 0;
74
+
75
+ return new Writable({
76
+ write(chunk, _encoding, callback) {
77
+ const text = formatLine(chunk.toString("utf8"), false);
78
+ bytes += Buffer.byteLength(text);
79
+ if (bytes >= MAX_FILE_BYTES) {
80
+ rotate(filePath, maxFiles);
81
+ bytes = 0;
82
+ }
83
+ try {
84
+ fs.appendFileSync(filePath, text);
85
+ } catch {
86
+ // best-effort file logging
87
+ }
88
+ callback();
89
+ }
90
+ });
91
+ }
92
+
93
+ const base = pino(
94
+ {
95
+ level: loggingConfig.level,
96
+ base: { service: appConfig.name },
97
+ timestamp: pino.stdTimeFunctions.isoTime
98
+ },
99
+ pino.multistream([
100
+ { stream: consoleWriter(), level: loggingConfig.level },
101
+ { stream: fileWriter(path.join(logDir, "app.log"), 5), level: "info" },
102
+ { stream: fileWriter(path.join(logDir, "fatal.log"), 3), level: "error" }
103
+ ])
104
+ );
105
+
106
+ type CompatLogger = {
107
+ fatal: (message: string, meta?: Record<string, unknown>) => void;
108
+ error: (message: string, meta?: Record<string, unknown>) => void;
109
+ warn: (message: string, meta?: Record<string, unknown>) => void;
110
+ info: (message: string, meta?: Record<string, unknown>) => void;
111
+ debug: (message: string, meta?: Record<string, unknown>) => void;
112
+ trace: (message: string, meta?: Record<string, unknown>) => void;
113
+ child: (bindings: Record<string, unknown>) => CompatLogger;
114
+ };
115
+
116
+ function compat(child: pino.Logger): CompatLogger {
117
+ const wrap = (log: (obj: object, msg?: string, ...args: unknown[]) => void) => (message: string, meta?: Record<string, unknown>) =>
118
+ log.call(child, meta ?? {}, message);
119
+
120
+ return {
121
+ fatal: wrap(child.fatal),
122
+ error: wrap(child.error),
123
+ warn: wrap(child.warn),
124
+ info: wrap(child.info),
125
+ debug: wrap(child.debug),
126
+ trace: wrap(child.trace),
127
+ child: (bindings) => compat(child.child(bindings))
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Why: Shared structured logger for app/runtime/framework internals.
133
+ * When: Any code needs operational logs or error reporting.
134
+ * Where: Imported across framework and modules.
135
+ * How: Emits JSON via pino to a color console sink plus rotating app/fatal
136
+ * files, keeping the legacy `(message, meta)` call signature.
137
+ */
138
+ export const logger: CompatLogger = compat(base);
139
+
140
+ process.on("uncaughtException", (error) => {
141
+ logger.error("Uncaught exception", { error: { name: error.name, message: error.message, stack: error.stack } });
142
+ process.exit(1);
143
+ });
144
+
145
+ process.on("unhandledRejection", (reason) => {
146
+ const error = reason instanceof Error ? reason : new Error(String(reason));
147
+ logger.error("Unhandled rejection", { error: { name: error.name, message: error.message, stack: error.stack } });
148
+ });
@@ -0,0 +1,56 @@
1
+ import nodemailer from "nodemailer";
2
+ import { mailConfig } from "@/config/index.js";
3
+ import { createCircuitBreaker, isCircuitBreakerError } from "@/framework/circuit-breaker/cb.js";
4
+ import { logger } from "@/framework/support/logger.js";
5
+
6
+ type MailPayload = {
7
+ to: string;
8
+ subject: string;
9
+ html?: string;
10
+ text?: string;
11
+ };
12
+
13
+ const transport = nodemailer.createTransport({
14
+ host: mailConfig.host,
15
+ port: mailConfig.port,
16
+ secure: mailConfig.encryption === "ssl",
17
+ auth: mailConfig.username ? { user: mailConfig.username, pass: mailConfig.password } : undefined
18
+ });
19
+
20
+ const mailBreaker = createCircuitBreaker({ name: "SMTP Mail" });
21
+
22
+ export const mail = {
23
+ /**
24
+ * Why: Sends transactional email through configured SMTP transport.
25
+ * When: Features need notifications/password reset/signup email.
26
+ * Where: Jobs and event handlers.
27
+ * How: Uses nodemailer transport and respects the fail-silent setting.
28
+ */
29
+ async sendMail(payload: MailPayload) {
30
+ try {
31
+ return await mailBreaker.exec(() =>
32
+ transport.sendMail({
33
+ from: mailConfig.fromAddress,
34
+ ...payload
35
+ })
36
+ );
37
+ } catch (error) {
38
+ // Distinguish between a Circuit Breaker rejection and an actual SMTP failure
39
+ if (isCircuitBreakerError(error)) {
40
+ logger.warn("Mail send skipped: Circuit breaker is open", {
41
+ to: payload.to,
42
+ subject: payload.subject
43
+ });
44
+ } else {
45
+ logger.error("Mail send failed", {
46
+ to: payload.to,
47
+ subject: payload.subject,
48
+ error: error instanceof Error ? error.message : error
49
+ });
50
+ }
51
+
52
+ if (!mailConfig.failSilent) throw error;
53
+ return null;
54
+ }
55
+ }
56
+ };
@@ -0,0 +1,23 @@
1
+ import bcrypt from "bcryptjs";
2
+
3
+ export const password = {
4
+ /**
5
+ * Why: Hashes plaintext password before persistence.
6
+ * When: Registration and password reset flows.
7
+ * Where: Auth controllers/seeders.
8
+ * How: Uses bcrypt with cost factor 10.
9
+ */
10
+ async hashPassword(inputPassword: string) {
11
+ return await bcrypt.hash(inputPassword, 10);
12
+ },
13
+
14
+ /**
15
+ * Why: Compares plaintext password to stored hash.
16
+ * When: Login/authentication checks.
17
+ * Where: Auth controllers/middleware.
18
+ * How: Uses bcrypt secure compare.
19
+ */
20
+ async verifyPassword(inputPassword: string, hashedPassword: string) {
21
+ return await bcrypt.compare(inputPassword, hashedPassword);
22
+ }
23
+ };
@@ -0,0 +1,75 @@
1
+ // 1xx Informational
2
+ export const CONTINUE = 100;
3
+ export const SWITCHING_PROTOCOLS = 101;
4
+ export const PROCESSING = 102;
5
+ export const EARLY_HINTS = 103;
6
+
7
+ // 2xx Success
8
+ export const OK = 200;
9
+ export const CREATED = 201;
10
+ export const ACCEPTED = 202;
11
+ export const NON_AUTHORITATIVE_INFORMATION = 203;
12
+ export const NO_CONTENT = 204;
13
+ export const RESET_CONTENT = 205;
14
+ export const PARTIAL_CONTENT = 206;
15
+ export const MULTI_STATUS = 207;
16
+ export const ALREADY_REPORTED = 208;
17
+ export const IM_USED = 226;
18
+
19
+ // 3xx Redirection
20
+ export const MULTIPLE_CHOICES = 300;
21
+ export const MOVED_PERMANENTLY = 301;
22
+ export const FOUND = 302;
23
+ export const MOVED_TEMPORARILY = 302;
24
+ export const SEE_OTHER = 303;
25
+ export const NOT_MODIFIED = 304;
26
+ export const USE_PROXY = 305;
27
+ export const TEMPORARY_REDIRECT = 307;
28
+ export const PERMANENT_REDIRECT = 308;
29
+
30
+ // 4xx Client Error
31
+ export const BAD_REQUEST = 400;
32
+ export const UNAUTHORIZED = 401;
33
+ export const PAYMENT_REQUIRED = 402;
34
+ export const FORBIDDEN = 403;
35
+ export const NOT_FOUND = 404;
36
+ export const METHOD_NOT_ALLOWED = 405;
37
+ export const NOT_ACCEPTABLE = 406;
38
+ export const PROXY_AUTHENTICATION_REQUIRED = 407;
39
+ export const REQUEST_TIMEOUT = 408;
40
+ export const CONFLICT = 409;
41
+ export const GONE = 410;
42
+ export const LENGTH_REQUIRED = 411;
43
+ export const PRECONDITION_FAILED = 412;
44
+ export const PAYLOAD_TOO_LARGE = 413;
45
+ export const REQUEST_TOO_LONG = 413;
46
+ export const URI_TOO_LONG = 414;
47
+ export const REQUEST_URI_TOO_LONG = 414;
48
+ export const UNSUPPORTED_MEDIA_TYPE = 415;
49
+ export const RANGE_NOT_SATISFIABLE = 416;
50
+ export const REQUESTED_RANGE_NOT_SATISFIABLE = 416;
51
+ export const EXPECTATION_FAILED = 417;
52
+ export const IM_A_TEAPOT = 418;
53
+ export const MISDIRECTED_REQUEST = 421;
54
+ export const UNPROCESSABLE_ENTITY = 422;
55
+ export const LOCKED = 423;
56
+ export const FAILED_DEPENDENCY = 424;
57
+ export const TOO_EARLY = 425;
58
+ export const UPGRADE_REQUIRED = 426;
59
+ export const PRECONDITION_REQUIRED = 428;
60
+ export const TOO_MANY_REQUESTS = 429;
61
+ export const REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
62
+ export const UNAVAILABLE_FOR_LEGAL_REASONS = 451;
63
+
64
+ // 5xx Server Error
65
+ export const INTERNAL_SERVER_ERROR = 500;
66
+ export const NOT_IMPLEMENTED = 501;
67
+ export const BAD_GATEWAY = 502;
68
+ export const SERVICE_UNAVAILABLE = 503;
69
+ export const GATEWAY_TIMEOUT = 504;
70
+ export const HTTP_VERSION_NOT_SUPPORTED = 505;
71
+ export const VARIANT_ALSO_NEGOTIATES = 506;
72
+ export const INSUFFICIENT_STORAGE = 507;
73
+ export const LOOP_DETECTED = 508;
74
+ export const NOT_EXTENDED = 510;
75
+ export const NETWORK_AUTHENTICATION_REQUIRED = 511;
@@ -0,0 +1,181 @@
1
+ import path from "node:path";
2
+ import type { FastifyRequest } from "fastify";
3
+ import type { MultipartFile } from "@fastify/multipart";
4
+
5
+ export type UploadedFile = {
6
+ fieldname: string;
7
+ originalname: string;
8
+ encoding: string;
9
+ mimetype: string;
10
+ size: number;
11
+ buffer: Buffer;
12
+ };
13
+
14
+ export type UploadOptions = {
15
+ /**
16
+ * Form field name that carries the file(s). Matches the client's FormData key.
17
+ */
18
+ field: string;
19
+ /**
20
+ * When set, accepts an array of up to `multiple` files on that field.
21
+ * Omit for a single-file upload (exposed as `request.upload`).
22
+ */
23
+ multiple?: number;
24
+ /**
25
+ * Maximum allowed file size in bytes. Defaults to unlimited.
26
+ */
27
+ maxSize?: number;
28
+ /**
29
+ * Allowed file extensions (lowercase, with dot), e.g. [".xlsx", ".xlsb"].
30
+ * Rejects others with a 422 message.
31
+ */
32
+ allowedExtensions?: string[];
33
+ /**
34
+ * Allowed MIME types, e.g. ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"].
35
+ * When provided alongside extensions, a file must satisfy both.
36
+ */
37
+ allowedMimeTypes?: string[];
38
+ };
39
+
40
+ export type UploadFieldsSpec = Array<{ name: string; maxCount?: number }>;
41
+
42
+ type UploadRules = Omit<UploadOptions, "field" | "multiple">;
43
+
44
+ function humanSize(bytes: number): string {
45
+ const units = ["B", "KB", "MB", "GB", "TB"];
46
+ let i = 0;
47
+ let size = bytes;
48
+ while (size >= 1024 && i < units.length - 1) {
49
+ size /= 1024;
50
+ i += 1;
51
+ }
52
+ return `${Math.round(size * 100) / 100} ${units[i]}`;
53
+ }
54
+
55
+ function validationError(message: string) {
56
+ const error = new Error(message) as Error & { statusCode: number };
57
+ error.statusCode = 422;
58
+ return error;
59
+ }
60
+
61
+ function checkRules(file: MultipartFile, rules: UploadRules) {
62
+ const ext = path.extname(file.filename).toLowerCase();
63
+ if (rules.allowedExtensions?.length && !rules.allowedExtensions.includes(ext)) {
64
+ throw validationError(`File must be ${rules.allowedExtensions.join(" or ")}`);
65
+ }
66
+ if (rules.allowedMimeTypes?.length && !rules.allowedMimeTypes.includes(file.mimetype)) {
67
+ throw validationError(`File type ${file.mimetype} is not allowed`);
68
+ }
69
+ }
70
+
71
+ async function toUpload(file: MultipartFile, rules: UploadRules): Promise<UploadedFile> {
72
+ checkRules(file, rules);
73
+
74
+ let buffer: Buffer;
75
+ try {
76
+ buffer = await file.toBuffer();
77
+ } catch {
78
+ throw validationError(`File size must be less than ${humanSize(rules.maxSize ?? 0)}`);
79
+ }
80
+
81
+ if (typeof rules.maxSize === "number" && buffer.length > rules.maxSize) {
82
+ throw validationError(`File size must be less than ${humanSize(rules.maxSize)}`);
83
+ }
84
+
85
+ return {
86
+ fieldname: file.fieldname,
87
+ originalname: file.filename,
88
+ encoding: file.encoding,
89
+ mimetype: file.mimetype,
90
+ size: buffer.length,
91
+ buffer
92
+ };
93
+ }
94
+
95
+ function rethrowMultipart(error: unknown, maxCount: number, rules: UploadRules): never {
96
+ const code = (error as { code?: string })?.code;
97
+ if (code === "FST_REQ_FILE_TOO_LARGE") {
98
+ throw validationError(`File size must be less than ${humanSize(rules.maxSize ?? 0)}`);
99
+ }
100
+ if (code === "FST_FILES_LIMIT" || code === "FST_PARTS_LIMIT") {
101
+ throw validationError(`Too many files, max ${maxCount} allowed`);
102
+ }
103
+ throw error;
104
+ }
105
+
106
+ /**
107
+ * Why: Parses multipart uploads into memory as a reusable Fastify preHandler.
108
+ * When: Any route needs to accept file uploads (single, multiple, or labeled fields).
109
+ * Where: Route files via `preHandler: upload({...})`.
110
+ * How: Wraps `@fastify/multipart` so handlers receive `request.upload` /
111
+ * `request.uploads` buffers and size/extension/MIME rules are enforced;
112
+ * failures are normalized into readable 422 errors before the handler runs.
113
+ */
114
+ export function upload(options: UploadOptions) {
115
+ const { field, multiple, ...rules } = options;
116
+ const maxCount = multiple ?? 1;
117
+
118
+ return async function uploadPreHandler(request: FastifyRequest) {
119
+ try {
120
+ if (multiple) {
121
+ const files: UploadedFile[] = [];
122
+ for await (const file of request.files({ limits: { files: maxCount, fileSize: rules.maxSize } })) {
123
+ if (file.fieldname !== field) continue;
124
+ if (files.length >= maxCount) break;
125
+ files.push(await toUpload(file, rules));
126
+ }
127
+ if (files.length === 0) throw validationError("File required");
128
+ request.uploads = files;
129
+ return;
130
+ }
131
+
132
+ const file = await request.file({ limits: { fileSize: rules.maxSize } });
133
+ if (!file || file.fieldname !== field) throw validationError("File required");
134
+ request.upload = await toUpload(file, rules);
135
+ } catch (error) {
136
+ if ((error as { statusCode?: number })?.statusCode === 422) throw error;
137
+ rethrowMultipart(error, maxCount, rules);
138
+ }
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Why: Parses multipart forms carrying multiple labeled file fields at once.
144
+ * When: A form uploads several different inputs (e.g. avatar + document) in one request.
145
+ * Where: Route files via `preHandler: fields([{ name: "avatar" }, { name: "docs", maxCount: 5 }])`.
146
+ * How: Iterates every uploaded file and groups it by field, enforcing per-field
147
+ * max counts and the shared size/MIME/extension rules.
148
+ */
149
+ export function fields(spec: UploadFieldsSpec, options: UploadRules = {}) {
150
+ const limits = new Map(spec.map((entry) => [entry.name, entry.maxCount ?? 1]));
151
+ const total = spec.reduce((sum, entry) => sum + (entry.maxCount ?? 1), 0);
152
+
153
+ return async function fieldsPreHandler(request: FastifyRequest) {
154
+ const grouped: Record<string, UploadedFile[]> = {};
155
+ let totalReceived = 0;
156
+
157
+ try {
158
+ for await (const file of request.files({ limits: { files: total, fileSize: options.maxSize } })) {
159
+ const maxCount = limits.get(file.fieldname);
160
+ if (maxCount === undefined) continue;
161
+
162
+ const bucket = (grouped[file.fieldname] ??= []);
163
+ if (bucket.length >= maxCount) {
164
+ throw validationError(`Too many files for ${file.fieldname}, max ${maxCount} allowed`);
165
+ }
166
+ if (totalReceived >= total) {
167
+ throw validationError(`Too many files, max ${total} allowed`);
168
+ }
169
+
170
+ bucket.push(await toUpload(file, options));
171
+ totalReceived += 1;
172
+ }
173
+ } catch (error) {
174
+ if ((error as { statusCode?: number })?.statusCode === 422) throw error;
175
+ rethrowMultipart(error, total, options);
176
+ }
177
+
178
+ if (totalReceived === 0) throw validationError("File required");
179
+ request.uploads = grouped;
180
+ };
181
+ }
@@ -0,0 +1,41 @@
1
+ import { appConfig } from "@/config/index.js";
2
+ import { hasUiBuild } from "@/framework/http/static.js";
3
+
4
+ export const urls = {
5
+ /**
6
+ * Why: Returns normalized app base URL without trailing slash.
7
+ * When: Building absolute links.
8
+ * Where: Mail/reset URL generation.
9
+ * How: Trims trailing slash from the configured app URL.
10
+ */
11
+ appUrl() {
12
+ return appConfig.url.replace(/\/$/, "");
13
+ },
14
+
15
+ /**
16
+ * Why: Builds absolute URL pointing at the SPA page that handles the path.
17
+ * When: Creating links for emails / external callbacks (reset, verify).
18
+ * Where: Auth/password reset and integrations.
19
+ * How: Three independent knobs decide the origin —
20
+ * appConfig.frontendUrl: the SPA is a separate build/origin (dev Vite
21
+ * dev server, or a separately deployed SPA). Link there.
22
+ * appConfig.uiEnabled + hasUiBuild(): the framework serves the built
23
+ * SPA itself, so the page resolves to the app origin.
24
+ * appConfig.uiEnabled + no UI build: the Vite dev server is still
25
+ * running (UI not built yet) — use the origin the maker-cli
26
+ * injected (NEXWIRE_FRONTEND_URL, dev Vite dev server).
27
+ * When BOTH origin knobs are unset (API-only mode) there is no SPA to
28
+ * point at — fall back to the app URL so the link is valid.
29
+ */
30
+ url(path = "") {
31
+ const base =
32
+ appConfig.frontendUrl || // explicit SPA origin — wins
33
+ (appConfig.uiEnabled
34
+ ? hasUiBuild()
35
+ ? urls.appUrl() // built SPA, framework serves it — same origin
36
+ : process.env.NEXWIRE_FRONTEND_URL || urls.appUrl() // dev Vite dev server — injected by maker-cli
37
+ : appConfig.url); // API-only
38
+ if (!path) return base;
39
+ return `${base}${path.startsWith("/") ? path : `/${path}`}`;
40
+ }
41
+ };