create-nexwire 4.3.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/express/src/framework/facade.ts +1 -0
- package/express/src/framework/http/logger.ts +13 -4
- package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
- package/express/src/framework/notification/channels/mail.ts +8 -6
- 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 +2 -2
- package/express/src/resources/src/components/Switch.vue +60 -65
- 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/stores/auth.ts +9 -6
- package/express/src/storage/database/nexwire.sqlite +0 -0
- 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/src/framework/facade.ts +1 -0
- package/hono/src/framework/http/logger.ts +86 -81
- package/hono/src/framework/maker-cli/runtime/core.mjs +1 -2
- package/hono/src/framework/notification/channels/mail.ts +8 -6
- 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 +2 -2
- package/hono/src/resources/src/components/Switch.vue +60 -65
- 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/stores/auth.ts +9 -6
- package/hono/src/storage/database/nexwire.sqlite +0 -0
- package/package.json +3 -2
- package/src/index.js +11 -8
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { queueJob } from "@/framework/queue/queue.js";
|
|
2
|
+
import { type BroadcastOptions, broadcast } from "@/framework/realtime/broadcast.js";
|
|
3
|
+
import { logger } from "@/framework/support/logger.js";
|
|
4
|
+
|
|
5
|
+
type CommandHandler = (payload: any) => Promise<any>;
|
|
6
|
+
|
|
7
|
+
const commandHandlers = new Map<string, CommandHandler>();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Why: Registers in-memory command handler for synchronous dispatch.
|
|
11
|
+
* When: Module startup files register command handlers.
|
|
12
|
+
* Where: Job/command style module code.
|
|
13
|
+
* How: Stores handler in a process-level map by command name.
|
|
14
|
+
*/
|
|
15
|
+
export function command(name: string, handler: CommandHandler) {
|
|
16
|
+
commandHandlers.set(name, handler);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Why: Executes command immediately or queues it for async processing.
|
|
21
|
+
* When: Application logic needs command pattern behavior.
|
|
22
|
+
* Where: Controllers, jobs, and service layers.
|
|
23
|
+
* How: Runs local handler unless async requested; otherwise enqueues.
|
|
24
|
+
*/
|
|
25
|
+
export async function dispatchCommand(name: string, payload: any, options: { async?: boolean; queue?: string } = {}) {
|
|
26
|
+
if (!options.async) {
|
|
27
|
+
const handler = commandHandlers.get(name);
|
|
28
|
+
if (handler) return await handler(payload);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return await queueJob(name, payload, { queue: options.queue || "default" });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Why: Dispatches domain events with optional queueing and websocket fan-out.
|
|
36
|
+
* When: Domain actions need side effects or realtime notifications.
|
|
37
|
+
* Where: Controllers/jobs across modules.
|
|
38
|
+
* How: Broadcasts via Socket.IO and optionally enqueues job.
|
|
39
|
+
*/
|
|
40
|
+
export async function dispatchEvent(name: string, payload: any, options: { queue?: boolean | string; broadcast?: BroadcastOptions } = {}) {
|
|
41
|
+
// 1. Handle Broadcast safely
|
|
42
|
+
if (options.broadcast) {
|
|
43
|
+
try {
|
|
44
|
+
// Await the broadcast so the circuit breaker and redis publish complete safely
|
|
45
|
+
await broadcast(name, payload, options.broadcast);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.error(`Failed to broadcast event: ${name}`, { error });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 2. Handle Queue safely
|
|
52
|
+
if (options.queue) {
|
|
53
|
+
try {
|
|
54
|
+
return await queueJob(name, payload, {
|
|
55
|
+
queue: typeof options.queue === "string" ? options.queue : "default"
|
|
56
|
+
});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
logger.error(`Failed to queue event: ${name}`, { error });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why: Central public API surface for framework consumers.
|
|
3
|
+
* When: Modules/middlewares need framework utilities.
|
|
4
|
+
* Where: App-layer imports should target this file.
|
|
5
|
+
* How: Re-exports stable, reusable helpers while hiding setup internals.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export * as lodash from "lodash-es";
|
|
9
|
+
// ==========================================
|
|
10
|
+
// 1. External & Core Utilities
|
|
11
|
+
// ==========================================
|
|
12
|
+
export { z } from "zod";
|
|
13
|
+
// ==========================================
|
|
14
|
+
// 6. Cache & Session
|
|
15
|
+
// ==========================================
|
|
16
|
+
export { cache } from "@/framework/cache/cache.js";
|
|
17
|
+
// ==========================================
|
|
18
|
+
// 2. Database
|
|
19
|
+
// ==========================================
|
|
20
|
+
export { database, db } from "@/framework/database/connection.js";
|
|
21
|
+
export { type PaginatedResult, paginate, paginateModel, paginateQuery, paginateTable } from "@/framework/database/paginate.js";
|
|
22
|
+
// ==========================================
|
|
23
|
+
// 4. Events & Queue
|
|
24
|
+
// ==========================================
|
|
25
|
+
export { command, dispatchCommand, dispatchEvent } from "@/framework/events/dispatcher.js";
|
|
26
|
+
// ==========================================
|
|
27
|
+
// 3. HTTP & Routing
|
|
28
|
+
// ==========================================
|
|
29
|
+
export { loginLimiter } from "@/framework/http/ratelimiter.js";
|
|
30
|
+
export { ValidationErrorSchema, validate } from "@/framework/http/validation.js";
|
|
31
|
+
export { broadcastChannel } from "@/framework/notification/channels/broadcast.js";
|
|
32
|
+
export { mailChannel } from "@/framework/notification/channels/mail.js";
|
|
33
|
+
export type { NotificationChannel, NotificationOptions } from "@/framework/notification/contracts.js";
|
|
34
|
+
// ==========================================
|
|
35
|
+
// 10. Notification Engine (Delivery Channels)
|
|
36
|
+
// ==========================================
|
|
37
|
+
export { notify } from "@/framework/notification/index.js";
|
|
38
|
+
export { registerChannel } from "@/framework/notification/registry.js";
|
|
39
|
+
// ==========================================
|
|
40
|
+
// 9. Providers (Storage/Auth)
|
|
41
|
+
// ==========================================
|
|
42
|
+
export { resolveAuthProvider, resolveNotificationProvider } from "@/framework/providers/adapter.js";
|
|
43
|
+
export type {
|
|
44
|
+
AuthProvider,
|
|
45
|
+
AuthProviderRefreshToken,
|
|
46
|
+
AuthProviderUser,
|
|
47
|
+
AuthUserContact,
|
|
48
|
+
AuthUserContext,
|
|
49
|
+
NotifiableTarget,
|
|
50
|
+
NotificationCreateInput,
|
|
51
|
+
NotificationListOptions,
|
|
52
|
+
NotificationPage,
|
|
53
|
+
NotificationProvider,
|
|
54
|
+
NotificationRecord
|
|
55
|
+
} from "@/framework/providers/contracts.js";
|
|
56
|
+
export { loadProviders } from "@/framework/providers/loader.js";
|
|
57
|
+
export {
|
|
58
|
+
clearAuthProvider,
|
|
59
|
+
clearNotificationProvider,
|
|
60
|
+
clearProvider,
|
|
61
|
+
getAuthProvider,
|
|
62
|
+
getNotificationProvider,
|
|
63
|
+
getProvider,
|
|
64
|
+
hasAuthProvider,
|
|
65
|
+
hasNotificationProvider,
|
|
66
|
+
hasProvider,
|
|
67
|
+
listProviders,
|
|
68
|
+
registerAuthProvider,
|
|
69
|
+
registerNotificationProvider,
|
|
70
|
+
registerProvider
|
|
71
|
+
} from "@/framework/providers/registry.js";
|
|
72
|
+
export { queue, queueJob, shouldQueue } from "@/framework/queue/queue.js";
|
|
73
|
+
// ==========================================
|
|
74
|
+
// 5. Realtime
|
|
75
|
+
// ==========================================
|
|
76
|
+
export { broadcast } from "@/framework/realtime/index.js";
|
|
77
|
+
export { defineSchedule } from "@/framework/scheduler/scheduler.js";
|
|
78
|
+
export { session } from "@/framework/session/session.js";
|
|
79
|
+
// ==========================================
|
|
80
|
+
// 7. Storage & Mail
|
|
81
|
+
// ==========================================
|
|
82
|
+
export { storage } from "@/framework/storage/storage.js";
|
|
83
|
+
// ==========================================
|
|
84
|
+
// 8. Security, Auth & Uploads
|
|
85
|
+
// ==========================================
|
|
86
|
+
export { cookie } from "@/framework/support/cookie.js";
|
|
87
|
+
export { jwt } from "@/framework/support/jwt.js";
|
|
88
|
+
// ==========================================
|
|
89
|
+
// 11. Utilities
|
|
90
|
+
// ==========================================
|
|
91
|
+
export { logger } from "@/framework/support/logger.js";
|
|
92
|
+
export { mail } from "@/framework/support/mail.js";
|
|
93
|
+
export { password } from "@/framework/support/password.js";
|
|
94
|
+
export * as HttpStatusCodes from "@/framework/support/status-codes.js";
|
|
95
|
+
export { fields, upload } from "@/framework/support/upload.js";
|
|
96
|
+
export { urls } from "@/framework/support/url.js";
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import cookie from "@fastify/cookie";
|
|
2
|
+
import cors from "@fastify/cors";
|
|
3
|
+
import rateLimit from "@fastify/rate-limit";
|
|
4
|
+
import Fastify from "fastify";
|
|
5
|
+
import fastifyPlugin from "fastify-plugin";
|
|
6
|
+
import { serializerCompiler, validatorCompiler, type ZodTypeProvider } from "fastify-type-provider-zod";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { appConfig, cookieConfig, loggingConfig, rateLimitConfig, redisConfig } from "@/config/index.js";
|
|
9
|
+
import { database } from "@/framework/database/connection.js";
|
|
10
|
+
import { corsOrigins } from "@/framework/http/cors.js";
|
|
11
|
+
import { registerErrorHandler, registerRequestLogger } from "@/framework/http/logger.js";
|
|
12
|
+
import { getMetrics, incrementRequestCount } from "@/framework/http/metrics.js";
|
|
13
|
+
import { configureOpenApi } from "@/framework/http/openapi.js";
|
|
14
|
+
import { registerRateLimit } from "@/framework/http/ratelimiter.js";
|
|
15
|
+
import { securityHook } from "@/framework/http/security.js";
|
|
16
|
+
import { hasUiBuild, registerStatic } from "@/framework/http/static.js";
|
|
17
|
+
import { redisClientIfReady } from "@/framework/redis/client.js";
|
|
18
|
+
import { sessionMiddleware } from "@/framework/session/session.js";
|
|
19
|
+
|
|
20
|
+
const HealthSchema = z.object({
|
|
21
|
+
message: z.string()
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const ReadySchema = z.object({
|
|
25
|
+
status: z.enum(["ready", "not_ready"]),
|
|
26
|
+
checks: z.record(z.string(), z.string())
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const RootSchema = z.object({
|
|
30
|
+
name: z.string(),
|
|
31
|
+
ok: z.boolean()
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Why: Wraps the request lifecycle hooks in one plugin so they load after the
|
|
36
|
+
* cookie/cors/rate-limit plugins. fastify-plugin breaks encapsulation, so
|
|
37
|
+
* the hooks and request decorators still reach every route on the root.
|
|
38
|
+
* When: App boot, before route registration.
|
|
39
|
+
* Where: HTTP app setup.
|
|
40
|
+
* How: Session first (needs parsed cookies), then security, metrics, logging.
|
|
41
|
+
*/
|
|
42
|
+
const infrastructure = fastifyPlugin(async (app) => {
|
|
43
|
+
app.decorateRequest("auth", null);
|
|
44
|
+
app.decorateRequest("sessionId", null);
|
|
45
|
+
|
|
46
|
+
app.addHook("onRequest", sessionMiddleware);
|
|
47
|
+
app.addHook("onRequest", securityHook);
|
|
48
|
+
app.addHook("onRequest", async () => {
|
|
49
|
+
incrementRequestCount();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (loggingConfig.httpRequests) {
|
|
53
|
+
registerRequestLogger(app);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Why: Builds the main HTTP app with plugins, health, and fallbacks.
|
|
59
|
+
* When: Kernel creates the application server instance.
|
|
60
|
+
* Where: Framework bootstrap path.
|
|
61
|
+
* How: Native Fastify plugins and hooks; no Express/Hono compatibility layer.
|
|
62
|
+
*/
|
|
63
|
+
export function createHttpApp() {
|
|
64
|
+
const app = Fastify({
|
|
65
|
+
logger: false,
|
|
66
|
+
// Match Hono and Express: a UUID per request instead of Fastify's `req-1`
|
|
67
|
+
// counter, so request ids are comparable across engines and log sinks.
|
|
68
|
+
genReqId: () => crypto.randomUUID()
|
|
69
|
+
}).withTypeProvider<ZodTypeProvider>();
|
|
70
|
+
|
|
71
|
+
app.setValidatorCompiler(validatorCompiler);
|
|
72
|
+
app.setSerializerCompiler(serializerCompiler);
|
|
73
|
+
|
|
74
|
+
// Error handling must exist before child route scopes are created, otherwise
|
|
75
|
+
// those scopes inherit Fastify's default handler and return 400 for Zod errors.
|
|
76
|
+
registerErrorHandler(app);
|
|
77
|
+
|
|
78
|
+
app.register(cookie, { secret: cookieConfig.secret });
|
|
79
|
+
app.register(cors, { origin: corsOrigins, credentials: true });
|
|
80
|
+
registerRateLimit(app);
|
|
81
|
+
|
|
82
|
+
app.register(infrastructure);
|
|
83
|
+
|
|
84
|
+
if (appConfig.openApiEnabled) {
|
|
85
|
+
configureOpenApi(app);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
app.get(
|
|
89
|
+
"/health",
|
|
90
|
+
{
|
|
91
|
+
schema: {
|
|
92
|
+
tags: ["System"],
|
|
93
|
+
description: "Application health status",
|
|
94
|
+
response: { 200: HealthSchema }
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
async () => ({ message: "Application is healthy" })
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
app.get(
|
|
101
|
+
"/ready",
|
|
102
|
+
{
|
|
103
|
+
schema: {
|
|
104
|
+
tags: ["System"],
|
|
105
|
+
description: "Readiness probe",
|
|
106
|
+
response: { 200: ReadySchema, 503: ReadySchema }
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
async (_request, reply) => {
|
|
110
|
+
const checks: Record<string, string> = {};
|
|
111
|
+
let allOk = true;
|
|
112
|
+
try {
|
|
113
|
+
database();
|
|
114
|
+
checks.database = "ok";
|
|
115
|
+
} catch {
|
|
116
|
+
checks.database = "error";
|
|
117
|
+
allOk = false;
|
|
118
|
+
}
|
|
119
|
+
if (redisConfig.enabled) {
|
|
120
|
+
if (redisClientIfReady()) {
|
|
121
|
+
checks.redis = "ok";
|
|
122
|
+
} else {
|
|
123
|
+
checks.redis = "error";
|
|
124
|
+
allOk = false;
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
checks.redis = "disabled";
|
|
128
|
+
}
|
|
129
|
+
reply.code(allOk ? 200 : 503);
|
|
130
|
+
return { status: allOk ? "ready" : "not_ready", checks } as const;
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
app.get(
|
|
135
|
+
"/live",
|
|
136
|
+
{
|
|
137
|
+
schema: {
|
|
138
|
+
tags: ["System"],
|
|
139
|
+
description: "Liveness probe",
|
|
140
|
+
response: { 200: z.object({ status: z.literal("alive") }) }
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
async () => ({ status: "alive" as const })
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
app.get("/metrics", async (_request, reply) => {
|
|
147
|
+
const m = getMetrics();
|
|
148
|
+
const output = [
|
|
149
|
+
"# HELP http_requests_total Total HTTP requests",
|
|
150
|
+
"# TYPE http_requests_total counter",
|
|
151
|
+
`http_requests_total ${m.requestCount}`,
|
|
152
|
+
"# HELP http_errors_total Total HTTP errors",
|
|
153
|
+
"# TYPE http_errors_total counter",
|
|
154
|
+
`http_errors_total ${m.errorCount}`
|
|
155
|
+
].join("\n");
|
|
156
|
+
reply.type("text/plain").send(output);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (!hasUiBuild()) {
|
|
160
|
+
app.get(
|
|
161
|
+
"/",
|
|
162
|
+
{
|
|
163
|
+
schema: {
|
|
164
|
+
tags: ["System"],
|
|
165
|
+
description: "API identity",
|
|
166
|
+
response: { 200: RootSchema }
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
async () => ({ name: appConfig.name, ok: true })
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
registerStatic(app);
|
|
174
|
+
|
|
175
|
+
return app;
|
|
176
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { corsConfig } from "@/config/index.js";
|
|
2
|
+
import { parseCsvOrFallback } from "@/framework/support/lifecycle.js";
|
|
3
|
+
|
|
4
|
+
const allowed = parseCsvOrFallback(corsConfig.origin, []);
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Why: Applies credential-aware CORS policy.
|
|
8
|
+
* When: Global HTTP request processing.
|
|
9
|
+
* Where: App plugin stack.
|
|
10
|
+
* How: `true` reflects the request origin in wildcard mode; otherwise only
|
|
11
|
+
* origins from the configured allowlist receive CORS headers.
|
|
12
|
+
*/
|
|
13
|
+
export const corsOrigins = corsConfig.origin === "*" ? true : allowed;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import "fastify";
|
|
2
|
+
import type { UploadedFile } from "@/framework/support/upload.js";
|
|
3
|
+
|
|
4
|
+
declare module "fastify" {
|
|
5
|
+
interface FastifyRequest {
|
|
6
|
+
auth: any;
|
|
7
|
+
sessionId: string | null;
|
|
8
|
+
/** Single-file upload, populated by `upload({ field })`. */
|
|
9
|
+
upload?: UploadedFile;
|
|
10
|
+
/** Multiple uploads or labeled fields, populated by `upload`/`fields`. */
|
|
11
|
+
uploads?: UploadedFile[] | Record<string, UploadedFile[]>;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { FastifyError, FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
|
2
|
+
import { hasZodFastifySchemaValidationErrors, isResponseSerializationError } from "fastify-type-provider-zod";
|
|
3
|
+
import { openApiConfig } from "@/config/index.js";
|
|
4
|
+
import { logger } from "@/framework/support/logger.js";
|
|
5
|
+
|
|
6
|
+
function requestIp(request: FastifyRequest) {
|
|
7
|
+
const forwarded = request.headers["x-forwarded-for"];
|
|
8
|
+
return String(request.headers["cf-connecting-ip"] || forwarded || request.headers["x-real-ip"] || request.ip || "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Probe endpoints and the queue dashboard are infrastructure traffic, not
|
|
12
|
+
// application requests. Skip them on every engine so the access log stays
|
|
13
|
+
// useful (the dev CLI polls /health while it boots).
|
|
14
|
+
const PROBE_PATHS = new Set(["/health", "/ready", "/live"]);
|
|
15
|
+
|
|
16
|
+
function shouldSkipHttpLog(pathname: string) {
|
|
17
|
+
return (
|
|
18
|
+
PROBE_PATHS.has(pathname) ||
|
|
19
|
+
pathname.startsWith("/queues") ||
|
|
20
|
+
pathname === "/favicon.ico" ||
|
|
21
|
+
pathname === openApiConfig.scalar.specUrl ||
|
|
22
|
+
pathname === openApiConfig.scalar.docsPath ||
|
|
23
|
+
pathname.startsWith(`${openApiConfig.scalar.docsPath}/`)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Why: Logs each HTTP request with latency and request metadata.
|
|
29
|
+
* When: Applied globally to app routes.
|
|
30
|
+
* Where: HTTP request lifecycle.
|
|
31
|
+
* How: An onResponse hook reads Fastify's built-in elapsed time, so no
|
|
32
|
+
* per-request start timestamp has to be stored.
|
|
33
|
+
*/
|
|
34
|
+
export function registerRequestLogger(app: FastifyInstance) {
|
|
35
|
+
app.addHook("onRequest", async (request, reply) => {
|
|
36
|
+
reply.header("X-Request-ID", request.id);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
app.addHook("onResponse", async (request, reply) => {
|
|
40
|
+
const pathname = (request.url || "").split("?")[0];
|
|
41
|
+
if (shouldSkipHttpLog(pathname)) return;
|
|
42
|
+
|
|
43
|
+
logger.info("HTTP Request", {
|
|
44
|
+
requestId: request.id,
|
|
45
|
+
method: request.method,
|
|
46
|
+
path: pathname,
|
|
47
|
+
url: request.url,
|
|
48
|
+
status: reply.statusCode,
|
|
49
|
+
duration_ms: Math.round(reply.elapsedTime),
|
|
50
|
+
ip: requestIp(request),
|
|
51
|
+
userAgent: request.headers["user-agent"] || null
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function zodIssues(error: FastifyError) {
|
|
57
|
+
return (error.validation ?? []).map((issue) => ({
|
|
58
|
+
path: issue.instancePath.split("/").filter(Boolean),
|
|
59
|
+
message: issue.message,
|
|
60
|
+
code: issue.keyword
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Why: Normalizes every error into the same JSON contract as Hono and Express.
|
|
66
|
+
* When: Any route or hook throws.
|
|
67
|
+
* Where: App error boundary.
|
|
68
|
+
* How: Zod failures become 422 with `{ success:false, error:{ name, issues } }`;
|
|
69
|
+
* response serialization failures stay 500; everything else uses statusCode.
|
|
70
|
+
*/
|
|
71
|
+
export function registerErrorHandler(app: FastifyInstance) {
|
|
72
|
+
app.setErrorHandler((error: FastifyError, request: FastifyRequest, reply: FastifyReply) => {
|
|
73
|
+
if (hasZodFastifySchemaValidationErrors(error)) {
|
|
74
|
+
const issues = zodIssues(error);
|
|
75
|
+
logger.warn("Request validation failed", { status: 422, issues });
|
|
76
|
+
return reply.code(422).send({
|
|
77
|
+
success: false,
|
|
78
|
+
error: { name: "ZodError", issues }
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isResponseSerializationError(error)) {
|
|
83
|
+
request.log.error({ err: error }, "Response serialization failed");
|
|
84
|
+
logger.error("Response serialization failed", { err: error });
|
|
85
|
+
return reply.code(500).send({ message: "Internal server error" });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const status =
|
|
89
|
+
typeof error.statusCode === "number" ? error.statusCode : typeof (error as any).status === "number" ? (error as any).status : 500;
|
|
90
|
+
|
|
91
|
+
const applicationError = error as any;
|
|
92
|
+
if (applicationError.success === false && applicationError.error?.issues) {
|
|
93
|
+
logger.warn("Request validation failed", { status, issues: applicationError.error.issues });
|
|
94
|
+
return reply.code(status).send({
|
|
95
|
+
success: false,
|
|
96
|
+
error: {
|
|
97
|
+
name: applicationError.error.name ?? "ZodError",
|
|
98
|
+
issues: applicationError.error.issues
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const message = status === 500 ? "Internal server error" : (error.message ?? "Internal server error");
|
|
104
|
+
const stack = process.env.NODE_ENV === "production" ? undefined : error.stack;
|
|
105
|
+
|
|
106
|
+
logger.error("Request failed", { status, message, stack: error.stack });
|
|
107
|
+
|
|
108
|
+
return reply.code(status).send({ message, ...(stack ? { stack } : {}) });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
let requestCount = 0;
|
|
2
|
+
let errorCount = 0;
|
|
3
|
+
|
|
4
|
+
export function incrementRequestCount() {
|
|
5
|
+
requestCount++;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function incrementErrorCount() {
|
|
9
|
+
errorCount++;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function resetMetrics() {
|
|
13
|
+
requestCount = 0;
|
|
14
|
+
errorCount = 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getMetrics() {
|
|
18
|
+
return {
|
|
19
|
+
requestCount,
|
|
20
|
+
errorCount
|
|
21
|
+
};
|
|
22
|
+
}
|