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.
- package/README.md +29 -12
- package/express/.env +1 -1
- package/express/package.json +1 -1
- package/express/src/config/database.ts +48 -29
- package/express/src/config/index.ts +83 -83
- package/express/src/config/openapi.ts +6 -24
- package/express/src/config/queue.ts +30 -30
- package/express/src/config/validate.ts +50 -40
- package/express/src/database/migrations/sqlite/0000_init.sql +47 -47
- package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
- package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/express/src/framework/cache/cache.ts +40 -8
- package/express/src/framework/circuit-breaker/cb.ts +88 -60
- package/express/src/framework/database/connection.ts +52 -27
- package/express/src/framework/events/dispatcher.ts +61 -49
- package/express/src/framework/facade.ts +47 -3
- package/express/src/framework/http/app.ts +28 -22
- package/express/src/framework/http/logger.ts +37 -17
- package/express/src/framework/http/openapi.ts +9 -11
- package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
- package/express/src/framework/modules/routes.ts +26 -21
- package/express/src/framework/notification/channels/broadcast.ts +15 -0
- package/express/src/framework/notification/channels/mail.ts +42 -0
- package/express/src/framework/notification/contracts.ts +29 -0
- package/express/src/framework/notification/index.ts +19 -94
- package/express/src/framework/notification/registry.ts +15 -0
- package/express/src/framework/providers/adapter.ts +27 -17
- package/express/src/framework/queue/queue.ts +43 -44
- package/express/src/framework/realtime/broadcast.ts +29 -5
- package/express/src/framework/realtime/socket.ts +149 -119
- package/express/src/framework/redis/client.ts +148 -151
- package/express/src/framework/runtime/server-adapter.ts +39 -34
- package/express/src/framework/scheduler/lock.ts +128 -116
- package/express/src/framework/server.ts +33 -49
- package/express/src/framework/session/session.ts +69 -14
- package/express/src/framework/storage/storage.ts +744 -785
- package/express/src/framework/support/mail.ts +15 -7
- package/express/src/middlewares/auth-middleware.ts +1 -2
- package/express/src/middlewares/role-middleware.ts +3 -13
- package/express/src/modules/auth/routes/index.ts +1 -2
- package/express/src/providers/notification.ts +4 -0
- package/express/src/resources/src/components/Switch.vue +60 -65
- package/express/src/resources/src/layouts/Layout/Header.vue +124 -105
- package/express/src/resources/src/pages/auth/forgetPassword.vue +23 -30
- package/express/src/resources/src/pages/auth/login.vue +25 -28
- package/express/src/resources/src/pages/auth/register.vue +35 -39
- package/express/src/resources/src/pages/auth/resetPassword.vue +29 -37
- package/express/src/resources/src/pages/auth/verifyEmail.vue +15 -19
- package/express/src/resources/src/plugins/gum.ts +519 -203
- package/express/src/resources/src/plugins/pulse.ts +157 -147
- package/express/src/resources/src/stores/auth.ts +9 -6
- package/express/src/resources/vite.config.ts +54 -43
- package/express/src/storage/database/nexwire.sqlite +0 -0
- package/express/src/storage/logs/app.log +0 -0
- package/express/src/storage/logs/fatal.log +0 -0
- package/express/src/types/global.d.ts +32 -26
- package/fastify/.dockerignore +14 -0
- package/fastify/.env +44 -0
- package/fastify/.env.example +42 -0
- package/fastify/.vscode/extensions.json +6 -0
- package/fastify/.vscode/settings.json +37 -0
- package/fastify/LICENSE +21 -0
- package/fastify/README.md +232 -0
- package/fastify/biome.json +99 -0
- package/fastify/drizzle.config.ts +25 -0
- package/fastify/gitignore-stub +31 -0
- package/fastify/package.json +130 -0
- package/fastify/public/.gitkeep +0 -0
- package/fastify/src/config/app.ts +41 -0
- package/fastify/src/config/auth.ts +12 -0
- package/fastify/src/config/cache.ts +16 -0
- package/fastify/src/config/cookie.ts +15 -0
- package/fastify/src/config/cors.ts +12 -0
- package/fastify/src/config/database.ts +48 -0
- package/fastify/src/config/index.ts +83 -0
- package/fastify/src/config/jwt.ts +19 -0
- package/fastify/src/config/logging.ts +13 -0
- package/fastify/src/config/mail.ts +24 -0
- package/fastify/src/config/openapi.ts +36 -0
- package/fastify/src/config/queue.ts +30 -0
- package/fastify/src/config/rateLimit.ts +17 -0
- package/fastify/src/config/realtime.ts +15 -0
- package/fastify/src/config/redis.ts +18 -0
- package/fastify/src/config/security.ts +44 -0
- package/fastify/src/config/session.ts +17 -0
- package/fastify/src/config/storage.ts +22 -0
- package/fastify/src/config/validate.ts +50 -0
- package/fastify/src/database/migrations/sqlite/0000_init.sql +48 -0
- package/fastify/src/database/migrations/sqlite/meta/0000_snapshot.json +324 -0
- package/fastify/src/database/migrations/sqlite/meta/_journal.json +13 -0
- package/fastify/src/database/schema.ts +2 -0
- package/fastify/src/env.ts +76 -0
- package/fastify/src/framework/cache/cache.ts +114 -0
- package/fastify/src/framework/circuit-breaker/cb.ts +88 -0
- package/fastify/src/framework/database/app-schema.ts +37 -0
- package/fastify/src/framework/database/connection.ts +223 -0
- package/fastify/src/framework/database/migrate-hooks.ts +87 -0
- package/fastify/src/framework/database/paginate.ts +226 -0
- package/fastify/src/framework/database/schema.ts +20 -0
- package/fastify/src/framework/database/seed.ts +152 -0
- package/fastify/src/framework/events/dispatcher.ts +61 -0
- package/fastify/src/framework/facade.ts +96 -0
- package/fastify/src/framework/http/app.ts +176 -0
- package/fastify/src/framework/http/cors.ts +13 -0
- package/fastify/src/framework/http/fastify.d.ts +13 -0
- package/fastify/src/framework/http/logger.ts +110 -0
- package/fastify/src/framework/http/metrics.ts +22 -0
- package/fastify/src/framework/http/openapi.ts +48 -0
- package/fastify/src/framework/http/ratelimiter.ts +47 -0
- package/fastify/src/framework/http/security.ts +25 -0
- package/fastify/src/framework/http/static.ts +83 -0
- package/fastify/src/framework/http/validation.ts +49 -0
- package/fastify/src/framework/kernel.ts +43 -0
- package/fastify/src/framework/maker-cli/db/core.mjs +291 -0
- package/fastify/src/framework/maker-cli/db/index.mjs +204 -0
- package/fastify/src/framework/maker-cli/deploy/core.mjs +1046 -0
- package/fastify/src/framework/maker-cli/deploy/index.mjs +91 -0
- package/fastify/src/framework/maker-cli/index.mjs +65 -0
- package/fastify/src/framework/maker-cli/middleware/core.mjs +41 -0
- package/fastify/src/framework/maker-cli/middleware/index.mjs +17 -0
- package/fastify/src/framework/maker-cli/module/core.mjs +1066 -0
- package/fastify/src/framework/maker-cli/module/index.mjs +211 -0
- package/fastify/src/framework/maker-cli/provider/core.mjs +49 -0
- package/fastify/src/framework/maker-cli/provider/index.mjs +12 -0
- package/fastify/src/framework/maker-cli/runtime/build-ui.mjs +16 -0
- package/fastify/src/framework/maker-cli/runtime/core.mjs +560 -0
- package/fastify/src/framework/maker-cli/runtime/index.mjs +120 -0
- package/fastify/src/framework/maker-cli/runtime/redis-commander-patch.cjs +20 -0
- package/fastify/src/framework/maker-cli/stubs/controller/openapi.ts.stub +74 -0
- package/fastify/src/framework/maker-cli/stubs/controller/plain.ts.stub +77 -0
- package/fastify/src/framework/maker-cli/stubs/controller/standalone.ts.stub +51 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +52 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +55 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +51 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +55 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/README.stub +59 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +45 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +45 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +45 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +68 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +68 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +67 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +41 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/README.stub +78 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +120 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +133 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +131 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +11 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +25 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +25 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +16 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +13 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +14 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +7 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +54 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +70 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +25 -0
- package/fastify/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +24 -0
- package/fastify/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
- package/fastify/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
- package/fastify/src/framework/maker-cli/stubs/job/name.ts.stub +25 -0
- package/fastify/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
- package/fastify/src/framework/maker-cli/stubs/middleware/name.ts.stub +15 -0
- package/fastify/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +10 -0
- package/fastify/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +10 -0
- package/fastify/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +10 -0
- package/fastify/src/framework/maker-cli/stubs/notification/controller.ts.stub +79 -0
- package/fastify/src/framework/maker-cli/stubs/notification/job.ts.stub +11 -0
- package/fastify/src/framework/maker-cli/stubs/notification/model.ts.stub +29 -0
- package/fastify/src/framework/maker-cli/stubs/notification/provider.ts.stub +174 -0
- package/fastify/src/framework/maker-cli/stubs/notification/route.api.ts.stub +71 -0
- package/fastify/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +15 -0
- package/fastify/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +26 -0
- package/fastify/src/framework/maker-cli/stubs/notification/schema.ts.stub +26 -0
- package/fastify/src/framework/maker-cli/stubs/provider/module-barrel.ts.stub +19 -0
- package/fastify/src/framework/maker-cli/stubs/provider/module.ts.stub +21 -0
- package/fastify/src/framework/maker-cli/stubs/provider/name.ts.stub +21 -0
- package/fastify/src/framework/maker-cli/stubs/route/api.ts.stub +101 -0
- package/fastify/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -0
- package/fastify/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +120 -0
- package/fastify/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +50 -0
- package/fastify/src/framework/maker-cli/stubs/schedule/name.ts.stub +36 -0
- package/fastify/src/framework/maker-cli/stubs/schema/name.plain.ts.stub +14 -0
- package/fastify/src/framework/maker-cli/stubs/schema/name.ts.stub +33 -0
- package/fastify/src/framework/maker-cli/stubs/seeder/name.ts.stub +18 -0
- package/fastify/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
- package/fastify/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
- package/fastify/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
- package/fastify/src/framework/maker-cli/stubs/test/unit.ts.stub +22 -0
- package/fastify/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
- package/fastify/src/framework/maker-cli/utils/env-config.mjs +44 -0
- package/fastify/src/framework/maker-cli/utils/env-db.mjs +61 -0
- package/fastify/src/framework/maker-cli/utils/file-ops.mjs +30 -0
- package/fastify/src/framework/maker-cli/utils/flags.mjs +14 -0
- package/fastify/src/framework/maker-cli/utils/help.mjs +379 -0
- package/fastify/src/framework/maker-cli/utils/naming.mjs +119 -0
- package/fastify/src/framework/maker-cli/utils/process.mjs +52 -0
- package/fastify/src/framework/maker-cli/utils/project-commands.mjs +201 -0
- package/fastify/src/framework/modules/discover.ts +57 -0
- package/fastify/src/framework/modules/routes.ts +31 -0
- package/fastify/src/framework/notification/channels/broadcast.ts +15 -0
- package/fastify/src/framework/notification/channels/mail.ts +42 -0
- package/fastify/src/framework/notification/contracts.ts +29 -0
- package/fastify/src/framework/notification/index.ts +59 -0
- package/fastify/src/framework/notification/registry.ts +15 -0
- package/fastify/src/framework/providers/adapter.ts +149 -0
- package/fastify/src/framework/providers/contracts.ts +126 -0
- package/fastify/src/framework/providers/loader.ts +105 -0
- package/fastify/src/framework/providers/registry.ts +88 -0
- package/fastify/src/framework/queue/clear.ts +22 -0
- package/fastify/src/framework/queue/queue.ts +401 -0
- package/fastify/src/framework/queue/ui.ts +194 -0
- package/fastify/src/framework/queue/worker.ts +61 -0
- package/fastify/src/framework/realtime/broadcast.ts +70 -0
- package/fastify/src/framework/realtime/index.ts +2 -0
- package/fastify/src/framework/realtime/socket-cookie.ts +72 -0
- package/fastify/src/framework/realtime/socket.ts +149 -0
- package/fastify/src/framework/realtime/types.ts +6 -0
- package/fastify/src/framework/realtime/ui.ts +16 -0
- package/fastify/src/framework/redis/client.ts +148 -0
- package/fastify/src/framework/runtime/runtime.ts +23 -0
- package/fastify/src/framework/runtime/server-adapter.ts +33 -0
- package/fastify/src/framework/scheduler/lock.ts +128 -0
- package/fastify/src/framework/scheduler/run.ts +48 -0
- package/fastify/src/framework/scheduler/scheduler.ts +155 -0
- package/fastify/src/framework/server.ts +162 -0
- package/fastify/src/framework/session/session.ts +160 -0
- package/fastify/src/framework/storage/storage.ts +744 -0
- package/fastify/src/framework/support/cookie.ts +71 -0
- package/fastify/src/framework/support/jwt.ts +33 -0
- package/fastify/src/framework/support/lifecycle.ts +35 -0
- package/fastify/src/framework/support/logger.ts +148 -0
- package/fastify/src/framework/support/mail.ts +56 -0
- package/fastify/src/framework/support/password.ts +23 -0
- package/fastify/src/framework/support/status-codes.ts +75 -0
- package/fastify/src/framework/support/upload.ts +181 -0
- package/fastify/src/framework/support/url.ts +41 -0
- package/fastify/src/middlewares/auth-middleware.ts +134 -0
- package/fastify/src/middlewares/role-middleware.ts +19 -0
- package/fastify/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
- package/fastify/src/modules/auth/__tests__/login.test.ts +171 -0
- package/fastify/src/modules/auth/__tests__/logout.test.ts +60 -0
- package/fastify/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
- package/fastify/src/modules/auth/__tests__/me.test.ts +91 -0
- package/fastify/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
- package/fastify/src/modules/auth/__tests__/register.test.ts +291 -0
- package/fastify/src/modules/auth/__tests__/resetpass.test.ts +206 -0
- package/fastify/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
- package/fastify/src/modules/auth/controllers/auth.ts +252 -0
- package/fastify/src/modules/auth/controllers/role.ts +35 -0
- package/fastify/src/modules/auth/database/models/role.ts +14 -0
- package/fastify/src/modules/auth/database/models/user.ts +46 -0
- package/fastify/src/modules/auth/database/seeders/role.ts +18 -0
- package/fastify/src/modules/auth/database/seeders/user.ts +35 -0
- package/fastify/src/modules/auth/facade.ts +12 -0
- package/fastify/src/modules/auth/helpers/auth.ts +115 -0
- package/fastify/src/modules/auth/jobs/forgetpass.ts +20 -0
- package/fastify/src/modules/auth/jobs/registeruser.ts +23 -0
- package/fastify/src/modules/auth/jobs/verifyemail.ts +18 -0
- package/fastify/src/modules/auth/routes/index.ts +185 -0
- package/fastify/src/modules/auth/routes/role.ts +56 -0
- package/fastify/src/modules/auth/schemas/auth.ts +116 -0
- package/fastify/src/modules/auth/services/forgetpassword.ts +66 -0
- package/fastify/src/modules/auth/services/login.ts +63 -0
- package/fastify/src/modules/auth/services/logout.ts +25 -0
- package/fastify/src/modules/auth/services/logoutalldevices.ts +19 -0
- package/fastify/src/modules/auth/services/me.ts +31 -0
- package/fastify/src/modules/auth/services/refreshtoken.ts +110 -0
- package/fastify/src/modules/auth/services/register.ts +141 -0
- package/fastify/src/modules/auth/services/resetpassword.ts +69 -0
- package/fastify/src/modules/auth/services/verifyemail.ts +77 -0
- package/fastify/src/modules/auth/types/auth.ts +31 -0
- package/fastify/src/providers/README.md +107 -0
- package/fastify/src/providers/notification.ts +4 -0
- package/fastify/src/resources/index.html +17 -0
- package/fastify/src/resources/src/App.vue +5 -0
- package/fastify/src/resources/src/assets/css/styles.css +14761 -0
- package/fastify/src/resources/src/assets/css/styles.css.map +1 -0
- package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin-ext.woff2 +0 -0
- package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin.woff2 +0 -0
- package/fastify/src/resources/src/assets/images/favicon/favicon.ico +0 -0
- package/fastify/src/resources/src/assets/images/logo.png +0 -0
- package/fastify/src/resources/src/assets/scss/custom.css +217 -0
- package/fastify/src/resources/src/assets/scss/custom.css.map +1 -0
- package/fastify/src/resources/src/assets/scss/custom.scss +1346 -0
- package/fastify/src/resources/src/components/Button.vue +32 -0
- package/fastify/src/resources/src/components/Checkbox.vue +32 -0
- package/fastify/src/resources/src/components/Datepicker.vue +106 -0
- package/fastify/src/resources/src/components/FeatureButton.vue +53 -0
- package/fastify/src/resources/src/components/FloatButton.vue +33 -0
- package/fastify/src/resources/src/components/Href.vue +32 -0
- package/fastify/src/resources/src/components/Input.vue +243 -0
- package/fastify/src/resources/src/components/InputGroup.vue +147 -0
- package/fastify/src/resources/src/components/InputPasswordToggle.vue +229 -0
- package/fastify/src/resources/src/components/Modal.vue +71 -0
- package/fastify/src/resources/src/components/Pagebar.vue +26 -0
- package/fastify/src/resources/src/components/Refresh.vue +19 -0
- package/fastify/src/resources/src/components/Select.vue +384 -0
- package/fastify/src/resources/src/components/Spinner.vue +42 -0
- package/fastify/src/resources/src/components/Switch.vue +60 -0
- package/fastify/src/resources/src/components/TextArea.vue +117 -0
- package/fastify/src/resources/src/components/Toast.vue +56 -0
- package/fastify/src/resources/src/components/datatable/DataTableSkeleton.vue +98 -0
- package/fastify/src/resources/src/components/datatable/Pagination.vue +147 -0
- package/fastify/src/resources/src/components/datatable/SelectOpption.vue +64 -0
- package/fastify/src/resources/src/components/datatable/index.vue +199 -0
- package/fastify/src/resources/src/composables/useAuth.ts +52 -0
- package/fastify/src/resources/src/helpers/assets.ts +51 -0
- package/fastify/src/resources/src/helpers/nformatter.ts +10 -0
- package/fastify/src/resources/src/helpers/utils.ts +75 -0
- package/fastify/src/resources/src/layouts/AuthLayout.vue +20 -0
- package/fastify/src/resources/src/layouts/Layout/Footer.vue +23 -0
- package/fastify/src/resources/src/layouts/Layout/Header.vue +124 -0
- package/fastify/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
- package/fastify/src/resources/src/layouts/Layout/index.vue +87 -0
- package/fastify/src/resources/src/main.ts +28 -0
- package/fastify/src/resources/src/pages/auth/forgetPassword.vue +76 -0
- package/fastify/src/resources/src/pages/auth/login.vue +96 -0
- package/fastify/src/resources/src/pages/auth/register.vue +129 -0
- package/fastify/src/resources/src/pages/auth/resetPassword.vue +110 -0
- package/fastify/src/resources/src/pages/auth/verifyEmail.vue +64 -0
- package/fastify/src/resources/src/pages/dashboard/index.vue +66 -0
- package/fastify/src/resources/src/plugins/axios.ts +31 -0
- package/fastify/src/resources/src/plugins/browserDetect.ts +55 -0
- package/fastify/src/resources/src/plugins/dialog.ts +216 -0
- package/fastify/src/resources/src/plugins/gum.ts +683 -0
- package/fastify/src/resources/src/plugins/pulse.ts +157 -0
- package/fastify/src/resources/src/plugins/routeProgress.ts +87 -0
- package/fastify/src/resources/src/router/index.ts +85 -0
- package/fastify/src/resources/src/stores/admin-ui.ts +148 -0
- package/fastify/src/resources/src/stores/auth.ts +187 -0
- package/fastify/src/resources/tsconfig.json +19 -0
- package/fastify/src/resources/vite.config.ts +64 -0
- package/fastify/src/storage/app/private/.gitkeep +0 -0
- package/fastify/src/storage/app/public/.gitkeep +0 -0
- package/fastify/src/storage/app/tmp/generated/.gitkeep +0 -0
- package/fastify/src/storage/database/nexwire.sqlite +0 -0
- package/fastify/src/storage/logs/.gitkeep +0 -0
- package/fastify/src/storage/logs/app.log +0 -0
- package/fastify/src/storage/logs/fatal.log +0 -0
- package/fastify/src/storage/tmp/.gitkeep +0 -0
- package/fastify/src/storage/trash/modules/.gitkeep +0 -0
- package/fastify/src/types/global.d.ts +32 -0
- package/fastify/tsconfig.json +19 -0
- package/fastify/vitest.config.ts +38 -0
- package/hono/.env +1 -1
- package/hono/package.json +1 -1
- package/hono/src/config/database.ts +48 -29
- package/hono/src/config/index.ts +83 -83
- package/hono/src/config/openapi.ts +6 -24
- package/hono/src/config/queue.ts +30 -30
- package/hono/src/config/validate.ts +50 -40
- package/hono/src/database/migrations/sqlite/0000_init.sql +47 -47
- package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
- package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/hono/src/framework/cache/cache.ts +40 -8
- package/hono/src/framework/circuit-breaker/cb.ts +88 -60
- package/hono/src/framework/database/connection.ts +52 -27
- package/hono/src/framework/events/dispatcher.ts +61 -49
- package/hono/src/framework/facade.ts +39 -1
- package/hono/src/framework/http/logger.ts +86 -81
- package/hono/src/framework/http/openapi.ts +7 -9
- package/hono/src/framework/maker-cli/runtime/core.mjs +1 -2
- package/hono/src/framework/notification/channels/broadcast.ts +15 -0
- package/hono/src/framework/notification/channels/mail.ts +42 -0
- package/hono/src/framework/notification/contracts.ts +29 -0
- package/hono/src/framework/notification/index.ts +19 -94
- package/hono/src/framework/notification/registry.ts +15 -0
- package/hono/src/framework/providers/adapter.ts +27 -17
- package/hono/src/framework/queue/queue.ts +43 -44
- package/hono/src/framework/realtime/broadcast.ts +29 -5
- package/hono/src/framework/realtime/socket.ts +217 -196
- package/hono/src/framework/redis/client.ts +148 -151
- package/hono/src/framework/scheduler/lock.ts +128 -116
- package/hono/src/framework/server.ts +27 -29
- package/hono/src/framework/session/session.ts +62 -14
- package/hono/src/framework/storage/storage.ts +744 -785
- package/hono/src/framework/support/mail.ts +56 -48
- package/hono/src/middlewares/auth-middleware.ts +1 -2
- package/hono/src/middlewares/role-middleware.ts +3 -13
- package/hono/src/modules/auth/routes/index.ts +1 -2
- package/hono/src/providers/notification.ts +4 -0
- package/hono/src/resources/src/components/Switch.vue +60 -65
- package/hono/src/resources/src/layouts/Layout/Header.vue +124 -105
- package/hono/src/resources/src/pages/auth/forgetPassword.vue +23 -29
- package/hono/src/resources/src/pages/auth/login.vue +26 -29
- package/hono/src/resources/src/pages/auth/register.vue +36 -40
- package/hono/src/resources/src/pages/auth/resetPassword.vue +29 -37
- package/hono/src/resources/src/pages/auth/verifyEmail.vue +15 -19
- package/hono/src/resources/src/plugins/gum.ts +519 -203
- package/hono/src/resources/src/plugins/pulse.ts +157 -147
- package/hono/src/resources/src/stores/auth.ts +9 -6
- package/hono/src/resources/vite.config.ts +54 -43
- package/hono/src/storage/database/nexwire.sqlite +0 -0
- package/hono/src/storage/logs/app.log +0 -0
- package/hono/src/storage/logs/fatal.log +0 -0
- package/hono/src/types/global.d.ts +32 -26
- package/package.json +4 -3
- package/src/index.js +143 -15
|
@@ -4,13 +4,38 @@ import type { App } from "vue";
|
|
|
4
4
|
import { computed, nextTick, reactive, ref, toRaw } from "vue";
|
|
5
5
|
import { type LocationQueryRaw, useRoute, useRouter } from "vue-router";
|
|
6
6
|
|
|
7
|
+
/* -------------------------------------------------------------------------- */
|
|
8
|
+
/* Types */
|
|
9
|
+
/* -------------------------------------------------------------------------- */
|
|
10
|
+
|
|
7
11
|
export type GumPluginOptions = {
|
|
8
12
|
rememberPrefix?: string;
|
|
9
13
|
recentlySuccessfulDuration?: number;
|
|
10
14
|
};
|
|
11
15
|
|
|
12
16
|
type GumMethod = "get" | "post" | "put" | "patch" | "delete";
|
|
13
|
-
type
|
|
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 & {
|
|
14
39
|
method?: GumMethod;
|
|
15
40
|
data?: Record<string, unknown> | FormData;
|
|
16
41
|
query?: Record<string, unknown>;
|
|
@@ -19,67 +44,44 @@ type GumVisitOptions = {
|
|
|
19
44
|
preserveState?: boolean;
|
|
20
45
|
preserveScroll?: boolean;
|
|
21
46
|
skipFetch?: boolean;
|
|
22
|
-
|
|
23
|
-
onStart?: () => void | Promise<void>;
|
|
24
|
-
onProgress?: (event: AxiosProgressEvent) => void;
|
|
25
|
-
onSuccess?: (response: AxiosResponse) => void | Promise<void>;
|
|
26
|
-
onError?: (errors: NormalizedErrors, error: AxiosError) => void | Promise<void>;
|
|
27
|
-
onFinish?: () => void | Promise<void>;
|
|
47
|
+
fetcher?: Fetcher;
|
|
28
48
|
};
|
|
29
49
|
|
|
30
|
-
|
|
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
|
+
|
|
31
66
|
type FormErrors<T> = Partial<Record<keyof T | string, string>>;
|
|
32
67
|
type NormalizedErrors = Record<string, string[] | string>;
|
|
33
|
-
type FormSubmitOptions = {
|
|
34
|
-
onBefore?: () => boolean | undefined | Promise<boolean | undefined>;
|
|
35
|
-
onStart?: () => void | Promise<void>;
|
|
36
|
-
onProgress?: (event: AxiosProgressEvent) => void | Promise<void>;
|
|
37
|
-
onSuccess?: (response: AxiosResponse) => void | Promise<void>;
|
|
38
|
-
onError?: (errors: NormalizedErrors, error: AxiosError) => void | Promise<void>;
|
|
39
|
-
onFinish?: () => void | Promise<void>;
|
|
40
|
-
};
|
|
41
68
|
|
|
42
69
|
type ValidationErrorPayload = {
|
|
43
70
|
errors?: Record<string, string[] | string>;
|
|
44
|
-
error?: {
|
|
45
|
-
issues?: Array<{
|
|
46
|
-
path?: Array<string | number>;
|
|
47
|
-
message?: string;
|
|
48
|
-
}>;
|
|
49
|
-
};
|
|
71
|
+
error?: { issues?: Array<{ path?: Array<string | number>; message?: string }> };
|
|
50
72
|
};
|
|
51
73
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const issues = payload?.error?.issues;
|
|
56
|
-
if (Array.isArray(issues)) {
|
|
57
|
-
issues.forEach((issue) => {
|
|
58
|
-
const key = issue.path?.map(String).join(".");
|
|
59
|
-
if (key) normalized[key] = String(issue.message || "Invalid value");
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return normalized;
|
|
64
|
-
}
|
|
74
|
+
/* -------------------------------------------------------------------------- */
|
|
75
|
+
/* Config + registry */
|
|
76
|
+
/* -------------------------------------------------------------------------- */
|
|
65
77
|
|
|
66
78
|
const config: Required<GumPluginOptions> = {
|
|
67
79
|
rememberPrefix: "gum",
|
|
68
80
|
recentlySuccessfulDuration: 2000
|
|
69
81
|
};
|
|
70
82
|
|
|
71
|
-
/**
|
|
72
|
-
* Why: preserveState=false should clear only state for one page.
|
|
73
|
-
* When: called by remember registration and GET visits.
|
|
74
|
-
* Where: used in this Gum plugin storage registry.
|
|
75
|
-
*/
|
|
76
83
|
const routeRememberKeys = new Map<string, Set<string>>();
|
|
77
84
|
|
|
78
|
-
/**
|
|
79
|
-
* Why: route keys must be consistent across query changes.
|
|
80
|
-
* When: before read/write in remember key registry.
|
|
81
|
-
* Where: internal helper for Gum path-based state tracking.
|
|
82
|
-
*/
|
|
83
85
|
function normalizePath(path: string) {
|
|
84
86
|
const clean = (path || "/").split("?")[0];
|
|
85
87
|
return clean || "/";
|
|
@@ -87,15 +89,10 @@ function normalizePath(path: string) {
|
|
|
87
89
|
|
|
88
90
|
function registerRememberKey(path: string, key: string) {
|
|
89
91
|
const routePath = normalizePath(path);
|
|
90
|
-
if (!routeRememberKeys.has(routePath)) routeRememberKeys.set(routePath, new Set
|
|
91
|
-
routeRememberKeys.get(routePath)
|
|
92
|
+
if (!routeRememberKeys.has(routePath)) routeRememberKeys.set(routePath, new Set());
|
|
93
|
+
routeRememberKeys.get(routePath)!.add(key);
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
/**
|
|
95
|
-
* Why: emulate Inertia preserveState=false behavior.
|
|
96
|
-
* When: a GET visit requests state reset.
|
|
97
|
-
* Where: removes entries from localStorage using Gum prefix.
|
|
98
|
-
*/
|
|
99
96
|
export function clearRememberForPath(path: string) {
|
|
100
97
|
const routePath = normalizePath(path);
|
|
101
98
|
const keys = routeRememberKeys.get(routePath);
|
|
@@ -103,38 +100,155 @@ export function clearRememberForPath(path: string) {
|
|
|
103
100
|
keys.forEach((key) => localStorage.removeItem(`${config.rememberPrefix}:${key}`));
|
|
104
101
|
}
|
|
105
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
|
+
|
|
106
184
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
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 }`.
|
|
110
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
|
+
|
|
111
218
|
export function useGumRemember<T extends object>(key: string, initial: T) {
|
|
112
219
|
const route = useRoute();
|
|
113
220
|
registerRememberKey(route.path, key);
|
|
114
|
-
|
|
115
221
|
return useStorage<T>(`${config.rememberPrefix}:${key}`, initial, localStorage, {
|
|
116
222
|
mergeDefaults: true
|
|
117
223
|
});
|
|
118
224
|
}
|
|
119
225
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
export function useGum() {
|
|
226
|
+
/* -------------------------------------------------------------------------- */
|
|
227
|
+
/* Visit API */
|
|
228
|
+
/* -------------------------------------------------------------------------- */
|
|
229
|
+
|
|
230
|
+
function buildVisitApi() {
|
|
126
231
|
const router = useRouter();
|
|
127
232
|
const route = useRoute();
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
+
}) {
|
|
136
249
|
const {
|
|
137
|
-
method
|
|
250
|
+
method,
|
|
251
|
+
url,
|
|
138
252
|
data,
|
|
139
253
|
query,
|
|
140
254
|
routePath,
|
|
@@ -142,193 +256,354 @@ export function useGum() {
|
|
|
142
256
|
preserveState = method !== "get",
|
|
143
257
|
preserveScroll = false,
|
|
144
258
|
skipFetch = false,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
onSuccess,
|
|
149
|
-
onError,
|
|
150
|
-
onFinish
|
|
151
|
-
} = options;
|
|
259
|
+
fetcher,
|
|
260
|
+
hooks = {}
|
|
261
|
+
} = params;
|
|
152
262
|
|
|
153
|
-
const
|
|
154
|
-
if (allow === false) return;
|
|
263
|
+
const { onBefore, onStart, onProgress, onSuccess, onError, onFinish } = hooks;
|
|
155
264
|
|
|
156
|
-
|
|
157
|
-
processing.value = true;
|
|
158
|
-
await onStart?.();
|
|
265
|
+
if (!(await runBefore(onBefore))) return;
|
|
159
266
|
|
|
160
|
-
|
|
267
|
+
const savedY = readScrollY();
|
|
268
|
+
trackStart();
|
|
161
269
|
|
|
270
|
+
let response: AxiosResponse | undefined;
|
|
162
271
|
try {
|
|
272
|
+
// onStart is inside try so a throw still unwinds processing.
|
|
273
|
+
await onStart?.();
|
|
274
|
+
|
|
163
275
|
if (!skipFetch) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
url,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
}
|
|
171
286
|
}
|
|
172
287
|
|
|
173
288
|
if (method === "get") {
|
|
174
|
-
const
|
|
175
|
-
if (!preserveState) clearRememberForPath(
|
|
176
|
-
|
|
177
|
-
const payload = {
|
|
178
|
-
path: targetRoutePath,
|
|
179
|
-
query: (query ?? route.query) as LocationQueryRaw
|
|
180
|
-
};
|
|
181
|
-
|
|
289
|
+
const targetPath = routePath ?? route.path;
|
|
290
|
+
if (!preserveState) clearRememberForPath(targetPath);
|
|
291
|
+
const payload = { path: targetPath, query: (query ?? route.query) as LocationQueryRaw };
|
|
182
292
|
if (replace) await router.replace(payload);
|
|
183
293
|
else await router.push(payload);
|
|
184
294
|
}
|
|
185
295
|
|
|
186
296
|
await onSuccess?.(response!);
|
|
187
297
|
return response;
|
|
188
|
-
} catch (error) {
|
|
189
|
-
const err = error as AxiosError<ValidationErrorPayload>;
|
|
190
|
-
const handled = !!options.onError;
|
|
191
|
-
await onError?.(normalizeValidationErrors(err.response?.data), error as AxiosError);
|
|
192
|
-
if (!handled) throw error;
|
|
193
298
|
} finally {
|
|
194
|
-
|
|
299
|
+
trackEnd();
|
|
195
300
|
await onFinish?.();
|
|
196
|
-
|
|
197
|
-
await nextTick();
|
|
198
|
-
requestAnimationFrame(() => window.scrollTo({ top: scrollY }));
|
|
199
|
-
}
|
|
301
|
+
await restoreScroll(preserveScroll, savedY);
|
|
200
302
|
}
|
|
201
303
|
}
|
|
202
304
|
|
|
203
|
-
|
|
204
|
-
|
|
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,
|
|
205
339
|
visit,
|
|
206
|
-
get: (url: string,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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({
|
|
220
412
|
method: "get",
|
|
413
|
+
url: route.path,
|
|
414
|
+
query: route.query as Record<string, unknown>,
|
|
221
415
|
replace: true,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
+
})
|
|
225
500
|
};
|
|
501
|
+
|
|
502
|
+
return api;
|
|
226
503
|
}
|
|
227
504
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
*/
|
|
233
|
-
|
|
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 ----------------------------------- */
|
|
234
513
|
const initial = structuredClone(defaults);
|
|
235
514
|
const data = reactive(structuredClone(defaults)) as T;
|
|
236
515
|
const errors = reactive<Record<string, string>>({});
|
|
237
516
|
const progress = ref<number | null>(null);
|
|
238
|
-
const
|
|
517
|
+
const formProcessing = ref(false);
|
|
239
518
|
const wasSuccessful = ref(false);
|
|
240
519
|
const recentlySuccessful = ref(false);
|
|
241
520
|
const isDirty = computed(() => JSON.stringify(toRaw(data)) !== JSON.stringify(initial));
|
|
242
521
|
|
|
243
|
-
/**
|
|
244
|
-
* Why: allow manual error assignment for custom validations.
|
|
245
|
-
* When: setting one field error outside server response.
|
|
246
|
-
* Where: useGumForm consumer code.
|
|
247
|
-
*/
|
|
248
522
|
function setError(field: keyof T | string, message: string) {
|
|
249
523
|
errors[String(field)] = message;
|
|
250
524
|
}
|
|
251
525
|
|
|
252
|
-
/**
|
|
253
|
-
* Why: keep error state in sync with user actions/submits.
|
|
254
|
-
* When: before submit or after field corrections.
|
|
255
|
-
* Where: useGumForm internal + consumer calls.
|
|
256
|
-
*/
|
|
257
526
|
function clearErrors(...fields: (keyof T | string)[]) {
|
|
258
527
|
if (!fields.length) {
|
|
259
|
-
Object.keys(errors).forEach((
|
|
528
|
+
Object.keys(errors).forEach((k) => delete errors[k]);
|
|
260
529
|
return;
|
|
261
530
|
}
|
|
262
|
-
fields.forEach((
|
|
531
|
+
fields.forEach((f) => delete errors[String(f)]);
|
|
263
532
|
}
|
|
264
533
|
|
|
265
|
-
/**
|
|
266
|
-
* Why: restore form values to initial defaults safely.
|
|
267
|
-
* When: cancel edit or after successful submission.
|
|
268
|
-
* Where: useGumForm consumer actions.
|
|
269
|
-
*/
|
|
270
534
|
function reset(...fields: (keyof T)[]) {
|
|
271
535
|
if (!fields.length) {
|
|
272
536
|
Object.assign(data, structuredClone(initial));
|
|
273
537
|
clearErrors();
|
|
274
538
|
return;
|
|
275
539
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
delete errors[String(field)];
|
|
540
|
+
fields.forEach((f) => {
|
|
541
|
+
(data as any)[f] = structuredClone(initial[f]);
|
|
542
|
+
delete errors[String(f)];
|
|
280
543
|
});
|
|
281
544
|
}
|
|
282
545
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const { onBefore, onStart, onProgress, onSuccess, onError, onFinish } =
|
|
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");
|
|
290
558
|
|
|
291
|
-
|
|
292
|
-
|
|
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));
|
|
293
568
|
|
|
294
569
|
wasSuccessful.value = false;
|
|
295
570
|
clearErrors();
|
|
296
|
-
|
|
297
|
-
|
|
571
|
+
formProcessing.value = true;
|
|
572
|
+
trackStart();
|
|
298
573
|
|
|
299
574
|
try {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
+
}
|
|
310
598
|
|
|
311
599
|
wasSuccessful.value = true;
|
|
312
|
-
recentlySuccessful
|
|
313
|
-
|
|
314
|
-
recentlySuccessful.value = false;
|
|
315
|
-
}, config.recentlySuccessfulDuration);
|
|
316
|
-
await onSuccess?.(response!);
|
|
600
|
+
startRecentTimer({ recentlySuccessful });
|
|
601
|
+
await onSuccess?.(response);
|
|
317
602
|
return response;
|
|
318
|
-
} catch (error) {
|
|
319
|
-
const err = error as AxiosError<ValidationErrorPayload>;
|
|
320
|
-
const normalizedErrors = normalizeValidationErrors(err.response?.data);
|
|
321
|
-
|
|
322
|
-
Object.entries(normalizedErrors).forEach(([key, value]) => {
|
|
323
|
-
errors[String(key)] = Array.isArray(value) ? value[0] : value;
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
const handled = !!options.onError;
|
|
327
|
-
await onError?.(normalizedErrors, error as AxiosError);
|
|
328
|
-
if (!handled) throw error;
|
|
329
603
|
} finally {
|
|
330
|
-
|
|
604
|
+
formProcessing.value = false;
|
|
331
605
|
progress.value = null;
|
|
606
|
+
trackEnd();
|
|
332
607
|
await onFinish?.();
|
|
333
608
|
}
|
|
334
609
|
}
|
|
@@ -337,26 +612,67 @@ export function useGumForm<T extends Record<string, unknown>>(defaults: T) {
|
|
|
337
612
|
data,
|
|
338
613
|
errors: errors as FormErrors<T>,
|
|
339
614
|
progress,
|
|
340
|
-
processing,
|
|
615
|
+
processing: formProcessing,
|
|
341
616
|
wasSuccessful,
|
|
342
617
|
recentlySuccessful,
|
|
343
618
|
isDirty,
|
|
344
619
|
setError,
|
|
345
620
|
clearErrors,
|
|
346
621
|
reset,
|
|
347
|
-
submit,
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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)
|
|
352
634
|
};
|
|
353
635
|
}
|
|
354
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
|
+
|
|
355
663
|
/**
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
* Where: src/resources/src/main.ts plugin registration.
|
|
664
|
+
* @deprecated Use `useGum(defaults).form` instead.
|
|
665
|
+
* Kept so existing pages don't break. Will be removed in a future major.
|
|
359
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
|
+
|
|
360
676
|
export const GumPlugin = {
|
|
361
677
|
install(_app: App, options: GumPluginOptions = {}) {
|
|
362
678
|
if (options.rememberPrefix) config.rememberPrefix = options.rememberPrefix;
|