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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { databaseConfig, detectDialectFrom } from "@/config/index.js";
|
|
4
|
+
import { loadAppSchema } from "@/framework/database/app-schema.js";
|
|
5
|
+
|
|
6
|
+
export type Dialect = "sqlite" | "mysql" | "postgresql";
|
|
7
|
+
|
|
8
|
+
let databaseInstance: any;
|
|
9
|
+
let pool: any;
|
|
10
|
+
let dialect: Dialect;
|
|
11
|
+
|
|
12
|
+
async function optionalImport<T = any>(name: string): Promise<T> {
|
|
13
|
+
return import(name) as Promise<T>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Why: Resolves the active DB driver from DATABASE_URL.
|
|
18
|
+
* When: Called during bootstrap and dialect-specific branching.
|
|
19
|
+
* Where: Used by init/close and schema generation helpers.
|
|
20
|
+
* How: Checks DATABASE_URL prefix and maps to sqlite/mysql/postgresql.
|
|
21
|
+
*/
|
|
22
|
+
export function detectDialect(): Dialect {
|
|
23
|
+
return detectDialectFrom(databaseConfig.url);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Why: Creates a singleton Drizzle connection for the current dialect.
|
|
28
|
+
* When: Called once at app/worker/scheduler startup.
|
|
29
|
+
* Where: Framework bootstrap entry points.
|
|
30
|
+
* How: Builds dialect-specific pool/client and stores it in module state.
|
|
31
|
+
*/
|
|
32
|
+
export async function initDatabase() {
|
|
33
|
+
if (databaseInstance) return databaseInstance;
|
|
34
|
+
|
|
35
|
+
dialect = detectDialect();
|
|
36
|
+
const schema = await loadAppSchema();
|
|
37
|
+
|
|
38
|
+
// --- SQLite (libsql) ---
|
|
39
|
+
if (dialect === "sqlite") {
|
|
40
|
+
let drizzleSqlite: any;
|
|
41
|
+
let createClient: any;
|
|
42
|
+
try {
|
|
43
|
+
[{ drizzle: drizzleSqlite }, { createClient }] = await Promise.all([
|
|
44
|
+
optionalImport("drizzle-orm/libsql"),
|
|
45
|
+
optionalImport("@libsql/client")
|
|
46
|
+
]);
|
|
47
|
+
} catch {
|
|
48
|
+
throw new Error("Missing sqlite dependencies. Install with: bun add drizzle-orm @libsql/client");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// databaseConfig.url is guaranteed to be "file:..."
|
|
52
|
+
const url = databaseConfig.url;
|
|
53
|
+
|
|
54
|
+
// Ensure the directory exists (skip for :memory: or absolute URLs)
|
|
55
|
+
if (!url.includes(":memory:")) {
|
|
56
|
+
const rawPath = url.replace(/^file:/i, "");
|
|
57
|
+
if (!rawPath.startsWith("//")) {
|
|
58
|
+
const file = path.resolve(process.cwd(), rawPath);
|
|
59
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pool = createClient({ url });
|
|
64
|
+
databaseInstance = drizzleSqlite(pool, { schema });
|
|
65
|
+
return databaseInstance;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- MySQL ---
|
|
69
|
+
if (dialect === "mysql") {
|
|
70
|
+
let drizzleMysql: any;
|
|
71
|
+
let mysql: any;
|
|
72
|
+
try {
|
|
73
|
+
[{ drizzle: drizzleMysql }, mysql] = await Promise.all([optionalImport("drizzle-orm/mysql2"), optionalImport("mysql2/promise")]);
|
|
74
|
+
} catch {
|
|
75
|
+
throw new Error("Missing mysql dependencies. Install with: bun add drizzle-orm mysql2");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pool = mysql.createPool({
|
|
79
|
+
uri: databaseConfig.url,
|
|
80
|
+
connectionLimit: databaseConfig.pool.max,
|
|
81
|
+
connectTimeout: databaseConfig.pool.connectTimeout,
|
|
82
|
+
enableKeepAlive: databaseConfig.pool.keepAlive,
|
|
83
|
+
...(databaseConfig.ssl ? { ssl: {} } : {})
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
databaseInstance = drizzleMysql(pool, { schema, mode: "default" });
|
|
87
|
+
return databaseInstance;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// --- PostgreSQL ---
|
|
91
|
+
let drizzlePg: any;
|
|
92
|
+
let postgres: any;
|
|
93
|
+
try {
|
|
94
|
+
[{ drizzle: drizzlePg }, { default: postgres }] = await Promise.all([
|
|
95
|
+
optionalImport("drizzle-orm/postgres-js"),
|
|
96
|
+
optionalImport("postgres")
|
|
97
|
+
]);
|
|
98
|
+
} catch {
|
|
99
|
+
throw new Error("Missing postgres dependencies. Install with: bun add drizzle-orm postgres");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pool = postgres(databaseConfig.url, {
|
|
103
|
+
max: databaseConfig.pool.max,
|
|
104
|
+
idle_timeout: Math.floor(databaseConfig.pool.idleTimeout / 1000),
|
|
105
|
+
connect_timeout: Math.floor(databaseConfig.pool.connectTimeout / 1000),
|
|
106
|
+
prepare: databaseConfig.prepare,
|
|
107
|
+
...(databaseConfig.ssl ? { ssl: "require" as const } : {})
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
databaseInstance = drizzlePg(pool, { schema });
|
|
111
|
+
return databaseInstance;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Why: Returns initialized Drizzle instance for queries.
|
|
116
|
+
* When: Used by app code after bootstrap.
|
|
117
|
+
* Where: Facade `db` proxy and direct internal consumers.
|
|
118
|
+
* How: Throws if initDatabase has not completed.
|
|
119
|
+
*/
|
|
120
|
+
export function database() {
|
|
121
|
+
if (!databaseInstance) throw new Error("Database is not initialized");
|
|
122
|
+
return databaseInstance;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Why: Exposes low-level pool/client for advanced operations.
|
|
127
|
+
* When: Needed by internals that require native pool behavior.
|
|
128
|
+
* Where: Scheduler lock and infrastructure helpers.
|
|
129
|
+
* How: Returns shared pool and guards against uninitialized access.
|
|
130
|
+
*/
|
|
131
|
+
export function databasePool() {
|
|
132
|
+
if (!pool) throw new Error("Database pool is not initialized");
|
|
133
|
+
return pool;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Why: Gracefully closes DB resources during shutdown/reset flows.
|
|
138
|
+
* When: Server stop, worker stop, migration/reset commands.
|
|
139
|
+
* Where: Runtime lifecycle scripts.
|
|
140
|
+
* How: Detects dialect, closes pool, and clears singleton state.
|
|
141
|
+
*/
|
|
142
|
+
export async function closeDatabase() {
|
|
143
|
+
if (!pool) return;
|
|
144
|
+
|
|
145
|
+
const activeDialect = databaseDialect();
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
if (activeDialect === "sqlite") {
|
|
149
|
+
await pool.close?.();
|
|
150
|
+
} else if (activeDialect === "mysql") {
|
|
151
|
+
await pool.end();
|
|
152
|
+
} else {
|
|
153
|
+
await pool.end();
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error("Error closing database pool:", error);
|
|
157
|
+
} finally {
|
|
158
|
+
pool = undefined;
|
|
159
|
+
databaseInstance = undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Why: Returns active dialect for conditional SQL/runtime behavior.
|
|
165
|
+
* When: Dialect-specific features are required.
|
|
166
|
+
* Where: Locking, schema, and shutdown helpers.
|
|
167
|
+
* How: Uses initialized dialect or falls back to detection.
|
|
168
|
+
*/
|
|
169
|
+
export function databaseDialect(): Dialect {
|
|
170
|
+
return dialect || detectDialect();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Why: Normalizes raw query results into a consistent { rows } shape.
|
|
175
|
+
* When: Drivers differ in what db.execute() returns:
|
|
176
|
+
* - postgres-js / libsql return a plain array of rows
|
|
177
|
+
* - mysql2 returns a [rows, fields] tuple
|
|
178
|
+
* - pg node clients already wrap rows in { rows }
|
|
179
|
+
* Consumers across modules read `result.rows`.
|
|
180
|
+
* Where: Applied to every db.execute() call.
|
|
181
|
+
* How: Wraps arrays of row objects (and the mysql2 tuple) into { rows },
|
|
182
|
+
* leaves object shapes untouched.
|
|
183
|
+
*/
|
|
184
|
+
function normalizeExecuteResult(result: unknown): unknown {
|
|
185
|
+
if (Array.isArray(result)) {
|
|
186
|
+
if (result.length === 2 && Array.isArray(result[1])) {
|
|
187
|
+
return { rows: result[0], fields: result[1] };
|
|
188
|
+
}
|
|
189
|
+
const isRowList = result.length === 0 || result.every((row) => row != null && typeof row === "object" && !Array.isArray(row));
|
|
190
|
+
if (isRowList) {
|
|
191
|
+
return { rows: result };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Why: Provides ergonomic global query surface without calling database().
|
|
199
|
+
* When: Used by modules, seeders, and facade consumers.
|
|
200
|
+
* Where: Exposed via framework facade as `db`.
|
|
201
|
+
* How: Proxy forwards property access to the initialized Drizzle instance.
|
|
202
|
+
*/
|
|
203
|
+
export const db = new Proxy(
|
|
204
|
+
{},
|
|
205
|
+
{
|
|
206
|
+
get(_target, property) {
|
|
207
|
+
const instance = database();
|
|
208
|
+
|
|
209
|
+
if (property === "execute") {
|
|
210
|
+
return async (query: unknown, params?: unknown) => {
|
|
211
|
+
if (!instance.execute) {
|
|
212
|
+
throw new Error("The current driver does not support raw execute.");
|
|
213
|
+
}
|
|
214
|
+
const raw = await instance.execute(query, params);
|
|
215
|
+
return normalizeExecuteResult(raw);
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const value = instance[property as keyof typeof instance];
|
|
220
|
+
return typeof value === "function" ? value.bind(instance) : value;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
) as any;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { closeDatabase, database, databasePool, detectDialect, initDatabase } from "@/framework/database/connection.js";
|
|
3
|
+
import { discoverModuleFiles, importFile } from "@/framework/modules/discover.js";
|
|
4
|
+
|
|
5
|
+
type HookMap = {
|
|
6
|
+
__migrationSql: true;
|
|
7
|
+
postgresql?: string[];
|
|
8
|
+
mysql?: string[];
|
|
9
|
+
sqlite?: string[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function isHookMap(value: unknown): value is HookMap {
|
|
13
|
+
if (!value || typeof value !== "object") return false;
|
|
14
|
+
return (value as HookMap).__migrationSql === true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function collectStatements(exportsObj: Record<string, unknown>, dialect: "postgresql" | "mysql" | "sqlite") {
|
|
18
|
+
const statements: string[] = [];
|
|
19
|
+
for (const value of Object.values(exportsObj)) {
|
|
20
|
+
if (!isHookMap(value)) continue;
|
|
21
|
+
const sqlList = value[dialect] || [];
|
|
22
|
+
for (const statement of sqlList) {
|
|
23
|
+
if (typeof statement === "string" && statement.trim()) statements.push(statement);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return statements;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function executeWithPoolFallback(statement: string, dialect: "postgresql" | "mysql" | "sqlite") {
|
|
30
|
+
const pool = databasePool();
|
|
31
|
+
if (dialect === "sqlite") {
|
|
32
|
+
await pool.executeMultiple(statement);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (dialect === "postgresql") {
|
|
36
|
+
await pool.unsafe(statement);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
await pool.query(statement);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function executeStatement(statement: string, dialect: "postgresql" | "mysql" | "sqlite") {
|
|
43
|
+
const drizzle = database();
|
|
44
|
+
try {
|
|
45
|
+
if (dialect === "sqlite") {
|
|
46
|
+
await drizzle.run(sql.raw(statement));
|
|
47
|
+
} else {
|
|
48
|
+
await drizzle.execute(sql.raw(statement));
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
await executeWithPoolFallback(statement, dialect);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function runModelMigrationHooks() {
|
|
56
|
+
const dialect = detectDialect();
|
|
57
|
+
const files = await discoverModuleFiles("**/database/models/*.{ts,js}");
|
|
58
|
+
|
|
59
|
+
await initDatabase();
|
|
60
|
+
try {
|
|
61
|
+
let applied = 0;
|
|
62
|
+
for (const file of files) {
|
|
63
|
+
const mod = await importFile(file);
|
|
64
|
+
const statements = collectStatements(mod, dialect);
|
|
65
|
+
if (!statements.length) continue;
|
|
66
|
+
|
|
67
|
+
for (const statement of statements) {
|
|
68
|
+
await executeStatement(statement, dialect);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
applied += statements.length;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (applied > 0) {
|
|
75
|
+
console.log(`Applied ${applied} model migration SQL statement(s) for ${dialect}.`);
|
|
76
|
+
}
|
|
77
|
+
} finally {
|
|
78
|
+
await closeDatabase();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (process.argv[1]?.endsWith("migrate-hooks.ts")) {
|
|
83
|
+
runModelMigrationHooks().catch((error) => {
|
|
84
|
+
console.error("Failed to run model migration hooks:", error instanceof Error ? error.message : error);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { count, getTableColumns, type SQL, sql } from "drizzle-orm";
|
|
2
|
+
import { db } from "@/framework/database/connection.js";
|
|
3
|
+
|
|
4
|
+
export type PaginatedResult<T> = {
|
|
5
|
+
current_page: number;
|
|
6
|
+
data: T[];
|
|
7
|
+
first_page_url: string | null;
|
|
8
|
+
from: number | null;
|
|
9
|
+
last_page: number;
|
|
10
|
+
last_page_url: string | null;
|
|
11
|
+
links: Array<{ url: string | null; label: string; page: number | null; active: boolean }>;
|
|
12
|
+
next_page_url: string | null;
|
|
13
|
+
path: string;
|
|
14
|
+
per_page: number;
|
|
15
|
+
prev_page_url: string | null;
|
|
16
|
+
to: number | null;
|
|
17
|
+
total: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type PaginateOptions = {
|
|
21
|
+
page?: number;
|
|
22
|
+
perPage?: number;
|
|
23
|
+
maxPerPage?: number;
|
|
24
|
+
path?: string;
|
|
25
|
+
where?: SQL<unknown>;
|
|
26
|
+
orderBy?: unknown[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type RequestLike = {
|
|
30
|
+
query: any;
|
|
31
|
+
path?: string;
|
|
32
|
+
url?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type PaginateQueryOptions<T> = {
|
|
36
|
+
page?: number;
|
|
37
|
+
perPage?: number;
|
|
38
|
+
maxPerPage?: number;
|
|
39
|
+
path?: string;
|
|
40
|
+
total: () => Promise<number>;
|
|
41
|
+
data: (limit: number, offset: number) => Promise<T[]>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type PaginateModelOptions<T = any> = {
|
|
45
|
+
table?: any;
|
|
46
|
+
query?: { findMany: (args: Record<string, any>) => Promise<T[]> };
|
|
47
|
+
where?: SQL<unknown>;
|
|
48
|
+
with?: Record<string, any>;
|
|
49
|
+
columns?: Record<string, any>;
|
|
50
|
+
extras?: Record<string, any>;
|
|
51
|
+
orderBy?: unknown;
|
|
52
|
+
page?: number;
|
|
53
|
+
perPage?: number;
|
|
54
|
+
maxPerPage?: number;
|
|
55
|
+
path?: string;
|
|
56
|
+
total?: () => Promise<number>;
|
|
57
|
+
data?: (params: { limit: number; offset: number }) => Promise<T[]>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function toPositiveInt(value: unknown, fallback: number) {
|
|
61
|
+
const parsed = Number(value);
|
|
62
|
+
if (!Number.isFinite(parsed) || parsed < 1) return fallback;
|
|
63
|
+
return Math.floor(parsed);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function pageUrl(path: string, page: number, perPage: number) {
|
|
67
|
+
if (!path) return null;
|
|
68
|
+
const delimiter = path.includes("?") ? "&" : "?";
|
|
69
|
+
return `${path}${delimiter}page=${page}&per_page=${perPage}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolvePath(req: RequestLike, path?: string) {
|
|
73
|
+
if (path) return path;
|
|
74
|
+
if (req.path) return req.path;
|
|
75
|
+
if (!req.url) return "";
|
|
76
|
+
try {
|
|
77
|
+
return new URL(req.url, "http://localhost").pathname;
|
|
78
|
+
} catch {
|
|
79
|
+
return req.url.split("?")[0];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function paginateQuery<T = any>(options: PaginateQueryOptions<T>): Promise<PaginatedResult<T>> {
|
|
84
|
+
const maxPerPage = toPositiveInt(options.maxPerPage, 100);
|
|
85
|
+
const perPage = Math.min(toPositiveInt(options.perPage, 15), maxPerPage);
|
|
86
|
+
const page = toPositiveInt(options.page, 1);
|
|
87
|
+
|
|
88
|
+
const total = Number(await options.total());
|
|
89
|
+
const lastPage = Math.max(1, Math.ceil(total / perPage));
|
|
90
|
+
const currentPage = Math.min(page, lastPage);
|
|
91
|
+
const safeOffset = (currentPage - 1) * perPage;
|
|
92
|
+
const data = await options.data(perPage, safeOffset);
|
|
93
|
+
|
|
94
|
+
const from = total === 0 ? null : safeOffset + 1;
|
|
95
|
+
const to = total === 0 ? null : safeOffset + data.length;
|
|
96
|
+
const path = options.path || "";
|
|
97
|
+
const firstPageUrl = pageUrl(path, 1, perPage);
|
|
98
|
+
const lastPageUrl = pageUrl(path, lastPage, perPage);
|
|
99
|
+
const prevPageUrl = currentPage > 1 ? pageUrl(path, currentPage - 1, perPage) : null;
|
|
100
|
+
const nextPageUrl = currentPage < lastPage ? pageUrl(path, currentPage + 1, perPage) : null;
|
|
101
|
+
|
|
102
|
+
const links: Array<{ url: string | null; label: string; page: number | null; active: boolean }> = [
|
|
103
|
+
{
|
|
104
|
+
url: prevPageUrl,
|
|
105
|
+
label: "« Previous",
|
|
106
|
+
page: currentPage > 1 ? currentPage - 1 : null,
|
|
107
|
+
active: false
|
|
108
|
+
}
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
for (let p = 1; p <= lastPage; p += 1) {
|
|
112
|
+
links.push({
|
|
113
|
+
url: pageUrl(path, p, perPage),
|
|
114
|
+
label: String(p),
|
|
115
|
+
page: p,
|
|
116
|
+
active: p === currentPage
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
links.push({
|
|
121
|
+
url: nextPageUrl,
|
|
122
|
+
label: "Next »",
|
|
123
|
+
page: currentPage < lastPage ? currentPage + 1 : null,
|
|
124
|
+
active: false
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
current_page: currentPage,
|
|
129
|
+
data,
|
|
130
|
+
first_page_url: firstPageUrl,
|
|
131
|
+
from,
|
|
132
|
+
last_page: lastPage,
|
|
133
|
+
last_page_url: lastPageUrl,
|
|
134
|
+
links,
|
|
135
|
+
next_page_url: nextPageUrl,
|
|
136
|
+
path,
|
|
137
|
+
per_page: perPage,
|
|
138
|
+
prev_page_url: prevPageUrl,
|
|
139
|
+
to,
|
|
140
|
+
total
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function paginate<T = any>(
|
|
145
|
+
req: RequestLike,
|
|
146
|
+
query: any,
|
|
147
|
+
perPage = 15,
|
|
148
|
+
options: { maxPerPage?: number; path?: string } = {}
|
|
149
|
+
): Promise<PaginatedResult<T>> {
|
|
150
|
+
const page = Number(req.query.page || 1);
|
|
151
|
+
const size = req.query.size;
|
|
152
|
+
const perPageQuery = req.query.per_page;
|
|
153
|
+
const perPageValue = Number(size || perPageQuery || perPage);
|
|
154
|
+
const resolvedPath = resolvePath(req, options.path);
|
|
155
|
+
|
|
156
|
+
return paginateQuery<T>({
|
|
157
|
+
page,
|
|
158
|
+
perPage: perPageValue,
|
|
159
|
+
maxPerPage: options.maxPerPage,
|
|
160
|
+
path: resolvedPath,
|
|
161
|
+
total: async () => {
|
|
162
|
+
const inner = db.select({ val: sql`1` }).from(query.as("_inner"));
|
|
163
|
+
const [row] = await db.select({ total: count() }).from(inner);
|
|
164
|
+
return Number(row?.total ?? 0);
|
|
165
|
+
},
|
|
166
|
+
data: (limit, offset) => query.limit(limit).offset(offset)
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function paginateModel<T = any>(req: RequestLike, options: PaginateModelOptions<T>): Promise<PaginatedResult<T>> {
|
|
171
|
+
const page = Number(req.query.page || options.page || 1);
|
|
172
|
+
const size = req.query.size;
|
|
173
|
+
const perPageQuery = req.query.per_page;
|
|
174
|
+
const perPageValue = Number(size || perPageQuery || options.perPage || 15);
|
|
175
|
+
const resolvedPath = resolvePath(req, options.path);
|
|
176
|
+
|
|
177
|
+
return paginateQuery<T>({
|
|
178
|
+
page,
|
|
179
|
+
perPage: perPageValue,
|
|
180
|
+
maxPerPage: options.maxPerPage,
|
|
181
|
+
path: resolvedPath,
|
|
182
|
+
total: async () => {
|
|
183
|
+
if (options.total) return Number(await options.total());
|
|
184
|
+
if (!options.table) throw new Error("paginateModel requires either table or total callback");
|
|
185
|
+
|
|
186
|
+
let totalQuery = db.select({ total: count() }).from(options.table);
|
|
187
|
+
if (options.where) totalQuery = totalQuery.where(options.where);
|
|
188
|
+
const [row] = await totalQuery;
|
|
189
|
+
return Number(row?.total ?? 0);
|
|
190
|
+
},
|
|
191
|
+
data: async (limit, offset) => {
|
|
192
|
+
if (options.data) return options.data({ limit, offset });
|
|
193
|
+
if (!options.query) throw new Error("paginateModel requires either query or data callback");
|
|
194
|
+
|
|
195
|
+
const args: Record<string, any> = { limit, offset };
|
|
196
|
+
if (options.where) args.where = options.where;
|
|
197
|
+
if (options.with) args.with = options.with;
|
|
198
|
+
if (options.columns) args.columns = options.columns;
|
|
199
|
+
if (options.extras) args.extras = options.extras;
|
|
200
|
+
if (options.orderBy) args.orderBy = options.orderBy;
|
|
201
|
+
|
|
202
|
+
return options.query.findMany(args);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function paginateTable<T = any>(db: any, table: any, options: PaginateOptions = {}): Promise<PaginatedResult<T>> {
|
|
208
|
+
return paginateQuery<T>({
|
|
209
|
+
page: options.page,
|
|
210
|
+
perPage: options.perPage,
|
|
211
|
+
maxPerPage: options.maxPerPage,
|
|
212
|
+
path: options.path,
|
|
213
|
+
total: async () => {
|
|
214
|
+
let totalQuery = db.select({ total: count() }).from(table);
|
|
215
|
+
if (options.where) totalQuery = totalQuery.where(options.where);
|
|
216
|
+
const totalRow = await totalQuery;
|
|
217
|
+
return Number(totalRow[0]?.total ?? 0);
|
|
218
|
+
},
|
|
219
|
+
data: async (limit, offset) => {
|
|
220
|
+
let dataQuery = db.select(getTableColumns(table)).from(table);
|
|
221
|
+
if (options.where) dataQuery = dataQuery.where(options.where);
|
|
222
|
+
if (options.orderBy?.length) dataQuery = dataQuery.orderBy(...options.orderBy);
|
|
223
|
+
return dataQuery.limit(limit).offset(offset);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { detectDialect } from "@/framework/database/connection.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Why: Picks dialect-specific model exports from composite schema modules.
|
|
5
|
+
* When: Building generated schema for active DB dialect.
|
|
6
|
+
* Where: Maker/database schema generation pipeline.
|
|
7
|
+
* How: Filters objects containing sqlite/mysql/postgresql keys and selects current dialect.
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeSchemaExports(source: Record<string, any>) {
|
|
10
|
+
const dialect = detectDialect();
|
|
11
|
+
const out: Record<string, any> = {};
|
|
12
|
+
|
|
13
|
+
for (const [key, value] of Object.entries(source)) {
|
|
14
|
+
if (value && typeof value === "object" && "sqlite" in value && "mysql" in value && "postgresql" in value) {
|
|
15
|
+
out[key] = value[dialect];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { closeDatabase, initDatabase } from "@/framework/database/connection.js";
|
|
3
|
+
import { discoverModuleFiles, importFile } from "@/framework/modules/discover.js";
|
|
4
|
+
|
|
5
|
+
const NAME_SYMBOL = Symbol.for("drizzle:Name");
|
|
6
|
+
const FK_SYMBOLS = [
|
|
7
|
+
Symbol.for("drizzle:PgInlineForeignKeys"),
|
|
8
|
+
Symbol.for("drizzle:MySqlInlineForeignKeys"),
|
|
9
|
+
Symbol.for("drizzle:SQLiteInlineForeignKeys")
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
interface SeederEntry {
|
|
13
|
+
file: string;
|
|
14
|
+
name: string;
|
|
15
|
+
execute: () => Promise<void>;
|
|
16
|
+
tableName?: string;
|
|
17
|
+
dependsOn: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getTableDeps(table: any): string[] {
|
|
21
|
+
for (const sym of FK_SYMBOLS) {
|
|
22
|
+
const fks = table[sym] as any[] | undefined;
|
|
23
|
+
if (!fks || fks.length === 0) continue;
|
|
24
|
+
const deps: string[] = [];
|
|
25
|
+
for (const fk of fks) {
|
|
26
|
+
const ref = fk.reference();
|
|
27
|
+
if (ref?.foreignTable) {
|
|
28
|
+
const name = ref.foreignTable[NAME_SYMBOL] as string;
|
|
29
|
+
if (name && !deps.includes(name)) deps.push(name);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return deps;
|
|
33
|
+
}
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await initDatabase();
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const moduleArg = process.argv.find((arg) => arg.startsWith("--module="));
|
|
41
|
+
const moduleName = moduleArg?.split("=")[1]?.trim();
|
|
42
|
+
const pattern = moduleName ? `${moduleName}/database/seeders/*.{ts,js}` : "**/database/seeders/*.{ts,js}";
|
|
43
|
+
|
|
44
|
+
const files = await discoverModuleFiles(pattern);
|
|
45
|
+
const seeders: SeederEntry[] = [];
|
|
46
|
+
|
|
47
|
+
for (const file of files) {
|
|
48
|
+
try {
|
|
49
|
+
const source = await readFile(file, "utf8");
|
|
50
|
+
const isUnfilledStub = !/^\s*export\s/m.test(source);
|
|
51
|
+
const mod = await importFile(file).catch((err: any) => {
|
|
52
|
+
console.warn(`[seed:skip] ${file}: ${err?.message || err}`);
|
|
53
|
+
return null as any;
|
|
54
|
+
});
|
|
55
|
+
if (!mod) continue;
|
|
56
|
+
if (typeof mod.default !== "function") {
|
|
57
|
+
if (isUnfilledStub) {
|
|
58
|
+
console.log(`Skipped unfilled seeder ${file} (no exports yet)`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
console.warn(`[seed:skip] ${file}: has no default export function`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const table: any = mod.table;
|
|
65
|
+
const entry: SeederEntry = {
|
|
66
|
+
file,
|
|
67
|
+
name: file
|
|
68
|
+
.split(/[\\/]/)
|
|
69
|
+
.pop()!
|
|
70
|
+
.replace(/\.(ts|js)$/, ""),
|
|
71
|
+
execute: () => mod.default(),
|
|
72
|
+
dependsOn: table ? getTableDeps(table) : []
|
|
73
|
+
};
|
|
74
|
+
if (table) {
|
|
75
|
+
entry.tableName = table[NAME_SYMBOL] as string;
|
|
76
|
+
}
|
|
77
|
+
seeders.push(entry);
|
|
78
|
+
} catch (err: any) {
|
|
79
|
+
console.warn(`[seed:skip] ${file}: ${err?.message || err}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const tableToSeeder = new Map<string, SeederEntry>();
|
|
84
|
+
for (const s of seeders) {
|
|
85
|
+
if (s.tableName) tableToSeeder.set(s.tableName, s);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const seederByName = new Map<string, SeederEntry>();
|
|
89
|
+
for (const s of seeders) {
|
|
90
|
+
seederByName.set(s.name, s);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const resolvedDeps = new Map<SeederEntry, SeederEntry[]>();
|
|
94
|
+
for (const s of seeders) {
|
|
95
|
+
const deps: SeederEntry[] = [];
|
|
96
|
+
for (const tableDep of s.dependsOn) {
|
|
97
|
+
const depSeeder = tableToSeeder.get(tableDep);
|
|
98
|
+
if (depSeeder && depSeeder !== s) {
|
|
99
|
+
deps.push(depSeeder);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
resolvedDeps.set(s, deps);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const inDegree = new Map<SeederEntry, number>();
|
|
106
|
+
const adj = new Map<SeederEntry, SeederEntry[]>();
|
|
107
|
+
for (const s of seeders) {
|
|
108
|
+
inDegree.set(s, 0);
|
|
109
|
+
adj.set(s, []);
|
|
110
|
+
}
|
|
111
|
+
for (const s of seeders) {
|
|
112
|
+
for (const dep of resolvedDeps.get(s)!) {
|
|
113
|
+
adj.get(dep)!.push(s);
|
|
114
|
+
inDegree.set(s, inDegree.get(s)! + 1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const queue: SeederEntry[] = [];
|
|
119
|
+
for (const [s, deg] of inDegree) {
|
|
120
|
+
if (deg === 0) queue.push(s);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const sorted: SeederEntry[] = [];
|
|
124
|
+
while (queue.length > 0) {
|
|
125
|
+
const s = queue.shift()!;
|
|
126
|
+
sorted.push(s);
|
|
127
|
+
for (const next of adj.get(s)!) {
|
|
128
|
+
const newDeg = inDegree.get(next)! - 1;
|
|
129
|
+
inDegree.set(next, newDeg);
|
|
130
|
+
if (newDeg === 0) queue.push(next);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (sorted.length !== seeders.length) {
|
|
135
|
+
const seeded = new Set(sorted.map((s) => s.name));
|
|
136
|
+
const missing = seeders.filter((s) => !seeded.has(s.name)).map((s) => s.name);
|
|
137
|
+
console.error(`Circular dependency detected among seeders: ${missing.join(", ")}`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const s of sorted) {
|
|
142
|
+
try {
|
|
143
|
+
await s.execute();
|
|
144
|
+
} catch (err: any) {
|
|
145
|
+
console.warn(`[seed:skip] ${s.name} failed: ${err?.message || err}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log(`Executed ${sorted.length} seeder file(s)${moduleName ? ` for module ${moduleName}` : ""}`);
|
|
150
|
+
} finally {
|
|
151
|
+
await closeDatabase();
|
|
152
|
+
}
|