exisjs 0.1.2
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 +52 -0
- package/bin/exis.js +2 -0
- package/dist/adapters/aws-lambda.d.ts +21 -0
- package/dist/adapters/aws-lambda.d.ts.map +1 -0
- package/dist/adapters/aws-lambda.js +110 -0
- package/dist/adapters/aws-lambda.js.map +1 -0
- package/dist/adapters/bun.d.ts +9 -0
- package/dist/adapters/bun.d.ts.map +1 -0
- package/dist/adapters/bun.js +15 -0
- package/dist/adapters/bun.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +10 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -0
- package/dist/adapters/cloudflare.js +14 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/deno.d.ts +7 -0
- package/dist/adapters/deno.d.ts.map +1 -0
- package/dist/adapters/deno.js +11 -0
- package/dist/adapters/deno.js.map +1 -0
- package/dist/adapters/fastly.d.ts +6 -0
- package/dist/adapters/fastly.d.ts.map +1 -0
- package/dist/adapters/fastly.js +12 -0
- package/dist/adapters/fastly.js.map +1 -0
- package/dist/adapters/fetch.d.ts +30 -0
- package/dist/adapters/fetch.d.ts.map +1 -0
- package/dist/adapters/fetch.js +119 -0
- package/dist/adapters/fetch.js.map +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +28 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/netlify.d.ts +8 -0
- package/dist/adapters/netlify.d.ts.map +1 -0
- package/dist/adapters/netlify.js +12 -0
- package/dist/adapters/netlify.js.map +1 -0
- package/dist/adapters/vercel.d.ts +14 -0
- package/dist/adapters/vercel.d.ts.map +1 -0
- package/dist/adapters/vercel.js +20 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/auth/jwt.d.ts +20 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/jwt.js +79 -0
- package/dist/auth/jwt.js.map +1 -0
- package/dist/auth/password.d.ts +25 -0
- package/dist/auth/password.d.ts.map +1 -0
- package/dist/auth/password.js +93 -0
- package/dist/auth/password.js.map +1 -0
- package/dist/auth/rbac.d.ts +10 -0
- package/dist/auth/rbac.d.ts.map +1 -0
- package/dist/auth/rbac.js +34 -0
- package/dist/auth/rbac.js.map +1 -0
- package/dist/auth/session.d.ts +23 -0
- package/dist/auth/session.d.ts.map +1 -0
- package/dist/auth/session.js +105 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +9 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/store.d.ts +48 -0
- package/dist/cache/store.d.ts.map +1 -0
- package/dist/cache/store.js +211 -0
- package/dist/cache/store.js.map +1 -0
- package/dist/cli/commands/build.d.ts +7 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +183 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +9 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +290 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/exports.d.ts +2 -0
- package/dist/cli/commands/exports.d.ts.map +1 -0
- package/dist/cli/commands/exports.js +76 -0
- package/dist/cli/commands/exports.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +5 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +149 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +37 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/repl.d.ts +6 -0
- package/dist/cli/commands/repl.d.ts.map +1 -0
- package/dist/cli/commands/repl.js +75 -0
- package/dist/cli/commands/repl.js.map +1 -0
- package/dist/cli/commands/start.d.ts +7 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +111 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/test.d.ts +7 -0
- package/dist/cli/commands/test.d.ts.map +1 -0
- package/dist/cli/commands/test.js +170 -0
- package/dist/cli/commands/test.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +183 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/manifest.d.ts +2 -0
- package/dist/cli/manifest.d.ts.map +1 -0
- package/dist/cli/manifest.js +160 -0
- package/dist/cli/manifest.js.map +1 -0
- package/dist/cli/optimizers/aot-routes.d.ts +2 -0
- package/dist/cli/optimizers/aot-routes.d.ts.map +1 -0
- package/dist/cli/optimizers/aot-routes.js +59 -0
- package/dist/cli/optimizers/aot-routes.js.map +1 -0
- package/dist/cli/optimizers/precompile-serializers.d.ts +2 -0
- package/dist/cli/optimizers/precompile-serializers.d.ts.map +1 -0
- package/dist/cli/optimizers/precompile-serializers.js +68 -0
- package/dist/cli/optimizers/precompile-serializers.js.map +1 -0
- package/dist/cli/optimizers/tree-shake.d.ts +2 -0
- package/dist/cli/optimizers/tree-shake.d.ts.map +1 -0
- package/dist/cli/optimizers/tree-shake.js +64 -0
- package/dist/cli/optimizers/tree-shake.js.map +1 -0
- package/dist/cli/routes.d.ts +2 -0
- package/dist/cli/routes.d.ts.map +1 -0
- package/dist/cli/routes.js +158 -0
- package/dist/cli/routes.js.map +1 -0
- package/dist/cli/utils.d.ts +24 -0
- package/dist/cli/utils.d.ts.map +1 -0
- package/dist/cli/utils.js +54 -0
- package/dist/cli/utils.js.map +1 -0
- package/dist/config/constants.d.ts +11 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +14 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/cron/parser.d.ts +2 -0
- package/dist/cron/parser.d.ts.map +1 -0
- package/dist/cron/parser.js +52 -0
- package/dist/cron/parser.js.map +1 -0
- package/dist/cron/scheduler.d.ts +22 -0
- package/dist/cron/scheduler.d.ts.map +1 -0
- package/dist/cron/scheduler.js +102 -0
- package/dist/cron/scheduler.js.map +1 -0
- package/dist/dataloader/dataloader.d.ts +23 -0
- package/dist/dataloader/dataloader.d.ts.map +1 -0
- package/dist/dataloader/dataloader.js +98 -0
- package/dist/dataloader/dataloader.js.map +1 -0
- package/dist/dataloader/registry.d.ts +17 -0
- package/dist/dataloader/registry.d.ts.map +1 -0
- package/dist/dataloader/registry.js +47 -0
- package/dist/dataloader/registry.js.map +1 -0
- package/dist/decorators/index.d.ts +63 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +460 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/di/container.d.ts +21 -0
- package/dist/di/container.d.ts.map +1 -0
- package/dist/di/container.js +81 -0
- package/dist/di/container.js.map +1 -0
- package/dist/di/inject.d.ts +3 -0
- package/dist/di/inject.d.ts.map +1 -0
- package/dist/di/inject.js +12 -0
- package/dist/di/inject.js.map +1 -0
- package/dist/exports/auth.d.ts +6 -0
- package/dist/exports/auth.d.ts.map +1 -0
- package/dist/exports/auth.js +46 -0
- package/dist/exports/auth.js.map +1 -0
- package/dist/exports/cache.d.ts +4 -0
- package/dist/exports/cache.d.ts.map +1 -0
- package/dist/exports/cache.js +20 -0
- package/dist/exports/cache.js.map +1 -0
- package/dist/exports/config.d.ts +4 -0
- package/dist/exports/config.d.ts.map +1 -0
- package/dist/exports/config.js +23 -0
- package/dist/exports/config.js.map +1 -0
- package/dist/exports/dataloader.d.ts +3 -0
- package/dist/exports/dataloader.d.ts.map +1 -0
- package/dist/exports/dataloader.js +19 -0
- package/dist/exports/dataloader.js.map +1 -0
- package/dist/exports/decorators.d.ts +2 -0
- package/dist/exports/decorators.d.ts.map +1 -0
- package/dist/exports/decorators.js +18 -0
- package/dist/exports/decorators.js.map +1 -0
- package/dist/exports/di.d.ts +4 -0
- package/dist/exports/di.d.ts.map +1 -0
- package/dist/exports/di.js +8 -0
- package/dist/exports/di.js.map +1 -0
- package/dist/exports/error.d.ts +2 -0
- package/dist/exports/error.d.ts.map +1 -0
- package/dist/exports/error.js +16 -0
- package/dist/exports/error.js.map +1 -0
- package/dist/exports/module.d.ts +3 -0
- package/dist/exports/module.d.ts.map +1 -0
- package/dist/exports/module.js +6 -0
- package/dist/exports/module.js.map +1 -0
- package/dist/exports/plugin.d.ts +3 -0
- package/dist/exports/plugin.d.ts.map +1 -0
- package/dist/exports/plugin.js +6 -0
- package/dist/exports/plugin.js.map +1 -0
- package/dist/exports/queue.d.ts +9 -0
- package/dist/exports/queue.d.ts.map +1 -0
- package/dist/exports/queue.js +16 -0
- package/dist/exports/queue.js.map +1 -0
- package/dist/exports/response.d.ts +2 -0
- package/dist/exports/response.d.ts.map +1 -0
- package/dist/exports/response.js +18 -0
- package/dist/exports/response.js.map +1 -0
- package/dist/exports/route.d.ts +14 -0
- package/dist/exports/route.d.ts.map +1 -0
- package/dist/exports/route.js +25 -0
- package/dist/exports/route.js.map +1 -0
- package/dist/exports/security.d.ts +3 -0
- package/dist/exports/security.d.ts.map +1 -0
- package/dist/exports/security.js +13 -0
- package/dist/exports/security.js.map +1 -0
- package/dist/exports/swagger.d.ts +3 -0
- package/dist/exports/swagger.d.ts.map +1 -0
- package/dist/exports/swagger.js +6 -0
- package/dist/exports/swagger.js.map +1 -0
- package/dist/exports/validation.d.ts +2 -0
- package/dist/exports/validation.d.ts.map +1 -0
- package/dist/exports/validation.js +8 -0
- package/dist/exports/validation.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/drizzle.d.ts +10 -0
- package/dist/integrations/drizzle.d.ts.map +1 -0
- package/dist/integrations/drizzle.js +34 -0
- package/dist/integrations/drizzle.js.map +1 -0
- package/dist/integrations/jwt.d.ts +12 -0
- package/dist/integrations/jwt.d.ts.map +1 -0
- package/dist/integrations/jwt.js +27 -0
- package/dist/integrations/jwt.js.map +1 -0
- package/dist/integrations/mongodb.d.ts +13 -0
- package/dist/integrations/mongodb.d.ts.map +1 -0
- package/dist/integrations/mongodb.js +48 -0
- package/dist/integrations/mongodb.js.map +1 -0
- package/dist/integrations/mongoose.d.ts +13 -0
- package/dist/integrations/mongoose.d.ts.map +1 -0
- package/dist/integrations/mongoose.js +58 -0
- package/dist/integrations/mongoose.js.map +1 -0
- package/dist/integrations/openai.d.ts +12 -0
- package/dist/integrations/openai.d.ts.map +1 -0
- package/dist/integrations/openai.js +47 -0
- package/dist/integrations/openai.js.map +1 -0
- package/dist/integrations/postgres.d.ts +13 -0
- package/dist/integrations/postgres.d.ts.map +1 -0
- package/dist/integrations/postgres.js +55 -0
- package/dist/integrations/postgres.js.map +1 -0
- package/dist/integrations/posthog.d.ts +13 -0
- package/dist/integrations/posthog.d.ts.map +1 -0
- package/dist/integrations/posthog.js +49 -0
- package/dist/integrations/posthog.js.map +1 -0
- package/dist/integrations/prisma.d.ts +10 -0
- package/dist/integrations/prisma.d.ts.map +1 -0
- package/dist/integrations/prisma.js +27 -0
- package/dist/integrations/prisma.js.map +1 -0
- package/dist/integrations/redis.d.ts +12 -0
- package/dist/integrations/redis.d.ts.map +1 -0
- package/dist/integrations/redis.js +48 -0
- package/dist/integrations/redis.js.map +1 -0
- package/dist/integrations/resend.d.ts +12 -0
- package/dist/integrations/resend.d.ts.map +1 -0
- package/dist/integrations/resend.js +47 -0
- package/dist/integrations/resend.js.map +1 -0
- package/dist/integrations/s3.d.ts +15 -0
- package/dist/integrations/s3.d.ts.map +1 -0
- package/dist/integrations/s3.js +60 -0
- package/dist/integrations/s3.js.map +1 -0
- package/dist/integrations/supabase.d.ts +13 -0
- package/dist/integrations/supabase.d.ts.map +1 -0
- package/dist/integrations/supabase.js +51 -0
- package/dist/integrations/supabase.js.map +1 -0
- package/dist/lib/start-repl.d.ts +2 -0
- package/dist/lib/start-repl.d.ts.map +1 -0
- package/dist/lib/start-repl.js +122 -0
- package/dist/lib/start-repl.js.map +1 -0
- package/dist/lib/start-server.d.ts +2 -0
- package/dist/lib/start-server.d.ts.map +1 -0
- package/dist/lib/start-server.js +103 -0
- package/dist/lib/start-server.js.map +1 -0
- package/dist/middleware/backpressure.d.ts +11 -0
- package/dist/middleware/backpressure.d.ts.map +1 -0
- package/dist/middleware/backpressure.js +55 -0
- package/dist/middleware/backpressure.js.map +1 -0
- package/dist/middleware/cache.d.ts +10 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +97 -0
- package/dist/middleware/cache.js.map +1 -0
- package/dist/middleware/compression.d.ts +3 -0
- package/dist/middleware/compression.d.ts.map +1 -0
- package/dist/middleware/compression.js +132 -0
- package/dist/middleware/compression.js.map +1 -0
- package/dist/middleware/dedupe.d.ts +10 -0
- package/dist/middleware/dedupe.d.ts.map +1 -0
- package/dist/middleware/dedupe.js +79 -0
- package/dist/middleware/dedupe.js.map +1 -0
- package/dist/middleware/exception-filter.d.ts +9 -0
- package/dist/middleware/exception-filter.d.ts.map +1 -0
- package/dist/middleware/exception-filter.js +26 -0
- package/dist/middleware/exception-filter.js.map +1 -0
- package/dist/middleware/guard.d.ts +11 -0
- package/dist/middleware/guard.d.ts.map +1 -0
- package/dist/middleware/guard.js +26 -0
- package/dist/middleware/guard.js.map +1 -0
- package/dist/middleware/interceptor.d.ts +4 -0
- package/dist/middleware/interceptor.d.ts.map +1 -0
- package/dist/middleware/interceptor.js +32 -0
- package/dist/middleware/interceptor.js.map +1 -0
- package/dist/middleware/ip-filter.d.ts +11 -0
- package/dist/middleware/ip-filter.d.ts.map +1 -0
- package/dist/middleware/ip-filter.js +27 -0
- package/dist/middleware/ip-filter.js.map +1 -0
- package/dist/middleware/middleware.d.ts +26 -0
- package/dist/middleware/middleware.d.ts.map +1 -0
- package/dist/middleware/middleware.js +271 -0
- package/dist/middleware/middleware.js.map +1 -0
- package/dist/middleware/pipe.d.ts +9 -0
- package/dist/middleware/pipe.d.ts.map +1 -0
- package/dist/middleware/pipe.js +24 -0
- package/dist/middleware/pipe.js.map +1 -0
- package/dist/middleware/security.d.ts +53 -0
- package/dist/middleware/security.d.ts.map +1 -0
- package/dist/middleware/security.js +257 -0
- package/dist/middleware/security.js.map +1 -0
- package/dist/module/module.d.ts +17 -0
- package/dist/module/module.d.ts.map +1 -0
- package/dist/module/module.js +37 -0
- package/dist/module/module.js.map +1 -0
- package/dist/observability/health.d.ts +23 -0
- package/dist/observability/health.d.ts.map +1 -0
- package/dist/observability/health.js +49 -0
- package/dist/observability/health.js.map +1 -0
- package/dist/observability/index.d.ts +4 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +10 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/otel.d.ts +27 -0
- package/dist/observability/otel.d.ts.map +1 -0
- package/dist/observability/otel.js +45 -0
- package/dist/observability/otel.js.map +1 -0
- package/dist/observability/prometheus.d.ts +30 -0
- package/dist/observability/prometheus.d.ts.map +1 -0
- package/dist/observability/prometheus.js +31 -0
- package/dist/observability/prometheus.js.map +1 -0
- package/dist/queue/client.d.ts +13 -0
- package/dist/queue/client.d.ts.map +1 -0
- package/dist/queue/client.js +64 -0
- package/dist/queue/client.js.map +1 -0
- package/dist/queue/drivers/MemoryDriver.d.ts +17 -0
- package/dist/queue/drivers/MemoryDriver.d.ts.map +1 -0
- package/dist/queue/drivers/MemoryDriver.js +109 -0
- package/dist/queue/drivers/MemoryDriver.js.map +1 -0
- package/dist/queue/drivers/RedisDriver.d.ts +18 -0
- package/dist/queue/drivers/RedisDriver.d.ts.map +1 -0
- package/dist/queue/drivers/RedisDriver.js +120 -0
- package/dist/queue/drivers/RedisDriver.js.map +1 -0
- package/dist/queue/index.d.ts +12 -0
- package/dist/queue/index.d.ts.map +1 -0
- package/dist/queue/index.js +27 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/types.d.ts +51 -0
- package/dist/queue/types.d.ts.map +1 -0
- package/dist/queue/types.js +3 -0
- package/dist/queue/types.js.map +1 -0
- package/dist/queue/worker.d.ts +18 -0
- package/dist/queue/worker.d.ts.map +1 -0
- package/dist/queue/worker.js +160 -0
- package/dist/queue/worker.js.map +1 -0
- package/dist/response/index.d.ts +16 -0
- package/dist/response/index.d.ts.map +1 -0
- package/dist/response/index.js +19 -0
- package/dist/response/index.js.map +1 -0
- package/dist/router/gateway.d.ts +64 -0
- package/dist/router/gateway.d.ts.map +1 -0
- package/dist/router/gateway.js +11 -0
- package/dist/router/gateway.js.map +1 -0
- package/dist/router/radix.d.ts +42 -0
- package/dist/router/radix.d.ts.map +1 -0
- package/dist/router/radix.js +523 -0
- package/dist/router/radix.js.map +1 -0
- package/dist/router/router.d.ts +59 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/router.js +386 -0
- package/dist/router/router.js.map +1 -0
- package/dist/server/app.d.ts +206 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +2191 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/cluster.d.ts +52 -0
- package/dist/server/cluster.d.ts.map +1 -0
- package/dist/server/cluster.js +165 -0
- package/dist/server/cluster.js.map +1 -0
- package/dist/server/context.d.ts +40 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +74 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/define.d.ts +14 -0
- package/dist/server/define.d.ts.map +1 -0
- package/dist/server/define.js +22 -0
- package/dist/server/define.js.map +1 -0
- package/dist/server/dep-graph.d.ts +2 -0
- package/dist/server/dep-graph.d.ts.map +1 -0
- package/dist/server/dep-graph.js +109 -0
- package/dist/server/dep-graph.js.map +1 -0
- package/dist/server/dev-error-overlay.d.ts +3 -0
- package/dist/server/dev-error-overlay.d.ts.map +1 -0
- package/dist/server/dev-error-overlay.js +116 -0
- package/dist/server/dev-error-overlay.js.map +1 -0
- package/dist/server/hot-reload.d.ts +28 -0
- package/dist/server/hot-reload.d.ts.map +1 -0
- package/dist/server/hot-reload.js +253 -0
- package/dist/server/hot-reload.js.map +1 -0
- package/dist/server/request.d.ts +71 -0
- package/dist/server/request.d.ts.map +1 -0
- package/dist/server/request.js +408 -0
- package/dist/server/request.js.map +1 -0
- package/dist/server/response.d.ts +36 -0
- package/dist/server/response.d.ts.map +1 -0
- package/dist/server/response.js +254 -0
- package/dist/server/response.js.map +1 -0
- package/dist/server/route.d.ts +148 -0
- package/dist/server/route.d.ts.map +1 -0
- package/dist/server/route.js +56 -0
- package/dist/server/route.js.map +1 -0
- package/dist/server/sse.d.ts +16 -0
- package/dist/server/sse.d.ts.map +1 -0
- package/dist/server/sse.js +51 -0
- package/dist/server/sse.js.map +1 -0
- package/dist/server/uws-adapter.d.ts +101 -0
- package/dist/server/uws-adapter.d.ts.map +1 -0
- package/dist/server/uws-adapter.js +338 -0
- package/dist/server/uws-adapter.js.map +1 -0
- package/dist/swagger/index.d.ts +9 -0
- package/dist/swagger/index.d.ts.map +1 -0
- package/dist/swagger/index.js +147 -0
- package/dist/swagger/index.js.map +1 -0
- package/dist/testing/client.d.ts +44 -0
- package/dist/testing/client.d.ts.map +1 -0
- package/dist/testing/client.js +105 -0
- package/dist/testing/client.js.map +1 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +128 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mocks.d.ts +25 -0
- package/dist/testing/mocks.d.ts.map +1 -0
- package/dist/testing/mocks.js +117 -0
- package/dist/testing/mocks.js.map +1 -0
- package/dist/testing/reporter.d.ts +13 -0
- package/dist/testing/reporter.d.ts.map +1 -0
- package/dist/testing/reporter.js +101 -0
- package/dist/testing/reporter.js.map +1 -0
- package/dist/threads/pool.d.ts +13 -0
- package/dist/threads/pool.d.ts.map +1 -0
- package/dist/threads/pool.js +93 -0
- package/dist/threads/pool.js.map +1 -0
- package/dist/threads/worker-runner.d.ts +2 -0
- package/dist/threads/worker-runner.d.ts.map +1 -0
- package/dist/threads/worker-runner.js +71 -0
- package/dist/threads/worker-runner.js.map +1 -0
- package/dist/types.d.ts +200 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/circuit-breaker.d.ts +30 -0
- package/dist/utils/circuit-breaker.d.ts.map +1 -0
- package/dist/utils/circuit-breaker.js +72 -0
- package/dist/utils/circuit-breaker.js.map +1 -0
- package/dist/utils/config.d.ts +9 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +229 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/env.d.ts +25 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +155 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/errors.d.ts +51 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +233 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +70 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/plugin.d.ts +7 -0
- package/dist/utils/plugin.d.ts.map +1 -0
- package/dist/utils/plugin.js +30 -0
- package/dist/utils/plugin.js.map +1 -0
- package/dist/utils/validator.d.ts +300 -0
- package/dist/utils/validator.d.ts.map +1 -0
- package/dist/utils/validator.js +719 -0
- package/dist/utils/validator.js.map +1 -0
- package/dist/websocket/server.d.ts +66 -0
- package/dist/websocket/server.d.ts.map +1 -0
- package/dist/websocket/server.js +156 -0
- package/dist/websocket/server.js.map +1 -0
- package/dist/websocket/socket.d.ts +119 -0
- package/dist/websocket/socket.d.ts.map +1 -0
- package/dist/websocket/socket.js +240 -0
- package/dist/websocket/socket.js.map +1 -0
- package/package.json +270 -0
|
@@ -0,0 +1,2191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.activeAppInstance = exports.app = exports.App = void 0;
|
|
37
|
+
exports.getActiveApp = getActiveApp;
|
|
38
|
+
const node_http_1 = require("node:http");
|
|
39
|
+
const node_https_1 = require("node:https");
|
|
40
|
+
const node_http2_1 = require("node:http2");
|
|
41
|
+
const ws_1 = require("ws");
|
|
42
|
+
const node_url_1 = require("node:url");
|
|
43
|
+
const validator_1 = require("../utils/validator");
|
|
44
|
+
const request_1 = require("./request");
|
|
45
|
+
const response_1 = require("./response");
|
|
46
|
+
const context_1 = require("./context");
|
|
47
|
+
const router_1 = require("../router/router");
|
|
48
|
+
const middleware_1 = require("../middleware/middleware");
|
|
49
|
+
const errors_1 = require("../utils/errors");
|
|
50
|
+
const server_1 = require("../websocket/server");
|
|
51
|
+
const socket_1 = require("../websocket/socket");
|
|
52
|
+
const config_1 = require("../utils/config");
|
|
53
|
+
const logger_1 = require("../utils/logger");
|
|
54
|
+
const hot_reload_1 = require("./hot-reload");
|
|
55
|
+
const dev_error_overlay_1 = require("./dev-error-overlay");
|
|
56
|
+
const uws_adapter_1 = require("./uws-adapter");
|
|
57
|
+
const client_1 = require("../queue/client");
|
|
58
|
+
const worker_1 = require("../queue/worker");
|
|
59
|
+
const scheduler_1 = require("../cron/scheduler");
|
|
60
|
+
const container_1 = require("../di/container");
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
62
|
+
class App {
|
|
63
|
+
get queueClient() {
|
|
64
|
+
return this._queueClient;
|
|
65
|
+
}
|
|
66
|
+
get queueWorker() {
|
|
67
|
+
return this._queueWorker;
|
|
68
|
+
}
|
|
69
|
+
constructor(options = {}) {
|
|
70
|
+
this.redirectServer = null;
|
|
71
|
+
this.apiDir = null;
|
|
72
|
+
this.globalMiddleware = [];
|
|
73
|
+
this.errorHandlers = [];
|
|
74
|
+
this._configured = false;
|
|
75
|
+
this._routesMounted = false;
|
|
76
|
+
this._loggerCreated = false;
|
|
77
|
+
this.registeredPlugins = new Map();
|
|
78
|
+
this.routeMap = new Map();
|
|
79
|
+
this.hotReloader = null;
|
|
80
|
+
this.lazyRoutes = new Map();
|
|
81
|
+
this.wsServer = new server_1.ExisWebSocketServer();
|
|
82
|
+
this.rawWsServer = new ws_1.WebSocketServer({ noServer: true });
|
|
83
|
+
// ─── uWebSockets.js Backend ──────────────────────────────────────────────────
|
|
84
|
+
this._useUws = false;
|
|
85
|
+
this._uwsApp = null;
|
|
86
|
+
this._uwsListenToken = null;
|
|
87
|
+
// ─── Lifecycle Hooks Registry ───────────────────────────────────────────────
|
|
88
|
+
this.hooks = {
|
|
89
|
+
ready: [],
|
|
90
|
+
close: [],
|
|
91
|
+
request: [],
|
|
92
|
+
response: [],
|
|
93
|
+
error: [],
|
|
94
|
+
route: [],
|
|
95
|
+
};
|
|
96
|
+
// ─── Dataloaders Registry ───────────────────────────────────────────────────
|
|
97
|
+
this._dataloaders = new Map();
|
|
98
|
+
this.explicitOptions = {};
|
|
99
|
+
// ─── Dependency Injection ───────────────────────────────────────────────────
|
|
100
|
+
this.container = new container_1.Container();
|
|
101
|
+
// ─── Queue ──────────────────────────────────────────────────────────────────
|
|
102
|
+
this._queueClient = null;
|
|
103
|
+
this._queueWorker = null;
|
|
104
|
+
this._cronScheduler = null;
|
|
105
|
+
this._pendingQueueJobs = [];
|
|
106
|
+
this.shutdownHooks = [];
|
|
107
|
+
// ─── App Initialization ────────────────────────────────────────────────────────
|
|
108
|
+
this._configLoaded = false;
|
|
109
|
+
this.explicitOptions = options;
|
|
110
|
+
this.options = (0, config_1.mergeConfig)(config_1.defaultConfig, options);
|
|
111
|
+
this.router = new router_1.Router();
|
|
112
|
+
this.ensureLogger();
|
|
113
|
+
// Queue setup is now deferred to app.create() when the config is loaded
|
|
114
|
+
if (this.options.queue) {
|
|
115
|
+
this._initQueue(this.options.queue);
|
|
116
|
+
}
|
|
117
|
+
// Auto-detect uWebSockets.js backend
|
|
118
|
+
if (options.server === 'uws' ||
|
|
119
|
+
(options.server !== 'node' &&
|
|
120
|
+
(0, uws_adapter_1.isUwsAvailable)() &&
|
|
121
|
+
this.options.env !== 'test')) {
|
|
122
|
+
this._useUws = true;
|
|
123
|
+
// Don't init Node server — we'll create the uWS app at listen() time
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
this._useUws = false;
|
|
127
|
+
this.server = this._initServer();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
ensureLogger() {
|
|
131
|
+
if (this._loggerCreated)
|
|
132
|
+
return;
|
|
133
|
+
this._loggerCreated = true;
|
|
134
|
+
const loggerOptions = (0, logger_1.resolveLoggerConfig)(this.options.logger);
|
|
135
|
+
if (process.env.__EXIS_REPL || process.env.__EXIS_TEST) {
|
|
136
|
+
loggerOptions.level = 'warn';
|
|
137
|
+
}
|
|
138
|
+
this.log = (0, logger_1.createLogger)(loggerOptions);
|
|
139
|
+
}
|
|
140
|
+
_initServer() {
|
|
141
|
+
let server;
|
|
142
|
+
if (this.options.ssl) {
|
|
143
|
+
if (this.options.http2 !== false) {
|
|
144
|
+
server = (0, node_http2_1.createSecureServer)({ allowHTTP1: true, ...this.options.ssl }, this.handle.bind(this));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
server = (0, node_https_1.createServer)(this.options.ssl, this.handle.bind(this));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
server = (0, node_http_1.createServer)(this.handle.bind(this));
|
|
152
|
+
}
|
|
153
|
+
server.on('upgrade', this._handleUpgrade.bind(this));
|
|
154
|
+
const keepAlive = this.options.keepAlive;
|
|
155
|
+
if (keepAlive) {
|
|
156
|
+
const kaConfig = keepAlive === true ? {} : keepAlive;
|
|
157
|
+
if ('keepAliveTimeout' in server)
|
|
158
|
+
server.keepAliveTimeout = kaConfig.timeoutMs ?? 5000;
|
|
159
|
+
if ('headersTimeout' in server)
|
|
160
|
+
server.headersTimeout = kaConfig.headersTimeoutMs ?? 60000;
|
|
161
|
+
if (kaConfig.maxRequests !== undefined &&
|
|
162
|
+
'maxRequestsPerSocket' in server) {
|
|
163
|
+
;
|
|
164
|
+
server.maxRequestsPerSocket = kaConfig.maxRequests;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return server;
|
|
168
|
+
}
|
|
169
|
+
// ─── Lifecycle Hooks Registration ───────────────────────────────────────────
|
|
170
|
+
onReady(cb) {
|
|
171
|
+
this.hooks.ready.push(cb);
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
onClose(cb) {
|
|
175
|
+
this.hooks.close.push(cb);
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
onRequest(cb) {
|
|
179
|
+
this.hooks.request.push(cb);
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
onResponse(cb) {
|
|
183
|
+
this.hooks.response.push(cb);
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
onError(cb) {
|
|
187
|
+
this.hooks.error.push(cb);
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
onRoute(cb) {
|
|
191
|
+
this.hooks.route.push(cb);
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
// ─── Dataloaders ────────────────────────────────────────────────────────────
|
|
195
|
+
dataloader(name, batchFn, options) {
|
|
196
|
+
if (this._dataloaders.has(name)) {
|
|
197
|
+
throw new Error(`Dataloader '${name}' is already registered`);
|
|
198
|
+
}
|
|
199
|
+
this._dataloaders.set(name, { batchFn, options });
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
// ─── Introspection ──────────────────────────────────────────────────────────
|
|
203
|
+
getRoutes() {
|
|
204
|
+
return this.router.getRoutes();
|
|
205
|
+
}
|
|
206
|
+
// ─── Plugin System ──────────────────────────────────────────────────────────
|
|
207
|
+
hasPlugin(name) {
|
|
208
|
+
return this.registeredPlugins.has(name);
|
|
209
|
+
}
|
|
210
|
+
async register(pluginOrInstance, legacyOptions) {
|
|
211
|
+
let plugin;
|
|
212
|
+
let options;
|
|
213
|
+
if ('plugin' in pluginOrInstance && !('register' in pluginOrInstance)) {
|
|
214
|
+
plugin = pluginOrInstance.plugin;
|
|
215
|
+
options = pluginOrInstance.options;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
plugin = pluginOrInstance;
|
|
219
|
+
options = legacyOptions;
|
|
220
|
+
}
|
|
221
|
+
if (this.registeredPlugins.has(plugin.name)) {
|
|
222
|
+
throw new Error(`Plugin '${plugin.name}' is already registered`);
|
|
223
|
+
}
|
|
224
|
+
if (plugin.dependencies) {
|
|
225
|
+
for (const dep of plugin.dependencies) {
|
|
226
|
+
if (!this.registeredPlugins.has(dep)) {
|
|
227
|
+
throw new Error(`Plugin '${plugin.name}' requires dependency '${dep}' which is not registered`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
232
|
+
let targetApp = this;
|
|
233
|
+
let pluginRouter;
|
|
234
|
+
if (plugin.encapsulate !== false) {
|
|
235
|
+
pluginRouter = new router_1.Router();
|
|
236
|
+
// Create a proxy that intercepts route and middleware registrations
|
|
237
|
+
targetApp = new Proxy(this, {
|
|
238
|
+
get(target, prop, receiver) {
|
|
239
|
+
if ([
|
|
240
|
+
'get',
|
|
241
|
+
'post',
|
|
242
|
+
'put',
|
|
243
|
+
'patch',
|
|
244
|
+
'delete',
|
|
245
|
+
'httpOptions',
|
|
246
|
+
'head',
|
|
247
|
+
'all',
|
|
248
|
+
'ws',
|
|
249
|
+
'query',
|
|
250
|
+
'connect',
|
|
251
|
+
'trace',
|
|
252
|
+
].includes(prop)) {
|
|
253
|
+
return (path, ...handlers) => {
|
|
254
|
+
const routerMethod = prop === 'httpOptions' ? 'options' : prop;
|
|
255
|
+
pluginRouter[routerMethod](path, ...handlers);
|
|
256
|
+
return receiver;
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (prop === 'use') {
|
|
260
|
+
return (...handlers) => {
|
|
261
|
+
for (const h of handlers) {
|
|
262
|
+
if (h.length === 4) {
|
|
263
|
+
// error handlers stay global for now, or we could scope them
|
|
264
|
+
target.use(h);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
pluginRouter.use(h);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return receiver;
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
// Encapsulate lifecycle hooks by converting them to scoped middleware!
|
|
274
|
+
if (['onRequest', 'onResponse'].includes(prop)) {
|
|
275
|
+
return (cb) => {
|
|
276
|
+
if (prop === 'onRequest') {
|
|
277
|
+
pluginRouter.use(async (req, res, next) => {
|
|
278
|
+
try {
|
|
279
|
+
await cb(req, res);
|
|
280
|
+
next();
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
next(err);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
else if (prop === 'onResponse') {
|
|
288
|
+
pluginRouter.use((req, res, next) => {
|
|
289
|
+
res.raw.on('finish', () => {
|
|
290
|
+
cb(req, res).catch(() => {
|
|
291
|
+
/* ignore */
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
next();
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return receiver;
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const value = Reflect.get(target, prop, receiver);
|
|
301
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
await plugin.register(targetApp, options);
|
|
306
|
+
if (pluginRouter) {
|
|
307
|
+
this.mount('/', pluginRouter);
|
|
308
|
+
}
|
|
309
|
+
this.registeredPlugins.set(plugin.name, plugin);
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
// ─── Middleware ─────────────────────────────────────────────────────────────
|
|
313
|
+
use(...handlers) {
|
|
314
|
+
for (const h of handlers) {
|
|
315
|
+
if (h.length === 4) {
|
|
316
|
+
this.errorHandlers.push(h);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
this.globalMiddleware.push(h);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return this;
|
|
323
|
+
}
|
|
324
|
+
get(path, ...handlers) {
|
|
325
|
+
this.router.get(path, ...handlers);
|
|
326
|
+
this.hooks.route.forEach((hook) => hook({ method: 'GET', path }));
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
post(path, ...handlers) {
|
|
330
|
+
this.router.post(path, ...handlers);
|
|
331
|
+
this.hooks.route.forEach((hook) => hook({ method: 'POST', path }));
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
put(path, ...handlers) {
|
|
335
|
+
this.router.put(path, ...handlers);
|
|
336
|
+
this.hooks.route.forEach((hook) => hook({ method: 'PUT', path }));
|
|
337
|
+
return this;
|
|
338
|
+
}
|
|
339
|
+
patch(path, ...handlers) {
|
|
340
|
+
this.router.patch(path, ...handlers);
|
|
341
|
+
this.hooks.route.forEach((hook) => hook({ method: 'PATCH', path }));
|
|
342
|
+
return this;
|
|
343
|
+
}
|
|
344
|
+
delete(path, ...handlers) {
|
|
345
|
+
this.router.delete(path, ...handlers);
|
|
346
|
+
this.hooks.route.forEach((hook) => hook({ method: 'DELETE', path }));
|
|
347
|
+
return this;
|
|
348
|
+
}
|
|
349
|
+
httpOptions(path, ...handlers) {
|
|
350
|
+
this.router.options(path, ...handlers);
|
|
351
|
+
this.hooks.route.forEach((hook) => hook({ method: 'OPTIONS', path }));
|
|
352
|
+
return this;
|
|
353
|
+
}
|
|
354
|
+
all(path, ...handlers) {
|
|
355
|
+
this.router.all(path, ...handlers);
|
|
356
|
+
this.hooks.route.forEach((hook) => hook({ method: 'ALL', path }));
|
|
357
|
+
return this;
|
|
358
|
+
}
|
|
359
|
+
head(path, ...handlers) {
|
|
360
|
+
this.router.head(path, ...handlers);
|
|
361
|
+
this.hooks.route.forEach((hook) => hook({ method: 'HEAD', path }));
|
|
362
|
+
return this;
|
|
363
|
+
}
|
|
364
|
+
connect(path, ...handlers) {
|
|
365
|
+
this.router.connect(path, ...handlers);
|
|
366
|
+
this.hooks.route.forEach((hook) => hook({ method: 'CONNECT', path }));
|
|
367
|
+
return this;
|
|
368
|
+
}
|
|
369
|
+
trace(path, ...handlers) {
|
|
370
|
+
this.router.trace(path, ...handlers);
|
|
371
|
+
this.hooks.route.forEach((hook) => hook({ method: 'TRACE', path }));
|
|
372
|
+
return this;
|
|
373
|
+
}
|
|
374
|
+
query(path, ...handlers) {
|
|
375
|
+
this.router.query(path, ...handlers);
|
|
376
|
+
this.hooks.route.forEach((hook) => hook({ method: 'QUERY', path }));
|
|
377
|
+
return this;
|
|
378
|
+
}
|
|
379
|
+
// ─── WebSocket Registration ──────────────────────────────────────────────────
|
|
380
|
+
ws(path, ...handlers) {
|
|
381
|
+
this.router.ws(path, ...handlers);
|
|
382
|
+
this.hooks.route.forEach((hook) => hook({ method: 'WS', path }));
|
|
383
|
+
return this;
|
|
384
|
+
}
|
|
385
|
+
publish(room, data) {
|
|
386
|
+
this.wsServer.publish(room, data);
|
|
387
|
+
}
|
|
388
|
+
// ─── Mount Sub-Router ─────────────────────────────────────────────────────────
|
|
389
|
+
mount(prefix, subRouter) {
|
|
390
|
+
// copy routes from sub-router with prefix prepended
|
|
391
|
+
for (const route of subRouter.getRoutes()) {
|
|
392
|
+
const newRouter = new router_1.Router();
|
|
393
|
+
const method = route.method.toLowerCase();
|
|
394
|
+
if (method === 'all') {
|
|
395
|
+
newRouter.all(prefix + route.path, ...route.handlers);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
;
|
|
399
|
+
newRouter[method](prefix + route.path, ...route.handlers);
|
|
400
|
+
}
|
|
401
|
+
for (const r of newRouter.getRoutes()) {
|
|
402
|
+
this.router.addRawRoute(r);
|
|
403
|
+
}
|
|
404
|
+
this.hooks.route.forEach((hook) => hook({ method: route.method, path: prefix + route.path }));
|
|
405
|
+
}
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
408
|
+
// ─── Class-Based Controllers Registration ──────────────────────────────────
|
|
409
|
+
registerControllers(controllers, basePath = '', prefixMiddlewares = []) {
|
|
410
|
+
const ROUTE_REGISTRY = Symbol.for('exisjs:routes');
|
|
411
|
+
const CONTROLLER_PREFIX = Symbol.for('exisjs:controller_prefix');
|
|
412
|
+
const CONTROLLER_HOST = Symbol.for('exisjs:controller_host');
|
|
413
|
+
const MIDDLEWARE_REGISTRY = Symbol.for('exisjs:middlewares');
|
|
414
|
+
const ROUTE_METADATA = Symbol.for('exisjs:route_metadata');
|
|
415
|
+
const LIFECYCLE_METADATA = Symbol.for('exisjs:lifecycle_metadata');
|
|
416
|
+
const PARAM_METADATA = Symbol.for('exisjs:param_metadata');
|
|
417
|
+
for (const ControllerClass of controllers) {
|
|
418
|
+
const prefix = ControllerClass.prototype[CONTROLLER_PREFIX] || '';
|
|
419
|
+
const host = ControllerClass.prototype[CONTROLLER_HOST];
|
|
420
|
+
const routes = ControllerClass.prototype[ROUTE_REGISTRY] || [];
|
|
421
|
+
const classMiddlewares = [
|
|
422
|
+
...(Array.isArray(ControllerClass.prototype[MIDDLEWARE_REGISTRY])
|
|
423
|
+
? ControllerClass.prototype[MIDDLEWARE_REGISTRY]
|
|
424
|
+
: ControllerClass.prototype[MIDDLEWARE_REGISTRY]?._classMiddlewares ||
|
|
425
|
+
[]),
|
|
426
|
+
];
|
|
427
|
+
const methodMiddlewares = (!Array.isArray(ControllerClass.prototype[MIDDLEWARE_REGISTRY])
|
|
428
|
+
? ControllerClass.prototype[MIDDLEWARE_REGISTRY]
|
|
429
|
+
: {}) || {};
|
|
430
|
+
const routeMetadataMap = ControllerClass.prototype[ROUTE_METADATA] || {};
|
|
431
|
+
const lifecycleMetadataMap = ControllerClass.prototype[LIFECYCLE_METADATA] || {};
|
|
432
|
+
const paramMetadataMap = ControllerClass.prototype[PARAM_METADATA] || {};
|
|
433
|
+
for (const route of routes) {
|
|
434
|
+
const method = route.method.toLowerCase();
|
|
435
|
+
const executeCore = async (req, res, next, streamOrSocket) => {
|
|
436
|
+
let instance = this.container.resolve(ControllerClass);
|
|
437
|
+
if (!instance) {
|
|
438
|
+
// Auto-instantiate if not provided via DI
|
|
439
|
+
instance = new ControllerClass();
|
|
440
|
+
this.container.provide(ControllerClass, { useValue: instance });
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
// 1. Run Guards
|
|
444
|
+
const routeLifecycle = lifecycleMetadataMap[route.handlerName] || {};
|
|
445
|
+
const guards = routeLifecycle.guards || [];
|
|
446
|
+
for (const guard of guards) {
|
|
447
|
+
let allowed = false;
|
|
448
|
+
if (typeof guard === 'function') {
|
|
449
|
+
if (guard.prototype &&
|
|
450
|
+
typeof guard.prototype.canActivate === 'function') {
|
|
451
|
+
let guardInstance = this.container.resolve(guard);
|
|
452
|
+
if (!guardInstance) {
|
|
453
|
+
guardInstance = new guard();
|
|
454
|
+
}
|
|
455
|
+
allowed = await guardInstance.canActivate(req);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
allowed = await guard(req);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (!allowed) {
|
|
462
|
+
if (res) {
|
|
463
|
+
res.status(403).json({
|
|
464
|
+
success: false,
|
|
465
|
+
error: { code: 'FORBIDDEN', message: 'Forbidden by Guard' },
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// 2. Resolve parameters
|
|
472
|
+
const paramMetadata = paramMetadataMap[route.handlerName] || [];
|
|
473
|
+
const args = [];
|
|
474
|
+
if (paramMetadata.length === 0) {
|
|
475
|
+
if (method === 'ws' || method === 'sse') {
|
|
476
|
+
args.push(req, streamOrSocket);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
args.push(req, res, next);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
for (const param of paramMetadata) {
|
|
484
|
+
if (!param) {
|
|
485
|
+
args.push(undefined);
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
let rawArg;
|
|
489
|
+
switch (param.type) {
|
|
490
|
+
case 'req':
|
|
491
|
+
rawArg = req;
|
|
492
|
+
break;
|
|
493
|
+
case 'res':
|
|
494
|
+
rawArg = res;
|
|
495
|
+
break;
|
|
496
|
+
case 'body':
|
|
497
|
+
if (req.body === undefined &&
|
|
498
|
+
['POST', 'PUT', 'PATCH'].includes(req.method)) {
|
|
499
|
+
const contentType = req.headers['content-type'] || '';
|
|
500
|
+
if (contentType.includes('json')) {
|
|
501
|
+
await req.json().catch(() => {
|
|
502
|
+
/* noop */
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
else if (contentType.includes('form')) {
|
|
506
|
+
await req.formData().catch(() => {
|
|
507
|
+
/* noop */
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
rawArg = req.body;
|
|
512
|
+
break;
|
|
513
|
+
case 'param':
|
|
514
|
+
rawArg = param.name ? req.params[param.name] : req.params;
|
|
515
|
+
break;
|
|
516
|
+
case 'query':
|
|
517
|
+
rawArg = param.name ? req.query[param.name] : req.query;
|
|
518
|
+
break;
|
|
519
|
+
case 'header':
|
|
520
|
+
rawArg = param.name
|
|
521
|
+
? req.headers[param.name.toLowerCase()]
|
|
522
|
+
: req.headers;
|
|
523
|
+
break;
|
|
524
|
+
case 'session':
|
|
525
|
+
rawArg = req.session;
|
|
526
|
+
break;
|
|
527
|
+
case 'next':
|
|
528
|
+
rawArg = next;
|
|
529
|
+
break;
|
|
530
|
+
case 'host':
|
|
531
|
+
rawArg = param.name
|
|
532
|
+
? req.params[param.name]
|
|
533
|
+
: req.hostname;
|
|
534
|
+
break;
|
|
535
|
+
case 'socket':
|
|
536
|
+
case 'stream':
|
|
537
|
+
rawArg = streamOrSocket;
|
|
538
|
+
break;
|
|
539
|
+
case 'ip':
|
|
540
|
+
rawArg = req.ip;
|
|
541
|
+
break;
|
|
542
|
+
case 'uploadedFile':
|
|
543
|
+
case 'uploadedFiles':
|
|
544
|
+
if (req.body === undefined &&
|
|
545
|
+
['POST', 'PUT', 'PATCH'].includes(req.method)) {
|
|
546
|
+
await req.formData().catch(() => {
|
|
547
|
+
/* noop */
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
if (req.files) {
|
|
551
|
+
const isMulti = param.type === 'uploadedFiles';
|
|
552
|
+
let files = [];
|
|
553
|
+
if (param.name) {
|
|
554
|
+
files = req.files.filter((f) => f.fieldname === param.name);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
files = req.files;
|
|
558
|
+
}
|
|
559
|
+
rawArg = isMulti ? files : files[0];
|
|
560
|
+
}
|
|
561
|
+
break;
|
|
562
|
+
default:
|
|
563
|
+
rawArg = undefined;
|
|
564
|
+
}
|
|
565
|
+
if (param.pipes && param.pipes.length > 0) {
|
|
566
|
+
for (const pipe of param.pipes) {
|
|
567
|
+
if (typeof pipe === 'function' &&
|
|
568
|
+
pipe.prototype?.transform) {
|
|
569
|
+
let pipeInstance = this.container.resolve(pipe);
|
|
570
|
+
if (!pipeInstance)
|
|
571
|
+
pipeInstance = new pipe();
|
|
572
|
+
rawArg = await pipeInstance.transform(rawArg, {
|
|
573
|
+
type: param.type,
|
|
574
|
+
name: param.name,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
else if (typeof pipe === 'object' &&
|
|
578
|
+
typeof pipe.transform === 'function') {
|
|
579
|
+
rawArg = await pipe.transform(rawArg, {
|
|
580
|
+
type: param.type,
|
|
581
|
+
name: param.name,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
else if (typeof pipe === 'object' &&
|
|
585
|
+
typeof pipe.parse === 'function') {
|
|
586
|
+
rawArg = await pipe.parse(rawArg);
|
|
587
|
+
}
|
|
588
|
+
else if (typeof pipe === 'function') {
|
|
589
|
+
rawArg = await pipe(rawArg);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
args.push(rawArg);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// 3. Execute Custom Interceptors
|
|
597
|
+
const interceptors = routeLifecycle.interceptors || [];
|
|
598
|
+
for (const interceptor of interceptors) {
|
|
599
|
+
if (typeof interceptor === 'function') {
|
|
600
|
+
if (interceptor.prototype &&
|
|
601
|
+
typeof interceptor.prototype.intercept === 'function') {
|
|
602
|
+
let interceptorInstance = this.container.resolve(interceptor);
|
|
603
|
+
if (!interceptorInstance) {
|
|
604
|
+
interceptorInstance = new interceptor();
|
|
605
|
+
}
|
|
606
|
+
await interceptorInstance.intercept(req, res);
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
await interceptor(req, res);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
// 4. Invoke the method
|
|
614
|
+
const result = await instance[route.handlerName](...args);
|
|
615
|
+
if (res && !res.headersSent) {
|
|
616
|
+
const routeMeta = routeMetadataMap[route.handlerName] || {};
|
|
617
|
+
if (routeMeta.redirect) {
|
|
618
|
+
res.redirect(routeMeta.redirect.url, routeMeta.redirect.statusCode);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
// Apply custom headers
|
|
622
|
+
if (routeMeta.headers) {
|
|
623
|
+
for (const [name, val] of Object.entries(routeMeta.headers)) {
|
|
624
|
+
res.setHeader(name, val);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// Apply custom HTTP Code
|
|
628
|
+
if (routeMeta.httpCode) {
|
|
629
|
+
res.status(routeMeta.httpCode);
|
|
630
|
+
}
|
|
631
|
+
if (result !== undefined) {
|
|
632
|
+
res.json(result);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
catch (err) {
|
|
637
|
+
let handled = false;
|
|
638
|
+
const routeLifecycle = lifecycleMetadataMap[route.handlerName] || {};
|
|
639
|
+
const filters = routeLifecycle.filters || [];
|
|
640
|
+
for (const filter of filters) {
|
|
641
|
+
if (typeof filter === 'function' && filter.prototype?.catch) {
|
|
642
|
+
let filterInstance = this.container.resolve(filter);
|
|
643
|
+
if (!filterInstance)
|
|
644
|
+
filterInstance = new filter();
|
|
645
|
+
await filterInstance.catch(err, { req, res });
|
|
646
|
+
handled = true;
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
else if (typeof filter === 'object' &&
|
|
650
|
+
typeof filter.catch === 'function') {
|
|
651
|
+
await filter.catch(err, { req, res });
|
|
652
|
+
handled = true;
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (!handled) {
|
|
657
|
+
if (next)
|
|
658
|
+
next(err);
|
|
659
|
+
else
|
|
660
|
+
console.error(`[Exis ${method.toUpperCase()} Error]`, err);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
let finalHandler;
|
|
665
|
+
if (method === 'ws' || method === 'sse') {
|
|
666
|
+
finalHandler = async (streamOrSocket, rawReq) => {
|
|
667
|
+
await executeCore(rawReq, undefined, undefined, streamOrSocket);
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
finalHandler = async (req, res, next) => {
|
|
672
|
+
await executeCore(req, res, next);
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
const fullPath = (basePath + prefix + route.path).replace(/\/+/g, '/') || '/';
|
|
676
|
+
const routeSpecificMiddlewares = methodMiddlewares[route.handlerName] || [];
|
|
677
|
+
const allMiddlewares = [
|
|
678
|
+
...prefixMiddlewares,
|
|
679
|
+
...classMiddlewares,
|
|
680
|
+
...routeSpecificMiddlewares,
|
|
681
|
+
];
|
|
682
|
+
const routeMeta = routeMetadataMap[route.handlerName] || {};
|
|
683
|
+
const finalHost = routeMeta.hosts || host;
|
|
684
|
+
if (route.schema || finalHost) {
|
|
685
|
+
const schema = route.schema || {};
|
|
686
|
+
if (finalHost)
|
|
687
|
+
schema.host = finalHost;
|
|
688
|
+
this.router[method](fullPath, ...allMiddlewares, schema, finalHandler);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
;
|
|
692
|
+
this.router[method](fullPath, ...allMiddlewares, finalHandler);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return this;
|
|
697
|
+
}
|
|
698
|
+
// ─── Dependency Injection ───────────────────────────────────────────────────
|
|
699
|
+
provide(token, provider) {
|
|
700
|
+
this.container.provide(token, provider);
|
|
701
|
+
return this;
|
|
702
|
+
}
|
|
703
|
+
resolve(token, requestCache) {
|
|
704
|
+
return this.container.resolve(token, requestCache);
|
|
705
|
+
}
|
|
706
|
+
// ─── Built-in Feature Toggles ─────────────────────────────────────────────────
|
|
707
|
+
applyBuiltins() {
|
|
708
|
+
if (this._configured)
|
|
709
|
+
return;
|
|
710
|
+
this._configured = true;
|
|
711
|
+
this.ensureLogger();
|
|
712
|
+
const { cors: corsOpt, helmet: helmetOpt, logger: logOpt, compression: compressionOpt, } = this.options;
|
|
713
|
+
// Request ID always on
|
|
714
|
+
this.globalMiddleware.unshift((0, middleware_1.requestId)());
|
|
715
|
+
if (logOpt !== false) {
|
|
716
|
+
this.globalMiddleware.push((0, middleware_1.requestLogger)(this.log));
|
|
717
|
+
}
|
|
718
|
+
if (compressionOpt) {
|
|
719
|
+
this.globalMiddleware.push((0, middleware_1.compression)());
|
|
720
|
+
}
|
|
721
|
+
if (helmetOpt !== false) {
|
|
722
|
+
this.globalMiddleware.push((0, middleware_1.helmet)());
|
|
723
|
+
}
|
|
724
|
+
if (corsOpt !== false) {
|
|
725
|
+
this.globalMiddleware.push((0, middleware_1.cors)(corsOpt === true ? {} : corsOpt));
|
|
726
|
+
}
|
|
727
|
+
if (this.options.plugins) {
|
|
728
|
+
for (const plugin of this.options.plugins) {
|
|
729
|
+
if (!this.hasPlugin(plugin.name)) {
|
|
730
|
+
// We can't await inside sync constructor easily, but plugins are usually sync in register.
|
|
731
|
+
// For async plugins in config, they should be loaded before app starts.
|
|
732
|
+
// App.register is async, so we'll push the promise and warn if it's not awaited
|
|
733
|
+
this.register(plugin).catch((err) => console.error(`[Exis] Failed to register plugin ${plugin.name} from config:`, err));
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
// rate limiting removed from core
|
|
738
|
+
// default error handler
|
|
739
|
+
if (this.errorHandlers.length === 0) {
|
|
740
|
+
this.errorHandlers.push((0, errors_1.createErrorHandler)(this.options.env === 'development'));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
getCompiledPipeline() {
|
|
744
|
+
if (this._compiledPipeline)
|
|
745
|
+
return this._compiledPipeline;
|
|
746
|
+
this.applyBuiltins();
|
|
747
|
+
this._compiledPipeline = [
|
|
748
|
+
...this.globalMiddleware,
|
|
749
|
+
(req, res, next) => this.router.handle(req, res, next),
|
|
750
|
+
middleware_1.notFound,
|
|
751
|
+
];
|
|
752
|
+
return this._compiledPipeline;
|
|
753
|
+
}
|
|
754
|
+
// ─── Web Standard Fetch (Edge Runtime) & Inject ──────────────────────────────
|
|
755
|
+
fetch(request, env, ctx) {
|
|
756
|
+
// Dynamic require to avoid circular dependencies and top-level execution
|
|
757
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
758
|
+
const { handleFetch } = require('../adapters/fetch');
|
|
759
|
+
return handleFetch(this, request, env, ctx);
|
|
760
|
+
}
|
|
761
|
+
async inject(options) {
|
|
762
|
+
const method = (options.method || 'GET').toUpperCase();
|
|
763
|
+
const url = options.url.startsWith('http')
|
|
764
|
+
? options.url
|
|
765
|
+
: `http://localhost${options.url}`;
|
|
766
|
+
const headers = new Headers();
|
|
767
|
+
if (options.headers) {
|
|
768
|
+
for (const [k, v] of Object.entries(options.headers)) {
|
|
769
|
+
headers.set(k, v);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
let bodyStr;
|
|
773
|
+
if (options.body) {
|
|
774
|
+
bodyStr =
|
|
775
|
+
typeof options.body === 'string'
|
|
776
|
+
? options.body
|
|
777
|
+
: JSON.stringify(options.body);
|
|
778
|
+
if (!headers.has('content-type') && typeof options.body !== 'string') {
|
|
779
|
+
headers.set('content-type', 'application/json');
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const req = new Request(url, {
|
|
783
|
+
method,
|
|
784
|
+
headers,
|
|
785
|
+
body: ['GET', 'HEAD', 'OPTIONS'].includes(method) ? undefined : bodyStr,
|
|
786
|
+
});
|
|
787
|
+
const fetchRes = await this.fetch(req);
|
|
788
|
+
const resText = await fetchRes.text();
|
|
789
|
+
let parsedBody = resText;
|
|
790
|
+
try {
|
|
791
|
+
parsedBody = JSON.parse(resText);
|
|
792
|
+
}
|
|
793
|
+
catch {
|
|
794
|
+
// ignore
|
|
795
|
+
}
|
|
796
|
+
const resHeaders = {};
|
|
797
|
+
fetchRes.headers.forEach((v, k) => {
|
|
798
|
+
resHeaders[k] = v;
|
|
799
|
+
});
|
|
800
|
+
return {
|
|
801
|
+
status: fetchRes.status,
|
|
802
|
+
headers: resHeaders,
|
|
803
|
+
body: parsedBody,
|
|
804
|
+
text: resText,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
// ─── Private Internal Pipeline Handler ─────────────────────────────────────────────────────
|
|
808
|
+
_executeWithContext(req, res, execution) {
|
|
809
|
+
const store = {
|
|
810
|
+
state: {},
|
|
811
|
+
afterCallbacks: [],
|
|
812
|
+
req: req,
|
|
813
|
+
res: res,
|
|
814
|
+
app: this,
|
|
815
|
+
diCache: new Map(),
|
|
816
|
+
};
|
|
817
|
+
res.raw.on('finish', () => {
|
|
818
|
+
for (const cb of store.afterCallbacks) {
|
|
819
|
+
try {
|
|
820
|
+
const promise = cb();
|
|
821
|
+
if (promise instanceof Promise) {
|
|
822
|
+
promise.catch((err) => {
|
|
823
|
+
this.log.error({ err }, 'Error in after() background task');
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
catch (err) {
|
|
828
|
+
this.log.error({ err }, 'Error in after() background task');
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
context_1.executionContext.run(store, execution);
|
|
833
|
+
}
|
|
834
|
+
handle(rawReq, rawRes) {
|
|
835
|
+
const res = new response_1.ExisResponse(rawRes);
|
|
836
|
+
const req = new request_1.ExisRequest(rawReq, res, this.options.trustProxy, this.options.bodyLimit);
|
|
837
|
+
res.req = req;
|
|
838
|
+
res.etagEnabled = this.options.etag === true;
|
|
839
|
+
req.log = this.log;
|
|
840
|
+
req._dataloaderFns = this._dataloaders;
|
|
841
|
+
this._executeWithContext(req, res, () => {
|
|
842
|
+
if (this.hooks.request.length === 0 && this.hooks.response.length === 0) {
|
|
843
|
+
(0, router_1.runHandlers)(this.getCompiledPipeline(), req, res, (err) => {
|
|
844
|
+
if (err) {
|
|
845
|
+
this._runErrorHandlers(err, req, res).catch((e) => {
|
|
846
|
+
this.log.error({ err: e }, 'Error in error handler');
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
this._handleWithHooks(req, res, rawRes);
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Handle a request from the uWebSockets.js backend.
|
|
857
|
+
* Uses the same middleware pipeline as Node HTTP but with uWS shims.
|
|
858
|
+
*/
|
|
859
|
+
handleUws(shimReq, shimRes) {
|
|
860
|
+
// Wrap uWS shims in ExisRequest/ExisResponse using type coercion
|
|
861
|
+
// The shims implement the same interface that ExisRequest/ExisResponse expect
|
|
862
|
+
const res = new response_1.ExisResponse(shimRes);
|
|
863
|
+
const req = new request_1.ExisRequest(shimReq, res, this.options.trustProxy, this.options.bodyLimit);
|
|
864
|
+
res.req = req;
|
|
865
|
+
res.etagEnabled = this.options.etag === true;
|
|
866
|
+
req.log = this.log;
|
|
867
|
+
req._dataloaderFns = this._dataloaders;
|
|
868
|
+
this._executeWithContext(req, res, () => {
|
|
869
|
+
if (this.hooks.request.length === 0 && this.hooks.response.length === 0) {
|
|
870
|
+
(0, router_1.runHandlers)(this.getCompiledPipeline(), req, res, (err) => {
|
|
871
|
+
if (err) {
|
|
872
|
+
this._runErrorHandlers(err, req, res).catch((e) => {
|
|
873
|
+
this.log.error({ err: e }, 'Error in error handler');
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
this._handleWithHooks(req, res, shimRes);
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
async _handleWithHooks(req, res, rawRes) {
|
|
883
|
+
// ─── onRequest Hook ───
|
|
884
|
+
if (this.hooks.request.length > 0) {
|
|
885
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
886
|
+
for (let i = 0; i < this.hooks.request.length; i++) {
|
|
887
|
+
await this.hooks.request[i](req, res);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (res.headersSent)
|
|
891
|
+
return;
|
|
892
|
+
// ─── onResponse Hook ───
|
|
893
|
+
if (this.hooks.response.length > 0) {
|
|
894
|
+
rawRes.on('finish', () => {
|
|
895
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
896
|
+
for (let i = 0; i < this.hooks.response.length; i++) {
|
|
897
|
+
Promise.resolve(this.hooks.response[i](req, res)).catch((err) => {
|
|
898
|
+
this.log.error({ err }, 'Error in onResponse hook');
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
(0, router_1.runHandlers)(this.getCompiledPipeline(), req, res, (err) => {
|
|
904
|
+
if (err) {
|
|
905
|
+
this._runErrorHandlers(err, req, res).catch((e) => {
|
|
906
|
+
this.log.error({ err: e }, 'Error in error handler');
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
async _runErrorHandlers(err, req, res) {
|
|
912
|
+
// ─── onError Hook ───
|
|
913
|
+
if (this.hooks.error.length > 0) {
|
|
914
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
915
|
+
for (let i = 0; i < this.hooks.error.length; i++) {
|
|
916
|
+
await this.hooks.error[i](err, req, res);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (res.headersSent)
|
|
920
|
+
return;
|
|
921
|
+
for (const handler of this.errorHandlers) {
|
|
922
|
+
let handled = false;
|
|
923
|
+
await handler(err, req, res, () => {
|
|
924
|
+
handled = true;
|
|
925
|
+
});
|
|
926
|
+
if (!handled || res.headersSent)
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
// fallback if no error handler ran
|
|
930
|
+
const defaultHandler = (0, errors_1.createErrorHandler)(this.options.env === 'development');
|
|
931
|
+
await defaultHandler(err, req, res, () => {
|
|
932
|
+
/* noop */
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
// ─── WebSocket Upgrade Handler ───────────────────────────────────────────────
|
|
936
|
+
async _handleUpgrade(rawReq, socket, head) {
|
|
937
|
+
this.applyBuiltins();
|
|
938
|
+
// Match route
|
|
939
|
+
const reqPath = (rawReq.url || '').split('?')[0];
|
|
940
|
+
const matched = this.router.match('WS', reqPath);
|
|
941
|
+
if (!matched) {
|
|
942
|
+
socket.write('HTTP/1.1 404 Not Found\\r\\n\\r\\n');
|
|
943
|
+
socket.destroy();
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
// Create a dummy ServerResponse so standard HTTP middleware can run and reject if needed
|
|
947
|
+
const rawRes = new node_http_1.ServerResponse(rawReq);
|
|
948
|
+
rawRes.assignSocket(socket);
|
|
949
|
+
const res = new response_1.ExisResponse(rawRes);
|
|
950
|
+
const originalMethod = rawReq.method;
|
|
951
|
+
const req = new request_1.ExisRequest(rawReq, res, this.options.trustProxy, this.options.bodyLimit);
|
|
952
|
+
res.req = req;
|
|
953
|
+
req.method = 'WS';
|
|
954
|
+
req.params = matched.params;
|
|
955
|
+
req.log = this.log;
|
|
956
|
+
// Build the middleware pipeline specifically for this WebSocket route
|
|
957
|
+
const pipeline = [
|
|
958
|
+
...this.globalMiddleware,
|
|
959
|
+
async (req, res, next) => {
|
|
960
|
+
// Exclude the last handler which is the actual WsHandler wrapper
|
|
961
|
+
const routeHandlers = matched.route.handlers.slice(0, -1);
|
|
962
|
+
(0, router_1.runHandlers)(routeHandlers, req, res, (err) => {
|
|
963
|
+
if (err)
|
|
964
|
+
return next(err);
|
|
965
|
+
next();
|
|
966
|
+
});
|
|
967
|
+
},
|
|
968
|
+
];
|
|
969
|
+
await (0, router_1.runHandlers)(pipeline, req, res, async (err) => {
|
|
970
|
+
if (err) {
|
|
971
|
+
this.log.error({ err }, 'Error during WebSocket upgrade middleware');
|
|
972
|
+
socket.write('HTTP/1.1 500 Internal Server Error\\r\\n\\r\\n');
|
|
973
|
+
socket.destroy();
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
if (res.headersSent) {
|
|
977
|
+
// A middleware intercepted and sent an HTTP response (e.g. 401 Unauthorized)
|
|
978
|
+
socket.destroy();
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
// Restore original HTTP method (usually 'GET') so the `ws` package doesn't reject with 405
|
|
982
|
+
rawReq.method = originalMethod;
|
|
983
|
+
// Middleware passed, perform upgrade
|
|
984
|
+
this.rawWsServer.handleUpgrade(rawReq, socket, head, (ws) => {
|
|
985
|
+
const exisWs = new socket_1.ExisWebSocket(ws, req, this.wsServer);
|
|
986
|
+
this.wsServer.track(exisWs);
|
|
987
|
+
req.ws = exisWs;
|
|
988
|
+
const finalHandler = matched.route.handlers[matched.route.handlers.length - 1];
|
|
989
|
+
Promise.resolve(finalHandler(req, res, () => {
|
|
990
|
+
/* noop */
|
|
991
|
+
})).catch((err) => {
|
|
992
|
+
this.log.error({ err }, 'Error in WebSocket handler');
|
|
993
|
+
ws.close(1011, 'Internal Server Error');
|
|
994
|
+
});
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* WebSocket Upgrade Handler for uWebSockets.js
|
|
1000
|
+
* Runs the middleware pipeline and if successful, upgrades the connection.
|
|
1001
|
+
*/
|
|
1002
|
+
async handleUwsUpgrade(shimReq, uwsRes, context) {
|
|
1003
|
+
this.applyBuiltins();
|
|
1004
|
+
const reqPath = (shimReq.url || '').split('?')[0];
|
|
1005
|
+
const matched = this.router.match('WS', reqPath);
|
|
1006
|
+
if (!matched) {
|
|
1007
|
+
// 404
|
|
1008
|
+
uwsRes.cork(() => {
|
|
1009
|
+
uwsRes.writeStatus('404 Not Found');
|
|
1010
|
+
uwsRes.end();
|
|
1011
|
+
});
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
let isAborted = false;
|
|
1015
|
+
uwsRes.onAborted(() => {
|
|
1016
|
+
isAborted = true;
|
|
1017
|
+
});
|
|
1018
|
+
const shimRes = new uws_adapter_1.UwsServerResponse(uwsRes);
|
|
1019
|
+
const res = new response_1.ExisResponse(shimRes);
|
|
1020
|
+
const req = new request_1.ExisRequest(shimReq, res, this.options.trustProxy, this.options.bodyLimit);
|
|
1021
|
+
res.req = req;
|
|
1022
|
+
req.method = 'WS';
|
|
1023
|
+
req.params = matched.params;
|
|
1024
|
+
req.log = this.log;
|
|
1025
|
+
const pipeline = [
|
|
1026
|
+
...this.globalMiddleware,
|
|
1027
|
+
async (req, res, next) => {
|
|
1028
|
+
const routeHandlers = matched.route.handlers.slice(0, -1);
|
|
1029
|
+
(0, router_1.runHandlers)(routeHandlers, req, res, (err) => {
|
|
1030
|
+
if (err)
|
|
1031
|
+
return next(err);
|
|
1032
|
+
next();
|
|
1033
|
+
});
|
|
1034
|
+
},
|
|
1035
|
+
];
|
|
1036
|
+
await (0, router_1.runHandlers)(pipeline, req, res, async (err) => {
|
|
1037
|
+
if (isAborted)
|
|
1038
|
+
return;
|
|
1039
|
+
if (err) {
|
|
1040
|
+
this.log.error({ err }, 'Error during uWS WebSocket upgrade middleware');
|
|
1041
|
+
uwsRes.cork(() => {
|
|
1042
|
+
uwsRes.writeStatus('500 Internal Server Error');
|
|
1043
|
+
uwsRes.end();
|
|
1044
|
+
});
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
if (res.headersSent) {
|
|
1048
|
+
return; // Middleware sent an HTTP response
|
|
1049
|
+
}
|
|
1050
|
+
// We need to fetch headers directly from the shimReq.headers
|
|
1051
|
+
const secWebSocketKey = shimReq.headers['sec-websocket-key'] || '';
|
|
1052
|
+
const secWebSocketProtocol = shimReq.headers['sec-websocket-protocol'] || '';
|
|
1053
|
+
const secWebSocketExtensions = shimReq.headers['sec-websocket-extensions'] || '';
|
|
1054
|
+
// Prepare user data for the upgraded websocket
|
|
1055
|
+
const userData = {
|
|
1056
|
+
req,
|
|
1057
|
+
res,
|
|
1058
|
+
exisWs: new socket_1.ExisWebSocket(null, req, this.wsServer), // the raw will be set to shim in open()
|
|
1059
|
+
finalHandler: matched.route.handlers[matched.route.handlers.length - 1],
|
|
1060
|
+
};
|
|
1061
|
+
this.wsServer.track(userData.exisWs);
|
|
1062
|
+
req.ws = userData.exisWs;
|
|
1063
|
+
uwsRes.cork(() => {
|
|
1064
|
+
uwsRes.upgrade(userData, secWebSocketKey, secWebSocketProtocol, secWebSocketExtensions, context);
|
|
1065
|
+
});
|
|
1066
|
+
// The open handler in createUwsApp will call finalHandler?
|
|
1067
|
+
// Actually we should just execute the finalHandler here since upgrade succeeds immediately if we don't block
|
|
1068
|
+
Promise.resolve(userData.finalHandler(req, res, () => {
|
|
1069
|
+
/* noop */
|
|
1070
|
+
})).catch((e) => {
|
|
1071
|
+
this.log.error({ err: e }, 'Error in uWS WebSocket handler');
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
// ─── Listen ───────────────────────────────────────────────────────────────────
|
|
1076
|
+
listen(portOrOptions, callback) {
|
|
1077
|
+
if (process.env.EXIS_CLI_MODE === '1') {
|
|
1078
|
+
return undefined;
|
|
1079
|
+
}
|
|
1080
|
+
this.applyBuiltins();
|
|
1081
|
+
let port;
|
|
1082
|
+
let host;
|
|
1083
|
+
let onListen;
|
|
1084
|
+
if (typeof portOrOptions === 'number') {
|
|
1085
|
+
port = portOrOptions;
|
|
1086
|
+
host = this.options.host;
|
|
1087
|
+
onListen = callback ? () => callback() : undefined;
|
|
1088
|
+
}
|
|
1089
|
+
else if (typeof portOrOptions === 'object') {
|
|
1090
|
+
if (portOrOptions.ssl) {
|
|
1091
|
+
this.options.ssl = portOrOptions.ssl;
|
|
1092
|
+
// Re-initialize server if ssl is passed during listen
|
|
1093
|
+
this.server = this._initServer();
|
|
1094
|
+
}
|
|
1095
|
+
if (portOrOptions.redirectHttp !== undefined) {
|
|
1096
|
+
this.options.redirectHttp = portOrOptions.redirectHttp;
|
|
1097
|
+
}
|
|
1098
|
+
port = portOrOptions.port ?? this.options.port;
|
|
1099
|
+
host = portOrOptions.host ?? this.options.host;
|
|
1100
|
+
onListen = portOrOptions.onListen;
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
port = this.options.port;
|
|
1104
|
+
host = this.options.host;
|
|
1105
|
+
}
|
|
1106
|
+
// Auto HTTP -> HTTPS redirect server (only for Node HTTP backend)
|
|
1107
|
+
// ─── uWebSockets.js listen path ───
|
|
1108
|
+
if (this._useUws) {
|
|
1109
|
+
this._uwsApp = (0, uws_adapter_1.createUwsApp)(this.handleUws.bind(this), this.handleUwsUpgrade.bind(this), this.options.ssl);
|
|
1110
|
+
this._uwsApp.listen(port, host, async (token) => {
|
|
1111
|
+
if (!token) {
|
|
1112
|
+
this.log.error(`Failed to listen on ${host}:${port} (uWS)`);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
this._uwsListenToken = token;
|
|
1116
|
+
const address = { port, host };
|
|
1117
|
+
if (this.hotReloader) {
|
|
1118
|
+
this.hotReloader.stop();
|
|
1119
|
+
}
|
|
1120
|
+
if (onListen) {
|
|
1121
|
+
onListen(address);
|
|
1122
|
+
}
|
|
1123
|
+
else {
|
|
1124
|
+
const url = `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}`;
|
|
1125
|
+
this.log.info({ url, env: this.options.env, backend: 'uws' }, `Server running at ${url} (uWebSockets.js)`);
|
|
1126
|
+
}
|
|
1127
|
+
if (this.options.env === 'production') {
|
|
1128
|
+
const handleShutdown = async () => {
|
|
1129
|
+
this.log.info('Received shutdown signal (SIGTERM/SIGINT)');
|
|
1130
|
+
try {
|
|
1131
|
+
await this.close();
|
|
1132
|
+
process.exit(0);
|
|
1133
|
+
}
|
|
1134
|
+
catch (err) {
|
|
1135
|
+
this.log.error({ err }, 'Error during graceful shutdown');
|
|
1136
|
+
process.exit(1);
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
process.once('SIGTERM', handleShutdown);
|
|
1140
|
+
process.once('SIGINT', handleShutdown);
|
|
1141
|
+
}
|
|
1142
|
+
// ─── onReady Hook ───
|
|
1143
|
+
for (const hook of this.hooks.ready) {
|
|
1144
|
+
await hook();
|
|
1145
|
+
}
|
|
1146
|
+
if (callback)
|
|
1147
|
+
callback();
|
|
1148
|
+
});
|
|
1149
|
+
// Return a minimal server-like object for compatibility
|
|
1150
|
+
return this.server;
|
|
1151
|
+
}
|
|
1152
|
+
// ─── Node HTTP listen path ───
|
|
1153
|
+
// Auto HTTP -> HTTPS redirect server
|
|
1154
|
+
if (this.options.ssl &&
|
|
1155
|
+
this.options.redirectHttp !== undefined &&
|
|
1156
|
+
this.options.redirectHttp !== false) {
|
|
1157
|
+
const redirectPort = typeof this.options.redirectHttp === 'number'
|
|
1158
|
+
? this.options.redirectHttp
|
|
1159
|
+
: 80;
|
|
1160
|
+
this.redirectServer = (0, node_http_1.createServer)((req, res) => {
|
|
1161
|
+
const targetHost = req.headers.host?.split(':')[0] || host;
|
|
1162
|
+
const actualPort = port === 0
|
|
1163
|
+
? this.server.address()?.port ||
|
|
1164
|
+
port
|
|
1165
|
+
: port;
|
|
1166
|
+
const targetPort = actualPort === 443 ? '' : `:${actualPort}`;
|
|
1167
|
+
res.writeHead(301, {
|
|
1168
|
+
Location: `https://${targetHost}${targetPort}${req.url || '/'}`,
|
|
1169
|
+
});
|
|
1170
|
+
res.end();
|
|
1171
|
+
});
|
|
1172
|
+
this.redirectServer.listen(redirectPort, host, () => {
|
|
1173
|
+
this.log.info(`Redirect server listening on port ${redirectPort} -> HTTPS port ${port}`);
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
this.server.on('error', (err) => {
|
|
1177
|
+
if (err.code === 'EADDRINUSE') {
|
|
1178
|
+
console.error(`\n\x1b[31m✗ Port ${port} is already in use.\x1b[0m`);
|
|
1179
|
+
console.error(` Try killing the process or use a different port in exis.config.ts\n`);
|
|
1180
|
+
process.exit(1);
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
this.log.error({ err }, 'Failed to start server');
|
|
1184
|
+
process.exit(1);
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
this.server.listen(port, host, async () => {
|
|
1188
|
+
const address = { port, host };
|
|
1189
|
+
if (this.options.env === 'production') {
|
|
1190
|
+
const handleShutdown = async () => {
|
|
1191
|
+
this.log.info('Received shutdown signal (SIGTERM/SIGINT)');
|
|
1192
|
+
try {
|
|
1193
|
+
await this.close();
|
|
1194
|
+
process.exit(0);
|
|
1195
|
+
}
|
|
1196
|
+
catch (err) {
|
|
1197
|
+
this.log.error({ err }, 'Error during graceful shutdown');
|
|
1198
|
+
process.exit(1);
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
process.once('SIGTERM', handleShutdown);
|
|
1202
|
+
process.once('SIGINT', handleShutdown);
|
|
1203
|
+
}
|
|
1204
|
+
// ─── onReady Hook ───
|
|
1205
|
+
for (const hook of this.hooks.ready) {
|
|
1206
|
+
await hook();
|
|
1207
|
+
}
|
|
1208
|
+
// Start queue worker if initialized
|
|
1209
|
+
if (this._queueWorker) {
|
|
1210
|
+
await this._queueWorker.start();
|
|
1211
|
+
}
|
|
1212
|
+
if (onListen) {
|
|
1213
|
+
onListen(address);
|
|
1214
|
+
}
|
|
1215
|
+
else {
|
|
1216
|
+
if (!process.env.__EXIS_DEV_SERVER && !process.env.__EXIS_CLI) {
|
|
1217
|
+
const url = `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}`;
|
|
1218
|
+
this.log.info({ url, env: this.options.env }, `Server running at ${url}`);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
if (callback)
|
|
1222
|
+
callback();
|
|
1223
|
+
});
|
|
1224
|
+
return this.server;
|
|
1225
|
+
}
|
|
1226
|
+
// ─── Banner ───────────────────────────────────────────────────────────────────
|
|
1227
|
+
async _printStartupBanner() {
|
|
1228
|
+
const port = this.options.port ?? 3000;
|
|
1229
|
+
const host = this.options.host ?? 'localhost';
|
|
1230
|
+
const c = {
|
|
1231
|
+
cyan: '\x1b[36m',
|
|
1232
|
+
green: '\x1b[32m',
|
|
1233
|
+
gray: '\x1b[90m',
|
|
1234
|
+
dim: '\x1b[2m',
|
|
1235
|
+
bold: '\x1b[1m',
|
|
1236
|
+
reset: '\x1b[0m',
|
|
1237
|
+
magenta: '\x1b[35m',
|
|
1238
|
+
bgMagenta: '\x1b[45m',
|
|
1239
|
+
white: '\x1b[37m',
|
|
1240
|
+
primary: '\x1b[38;2;160;70;255m',
|
|
1241
|
+
blue: '\x1b[38;2;41;169;206m',
|
|
1242
|
+
};
|
|
1243
|
+
let fwVersion = '0.0.0';
|
|
1244
|
+
try {
|
|
1245
|
+
const fs = await Promise.resolve().then(() => __importStar(require('node:fs')));
|
|
1246
|
+
const path = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
1247
|
+
const pkgPath = path.join(__dirname, '../../package.json');
|
|
1248
|
+
fwVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
|
|
1249
|
+
}
|
|
1250
|
+
catch {
|
|
1251
|
+
/* ignore */
|
|
1252
|
+
}
|
|
1253
|
+
const displayHost = host === '0.0.0.0' || host === '127.0.0.1' ? 'localhost' : host;
|
|
1254
|
+
let localIp = '<YOUR_IP>';
|
|
1255
|
+
try {
|
|
1256
|
+
const os = await Promise.resolve().then(() => __importStar(require('node:os')));
|
|
1257
|
+
const nets = os.networkInterfaces();
|
|
1258
|
+
for (const name of Object.keys(nets)) {
|
|
1259
|
+
for (const net of nets[name] || []) {
|
|
1260
|
+
if (net.family === 'IPv4' && !net.internal) {
|
|
1261
|
+
localIp = net.address;
|
|
1262
|
+
break;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
if (localIp !== '<YOUR_IP>')
|
|
1266
|
+
break;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
catch {
|
|
1270
|
+
/* ignore */
|
|
1271
|
+
}
|
|
1272
|
+
const networkHost = host === '0.0.0.0'
|
|
1273
|
+
? `http://${localIp}:${port}`
|
|
1274
|
+
: 'use --host 0.0.0.0 to expose';
|
|
1275
|
+
const readyMs = 125;
|
|
1276
|
+
const displayEnv = this.options.env || process.env.NODE_ENV || 'development';
|
|
1277
|
+
console.log(`\n ${c.primary}${c.bold}EXIS v${fwVersion}${c.reset} ready in ${c.bold}${readyMs} ms${c.reset}\n`);
|
|
1278
|
+
console.log(` ${c.white}➜${c.reset} ${c.bold}Local:${c.reset} ${c.blue}http://${displayHost}:${port}/${c.reset}`);
|
|
1279
|
+
console.log(` ${c.white}➜${c.reset} ${c.dim}Network:${c.reset} ${c.blue}${networkHost}/${c.reset}`);
|
|
1280
|
+
console.log(` ${c.white}➜${c.reset} ${c.dim}Environ:${c.reset} ${c.green}${displayEnv}${c.reset}`);
|
|
1281
|
+
console.log(` ${c.white}➜${c.reset} press ${c.bold}h + enter${c.reset} ${c.dim}to show help${c.reset}\n`);
|
|
1282
|
+
}
|
|
1283
|
+
// ─── Graceful Shutdown ────────────────────────────────────────────────────────
|
|
1284
|
+
onShutdown(hook) {
|
|
1285
|
+
this.shutdownHooks.push(hook);
|
|
1286
|
+
return this;
|
|
1287
|
+
}
|
|
1288
|
+
close(timeout = 5000) {
|
|
1289
|
+
return new Promise((resolve, reject) => {
|
|
1290
|
+
this.log.info('Initiating graceful shutdown');
|
|
1291
|
+
let finishCalled = false;
|
|
1292
|
+
const finish = async () => {
|
|
1293
|
+
if (finishCalled)
|
|
1294
|
+
return;
|
|
1295
|
+
finishCalled = true;
|
|
1296
|
+
try {
|
|
1297
|
+
for (const hook of this.shutdownHooks) {
|
|
1298
|
+
await hook();
|
|
1299
|
+
}
|
|
1300
|
+
// Also execute onClose hooks
|
|
1301
|
+
for (const hook of this.hooks.close) {
|
|
1302
|
+
await hook();
|
|
1303
|
+
}
|
|
1304
|
+
if (this.onCloseHook) {
|
|
1305
|
+
await this.onCloseHook(this);
|
|
1306
|
+
}
|
|
1307
|
+
if (this._cronScheduler) {
|
|
1308
|
+
this._cronScheduler.stop();
|
|
1309
|
+
}
|
|
1310
|
+
if (this._queueWorker) {
|
|
1311
|
+
await this._queueWorker.stop();
|
|
1312
|
+
}
|
|
1313
|
+
if (this._queueClient) {
|
|
1314
|
+
await this._queueClient.close();
|
|
1315
|
+
}
|
|
1316
|
+
if (this.hotReloader) {
|
|
1317
|
+
await this.hotReloader.stop();
|
|
1318
|
+
}
|
|
1319
|
+
this.log.info('Graceful shutdown completed');
|
|
1320
|
+
resolve();
|
|
1321
|
+
}
|
|
1322
|
+
catch (err) {
|
|
1323
|
+
this.log.error({ err }, 'Error executing shutdown hooks');
|
|
1324
|
+
reject(err);
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
if (!this._useUws && (!this.server || !this.server.listening)) {
|
|
1328
|
+
finish();
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
if (this._useUws && !this._uwsListenToken) {
|
|
1332
|
+
finish();
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
// Close idle keep-alive connections immediately
|
|
1336
|
+
if (!this._useUws &&
|
|
1337
|
+
this.server &&
|
|
1338
|
+
'closeIdleConnections' in this.server) {
|
|
1339
|
+
;
|
|
1340
|
+
this.server.closeIdleConnections?.();
|
|
1341
|
+
}
|
|
1342
|
+
// Set timeout to force close active connections
|
|
1343
|
+
const timer = setTimeout(() => {
|
|
1344
|
+
this.log.warn(`Shutdown timeout of ${timeout}ms exceeded, forcefully terminating active connections`);
|
|
1345
|
+
if (!this._useUws &&
|
|
1346
|
+
this.server &&
|
|
1347
|
+
'closeAllConnections' in this.server) {
|
|
1348
|
+
;
|
|
1349
|
+
this.server.closeAllConnections?.();
|
|
1350
|
+
}
|
|
1351
|
+
finish(); // Ensure finish is called on timeout
|
|
1352
|
+
}, timeout);
|
|
1353
|
+
const cleanupAndFinish = async () => {
|
|
1354
|
+
clearTimeout(timer);
|
|
1355
|
+
await finish();
|
|
1356
|
+
};
|
|
1357
|
+
this.wsServer.close();
|
|
1358
|
+
for (const client of this.rawWsServer.clients) {
|
|
1359
|
+
client.terminate();
|
|
1360
|
+
}
|
|
1361
|
+
this.rawWsServer.close();
|
|
1362
|
+
if (this.redirectServer) {
|
|
1363
|
+
this.redirectServer.close();
|
|
1364
|
+
}
|
|
1365
|
+
if (this._useUws && this._uwsApp) {
|
|
1366
|
+
this._uwsApp.close(this._uwsListenToken);
|
|
1367
|
+
this._uwsListenToken = null;
|
|
1368
|
+
cleanupAndFinish();
|
|
1369
|
+
}
|
|
1370
|
+
else {
|
|
1371
|
+
this.server.close((err) => {
|
|
1372
|
+
if (err)
|
|
1373
|
+
reject(err);
|
|
1374
|
+
else
|
|
1375
|
+
cleanupAndFinish();
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
// ─── Expose internals ─────────────────────────────────────────────────────────
|
|
1381
|
+
getRouter() {
|
|
1382
|
+
return this.router;
|
|
1383
|
+
}
|
|
1384
|
+
async loadAllRoutes() {
|
|
1385
|
+
for (const [filePath, entry] of this.lazyRoutes.entries()) {
|
|
1386
|
+
if (!entry.loaded) {
|
|
1387
|
+
entry.loaded = true;
|
|
1388
|
+
this.router.removeRoutesBySource('lazy:' + filePath);
|
|
1389
|
+
const routePath = this.routeMap.get(filePath);
|
|
1390
|
+
if (routePath) {
|
|
1391
|
+
try {
|
|
1392
|
+
await this.mountRouteFile(filePath, routePath);
|
|
1393
|
+
}
|
|
1394
|
+
catch (e) {
|
|
1395
|
+
this.log.error({ err: e, file: filePath }, 'Failed to eager-load route');
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
getServer() {
|
|
1402
|
+
return this.server;
|
|
1403
|
+
}
|
|
1404
|
+
getConfig() {
|
|
1405
|
+
return this.options;
|
|
1406
|
+
}
|
|
1407
|
+
async create(cwd) {
|
|
1408
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
1409
|
+
exports.activeAppInstance = this;
|
|
1410
|
+
const root = cwd ?? process.cwd();
|
|
1411
|
+
if (!this._configLoaded) {
|
|
1412
|
+
const { loadConfig } = await Promise.resolve().then(() => __importStar(require('../utils/config')));
|
|
1413
|
+
const fileConfig = await loadConfig(root);
|
|
1414
|
+
// File config takes precedence over defaults, but explicit options take highest precedence
|
|
1415
|
+
this.options = (0, config_1.mergeConfig)(config_1.defaultConfig, fileConfig);
|
|
1416
|
+
this.options = (0, config_1.mergeConfig)(this.options, this.explicitOptions);
|
|
1417
|
+
// Recreate logger now that the user's config has been loaded
|
|
1418
|
+
this._loggerCreated = false;
|
|
1419
|
+
this.ensureLogger();
|
|
1420
|
+
this._configLoaded = true;
|
|
1421
|
+
if ((process.env.__EXIS_DEV_SERVER || process.env.__EXIS_CLI) &&
|
|
1422
|
+
!process.env.__EXIS_REPL &&
|
|
1423
|
+
!process.env.__EXIS_TEST) {
|
|
1424
|
+
await this._printStartupBanner();
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
// Initialize Queue after config is loaded
|
|
1428
|
+
if (this.options.queue && !this._queueClient) {
|
|
1429
|
+
this._initQueue(this.options.queue);
|
|
1430
|
+
}
|
|
1431
|
+
// Flush any pending jobs that were registered before config was loaded
|
|
1432
|
+
if (this._pendingQueueJobs.length > 0) {
|
|
1433
|
+
if (!this._queueWorker) {
|
|
1434
|
+
if (this.options.queue?.enableWorkers !== false) {
|
|
1435
|
+
throw new Error('Queue worker is not initialized. Please configure ExisConfig.queue first.');
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
else {
|
|
1439
|
+
for (const job of this._pendingQueueJobs) {
|
|
1440
|
+
this._queueWorker.registerJob(job);
|
|
1441
|
+
if (this._cronScheduler && job.cron) {
|
|
1442
|
+
this._cronScheduler.registerJob(job);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
this._pendingQueueJobs = [];
|
|
1447
|
+
}
|
|
1448
|
+
if (this._routesMounted)
|
|
1449
|
+
return this;
|
|
1450
|
+
// Automatically scan and mount file-based routes and jobs
|
|
1451
|
+
await this.autoMountRoutes(root);
|
|
1452
|
+
await this.autoMountJobs(root);
|
|
1453
|
+
this._routesMounted = true;
|
|
1454
|
+
// Start Hot Route Reloading in dev mode
|
|
1455
|
+
if (this.options.env === 'development' && this._allApiDirs) {
|
|
1456
|
+
this.hotReloader = new hot_reload_1.HotReloader({
|
|
1457
|
+
apiDirs: this._allApiDirs,
|
|
1458
|
+
router: this.router,
|
|
1459
|
+
routeMap: this.routeMap,
|
|
1460
|
+
mountRoute: (filePath, routePath) => this.mountRouteFile(filePath, routePath),
|
|
1461
|
+
});
|
|
1462
|
+
await this.hotReloader.start();
|
|
1463
|
+
}
|
|
1464
|
+
return this;
|
|
1465
|
+
}
|
|
1466
|
+
// ─── Auto-Mount File-Based Routes ─────────────────────────────────────────────
|
|
1467
|
+
async autoMountRoutes(root) {
|
|
1468
|
+
const fs = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
1469
|
+
const path = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
1470
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
1471
|
+
const isDev = this.options.env === 'development';
|
|
1472
|
+
// Try to load the pre-built manifest first (O(1) boot)
|
|
1473
|
+
const manifestPath = path.join(root, '.exis', 'routes-manifest.js');
|
|
1474
|
+
try {
|
|
1475
|
+
const stat = await fs.stat(manifestPath);
|
|
1476
|
+
if (stat.isFile()) {
|
|
1477
|
+
let url = (0, node_url_1.pathToFileURL)(manifestPath).href;
|
|
1478
|
+
if (!isProd)
|
|
1479
|
+
url += '?t=' + Date.now(); // cache bust for dev
|
|
1480
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
1481
|
+
const mod = await dynamicImport(url);
|
|
1482
|
+
const manifest = mod.manifest;
|
|
1483
|
+
if (Array.isArray(manifest)) {
|
|
1484
|
+
for (const entry of manifest) {
|
|
1485
|
+
const { routePath, module: routeMod, filePath } = entry;
|
|
1486
|
+
const normalizedPath = path.resolve(root, filePath);
|
|
1487
|
+
this.routeMap.set(normalizedPath, routePath);
|
|
1488
|
+
const CONTROLLER_PREFIX = Symbol.for('exisjs:controller_prefix');
|
|
1489
|
+
const isController = (obj) => obj &&
|
|
1490
|
+
obj.prototype &&
|
|
1491
|
+
obj.prototype[CONTROLLER_PREFIX] !== undefined;
|
|
1492
|
+
const routerInstance = routeMod.router || routeMod.default || routeMod;
|
|
1493
|
+
if (isController(routerInstance)) {
|
|
1494
|
+
this.registerControllers([routerInstance], routePath);
|
|
1495
|
+
}
|
|
1496
|
+
else {
|
|
1497
|
+
this.mountRouteWithSource(routePath, routerInstance, normalizedPath);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
// Skip the filesystem scan completely!
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
catch {
|
|
1506
|
+
// Fall back to scanning the filesystem
|
|
1507
|
+
}
|
|
1508
|
+
const searchDirs = isProd
|
|
1509
|
+
? [
|
|
1510
|
+
path.join(root, '.exis', 'server', 'src', 'http'),
|
|
1511
|
+
path.join(root, '.exis', 'server', 'http'),
|
|
1512
|
+
path.join(root, 'dist', 'src', 'http'),
|
|
1513
|
+
path.join(root, 'dist', 'http'),
|
|
1514
|
+
path.join(root, 'src', 'http'),
|
|
1515
|
+
path.join(root, 'http'),
|
|
1516
|
+
]
|
|
1517
|
+
: [path.join(root, 'src', 'http'), path.join(root, 'http')];
|
|
1518
|
+
const appDirs = [];
|
|
1519
|
+
for (const dir of searchDirs) {
|
|
1520
|
+
try {
|
|
1521
|
+
const stat = await fs.stat(dir);
|
|
1522
|
+
if (stat.isDirectory()) {
|
|
1523
|
+
const baseName = path.basename(path.dirname(dir)) === 'src' ? 'src/http' : 'http';
|
|
1524
|
+
const alreadyHasCompiledOrRawForThisBase = appDirs.some((existing) => {
|
|
1525
|
+
const existingBase = path.basename(path.dirname(existing)) === 'src'
|
|
1526
|
+
? 'src/http'
|
|
1527
|
+
: 'http';
|
|
1528
|
+
return existingBase === baseName;
|
|
1529
|
+
});
|
|
1530
|
+
if (!alreadyHasCompiledOrRawForThisBase) {
|
|
1531
|
+
appDirs.push(dir);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
catch {
|
|
1536
|
+
/* ignore */
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (appDirs.length === 0)
|
|
1540
|
+
return;
|
|
1541
|
+
this.apiDir = appDirs[0] // keep property name for backwards compatibility
|
|
1542
|
+
;
|
|
1543
|
+
this._allApiDirs = appDirs;
|
|
1544
|
+
const cc = {
|
|
1545
|
+
green: '\x1b[32m',
|
|
1546
|
+
cyan: '\x1b[36m',
|
|
1547
|
+
gray: '\x1b[90m',
|
|
1548
|
+
reset: '\x1b[0m',
|
|
1549
|
+
};
|
|
1550
|
+
for (const appDir of appDirs) {
|
|
1551
|
+
const routes = await this.scanDirectory(appDir);
|
|
1552
|
+
for (const { filePath, routePath } of routes) {
|
|
1553
|
+
if (filePath.endsWith('route.ts') || filePath.endsWith('route.js')) {
|
|
1554
|
+
const normalized = path.resolve(filePath);
|
|
1555
|
+
this.routeMap.set(normalized, routePath);
|
|
1556
|
+
if (isDev) {
|
|
1557
|
+
const lazyKey = normalized;
|
|
1558
|
+
this.lazyRoutes.set(lazyKey, {
|
|
1559
|
+
filePath: normalized,
|
|
1560
|
+
loaded: false,
|
|
1561
|
+
});
|
|
1562
|
+
const lazyHandler = async (req, res, next) => {
|
|
1563
|
+
const entry = this.lazyRoutes.get(lazyKey);
|
|
1564
|
+
if (entry && !entry.loaded) {
|
|
1565
|
+
entry.loaded = true;
|
|
1566
|
+
this.router.removeRoutesBySource('lazy:' + lazyKey);
|
|
1567
|
+
try {
|
|
1568
|
+
await this.mountRouteFile(normalized, routePath);
|
|
1569
|
+
const relative = path
|
|
1570
|
+
.relative(process.cwd(), normalized)
|
|
1571
|
+
.replace(/\\/g, '/');
|
|
1572
|
+
console.log(` ${cc.green}✓${cc.reset} Lazy loaded ${cc.cyan}${relative}${cc.reset}`);
|
|
1573
|
+
}
|
|
1574
|
+
catch (err) {
|
|
1575
|
+
(0, dev_error_overlay_1.formatDevError)(err, normalized);
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
await this.router.handle(req, res, next);
|
|
1579
|
+
}
|
|
1580
|
+
else {
|
|
1581
|
+
next?.();
|
|
1582
|
+
}
|
|
1583
|
+
};
|
|
1584
|
+
const lazyRouter = new router_1.Router();
|
|
1585
|
+
lazyRouter.all(routePath, lazyHandler);
|
|
1586
|
+
lazyRouter.all(routePath + '/*path', lazyHandler);
|
|
1587
|
+
for (const r of lazyRouter.getRoutes()) {
|
|
1588
|
+
r.sourceFile = 'lazy:' + lazyKey;
|
|
1589
|
+
this.router.addRawRoute(r);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
else {
|
|
1593
|
+
try {
|
|
1594
|
+
await this.mountRouteFile(normalized, routePath);
|
|
1595
|
+
}
|
|
1596
|
+
catch (err) {
|
|
1597
|
+
this.log.error({ err, file: filePath }, `Failed to load route file: ${filePath}`);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
// ─── Route File Mounting (shared by autoMount and HotReloader) ──────────────
|
|
1605
|
+
// ─── Auto-Mount File-Based Jobs ──────────────────────────────────────────────
|
|
1606
|
+
async autoMountJobs(root) {
|
|
1607
|
+
if (!this._queueWorker)
|
|
1608
|
+
return;
|
|
1609
|
+
const fs = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
1610
|
+
const path = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
1611
|
+
const jobsDir = path.join(root, 'src', 'jobs');
|
|
1612
|
+
try {
|
|
1613
|
+
const entries = await fs.readdir(jobsDir, { withFileTypes: true });
|
|
1614
|
+
for (const entry of entries) {
|
|
1615
|
+
if (entry.isFile() &&
|
|
1616
|
+
(entry.name.endsWith('.ts') || entry.name.endsWith('.js'))) {
|
|
1617
|
+
const filePath = path.join(jobsDir, entry.name);
|
|
1618
|
+
const name = entry.name.replace(/\.(ts|js)$/, '');
|
|
1619
|
+
try {
|
|
1620
|
+
// Load the definition directly (no compilation needed here just to get the schema)
|
|
1621
|
+
const mod = await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)));
|
|
1622
|
+
const jobDef = mod.default || mod;
|
|
1623
|
+
if (jobDef) {
|
|
1624
|
+
const def = {
|
|
1625
|
+
name: jobDef.name || name,
|
|
1626
|
+
filePath, // Attach filePath for Thread Pool execution
|
|
1627
|
+
cron: jobDef.cron,
|
|
1628
|
+
schema: jobDef.schema,
|
|
1629
|
+
defaultOptions: jobDef.defaultOptions,
|
|
1630
|
+
};
|
|
1631
|
+
this._queueWorker.registerJob(def);
|
|
1632
|
+
if (this._cronScheduler && jobDef.cron) {
|
|
1633
|
+
this._cronScheduler.registerJob(def);
|
|
1634
|
+
}
|
|
1635
|
+
this.log.info(`Registered background job: ${name}`);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
catch (err) {
|
|
1639
|
+
this.log.error(`Failed to load job file ${filePath}: ${err}`);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
catch {
|
|
1645
|
+
// Ignore if jobs directory does not exist
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
async mountRouteFile(filePath, routePath) {
|
|
1649
|
+
let mod;
|
|
1650
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
1651
|
+
try {
|
|
1652
|
+
const url = (0, node_url_1.pathToFileURL)(filePath).href + '?t=' + Date.now();
|
|
1653
|
+
mod = await dynamicImport(url);
|
|
1654
|
+
}
|
|
1655
|
+
catch {
|
|
1656
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1657
|
+
mod = require(filePath);
|
|
1658
|
+
}
|
|
1659
|
+
const path = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
1660
|
+
const fs = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
1661
|
+
const dirname = path.dirname(filePath);
|
|
1662
|
+
const apiDir = this.apiDir || dirname;
|
|
1663
|
+
const normDirname = path.resolve(dirname).replace(/\\/g, '/').toLowerCase();
|
|
1664
|
+
const normApiDir = path.resolve(apiDir).replace(/\\/g, '/').toLowerCase();
|
|
1665
|
+
const segments = normDirname.startsWith(normApiDir)
|
|
1666
|
+
? normDirname.slice(normApiDir.length).split('/').filter(Boolean)
|
|
1667
|
+
: [];
|
|
1668
|
+
const allMiddlewares = [];
|
|
1669
|
+
const allFilters = [];
|
|
1670
|
+
const allGuards = [];
|
|
1671
|
+
const allInterceptors = [];
|
|
1672
|
+
let allMetadata = {};
|
|
1673
|
+
let allCors = undefined;
|
|
1674
|
+
let allHeaders = {};
|
|
1675
|
+
const dirsToCheck = [apiDir];
|
|
1676
|
+
let tempDir = apiDir;
|
|
1677
|
+
for (const segment of segments) {
|
|
1678
|
+
tempDir = path.join(tempDir, segment);
|
|
1679
|
+
dirsToCheck.push(tempDir);
|
|
1680
|
+
}
|
|
1681
|
+
for (const dir of dirsToCheck) {
|
|
1682
|
+
try {
|
|
1683
|
+
const gwPathJs = path.join(dir, 'gateway.js');
|
|
1684
|
+
const gwPathTs = path.join(dir, 'gateway.ts');
|
|
1685
|
+
let targetGw = '';
|
|
1686
|
+
if (await fs.stat(gwPathTs).catch(() => null))
|
|
1687
|
+
targetGw = gwPathTs;
|
|
1688
|
+
else if (await fs.stat(gwPathJs).catch(() => null))
|
|
1689
|
+
targetGw = gwPathJs;
|
|
1690
|
+
if (targetGw) {
|
|
1691
|
+
const url = (0, node_url_1.pathToFileURL)(targetGw).href + '?t=' + Date.now();
|
|
1692
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1693
|
+
const gwMod = await dynamicImport(url).catch(() => require(targetGw));
|
|
1694
|
+
const gwConfig = gwMod.default || gwMod;
|
|
1695
|
+
if (gwConfig) {
|
|
1696
|
+
const isExcluded = (reqPath, reqMethod) => {
|
|
1697
|
+
if (!gwConfig.exclude)
|
|
1698
|
+
return false;
|
|
1699
|
+
for (const rule of gwConfig.exclude) {
|
|
1700
|
+
if (typeof rule === 'string') {
|
|
1701
|
+
if (rule.endsWith('/*')) {
|
|
1702
|
+
if (reqPath.startsWith(rule.slice(0, -2)))
|
|
1703
|
+
return true;
|
|
1704
|
+
}
|
|
1705
|
+
else if (reqPath === rule)
|
|
1706
|
+
return true;
|
|
1707
|
+
}
|
|
1708
|
+
else {
|
|
1709
|
+
const pathMatch = rule.path.endsWith('/*')
|
|
1710
|
+
? reqPath.startsWith(rule.path.slice(0, -2))
|
|
1711
|
+
: reqPath === rule.path;
|
|
1712
|
+
if (pathMatch) {
|
|
1713
|
+
if (!rule.methods ||
|
|
1714
|
+
rule.methods.includes(reqMethod))
|
|
1715
|
+
return true;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return false;
|
|
1720
|
+
};
|
|
1721
|
+
if (gwConfig.middleware) {
|
|
1722
|
+
const wrapped = gwConfig.middleware.map((m) => (req, res, next) => isExcluded(req.path, req.method) ? next() : m(req, res, next));
|
|
1723
|
+
allMiddlewares.push(...wrapped);
|
|
1724
|
+
}
|
|
1725
|
+
if (gwConfig.filters) {
|
|
1726
|
+
const wrapped = gwConfig.filters.map((f) => (err, req, res, next) => isExcluded(req.path, req.method)
|
|
1727
|
+
? next(err)
|
|
1728
|
+
: f.prototype && f.prototype.catch
|
|
1729
|
+
? new f().catch(err, req, res, next)
|
|
1730
|
+
: f(err, req, res, next));
|
|
1731
|
+
allFilters.push(...wrapped);
|
|
1732
|
+
}
|
|
1733
|
+
if (gwConfig.guards) {
|
|
1734
|
+
const wrapped = gwConfig.guards.map((g) => {
|
|
1735
|
+
return async (req) => {
|
|
1736
|
+
if (isExcluded(req.path, req.method))
|
|
1737
|
+
return true;
|
|
1738
|
+
return typeof g === 'function'
|
|
1739
|
+
? g.prototype?.canActivate
|
|
1740
|
+
? new g().canActivate(req)
|
|
1741
|
+
: g(req)
|
|
1742
|
+
: g.canActivate(req);
|
|
1743
|
+
};
|
|
1744
|
+
});
|
|
1745
|
+
allGuards.push(...wrapped);
|
|
1746
|
+
}
|
|
1747
|
+
if (gwConfig.interceptors) {
|
|
1748
|
+
const wrapped = gwConfig.interceptors.map((i) => {
|
|
1749
|
+
return async (req, res) => {
|
|
1750
|
+
if (isExcluded(req.path, req.method))
|
|
1751
|
+
return;
|
|
1752
|
+
return typeof i === 'function'
|
|
1753
|
+
? i.prototype?.intercept
|
|
1754
|
+
? new i().intercept(req, res)
|
|
1755
|
+
: i(req, res)
|
|
1756
|
+
: i.intercept(req, res);
|
|
1757
|
+
};
|
|
1758
|
+
});
|
|
1759
|
+
allInterceptors.push(...wrapped);
|
|
1760
|
+
}
|
|
1761
|
+
if (gwConfig.timeout !== undefined) {
|
|
1762
|
+
const baseTimeout = gwConfig.timeout;
|
|
1763
|
+
const timeoutMiddleware = (req, res, next) => {
|
|
1764
|
+
if (isExcluded(req.path, req.method))
|
|
1765
|
+
return next();
|
|
1766
|
+
const ms = typeof baseTimeout === 'function'
|
|
1767
|
+
? baseTimeout(req)
|
|
1768
|
+
: baseTimeout;
|
|
1769
|
+
if (ms) {
|
|
1770
|
+
req.timeoutTimer = setTimeout(() => {
|
|
1771
|
+
if (!res.headersSent) {
|
|
1772
|
+
res
|
|
1773
|
+
.status(408)
|
|
1774
|
+
.json({
|
|
1775
|
+
success: false,
|
|
1776
|
+
error: {
|
|
1777
|
+
code: 'REQUEST_TIMEOUT',
|
|
1778
|
+
message: 'Request Timeout',
|
|
1779
|
+
},
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
}, ms);
|
|
1783
|
+
res.raw.on('finish', () => clearTimeout(req.timeoutTimer));
|
|
1784
|
+
}
|
|
1785
|
+
next();
|
|
1786
|
+
};
|
|
1787
|
+
allMiddlewares.push(timeoutMiddleware);
|
|
1788
|
+
}
|
|
1789
|
+
if (gwConfig.metadata) {
|
|
1790
|
+
allMetadata = { ...allMetadata, ...gwConfig.metadata };
|
|
1791
|
+
}
|
|
1792
|
+
if (gwConfig.cors !== undefined)
|
|
1793
|
+
allCors = gwConfig.cors;
|
|
1794
|
+
if (gwConfig.headers)
|
|
1795
|
+
allHeaders = { ...allHeaders, ...gwConfig.headers };
|
|
1796
|
+
if (gwConfig.plugins) {
|
|
1797
|
+
for (const plugin of gwConfig.plugins) {
|
|
1798
|
+
await this.register(plugin);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
if (gwConfig.imports) {
|
|
1802
|
+
for (const mod of gwConfig.imports) {
|
|
1803
|
+
const name = 'plugin' in mod ? mod.plugin.name : mod.name;
|
|
1804
|
+
if (!this.hasPlugin(name)) {
|
|
1805
|
+
await this.register(mod);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
if (gwConfig.providers) {
|
|
1810
|
+
for (const [token, providerConfig] of gwConfig.providers) {
|
|
1811
|
+
this.provide(token, providerConfig);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
catch {
|
|
1818
|
+
/* ignore */
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
if (!mod.config)
|
|
1822
|
+
mod.config = {};
|
|
1823
|
+
if (mod.config.middleware)
|
|
1824
|
+
allMiddlewares.push(...mod.config.middleware);
|
|
1825
|
+
if (mod.config.cors !== undefined)
|
|
1826
|
+
allCors = mod.config.cors;
|
|
1827
|
+
if (mod.config.headers)
|
|
1828
|
+
allHeaders = { ...allHeaders, ...mod.config.headers };
|
|
1829
|
+
if (mod.config.plugins) {
|
|
1830
|
+
for (const plugin of mod.config.plugins) {
|
|
1831
|
+
await this.register(plugin);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
// Inject Gateway features into module config for compileFunctionalController and OOP
|
|
1835
|
+
mod.config.filters = [
|
|
1836
|
+
...allFilters,
|
|
1837
|
+
...(mod.config.filters
|
|
1838
|
+
? Array.isArray(mod.config.filters)
|
|
1839
|
+
? mod.config.filters
|
|
1840
|
+
: [mod.config.filters]
|
|
1841
|
+
: []),
|
|
1842
|
+
];
|
|
1843
|
+
mod.config.guards = [
|
|
1844
|
+
...allGuards,
|
|
1845
|
+
...(mod.config.guards
|
|
1846
|
+
? Array.isArray(mod.config.guards)
|
|
1847
|
+
? mod.config.guards
|
|
1848
|
+
: [mod.config.guards]
|
|
1849
|
+
: []),
|
|
1850
|
+
];
|
|
1851
|
+
mod.config.interceptors = [
|
|
1852
|
+
...allInterceptors,
|
|
1853
|
+
...(mod.config.interceptors
|
|
1854
|
+
? Array.isArray(mod.config.interceptors)
|
|
1855
|
+
? mod.config.interceptors
|
|
1856
|
+
: [mod.config.interceptors]
|
|
1857
|
+
: []),
|
|
1858
|
+
];
|
|
1859
|
+
mod.config.metadata = { ...allMetadata, ...(mod.config.metadata || {}) };
|
|
1860
|
+
const prefixMiddlewares = [];
|
|
1861
|
+
if (allCors !== undefined) {
|
|
1862
|
+
if (allCors === true)
|
|
1863
|
+
prefixMiddlewares.push((0, middleware_1.cors)({}));
|
|
1864
|
+
else if (allCors !== false)
|
|
1865
|
+
prefixMiddlewares.push((0, middleware_1.cors)(allCors));
|
|
1866
|
+
}
|
|
1867
|
+
if (Object.keys(allHeaders).length > 0) {
|
|
1868
|
+
prefixMiddlewares.push((req, res, next) => {
|
|
1869
|
+
for (const [k, v] of Object.entries(allHeaders))
|
|
1870
|
+
res.setHeader(k, v);
|
|
1871
|
+
next();
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
prefixMiddlewares.push(...allMiddlewares);
|
|
1875
|
+
const isRouter = (obj) => obj && 'handle' in obj && 'get' in obj;
|
|
1876
|
+
const CONTROLLER_PREFIX = Symbol.for('exisjs:controller_prefix');
|
|
1877
|
+
const isController = (obj) => obj && obj.prototype && obj.prototype[CONTROLLER_PREFIX] !== undefined;
|
|
1878
|
+
if (mod.default && mod.default.__isController) {
|
|
1879
|
+
// ─── THE NEW PERFECT FUNCTIONAL CONTROLLER ───
|
|
1880
|
+
const compiledRouter = this.compileFunctionalController(mod.default);
|
|
1881
|
+
this.mountRouteWithSource(routePath, compiledRouter, filePath, prefixMiddlewares);
|
|
1882
|
+
}
|
|
1883
|
+
else if (mod.router && isRouter(mod.router)) {
|
|
1884
|
+
this.mountRouteWithSource(routePath, mod.router, filePath, prefixMiddlewares);
|
|
1885
|
+
}
|
|
1886
|
+
else if (mod.default && isController(mod.default)) {
|
|
1887
|
+
this.registerControllers([mod.default], routePath, prefixMiddlewares);
|
|
1888
|
+
}
|
|
1889
|
+
else if (mod.default && isRouter(mod.default)) {
|
|
1890
|
+
this.mountRouteWithSource(routePath, mod.default, filePath, prefixMiddlewares);
|
|
1891
|
+
}
|
|
1892
|
+
else if (isRouter(mod)) {
|
|
1893
|
+
this.mountRouteWithSource(routePath, mod, filePath, prefixMiddlewares);
|
|
1894
|
+
}
|
|
1895
|
+
else {
|
|
1896
|
+
this.log.warn(`\n[WARN] Route file \x1b[36m${filePath}\x1b[0m does not export a recognized router or controller.\n`);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
compileFunctionalController(config) {
|
|
1900
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1901
|
+
const { Router } = require('../router/router');
|
|
1902
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1903
|
+
const { cors } = require('../middleware/middleware');
|
|
1904
|
+
const router = new Router();
|
|
1905
|
+
const fileMiddleware = [];
|
|
1906
|
+
if (config.cors) {
|
|
1907
|
+
fileMiddleware.push(config.cors === true ? cors({}) : cors(config.cors));
|
|
1908
|
+
}
|
|
1909
|
+
if (config.middleware) {
|
|
1910
|
+
fileMiddleware.push(...(Array.isArray(config.middleware)
|
|
1911
|
+
? config.middleware
|
|
1912
|
+
: [config.middleware]));
|
|
1913
|
+
}
|
|
1914
|
+
const { onError, onResponse } = config;
|
|
1915
|
+
for (const [key, routeConfig] of Object.entries(config)) {
|
|
1916
|
+
if ([
|
|
1917
|
+
'cors',
|
|
1918
|
+
'middleware',
|
|
1919
|
+
'onError',
|
|
1920
|
+
'onResponse',
|
|
1921
|
+
'__isController',
|
|
1922
|
+
].includes(key))
|
|
1923
|
+
continue;
|
|
1924
|
+
const rc = routeConfig;
|
|
1925
|
+
if (!rc || !rc.method || !rc.path || !rc.handle)
|
|
1926
|
+
continue;
|
|
1927
|
+
const routeMiddlewares = [...fileMiddleware];
|
|
1928
|
+
if (rc.cors) {
|
|
1929
|
+
routeMiddlewares.push(rc.cors === true ? cors({}) : cors(rc.cors));
|
|
1930
|
+
}
|
|
1931
|
+
if (rc.middleware) {
|
|
1932
|
+
routeMiddlewares.push(...(Array.isArray(rc.middleware) ? rc.middleware : [rc.middleware]));
|
|
1933
|
+
}
|
|
1934
|
+
// ─── SUPER HANDLER WRAPPER ───
|
|
1935
|
+
const superHandler = async (req, res, next) => {
|
|
1936
|
+
if (onResponse) {
|
|
1937
|
+
res.raw.on('finish', () => onResponse(req, res));
|
|
1938
|
+
}
|
|
1939
|
+
try {
|
|
1940
|
+
// 1. Run Guards
|
|
1941
|
+
const routeGuards = [...(config.guards || []), ...(rc.guards || [])];
|
|
1942
|
+
for (const guard of routeGuards) {
|
|
1943
|
+
const allowed = await (typeof guard === 'function'
|
|
1944
|
+
? guard.prototype?.canActivate
|
|
1945
|
+
? new guard().canActivate(req)
|
|
1946
|
+
: guard(req)
|
|
1947
|
+
: guard.canActivate(req));
|
|
1948
|
+
if (!allowed) {
|
|
1949
|
+
if (res && !res.headersSent) {
|
|
1950
|
+
res
|
|
1951
|
+
.status(403)
|
|
1952
|
+
.json({
|
|
1953
|
+
success: false,
|
|
1954
|
+
error: { code: 'FORBIDDEN', message: 'Forbidden by Guard' },
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
// Build Context
|
|
1961
|
+
const ctx = {
|
|
1962
|
+
body: req.body,
|
|
1963
|
+
query: req.query,
|
|
1964
|
+
params: req.params,
|
|
1965
|
+
headers: req.headers,
|
|
1966
|
+
req,
|
|
1967
|
+
res,
|
|
1968
|
+
app: this,
|
|
1969
|
+
socket: req.ws,
|
|
1970
|
+
...req, // Spread req to allow access to user, session, etc.
|
|
1971
|
+
};
|
|
1972
|
+
const result = await rc.handle(ctx);
|
|
1973
|
+
// 2. Run Interceptors
|
|
1974
|
+
const routeInterceptors = [
|
|
1975
|
+
...(config.interceptors || []),
|
|
1976
|
+
...(rc.interceptors || []),
|
|
1977
|
+
];
|
|
1978
|
+
for (const interceptor of routeInterceptors) {
|
|
1979
|
+
await (typeof interceptor === 'function'
|
|
1980
|
+
? interceptor.prototype?.intercept
|
|
1981
|
+
? new interceptor().intercept(req, res)
|
|
1982
|
+
: interceptor(req, res)
|
|
1983
|
+
: interceptor.intercept(req, res));
|
|
1984
|
+
}
|
|
1985
|
+
if (result !== undefined && !res.headersSent) {
|
|
1986
|
+
if (typeof result === 'object' && result !== null) {
|
|
1987
|
+
res.json(result);
|
|
1988
|
+
}
|
|
1989
|
+
else {
|
|
1990
|
+
res.send(String(result));
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
catch (err) {
|
|
1995
|
+
if (onError) {
|
|
1996
|
+
try {
|
|
1997
|
+
await onError(err, req, res);
|
|
1998
|
+
}
|
|
1999
|
+
catch (hookErr) {
|
|
2000
|
+
next(hookErr);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
else {
|
|
2004
|
+
next(err); // Hands off to ExisJS global error handler (which handles HttpErrors automatically!)
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
};
|
|
2008
|
+
// Mount to router
|
|
2009
|
+
const method = rc.method.toLowerCase();
|
|
2010
|
+
const schema = {};
|
|
2011
|
+
const isValidatorOrPipe = (v) => v.parse ||
|
|
2012
|
+
v.transform ||
|
|
2013
|
+
(typeof v === 'function' && v.prototype?.transform);
|
|
2014
|
+
if (rc.body) {
|
|
2015
|
+
schema.body = isValidatorOrPipe(rc.body) ? rc.body : validator_1.v.object(rc.body);
|
|
2016
|
+
}
|
|
2017
|
+
if (rc.query) {
|
|
2018
|
+
schema.query = isValidatorOrPipe(rc.query)
|
|
2019
|
+
? rc.query
|
|
2020
|
+
: validator_1.v.object(rc.query);
|
|
2021
|
+
}
|
|
2022
|
+
if (rc.params) {
|
|
2023
|
+
schema.params = isValidatorOrPipe(rc.params)
|
|
2024
|
+
? rc.params
|
|
2025
|
+
: validator_1.v.object(rc.params);
|
|
2026
|
+
}
|
|
2027
|
+
if (rc.host) {
|
|
2028
|
+
schema.host = rc.host;
|
|
2029
|
+
}
|
|
2030
|
+
const combinedFilters = [
|
|
2031
|
+
...(config.filters
|
|
2032
|
+
? Array.isArray(config.filters)
|
|
2033
|
+
? config.filters
|
|
2034
|
+
: [config.filters]
|
|
2035
|
+
: []),
|
|
2036
|
+
...(rc.filters
|
|
2037
|
+
? Array.isArray(rc.filters)
|
|
2038
|
+
? rc.filters
|
|
2039
|
+
: [rc.filters]
|
|
2040
|
+
: []),
|
|
2041
|
+
];
|
|
2042
|
+
if (combinedFilters.length > 0) {
|
|
2043
|
+
schema.filters = combinedFilters;
|
|
2044
|
+
}
|
|
2045
|
+
const combinedMetadata = {
|
|
2046
|
+
...(config.metadata || {}),
|
|
2047
|
+
...(rc.metadata || {}),
|
|
2048
|
+
};
|
|
2049
|
+
if (Object.keys(combinedMetadata).length > 0) {
|
|
2050
|
+
schema.metadata = combinedMetadata;
|
|
2051
|
+
}
|
|
2052
|
+
if (method === 'sse') {
|
|
2053
|
+
const sseHandler = async (stream, rawReq) => {
|
|
2054
|
+
try {
|
|
2055
|
+
const ctx = {
|
|
2056
|
+
body: rawReq.body,
|
|
2057
|
+
query: rawReq.query,
|
|
2058
|
+
params: rawReq.params,
|
|
2059
|
+
headers: rawReq.headers,
|
|
2060
|
+
req: rawReq,
|
|
2061
|
+
res: undefined, // Res is not available in standard SSE after handshake
|
|
2062
|
+
app: this,
|
|
2063
|
+
stream,
|
|
2064
|
+
...rawReq,
|
|
2065
|
+
};
|
|
2066
|
+
await rc.handle(ctx);
|
|
2067
|
+
}
|
|
2068
|
+
catch (err) {
|
|
2069
|
+
if (onError) {
|
|
2070
|
+
await onError(err, rawReq, {});
|
|
2071
|
+
}
|
|
2072
|
+
else {
|
|
2073
|
+
console.error('[Exis SSE Error]', err);
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
router.sse(rc.path, ...routeMiddlewares, sseHandler);
|
|
2078
|
+
}
|
|
2079
|
+
else {
|
|
2080
|
+
if (Object.keys(schema).length > 0) {
|
|
2081
|
+
router[method](rc.path, ...routeMiddlewares, schema, superHandler);
|
|
2082
|
+
}
|
|
2083
|
+
else {
|
|
2084
|
+
router[method](rc.path, ...routeMiddlewares, superHandler);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return router;
|
|
2089
|
+
}
|
|
2090
|
+
mountRouteWithSource(prefix, subRouter, sourceFile, prefixMiddlewares = []) {
|
|
2091
|
+
for (const route of subRouter.getRoutes()) {
|
|
2092
|
+
const fullPath = (prefix + route.path).replace(/\/+/g, '/');
|
|
2093
|
+
const r = { ...route, path: fullPath, sourceFile };
|
|
2094
|
+
if (prefixMiddlewares.length > 0) {
|
|
2095
|
+
r.handlers = [...prefixMiddlewares, ...r.handlers];
|
|
2096
|
+
}
|
|
2097
|
+
this.router.addRawRoute(r);
|
|
2098
|
+
if (this.hooks.route.length > 0) {
|
|
2099
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
2100
|
+
for (let i = 0; i < this.hooks.route.length; i++) {
|
|
2101
|
+
this.hooks.route[i]({ method: route.method, path: fullPath });
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
return this;
|
|
2106
|
+
}
|
|
2107
|
+
async scanDirectory(dir, baseRoute = '/') {
|
|
2108
|
+
const fs = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
2109
|
+
const path = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
2110
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2111
|
+
const results = [];
|
|
2112
|
+
for (const entry of entries) {
|
|
2113
|
+
const fullPath = path.join(dir, entry.name);
|
|
2114
|
+
if (entry.isDirectory()) {
|
|
2115
|
+
let segment = entry.name;
|
|
2116
|
+
if (segment.startsWith('(') && segment.endsWith(')')) {
|
|
2117
|
+
const subResults = await this.scanDirectory(fullPath, baseRoute);
|
|
2118
|
+
results.push(...subResults);
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
if (segment.startsWith('[...') && segment.endsWith(']')) {
|
|
2122
|
+
segment = '*' + segment.slice(4, -1);
|
|
2123
|
+
}
|
|
2124
|
+
else {
|
|
2125
|
+
segment = segment.replace(/\[(.*?)\]/g, ':$1');
|
|
2126
|
+
}
|
|
2127
|
+
const nextBase = baseRoute === '/' ? `/${segment}` : `${baseRoute}/${segment}`;
|
|
2128
|
+
const subResults = await this.scanDirectory(fullPath, nextBase);
|
|
2129
|
+
results.push(...subResults);
|
|
2130
|
+
}
|
|
2131
|
+
else {
|
|
2132
|
+
results.push({ filePath: fullPath, routePath: baseRoute });
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
return results;
|
|
2136
|
+
}
|
|
2137
|
+
// ─── Queues ─────────────────────────────────────────────────────────────────
|
|
2138
|
+
/**
|
|
2139
|
+
* Registers a background job worker handler.
|
|
2140
|
+
*/
|
|
2141
|
+
queue(name, handler, options) {
|
|
2142
|
+
if (!this._queueWorker) {
|
|
2143
|
+
if (this.options.queue?.enableWorkers === false) {
|
|
2144
|
+
// workers disabled explicitly, gracefully skip registering
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
// Worker not initialized yet (e.g. config loading is pending), buffer the job
|
|
2148
|
+
this._pendingQueueJobs.push({
|
|
2149
|
+
name,
|
|
2150
|
+
handler: handler,
|
|
2151
|
+
...options,
|
|
2152
|
+
});
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
this._queueWorker.registerJob({
|
|
2156
|
+
name,
|
|
2157
|
+
handler: handler,
|
|
2158
|
+
...options,
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Enqueues a payload for background processing.
|
|
2163
|
+
*/
|
|
2164
|
+
async enqueue(name, payload, opts) {
|
|
2165
|
+
if (!this._queueClient) {
|
|
2166
|
+
throw new Error(`Queue is not initialized. Please configure ExisConfig.queue first.`);
|
|
2167
|
+
}
|
|
2168
|
+
return this._queueClient.enqueue(name, payload, opts);
|
|
2169
|
+
}
|
|
2170
|
+
_initQueue(qConfig) {
|
|
2171
|
+
if (!this._queueClient) {
|
|
2172
|
+
this._queueClient = new client_1.ExisQueue(qConfig);
|
|
2173
|
+
}
|
|
2174
|
+
const enableWorkers = qConfig.enableWorkers ?? true;
|
|
2175
|
+
if (enableWorkers && !this._queueWorker) {
|
|
2176
|
+
this._queueWorker = new worker_1.ExisWorker(qConfig, this.log, this._queueClient.getDriver());
|
|
2177
|
+
this._queueWorker.start();
|
|
2178
|
+
const redisClient = this._queueWorker.redis || null;
|
|
2179
|
+
this._cronScheduler = new scheduler_1.CronScheduler(this._queueWorker, redisClient, this.log);
|
|
2180
|
+
this._cronScheduler.start();
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
exports.App = App;
|
|
2185
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
2186
|
+
exports.app = new App();
|
|
2187
|
+
exports.activeAppInstance = null;
|
|
2188
|
+
function getActiveApp() {
|
|
2189
|
+
return exports.activeAppInstance || exports.app;
|
|
2190
|
+
}
|
|
2191
|
+
//# sourceMappingURL=app.js.map
|