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,48 @@
|
|
|
1
|
+
import { initDatabase } from "@/framework/database/connection.js";
|
|
2
|
+
import { bootQueueJobs, stopQueueRuntime } from "@/framework/queue/queue.js";
|
|
3
|
+
import { closeRedis, initRedis } from "@/framework/redis/client.js";
|
|
4
|
+
import { startScheduler, stopScheduler } from "@/framework/scheduler/scheduler.js";
|
|
5
|
+
import { registerShutdownSignals, type ShutdownSignal } from "@/framework/support/lifecycle.js";
|
|
6
|
+
import { logger } from "@/framework/support/logger.js";
|
|
7
|
+
|
|
8
|
+
export interface SchedulerHandle {
|
|
9
|
+
count: number;
|
|
10
|
+
shutdown: (signal: ShutdownSignal) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Why: Starts the scheduler and wires DB/Redis/queue shutdown.
|
|
15
|
+
* When: CLI `schedule:work` command or E2E test bootstrap.
|
|
16
|
+
* Where: Scheduler entrypoint.
|
|
17
|
+
* How: Inits DB/Redis, boots queue jobs, starts scheduler, registers signals.
|
|
18
|
+
* When called as the main module, also prints the startup banner.
|
|
19
|
+
*/
|
|
20
|
+
export async function startSchedulerRuntime(): Promise<SchedulerHandle> {
|
|
21
|
+
await initDatabase();
|
|
22
|
+
await initRedis();
|
|
23
|
+
await bootQueueJobs();
|
|
24
|
+
const count = await startScheduler();
|
|
25
|
+
console.log(`Scheduler started [${count} schedule(s)]`);
|
|
26
|
+
|
|
27
|
+
let shuttingDown = false;
|
|
28
|
+
|
|
29
|
+
async function shutdown(signal: ShutdownSignal) {
|
|
30
|
+
if (shuttingDown) return;
|
|
31
|
+
shuttingDown = true;
|
|
32
|
+
|
|
33
|
+
logger.info("Scheduler shutdown signal received", { signal });
|
|
34
|
+
await Promise.allSettled([Promise.resolve(stopScheduler()), stopQueueRuntime()]);
|
|
35
|
+
await closeRedis();
|
|
36
|
+
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
registerShutdownSignals(shutdown);
|
|
41
|
+
|
|
42
|
+
return { count, shutdown };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const isMain = process.argv[1]?.endsWith("run.ts") || process.argv[1]?.endsWith("run.js");
|
|
46
|
+
if (isMain) {
|
|
47
|
+
await startSchedulerRuntime();
|
|
48
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Cron } from "croner";
|
|
2
|
+
import { discoverModuleFiles, importFile } from "@/framework/modules/discover.js";
|
|
3
|
+
import { getQueue } from "@/framework/queue/queue.js";
|
|
4
|
+
import { runWithLock } from "@/framework/scheduler/lock.js";
|
|
5
|
+
|
|
6
|
+
type Schedule = {
|
|
7
|
+
name: string;
|
|
8
|
+
expression: string;
|
|
9
|
+
handler?: () => void | Promise<void>;
|
|
10
|
+
/** Queue mode: on each tick dispatches `job` to the given queue for its worker to process. */
|
|
11
|
+
queue?: string;
|
|
12
|
+
/** Queue mode: job name enqueued on each tick (defaults to `name`). */
|
|
13
|
+
job?: string;
|
|
14
|
+
/** Queue mode: static data passed to every enqueued job. */
|
|
15
|
+
data?: any;
|
|
16
|
+
timezone?: string;
|
|
17
|
+
/** Queue mode: also dispatch one job immediately at scheduler boot. */
|
|
18
|
+
immediately?: boolean;
|
|
19
|
+
runOnInit?: boolean;
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
ttlMs?: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const schedules: Schedule[] = [];
|
|
25
|
+
const tasks: Cron[] = [];
|
|
26
|
+
|
|
27
|
+
const PAD = 60;
|
|
28
|
+
|
|
29
|
+
function pad(str: string, len: number, char = " ") {
|
|
30
|
+
return str.length >= len ? str : str + char.repeat(len - str.length);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function printHeader() {
|
|
34
|
+
console.log("");
|
|
35
|
+
console.log("Running Scheduled Commands");
|
|
36
|
+
console.log(`${"=".repeat(PAD)}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function printScheduleResult(name: string, startMs: number, success: boolean, _message?: string) {
|
|
40
|
+
const elapsed = ((Date.now() - startMs) / 1000).toFixed(2);
|
|
41
|
+
const now = new Date().toLocaleString("en-GB", { hour12: true });
|
|
42
|
+
const dots = ".".repeat(Math.max(1, PAD - name.length - 8));
|
|
43
|
+
const status = success ? `${elapsed}s` : `FAIL`;
|
|
44
|
+
console.log(` ${name} ${dots} ${status}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Why: Registers a cron schedule definition at runtime.
|
|
49
|
+
* When: Module console/schedule files are imported.
|
|
50
|
+
* Where: Scheduler-related module files.
|
|
51
|
+
* How: Pushes normalized schedule config into in-memory registry. Handler
|
|
52
|
+
* schedules run an in-process callback on each tick; queue-mode schedules
|
|
53
|
+
* dispatch a job to their queue on each tick. `schedule:work` is the sole
|
|
54
|
+
* trigger — queue workers never fire schedules on their own.
|
|
55
|
+
*/
|
|
56
|
+
export function defineSchedule(schedule: Schedule) {
|
|
57
|
+
const normalized = { enabled: true, runOnInit: false, ttlMs: 120_000, ...schedule };
|
|
58
|
+
schedules.push(normalized);
|
|
59
|
+
|
|
60
|
+
if (normalized.queue) {
|
|
61
|
+
// nothing to pre-register: schedule:work dispatches on each tick
|
|
62
|
+
} else if (!normalized.handler) {
|
|
63
|
+
console.warn(`Schedule "${normalized.name}" has no handler and no queue; nothing will run`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Why: Loads module schedule files so schedules are registered.
|
|
69
|
+
* When: Scheduler boot process starts.
|
|
70
|
+
* Where: Scheduler worker startup.
|
|
71
|
+
* How: Discovers schedules/console files and imports them.
|
|
72
|
+
*/
|
|
73
|
+
export async function bootSchedules() {
|
|
74
|
+
const files = await discoverModuleFiles("**/{schedules,console}/*.{ts,js}");
|
|
75
|
+
for (const file of files) await importFile(file);
|
|
76
|
+
return files.length;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function handlerRun(schedule: Schedule) {
|
|
80
|
+
return async () => {
|
|
81
|
+
await runWithLock(
|
|
82
|
+
schedule.name,
|
|
83
|
+
async () => {
|
|
84
|
+
const startMs = Date.now();
|
|
85
|
+
try {
|
|
86
|
+
await schedule.handler!();
|
|
87
|
+
printScheduleResult(schedule.name, startMs, true);
|
|
88
|
+
} catch (err: any) {
|
|
89
|
+
printScheduleResult(schedule.name, startMs, false, err?.message || String(err));
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{ ttlMs: schedule.ttlMs }
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function queueDispatchRun(schedule: Schedule) {
|
|
98
|
+
return async () => {
|
|
99
|
+
await runWithLock(
|
|
100
|
+
schedule.name,
|
|
101
|
+
async () => {
|
|
102
|
+
const startMs = Date.now();
|
|
103
|
+
try {
|
|
104
|
+
const queue = getQueue(schedule.queue);
|
|
105
|
+
if (!queue) throw new Error(`Queue "${schedule.queue}" is unavailable`);
|
|
106
|
+
await queue.add(schedule.job ?? schedule.name, schedule.data ?? {});
|
|
107
|
+
printScheduleResult(schedule.name, startMs, true);
|
|
108
|
+
} catch (err: any) {
|
|
109
|
+
printScheduleResult(schedule.name, startMs, false, err?.message || String(err));
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{ ttlMs: schedule.ttlMs }
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Why: Starts cron tasks for all enabled schedules.
|
|
119
|
+
* When: Scheduler worker process boots.
|
|
120
|
+
* Where: `schedule:work` runtime.
|
|
121
|
+
* How: Creates a croner task per enabled schedule — handler schedules run the
|
|
122
|
+
* callback in-process, queue-mode schedules dispatch a job to their queue
|
|
123
|
+
* on each tick (the target worker processes it). Boots once for
|
|
124
|
+
* `runOnInit` handlers and `immediately` queue schedules.
|
|
125
|
+
*/
|
|
126
|
+
export async function startScheduler() {
|
|
127
|
+
await bootSchedules();
|
|
128
|
+
|
|
129
|
+
for (const schedule of schedules) {
|
|
130
|
+
if (!schedule.enabled) continue;
|
|
131
|
+
const run = schedule.handler ? handlerRun(schedule) : queueDispatchRun(schedule);
|
|
132
|
+
tasks.push(new Cron(schedule.expression, { timezone: schedule.timezone, name: schedule.name }, run));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
printHeader();
|
|
136
|
+
|
|
137
|
+
for (const schedule of schedules) {
|
|
138
|
+
if (!schedule.enabled) continue;
|
|
139
|
+
if (schedule.handler && schedule.runOnInit) await handlerRun(schedule)();
|
|
140
|
+
if (schedule.queue && schedule.immediately) await queueDispatchRun(schedule)();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return schedules.length;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Why: Stops and destroys active cron tasks.
|
|
148
|
+
* When: Scheduler shutdown.
|
|
149
|
+
* Where: Process lifecycle handlers.
|
|
150
|
+
* How: Iterates task registry and invokes stop on each croner job.
|
|
151
|
+
*/
|
|
152
|
+
export async function stopScheduler() {
|
|
153
|
+
await Promise.all(tasks.map((task) => task.stop()));
|
|
154
|
+
tasks.length = 0;
|
|
155
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { appConfig, mailConfig, realtimeConfig, redisConfig } from "@/config/index.js";
|
|
5
|
+
import { createKernel } from "@/framework/kernel.js";
|
|
6
|
+
import { stopQueueRuntime } from "@/framework/queue/queue.js";
|
|
7
|
+
import { stopQueueDashboard } from "@/framework/queue/ui.js";
|
|
8
|
+
import { broadcast, closeRealtime, socketServer, WS_PATH } from "@/framework/realtime/index.js";
|
|
9
|
+
import { setupSocketAdminUI } from "@/framework/realtime/ui.js";
|
|
10
|
+
import { closeRedis, redisClientIfReady, redisError, redisReady } from "@/framework/redis/client.js";
|
|
11
|
+
import { type HttpServerHandle, startHttpServer } from "@/framework/runtime/server-adapter.js";
|
|
12
|
+
import { parseCsvOrFallback, registerShutdownSignals, type ShutdownSignal } from "@/framework/support/lifecycle.js";
|
|
13
|
+
import { logger } from "@/framework/support/logger.js";
|
|
14
|
+
|
|
15
|
+
const redisBackedServices = "cache, session, queue, events, Queue Dashboard";
|
|
16
|
+
|
|
17
|
+
function serverUrl(server: HttpServerHandle, pathname = "") {
|
|
18
|
+
const address = server.address();
|
|
19
|
+
const port = typeof address === "object" && address ? address.port : appConfig.port;
|
|
20
|
+
try {
|
|
21
|
+
const currentUrl = new URL(appConfig.url);
|
|
22
|
+
currentUrl.port = String(port);
|
|
23
|
+
if (currentUrl.hostname === "0.0.0.0" || currentUrl.hostname === "::") {
|
|
24
|
+
currentUrl.hostname = "localhost";
|
|
25
|
+
}
|
|
26
|
+
return `${currentUrl.toString().replace(/\/$/, "")}${pathname}`;
|
|
27
|
+
} catch {
|
|
28
|
+
return `http://localhost:${port}${pathname}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ServerHandle {
|
|
33
|
+
app: Awaited<ReturnType<typeof createKernel>>["app"];
|
|
34
|
+
bullBoard: Awaited<ReturnType<typeof createKernel>>["bullBoard"];
|
|
35
|
+
server: HttpServerHandle;
|
|
36
|
+
realtime: any; // Type for Fastify is simpler
|
|
37
|
+
broadcastSubClient: ReturnType<typeof redisClientIfReady> | null;
|
|
38
|
+
shutdown: (signal: ShutdownSignal) => Promise<void>;
|
|
39
|
+
closeHttpServer: () => Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function startServer(): Promise<ServerHandle> {
|
|
43
|
+
const { app, bullBoard } = await createKernel();
|
|
44
|
+
|
|
45
|
+
// 1. Start the server (this internally attaches Socket.IO for Fastify)
|
|
46
|
+
const server = await startHttpServer(app, appConfig.port);
|
|
47
|
+
|
|
48
|
+
// 2. Grab the existing Socket.IO instance
|
|
49
|
+
const io = socketServer();
|
|
50
|
+
const realtime = io ? { io } : null;
|
|
51
|
+
const socketAdmin = setupSocketAdminUI(io);
|
|
52
|
+
|
|
53
|
+
let shuttingDown = false;
|
|
54
|
+
let broadcastSubClient: ReturnType<typeof redisClientIfReady> | null = null;
|
|
55
|
+
|
|
56
|
+
async function closeHttpServer() {
|
|
57
|
+
await server.close();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function shutdown(signal: ShutdownSignal) {
|
|
61
|
+
if (shuttingDown) return;
|
|
62
|
+
shuttingDown = true;
|
|
63
|
+
|
|
64
|
+
logger.info("Shutdown signal received", { signal });
|
|
65
|
+
if (broadcastSubClient) {
|
|
66
|
+
try {
|
|
67
|
+
await broadcastSubClient.quit();
|
|
68
|
+
} catch {
|
|
69
|
+
broadcastSubClient.disconnect();
|
|
70
|
+
}
|
|
71
|
+
broadcastSubClient = null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// closeRealtime is handled by server.close() adapter
|
|
75
|
+
await Promise.allSettled([stopQueueRuntime(), closeHttpServer()]);
|
|
76
|
+
stopQueueDashboard();
|
|
77
|
+
await closeRedis();
|
|
78
|
+
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
registerShutdownSignals(shutdown);
|
|
83
|
+
|
|
84
|
+
if (redisConfig.enabled && realtime) {
|
|
85
|
+
const redis = redisClientIfReady();
|
|
86
|
+
if (redis) {
|
|
87
|
+
broadcastSubClient = redis.duplicate();
|
|
88
|
+
await broadcastSubClient.connect();
|
|
89
|
+
const channel = `${redisConfig.prefix}:broadcast`;
|
|
90
|
+
await broadcastSubClient.subscribe(channel);
|
|
91
|
+
broadcastSubClient.on("message", (_channel: string, message: string) => {
|
|
92
|
+
try {
|
|
93
|
+
const { event, payload, options } = JSON.parse(message);
|
|
94
|
+
if (event) broadcast(event, payload, options);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
logger.error("Broadcast relay error", { error });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const views = new Set(parseCsvOrFallback(process.env.NEXWIRE_DEV_VIEWS, []).map((view) => view.toLowerCase()));
|
|
103
|
+
|
|
104
|
+
if (appConfig.openApiEnabled) {
|
|
105
|
+
console.log(`API Docs: ${serverUrl(server, "/api-docs")}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (appConfig.uiEnabled) {
|
|
109
|
+
console.log(`UI: ${process.env.NEXWIRE_FRONTEND_URL}`);
|
|
110
|
+
} else {
|
|
111
|
+
console.log(chalk.gray("UI: .env -> UI=false (API-only mode)"));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (redisConfig.enabled && bullBoard.enabled) {
|
|
115
|
+
console.log(`Queue: ${serverUrl(server, bullBoard.basePath)}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (views.has("maildev")) {
|
|
119
|
+
console.log(`MailDev: http://localhost:${mailConfig.maildev.webPort} (SMTP ${mailConfig.maildev.smtpPort})`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (views.has("studio")) {
|
|
123
|
+
console.log("Drizzle: https://local.drizzle.studio");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const wsUrl = serverUrl(server, WS_PATH).replace(/^http/, "ws");
|
|
127
|
+
if (!realtimeConfig.enabled) {
|
|
128
|
+
console.log(chalk.gray("Realtime (Socket.IO) disabled"));
|
|
129
|
+
} else if (realtime) {
|
|
130
|
+
if (socketAdmin.enabled) {
|
|
131
|
+
console.log(`Realtime: https://admin.socket.io | ${wsUrl}`);
|
|
132
|
+
} else {
|
|
133
|
+
console.log(`Realtime: ${wsUrl}`);
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
console.log(chalk.yellow("Realtime (Socket.IO) unavailable"));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (redisConfig.enabled && views.has("redis")) {
|
|
140
|
+
console.log(`Redis UI: http://localhost:${redisConfig.commanderPort}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!redisConfig.enabled) {
|
|
144
|
+
console.log(chalk.cyan(`Redis: .env -> REDIS=false (disabled ${redisBackedServices})`));
|
|
145
|
+
} else if (redisReady()) {
|
|
146
|
+
console.log(chalk.green(`Redis connected: ${redisConfig.url}`));
|
|
147
|
+
console.log(chalk.green(`Redis-backed services enabled: ${redisBackedServices}`));
|
|
148
|
+
} else {
|
|
149
|
+
console.log(chalk.yellow(`Redis unavailable: ${redisError() || "not connected"}`));
|
|
150
|
+
console.log(chalk.yellow(`Redis-backed services unavailable: ${redisBackedServices}`));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log(`${appConfig.name} API running on ${serverUrl(server)}`);
|
|
154
|
+
|
|
155
|
+
return { app, bullBoard, server, realtime, broadcastSubClient, shutdown, closeHttpServer };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
159
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(currentFile);
|
|
160
|
+
if (isMain) {
|
|
161
|
+
await startServer();
|
|
162
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
3
|
+
import { appConfig, redisConfig, sessionConfig } from "@/config/index.js";
|
|
4
|
+
import { createCircuitBreaker, isCircuitBreakerError } from "@/framework/circuit-breaker/cb.js";
|
|
5
|
+
import { redisClientIfReady } from "@/framework/redis/client.js";
|
|
6
|
+
|
|
7
|
+
// Initialize the breaker for Session operations
|
|
8
|
+
const sessionBreaker = createCircuitBreaker({
|
|
9
|
+
name: "Redis Session",
|
|
10
|
+
failureThreshold: 3,
|
|
11
|
+
resetTimeout: 30000
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function originOf(url?: string) {
|
|
15
|
+
if (!url) return "";
|
|
16
|
+
try {
|
|
17
|
+
return new URL(url).origin;
|
|
18
|
+
} catch {
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function needsCrossSiteCookie() {
|
|
24
|
+
const appOrigin = originOf(appConfig.url);
|
|
25
|
+
const frontendOrigin = originOf(appConfig.frontendUrl);
|
|
26
|
+
return Boolean(appOrigin && frontendOrigin && appOrigin !== frontendOrigin);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sessionKey(id: string) {
|
|
30
|
+
return `${sessionConfig.keyPrefix}:${id}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Global hook that ensures every request has a session cookie.
|
|
35
|
+
*
|
|
36
|
+
* Runs in an onRequest hook, after `@fastify/cookie` has parsed the header, so
|
|
37
|
+
* `request.cookies` is available. The session id is exposed on the request for
|
|
38
|
+
* downstream handlers, mirroring Express's `res.locals.sessionId`.
|
|
39
|
+
*/
|
|
40
|
+
export async function sessionMiddleware(request: FastifyRequest, reply: FastifyReply) {
|
|
41
|
+
let sessionId = request.cookies?.[sessionConfig.cookieName];
|
|
42
|
+
|
|
43
|
+
if (!sessionId) {
|
|
44
|
+
sessionId = randomUUID();
|
|
45
|
+
const crossSiteCookie = needsCrossSiteCookie();
|
|
46
|
+
reply.setCookie(sessionConfig.cookieName, sessionId, {
|
|
47
|
+
httpOnly: true,
|
|
48
|
+
sameSite: crossSiteCookie ? "none" : "lax",
|
|
49
|
+
secure: crossSiteCookie,
|
|
50
|
+
path: "/",
|
|
51
|
+
// @fastify/cookie expects maxAge in seconds.
|
|
52
|
+
maxAge: sessionConfig.ttlSeconds
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
request.sessionId = sessionId;
|
|
57
|
+
|
|
58
|
+
// Refresh is safely wrapped in the breaker internally
|
|
59
|
+
await session.refresh(sessionId);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Redis-backed server-side session store. */
|
|
63
|
+
export const session = {
|
|
64
|
+
async start(data: Record<string, unknown> = {}) {
|
|
65
|
+
const client = redisClientIfReady();
|
|
66
|
+
if (!client) return "";
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
return await sessionBreaker.exec(async () => {
|
|
70
|
+
const id = randomUUID();
|
|
71
|
+
await client.set(sessionKey(id), JSON.stringify(data), "EX", sessionConfig.ttlSeconds);
|
|
72
|
+
return id;
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (isCircuitBreakerError(error)) return "";
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
async all<T = Record<string, unknown>>(id: string): Promise<T | null> {
|
|
81
|
+
const client = redisClientIfReady();
|
|
82
|
+
if (!client) return null;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
return await sessionBreaker.exec(async () => {
|
|
86
|
+
const data = await client.get(sessionKey(id));
|
|
87
|
+
return data ? (JSON.parse(data) as T) : null;
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (isCircuitBreakerError(error)) return null;
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async get<T>(id: string, key: string): Promise<T | null> {
|
|
96
|
+
const data = await session.all<Record<string, T>>(id);
|
|
97
|
+
return data?.[key] ?? null;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
async set(id: string, key: string, value: unknown) {
|
|
101
|
+
const client = redisClientIfReady();
|
|
102
|
+
if (!client) return false;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
return await sessionBreaker.exec(async () => {
|
|
106
|
+
// Read through the client directly rather than `session.all()`. Calling
|
|
107
|
+
// another breaker-wrapped method from inside `exec` deadlocks the
|
|
108
|
+
// half-open probe: the inner call sees the outer probe in flight and
|
|
109
|
+
// rejects, so a `set` could never be the call that closes the breaker.
|
|
110
|
+
const raw = await client.get(sessionKey(id));
|
|
111
|
+
const data = raw ? (JSON.parse(raw) as Record<string, unknown>) : {};
|
|
112
|
+
data[key] = value;
|
|
113
|
+
await client.set(sessionKey(id), JSON.stringify(data), "EX", sessionConfig.ttlSeconds);
|
|
114
|
+
return true;
|
|
115
|
+
});
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (isCircuitBreakerError(error)) return false;
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/** @deprecated Use set() instead. */
|
|
123
|
+
async put(id: string, key: string, value: unknown) {
|
|
124
|
+
return this.set(id, key, value);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
async refresh(id: string) {
|
|
128
|
+
const client = redisClientIfReady();
|
|
129
|
+
if (!client) return false;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
return await sessionBreaker.exec(async () => {
|
|
133
|
+
await client.expire(sessionKey(id), sessionConfig.ttlSeconds);
|
|
134
|
+
return true;
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (isCircuitBreakerError(error)) return false;
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
async destroy(id: string) {
|
|
143
|
+
const client = redisClientIfReady();
|
|
144
|
+
if (!client) return false;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
return await sessionBreaker.exec(async () => {
|
|
148
|
+
await client.del(sessionKey(id));
|
|
149
|
+
return true;
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (isCircuitBreakerError(error)) return false;
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
isAvailable() {
|
|
158
|
+
return redisConfig.enabled && redisClientIfReady() !== null && sessionBreaker.getState() !== "open";
|
|
159
|
+
}
|
|
160
|
+
};
|