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,683 @@
1
+ import { useStorage } from "@vueuse/core";
2
+ import axios, { type AxiosError, type AxiosProgressEvent, type AxiosResponse } from "axios";
3
+ import type { App } from "vue";
4
+ import { computed, nextTick, reactive, ref, toRaw } from "vue";
5
+ import { type LocationQueryRaw, useRoute, useRouter } from "vue-router";
6
+
7
+ /* -------------------------------------------------------------------------- */
8
+ /* Types */
9
+ /* -------------------------------------------------------------------------- */
10
+
11
+ export type GumPluginOptions = {
12
+ rememberPrefix?: string;
13
+ recentlySuccessfulDuration?: number;
14
+ };
15
+
16
+ type GumMethod = "get" | "post" | "put" | "patch" | "delete";
17
+ type FormMethod = "post" | "put" | "patch" | "delete";
18
+
19
+ type LifecycleHooks = {
20
+ onBefore?: () => boolean | undefined | Promise<boolean | undefined>;
21
+ onStart?: () => void | Promise<void>;
22
+ onProgress?: (event: AxiosProgressEvent) => void;
23
+ onSuccess?: (response: AxiosResponse) => void | Promise<void>;
24
+ onError?: (errors: NormalizedErrors, error: AxiosError) => void | Promise<void>;
25
+ onFinish?: () => void | Promise<void>;
26
+ };
27
+
28
+ /**
29
+ * A fetcher returns either an AxiosResponse or any plain payload.
30
+ * Gum wraps plain values into `{ data, status: 200 }`, so store methods
31
+ * returning `Promise<string>` / `Promise<User>` work without a wrapper.
32
+ *
33
+ * Typing is intentionally loose (`Promise<unknown>`); callers should not
34
+ * rely on the fetcher's return type being narrow.
35
+ */
36
+ type Fetcher = (payload: Record<string, unknown> | FormData) => Promise<unknown>;
37
+
38
+ type GumVisitOptions = LifecycleHooks & {
39
+ method?: GumMethod;
40
+ data?: Record<string, unknown> | FormData;
41
+ query?: Record<string, unknown>;
42
+ routePath?: string;
43
+ replace?: boolean;
44
+ preserveState?: boolean;
45
+ preserveScroll?: boolean;
46
+ skipFetch?: boolean;
47
+ fetcher?: Fetcher;
48
+ };
49
+
50
+ /** URL-less visit: fetcher REQUIRED, method injected by the helper. */
51
+ type VisitWithFetcherOptions = Omit<GumVisitOptions, "method" | "fetcher"> & { fetcher: Fetcher };
52
+ /** For body-carrying methods (post/put/patch/delete). */
53
+ type VisitWithFetcherDataOptions = VisitWithFetcherOptions & {
54
+ data?: Record<string, unknown> | FormData;
55
+ };
56
+
57
+ type FormSubmitOptions<T> = LifecycleHooks & {
58
+ validate?: (data: T) => Record<string, string> | null | undefined;
59
+ transform?: (data: T) => Record<string, unknown> | FormData;
60
+ fetcher?: Fetcher;
61
+ };
62
+
63
+ /** URL-less form submit: fetcher REQUIRED, method injected by the helper. */
64
+ type FormWithFetcherOptions<T> = Omit<FormSubmitOptions<T>, "fetcher"> & { fetcher: Fetcher };
65
+
66
+ type FormErrors<T> = Partial<Record<keyof T | string, string>>;
67
+ type NormalizedErrors = Record<string, string[] | string>;
68
+
69
+ type ValidationErrorPayload = {
70
+ errors?: Record<string, string[] | string>;
71
+ error?: { issues?: Array<{ path?: Array<string | number>; message?: string }> };
72
+ };
73
+
74
+ /* -------------------------------------------------------------------------- */
75
+ /* Config + registry */
76
+ /* -------------------------------------------------------------------------- */
77
+
78
+ const config: Required<GumPluginOptions> = {
79
+ rememberPrefix: "gum",
80
+ recentlySuccessfulDuration: 2000
81
+ };
82
+
83
+ const routeRememberKeys = new Map<string, Set<string>>();
84
+
85
+ function normalizePath(path: string) {
86
+ const clean = (path || "/").split("?")[0];
87
+ return clean || "/";
88
+ }
89
+
90
+ function registerRememberKey(path: string, key: string) {
91
+ const routePath = normalizePath(path);
92
+ if (!routeRememberKeys.has(routePath)) routeRememberKeys.set(routePath, new Set());
93
+ routeRememberKeys.get(routePath)!.add(key);
94
+ }
95
+
96
+ export function clearRememberForPath(path: string) {
97
+ const routePath = normalizePath(path);
98
+ const keys = routeRememberKeys.get(routePath);
99
+ if (!keys) return;
100
+ keys.forEach((key) => localStorage.removeItem(`${config.rememberPrefix}:${key}`));
101
+ }
102
+
103
+ /* -------------------------------------------------------------------------- */
104
+ /* Global processing tracker (shared across all useGum() instances) */
105
+ /* -------------------------------------------------------------------------- */
106
+
107
+ const globalProcessing = ref(false);
108
+ let activeRequests = 0;
109
+
110
+ function trackStart() {
111
+ activeRequests++;
112
+ globalProcessing.value = true;
113
+ }
114
+
115
+ function trackEnd() {
116
+ activeRequests = Math.max(0, activeRequests - 1);
117
+ if (activeRequests === 0) globalProcessing.value = false;
118
+ }
119
+
120
+ /* -------------------------------------------------------------------------- */
121
+ /* Small shared helpers */
122
+ /* -------------------------------------------------------------------------- */
123
+
124
+ export function normalizeValidationErrors(payload?: ValidationErrorPayload): NormalizedErrors {
125
+ const normalized: NormalizedErrors = { ...payload?.errors };
126
+ const issues = payload?.error?.issues;
127
+ if (Array.isArray(issues)) {
128
+ issues.forEach((issue) => {
129
+ const key = issue.path?.map(String).join(".");
130
+ if (key) normalized[key] = String(issue.message || "Invalid value");
131
+ });
132
+ }
133
+ return normalized;
134
+ }
135
+
136
+ async function runBefore(onBefore?: LifecycleHooks["onBefore"]) {
137
+ const allow = await onBefore?.();
138
+ return allow !== false;
139
+ }
140
+
141
+ async function restoreScroll(enabled: boolean, y: number) {
142
+ if (!enabled) return;
143
+ await nextTick();
144
+ // Double rAF: survives one layout pass (images/fonts shifting content).
145
+ requestAnimationFrame(() => requestAnimationFrame(() => window.scrollTo({ top: y, behavior: "instant" as ScrollBehavior })));
146
+ }
147
+
148
+ function startRecentTimer(target: { recentlySuccessful: { value: boolean } }) {
149
+ target.recentlySuccessful.value = true;
150
+ setTimeout(() => {
151
+ target.recentlySuccessful.value = false;
152
+ }, config.recentlySuccessfulDuration);
153
+ }
154
+
155
+ function assertSource(url: string | undefined, fetcher: Fetcher | undefined, ctx: string) {
156
+ if (!url && !fetcher) {
157
+ throw new Error(
158
+ `gum: ${ctx} requires either "url" or "fetcher". Pass a URL for axios, or a fetcher (e.g. a store method) to delegate the call.`
159
+ );
160
+ }
161
+ if (import.meta.env?.DEV && url && fetcher) {
162
+ console.warn(`gum: ${ctx} — "url" is ignored when "fetcher" is provided.`);
163
+ }
164
+ }
165
+
166
+ /** Read the current scroll position — plain read, no per-instance listeners. */
167
+ function readScrollY(): number {
168
+ return typeof window !== "undefined" ? window.scrollY : 0;
169
+ }
170
+
171
+ /* -------------------------------------------------------------------------- */
172
+ /* Shared core executor — dumb transport, never swallows errors */
173
+ /* -------------------------------------------------------------------------- */
174
+
175
+ type ExecuteOptions = {
176
+ method: GumMethod;
177
+ url?: string;
178
+ payload: Record<string, unknown> | FormData | undefined;
179
+ query?: Record<string, unknown>;
180
+ fetcher?: Fetcher;
181
+ onProgress?: (event: AxiosProgressEvent) => void;
182
+ };
183
+
184
+ /**
185
+ * Execute the network call. Throws on error — the caller normalizes and
186
+ * decides whether to rethrow based on whether `onError` was provided.
187
+ *
188
+ * Fetcher results:
189
+ * - If it looks like an AxiosResponse (object with `data` and a numeric `status`),
190
+ * it is returned as-is.
191
+ * - Otherwise it is wrapped into `{ data: result, status: 200 }`.
192
+ */
193
+ async function execute(options: ExecuteOptions): Promise<AxiosResponse> {
194
+ if (options.fetcher) {
195
+ const result = await options.fetcher(options.payload ?? {});
196
+ const looksLikeResponse =
197
+ result !== null &&
198
+ typeof result === "object" &&
199
+ "data" in (result as Record<string, unknown>) &&
200
+ typeof (result as { status?: unknown }).status === "number";
201
+ if (looksLikeResponse) return result as AxiosResponse;
202
+ return { data: result, status: 200, statusText: "OK", headers: {}, config: {} } as AxiosResponse;
203
+ }
204
+
205
+ return await axios.request({
206
+ method: options.method,
207
+ url: options.url,
208
+ params: options.query,
209
+ data: options.method === "get" ? undefined : options.payload,
210
+ onUploadProgress: options.onProgress
211
+ });
212
+ }
213
+
214
+ /* -------------------------------------------------------------------------- */
215
+ /* Remember */
216
+ /* -------------------------------------------------------------------------- */
217
+
218
+ export function useGumRemember<T extends object>(key: string, initial: T) {
219
+ const route = useRoute();
220
+ registerRememberKey(route.path, key);
221
+ return useStorage<T>(`${config.rememberPrefix}:${key}`, initial, localStorage, {
222
+ mergeDefaults: true
223
+ });
224
+ }
225
+
226
+ /* -------------------------------------------------------------------------- */
227
+ /* Visit API */
228
+ /* -------------------------------------------------------------------------- */
229
+
230
+ function buildVisitApi() {
231
+ const router = useRouter();
232
+ const route = useRoute();
233
+
234
+ /* ---------------------------- visit core ------------------------------- */
235
+
236
+ async function runVisit(params: {
237
+ method: GumMethod;
238
+ url?: string;
239
+ data?: Record<string, unknown> | FormData;
240
+ query?: Record<string, unknown>;
241
+ routePath?: string;
242
+ replace?: boolean;
243
+ preserveState?: boolean;
244
+ preserveScroll?: boolean;
245
+ skipFetch?: boolean;
246
+ fetcher?: Fetcher;
247
+ hooks?: GumVisitOptions;
248
+ }) {
249
+ const {
250
+ method,
251
+ url,
252
+ data,
253
+ query,
254
+ routePath,
255
+ replace = false,
256
+ preserveState = method !== "get",
257
+ preserveScroll = false,
258
+ skipFetch = false,
259
+ fetcher,
260
+ hooks = {}
261
+ } = params;
262
+
263
+ const { onBefore, onStart, onProgress, onSuccess, onError, onFinish } = hooks;
264
+
265
+ if (!(await runBefore(onBefore))) return;
266
+
267
+ const savedY = readScrollY();
268
+ trackStart();
269
+
270
+ let response: AxiosResponse | undefined;
271
+ try {
272
+ // onStart is inside try so a throw still unwinds processing.
273
+ await onStart?.();
274
+
275
+ if (!skipFetch) {
276
+ assertSource(url, fetcher, "visit");
277
+ try {
278
+ response = await execute({ method, url, payload: data, query, fetcher, onProgress });
279
+ } catch (error) {
280
+ const err = error as AxiosError<ValidationErrorPayload>;
281
+ const normalized = normalizeValidationErrors(err.response?.data);
282
+ await onError?.(normalized, err);
283
+ if (!onError) throw error;
284
+ return;
285
+ }
286
+ }
287
+
288
+ if (method === "get") {
289
+ const targetPath = routePath ?? route.path;
290
+ if (!preserveState) clearRememberForPath(targetPath);
291
+ const payload = { path: targetPath, query: (query ?? route.query) as LocationQueryRaw };
292
+ if (replace) await router.replace(payload);
293
+ else await router.push(payload);
294
+ }
295
+
296
+ await onSuccess?.(response!);
297
+ return response;
298
+ } finally {
299
+ trackEnd();
300
+ await onFinish?.();
301
+ await restoreScroll(preserveScroll, savedY);
302
+ }
303
+ }
304
+
305
+ function visit(url: string, options?: GumVisitOptions): Promise<AxiosResponse | undefined>;
306
+ function visit(options: GumVisitOptions & { fetcher: Fetcher }): Promise<AxiosResponse | undefined>;
307
+ function visit(urlOrOptions: string | (GumVisitOptions & { fetcher: Fetcher }), options: GumVisitOptions = {}) {
308
+ if (typeof urlOrOptions === "object") {
309
+ return runVisit({
310
+ method: urlOrOptions.method ?? "get",
311
+ fetcher: urlOrOptions.fetcher,
312
+ data: urlOrOptions.data,
313
+ query: urlOrOptions.query,
314
+ routePath: urlOrOptions.routePath,
315
+ replace: urlOrOptions.replace,
316
+ preserveState: urlOrOptions.preserveState,
317
+ preserveScroll: urlOrOptions.preserveScroll,
318
+ skipFetch: urlOrOptions.skipFetch,
319
+ hooks: urlOrOptions
320
+ });
321
+ }
322
+ return runVisit({
323
+ method: options.method ?? "get",
324
+ url: urlOrOptions,
325
+ data: options.data,
326
+ query: options.query,
327
+ routePath: options.routePath,
328
+ replace: options.replace,
329
+ preserveState: options.preserveState,
330
+ preserveScroll: options.preserveScroll,
331
+ skipFetch: options.skipFetch,
332
+ fetcher: options.fetcher,
333
+ hooks: options
334
+ });
335
+ }
336
+
337
+ const api = {
338
+ processing: globalProcessing,
339
+ visit,
340
+ get: (url: string, o: GumVisitOptions = {}) =>
341
+ runVisit({
342
+ method: "get",
343
+ url,
344
+ query: o.query,
345
+ routePath: o.routePath,
346
+ replace: o.replace,
347
+ preserveState: o.preserveState,
348
+ preserveScroll: o.preserveScroll,
349
+ skipFetch: o.skipFetch,
350
+ fetcher: o.fetcher,
351
+ hooks: o
352
+ }),
353
+ post: (url: string, data?: GumVisitOptions["data"], o: GumVisitOptions = {}) =>
354
+ runVisit({
355
+ method: "post",
356
+ url,
357
+ data,
358
+ query: o.query,
359
+ routePath: o.routePath,
360
+ replace: o.replace,
361
+ preserveState: o.preserveState,
362
+ preserveScroll: o.preserveScroll,
363
+ skipFetch: o.skipFetch,
364
+ fetcher: o.fetcher,
365
+ hooks: o
366
+ }),
367
+ put: (url: string, data?: GumVisitOptions["data"], o: GumVisitOptions = {}) =>
368
+ runVisit({
369
+ method: "put",
370
+ url,
371
+ data,
372
+ query: o.query,
373
+ routePath: o.routePath,
374
+ replace: o.replace,
375
+ preserveState: o.preserveState,
376
+ preserveScroll: o.preserveScroll,
377
+ skipFetch: o.skipFetch,
378
+ fetcher: o.fetcher,
379
+ hooks: o
380
+ }),
381
+ patch: (url: string, data?: GumVisitOptions["data"], o: GumVisitOptions = {}) =>
382
+ runVisit({
383
+ method: "patch",
384
+ url,
385
+ data,
386
+ query: o.query,
387
+ routePath: o.routePath,
388
+ replace: o.replace,
389
+ preserveState: o.preserveState,
390
+ preserveScroll: o.preserveScroll,
391
+ skipFetch: o.skipFetch,
392
+ fetcher: o.fetcher,
393
+ hooks: o
394
+ }),
395
+ delete: (url: string, o: GumVisitOptions = {}) =>
396
+ // Forward o.data so DELETE bodies keep working (legacy behavior).
397
+ runVisit({
398
+ method: "delete",
399
+ url,
400
+ data: o.data,
401
+ query: o.query,
402
+ routePath: o.routePath,
403
+ replace: o.replace,
404
+ preserveState: o.preserveState,
405
+ preserveScroll: o.preserveScroll,
406
+ skipFetch: o.skipFetch,
407
+ fetcher: o.fetcher,
408
+ hooks: o
409
+ }),
410
+ reload: (o: GumVisitOptions = {}) =>
411
+ runVisit({
412
+ method: "get",
413
+ url: route.path,
414
+ query: route.query as Record<string, unknown>,
415
+ replace: true,
416
+ preserveState: o.preserveState,
417
+ preserveScroll: o.preserveScroll,
418
+ skipFetch: o.skipFetch,
419
+ fetcher: o.fetcher,
420
+ hooks: o
421
+ }),
422
+
423
+ /* URL-less, fetcher-based variants */
424
+ getWith: (o: VisitWithFetcherOptions): Promise<AxiosResponse | undefined> =>
425
+ runVisit({
426
+ method: "get",
427
+ query: o.query,
428
+ routePath: o.routePath,
429
+ replace: o.replace,
430
+ preserveState: o.preserveState,
431
+ preserveScroll: o.preserveScroll,
432
+ skipFetch: o.skipFetch,
433
+ fetcher: o.fetcher,
434
+ hooks: o
435
+ }),
436
+ postWith: (o: VisitWithFetcherDataOptions): Promise<AxiosResponse | undefined> =>
437
+ runVisit({
438
+ method: "post",
439
+ data: o.data,
440
+ query: o.query,
441
+ routePath: o.routePath,
442
+ replace: o.replace,
443
+ preserveState: o.preserveState,
444
+ preserveScroll: o.preserveScroll,
445
+ skipFetch: o.skipFetch,
446
+ fetcher: o.fetcher,
447
+ hooks: o
448
+ }),
449
+ putWith: (o: VisitWithFetcherDataOptions): Promise<AxiosResponse | undefined> =>
450
+ runVisit({
451
+ method: "put",
452
+ data: o.data,
453
+ query: o.query,
454
+ routePath: o.routePath,
455
+ replace: o.replace,
456
+ preserveState: o.preserveState,
457
+ preserveScroll: o.preserveScroll,
458
+ skipFetch: o.skipFetch,
459
+ fetcher: o.fetcher,
460
+ hooks: o
461
+ }),
462
+ patchWith: (o: VisitWithFetcherDataOptions): Promise<AxiosResponse | undefined> =>
463
+ runVisit({
464
+ method: "patch",
465
+ data: o.data,
466
+ query: o.query,
467
+ routePath: o.routePath,
468
+ replace: o.replace,
469
+ preserveState: o.preserveState,
470
+ preserveScroll: o.preserveScroll,
471
+ skipFetch: o.skipFetch,
472
+ fetcher: o.fetcher,
473
+ hooks: o
474
+ }),
475
+ deleteWith: (o: VisitWithFetcherDataOptions): Promise<AxiosResponse | undefined> =>
476
+ runVisit({
477
+ method: "delete",
478
+ data: o.data,
479
+ query: o.query,
480
+ routePath: o.routePath,
481
+ replace: o.replace,
482
+ preserveState: o.preserveState,
483
+ preserveScroll: o.preserveScroll,
484
+ skipFetch: o.skipFetch,
485
+ fetcher: o.fetcher,
486
+ hooks: o
487
+ }),
488
+ reloadWith: (o: VisitWithFetcherOptions): Promise<AxiosResponse | undefined> =>
489
+ runVisit({
490
+ method: "get",
491
+ query: (o.query ?? route.query) as Record<string, unknown>,
492
+ routePath: o.routePath,
493
+ replace: true,
494
+ preserveState: o.preserveState,
495
+ preserveScroll: o.preserveScroll,
496
+ skipFetch: o.skipFetch,
497
+ fetcher: o.fetcher,
498
+ hooks: o
499
+ })
500
+ };
501
+
502
+ return api;
503
+ }
504
+
505
+ type GumApi = ReturnType<typeof buildVisitApi>;
506
+
507
+ /* -------------------------------------------------------------------------- */
508
+ /* Form state */
509
+ /* -------------------------------------------------------------------------- */
510
+
511
+ function createForm<T extends Record<string, unknown>>(defaults: T) {
512
+ /* ------------------------ form state ----------------------------------- */
513
+ const initial = structuredClone(defaults);
514
+ const data = reactive(structuredClone(defaults)) as T;
515
+ const errors = reactive<Record<string, string>>({});
516
+ const progress = ref<number | null>(null);
517
+ const formProcessing = ref(false);
518
+ const wasSuccessful = ref(false);
519
+ const recentlySuccessful = ref(false);
520
+ const isDirty = computed(() => JSON.stringify(toRaw(data)) !== JSON.stringify(initial));
521
+
522
+ function setError(field: keyof T | string, message: string) {
523
+ errors[String(field)] = message;
524
+ }
525
+
526
+ function clearErrors(...fields: (keyof T | string)[]) {
527
+ if (!fields.length) {
528
+ Object.keys(errors).forEach((k) => delete errors[k]);
529
+ return;
530
+ }
531
+ fields.forEach((f) => delete errors[String(f)]);
532
+ }
533
+
534
+ function reset(...fields: (keyof T)[]) {
535
+ if (!fields.length) {
536
+ Object.assign(data, structuredClone(initial));
537
+ clearErrors();
538
+ return;
539
+ }
540
+ fields.forEach((f) => {
541
+ (data as any)[f] = structuredClone(initial[f]);
542
+ delete errors[String(f)];
543
+ });
544
+ }
545
+
546
+ async function submitForm(
547
+ method: FormMethod,
548
+ url: string | undefined,
549
+ payload: Record<string, unknown> | undefined,
550
+ opts: FormSubmitOptions<T>
551
+ ): Promise<AxiosResponse | undefined> {
552
+ const { onBefore, onStart, onProgress, onSuccess, onError, onFinish, validate, transform, fetcher } = opts;
553
+ const userOnError = onError;
554
+
555
+ if (!(await runBefore(onBefore))) return;
556
+
557
+ assertSource(url, fetcher, "submit");
558
+
559
+ // Local validation — only abort if there ARE errors (empty {} passes through).
560
+ const validationErrors = validate?.(toRaw(data) as T);
561
+ if (validationErrors && Object.keys(validationErrors).length > 0) {
562
+ clearErrors();
563
+ Object.entries(validationErrors).forEach(([k, v]) => setError(k, v));
564
+ return;
565
+ }
566
+
567
+ const body = transform ? transform(toRaw(data) as T) : (payload ?? toRaw(data));
568
+
569
+ wasSuccessful.value = false;
570
+ clearErrors();
571
+ formProcessing.value = true;
572
+ trackStart();
573
+
574
+ try {
575
+ // onStart inside try — a throw still unwinds processing.
576
+ await onStart?.();
577
+
578
+ let response: AxiosResponse;
579
+ try {
580
+ response = await execute({
581
+ method,
582
+ url,
583
+ payload: body,
584
+ fetcher,
585
+ onProgress: (event) => {
586
+ onProgress?.(event);
587
+ if (event.total) progress.value = Math.round((event.loaded * 100) / event.total);
588
+ }
589
+ });
590
+ } catch (error) {
591
+ const err = error as AxiosError<ValidationErrorPayload>;
592
+ const normalized = normalizeValidationErrors(err.response?.data);
593
+ Object.entries(normalized).forEach(([k, v]) => setError(k, Array.isArray(v) ? v[0] : v));
594
+ await userOnError?.(normalized, err);
595
+ if (!userOnError) throw error;
596
+ return;
597
+ }
598
+
599
+ wasSuccessful.value = true;
600
+ startRecentTimer({ recentlySuccessful });
601
+ await onSuccess?.(response);
602
+ return response;
603
+ } finally {
604
+ formProcessing.value = false;
605
+ progress.value = null;
606
+ trackEnd();
607
+ await onFinish?.();
608
+ }
609
+ }
610
+
611
+ return {
612
+ data,
613
+ errors: errors as FormErrors<T>,
614
+ progress,
615
+ processing: formProcessing,
616
+ wasSuccessful,
617
+ recentlySuccessful,
618
+ isDirty,
619
+ setError,
620
+ clearErrors,
621
+ reset,
622
+ submit: (method: FormMethod, url: string | undefined, payload?: Record<string, unknown>, opts?: FormSubmitOptions<T>) =>
623
+ submitForm(method, url, payload, opts ?? {}),
624
+ post: (url: string, payload?: Record<string, unknown>, opts?: FormSubmitOptions<T>) => submitForm("post", url, payload, opts ?? {}),
625
+ put: (url: string, payload?: Record<string, unknown>, opts?: FormSubmitOptions<T>) => submitForm("put", url, payload, opts ?? {}),
626
+ patch: (url: string, payload?: Record<string, unknown>, opts?: FormSubmitOptions<T>) => submitForm("patch", url, payload, opts ?? {}),
627
+ delete: (url: string, payload?: Record<string, unknown>, opts?: FormSubmitOptions<T>) => submitForm("delete", url, payload, opts ?? {}),
628
+
629
+ /* URL-less, fetcher-based variants */
630
+ postWith: (o: FormWithFetcherOptions<T>) => submitForm("post", undefined, undefined, o),
631
+ putWith: (o: FormWithFetcherOptions<T>) => submitForm("put", undefined, undefined, o),
632
+ patchWith: (o: FormWithFetcherOptions<T>) => submitForm("patch", undefined, undefined, o),
633
+ deleteWith: (o: FormWithFetcherOptions<T>) => submitForm("delete", undefined, undefined, o)
634
+ };
635
+ }
636
+
637
+ type GumForm<T extends Record<string, unknown>> = ReturnType<typeof createForm<T>>;
638
+
639
+ /* -------------------------------------------------------------------------- */
640
+ /* useGum — unified visit + form */
641
+ /* -------------------------------------------------------------------------- */
642
+
643
+ /**
644
+ * Visit API only. `form` is not available when no defaults are passed.
645
+ */
646
+ export function useGum(): GumApi & { form?: undefined };
647
+
648
+ /**
649
+ * Visit API plus form state. Pass the form defaults as the first argument.
650
+ */
651
+ export function useGum<T extends Record<string, unknown>>(defaults: T): GumApi & { form: GumForm<T> };
652
+
653
+ export function useGum<T extends Record<string, unknown>>(defaults?: T): GumApi & { form?: GumForm<T> } {
654
+ const api = buildVisitApi();
655
+ if (defaults === undefined) return api;
656
+ return { ...api, form: createForm(defaults) };
657
+ }
658
+
659
+ /* -------------------------------------------------------------------------- */
660
+ /* Backward-compat alias */
661
+ /* -------------------------------------------------------------------------- */
662
+
663
+ /**
664
+ * @deprecated Use `useGum(defaults).form` instead.
665
+ * Kept so existing pages don't break. Will be removed in a future major.
666
+ */
667
+ export function useGumForm<T extends Record<string, unknown>>(defaults: T): GumForm<T> {
668
+ const { form } = useGum(defaults);
669
+ return form;
670
+ }
671
+
672
+ /* -------------------------------------------------------------------------- */
673
+ /* Plugin */
674
+ /* -------------------------------------------------------------------------- */
675
+
676
+ export const GumPlugin = {
677
+ install(_app: App, options: GumPluginOptions = {}) {
678
+ if (options.rememberPrefix) config.rememberPrefix = options.rememberPrefix;
679
+ if (typeof options.recentlySuccessfulDuration === "number") {
680
+ config.recentlySuccessfulDuration = options.recentlySuccessfulDuration;
681
+ }
682
+ }
683
+ };